pax_global_header00006660000000000000000000000064127640234000014510gustar00rootroot0000000000000052 comment=8fcad72ea3023132fb4ddf904d230bc7096ac637 jung-jung-2.1.1/000077500000000000000000000000001276402340000134155ustar00rootroot00000000000000jung-jung-2.1.1/.gitignore000066400000000000000000000002561276402340000154100ustar00rootroot00000000000000#General *.orig ~* *~ *.bak # Maven target/ *.ser *.ec # IntelliJ Idea .idea/ out/ *.ipr *.iws *.iml # Eclipse .classpath .project .settings/ .metadata/ .factoryPath bin/ jung-jung-2.1.1/.travis.yml000066400000000000000000000034151276402340000155310ustar00rootroot00000000000000# Per https://docs.travis-ci.com/user/languages/java sudo: false language: java jdk: - oraclejdk8 - oraclejdk7 - openjdk7 install: - mvn -v - mvn -B install -U -DskipTests=true script: - mvn -B verify -U -Dmaven.javadoc.skip=true after_success: - tools/deploy_snapshots.sh cache: directories: - ${HOME}/.m2 branches: only: - master - /^release.*$/ env: global: - secure: "ZDWaFgXd/IyJUt4vs9pZnljLyhjbAzvTpkWMg+K0aaUhn+NV3cLHuKvghJXSXTF0/dbKRO4wVWat1xNZm6Ec1zWVeRmmJhoqjh65DozAslQdZAnbEGxEMdXPNqxFs0bEijEQ2GUbdh18tjqDonZn6gEXExr/OFiANJD9lkUWUZkX3TieXrMV8l4H5ywyokYzDO2iKRHKLZeci9oBWdmd6/ER9Luumn67wesZbGKZ9OSODtXGjqTdrh+WpvH9NmrqKCZLCFG1TLAD0cUkqKLba5aXTRD0WIRyX8iZwQzYF8v+j2812x3PHKKN85OGbBtVU+FrUqbvmYtTpGMjn4DKO1XMUvAXSXdpehY71Vw7eU5XXvm4OihOzDLW2hV//cE0yjuqjQDLC9jA2IACkTq7z8oQJ8JLzOxnWQAWJdS5893ZXOIBCn/yifVGIwDUA8lU2RiippCJkplpQoYMv//NN8t8Aazpz22yR38s5hBPGuz+70bTQ45O/uuhMa+81nOMzwbqq3Lu3aUNgqEzEHmEnX91XZLsKbPNL+i3TYf0mCF4EAXGdpV5pOXnjlCqO55SrxGojLKp/TcvMQT1KmD4nwvP5mL03UxqynDllLAz0AhLUZHdn5uCFTWIOto8mw27XJroSY99eiFhS5WqMuo7uxyJlHeqQTSFJx6ckGmBfSI=" - secure: "lgiLAdXkJZzTwWmZoX5b+/ZZJUYvI1qB/tAlnZuCxiupxv3Lx9JcYH7eI5zKEjCc8ahwxF0A7ZAa+KPEeEQ9RlvrtSY1jBVHCZWIo1rvgg9Jeynhs9yYBXhcgxYl4jGlp7AZz7ZviHNLXtKbFlf/DJnCIruoIetW/Na0Y+k124OAOuHZrObOM5bhldVzYPMl8lZP171k67M3e9y5Lxv4o41DKOGNzBo3oGtfprW/cO3Nna6UfthbTuzrsqGXYPLU5PN3xhKyEgk8byC2hkzXZzje1IgKn6qLvkhKPNWmYRIf+DFO4f22bMLtzfXG2gfAb9RHMDl1TvemOBA/VIKhGAwnzhJqEkcVY482n39E9bq+/Wqhliwv+XVcBnh8oNMbr8OUEP9OO1ys4isNSMysaG3LPGdYt9d82CFpWDO1gFZ3qdxze/y7Fd/yC+mu62KFn6Jxg9w94kGIzWqrv9DUDgLMougCLgX+W+lhlLxgZ9ecwJPXXlKpoEPI+TSoEkurzcTlR1sRj1quAveaC8+FcxiQFV67XkCd7mQe2kUeNtb77kV1u6OpZL6MUZjV32FxZxxkZH3lAjOLt0hTyqGMpZXiVeJoUVXPOmX+7Em6O7Dd1j+q0lwt6n39YnWmTtLovJPVZDAZBgOvHPttSg5f854o6YYBBEZyrEiHE2zWgJo=" jung-jung-2.1.1/AUTHORS000066400000000000000000000005041276402340000144640ustar00rootroot00000000000000JUNG was originally created in 2003 by (in alphabetical order): Danyel Fisher Joshua O'Madadhain Scott White Later contributors have included: Tom Nelson Yan-Biao Boey W. Giordano Masanori Harada Nathan Mittler Jasper Voskuilen John Yesberg Sponsoring organizations: Regents of the University of California Google, Inc. jung-jung-2.1.1/CONTRIBUTING.md000066400000000000000000000024251276402340000156510ustar00rootroot00000000000000Contributing ============ If you would like to contribute code to the JUNG Project you can do so through GitHub by forking the repository and sending a pull request. When submitting code, please make every effort to follow existing conventions and style in order to keep the code as readable as possible. Where appropriate, please provide unit tests. Unit tests should be JUnit based and should be added to `/jung//src/test/java`. Please make sure your code compiles by running `mvn clean test` which will build and run the tests. All pull requests will be validated by Travis-ci in any case and typically must pass before being merged. If you are adding or modifying files you should add the following copyright statement in a language-appropriate comment at the top of the file: ``` /* * Copyright (c) , the JUNG Project and the Regents of the University * of California. All rights reserved. * * This software is open-source under the BSD license; see * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ ``` All files will be released to users of JUNG under a BSD license Before your code can be accepted into the project you must sign the [Individual Contributor License Agreement (CLA)][CLA]. [CLA]: https://cla-assistant.io/jrtom/jung jung-jung-2.1.1/LICENSE000066400000000000000000000040311276402340000144200ustar00rootroot00000000000000The license below is the BSD open-source license. See http://www.opensource.org/licenses/BSD-3-Clause with: = Regents of the University of California and the JUNG Project Authors = University of California = 2003 It allows redistribution of JUNG freely, albeit with acknowledgement of JUNG's being a component in the redistributed software. However, we would greatly appreciate it if you can let us know what you are doing with JUNG. -- THE JUNG LICENSE Copyright (c) 2003-2015, Regents of the University of California and the JUNG Project Authors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of JUNG, nor that of the University of California, nor the names of its contributors, may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jung-jung-2.1.1/README.md000066400000000000000000000115561276402340000147040ustar00rootroot00000000000000## JUNG: The Java Universal Network/Graph Framework [![Build Status](https://travis-ci.org/jrtom/jung.svg?branch=master)](https://travis-ci.org/jrtom/jung) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.sf.jung/jung-algorithms/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.sf.jung/jung-algorithms) JUNG is a software library that provides a common and extendible language for the modeling, analysis, and visualization of data that can be represented as a graph or network. Its basis in Java allows JUNG-based applications to make use of the extensive built-in capabilities of the Java API, as well as those of other existing third-party Java libraries. [**JUNG Website**](http://jrtom.github.io/jung/) ### Latest Release The most recent version of JUNG is [version 2.1](https://github.com/jrtom/jung/releases/tag/jung-2.1), released 18 March 2016. * [Javadoc](http://jrtom.github.io/jung/javadoc/index.html) * [Maven Search Repository](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22net.sf.jung%22%20AND%20v%3A%222.1%22%20AND%20(a%3A%22jung-api%22%20OR%20a%3A%22jung-graph-impl%22%20OR%20a%3A%22jung-visualization%22%20OR%20a%3A%22jung-algorithms%22%20OR%20a%3A%22jung-samples%22%20OR%20a%3A%22jung-io%22)) * `jung-api`: [jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-api/2.1/jung-api-2.1.jar), [source jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-api/2.1/jung-api-2.1-sources.jar), [documentation jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-api/2.1/jung-api-2.1-javadoc.jar) * `jung-graph-impl`: [jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-graph-impl/2.1/jung-graph-impl-2.1.jar), [source jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-graph-impl/2.1/jung-graph-impl-2.1-sources.jar), [documentation jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-graph-impl/2.1/jung-graph-impl-2.1-javadoc.jar) * `jung-algorithms`: [jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-algorithms/2.1/jung-algorithms-2.1.jar), [source jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-algorithms/2.1/jung-algorithms-2.1-sources.jar), [documentation jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-algorithms/2.1/jung-algorithms-2.1-javadoc.jar) * `jung-io`: [jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-io/2.1/jung-io-2.1.jar), [source jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-io/2.1/jung-io-2.1-sources.jar), [documentation jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-io/2.1/jung-io-2.1-javadoc.jar) * `jung-visualization`: [jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-visualization/2.1/jung-visualization-2.1.jar), [source jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-visualization/2.1/jung-visualization-2.1-sources.jar), [documentation jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-visualization/2.1/jung-visualization-2.1-javadoc.jar) * `jung-samples`: [jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-samples/2.1/jung-samples-2.1.jar), [source jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-samples/2.1/jung-samples-2.1-sources.jar), [documentation jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-samples/2.1/jung-samples-2.1-javadoc.jar) To add a dependency on this release of JUNG using Maven, use the following for each JUNG subpackage that you need: ```xml net.sf.jung jung-[subpackage] 2.1 ``` ### Snapshots Snapshots of JUNG built from the `master` branch are available through Maven using version `2.2-SNAPSHOT`. ### Links * [GitHub project](https://github.com/jrtom/jung) * [Issue tracker: report a defect or make a feature request](https://github.com/jrtom/jung/issues/new) * [StackOverflow: Ask "how-to" and "why-didn't-it-work" questions](https://stackoverflow.com/questions/ask?tags=jung+java) ### Contributions JUNG is currently administered primarily by @jrtom, one of the original co-creators of the JUNG project. Bug fixes (with tests) are appreciated and will generally be acted upon pretty quickly if the fix is a clear win. If you'd like to add a feature, or suggest a way that things could be done better, more cleanly, or more efficiently, we really appreciate it, we encourage you to [open an issue](https://github.com/jrtom/jung/issues/new), and you're welcome to make a pull request to show off a proof of concept. However, at the moment we're largely focused on some big architectural changes that are going to touch essentially everything in JUNG. Once those changes land, we'll have more time and energy available to consider other changes. jung-jung-2.1.1/RELEASE.md000066400000000000000000000213511276402340000150210ustar00rootroot00000000000000# Releasing JUNG ## Overview At a high level, the steps involved are as follows: * Preconditions * Create a release branch * Update versions * Tag the release * Build and deploy the release to sonatype * Verify the release on sonatype * Release the bits on oss.sonatype.org to the public repo * Push the tag to github Each step has some idiosyncracies, and follows below. > ***Note:*** *Any specific version numbers should be assumed to be examples and the real, > current version numbers should be substituted.* ## Detail ### Preconditions > ***Note:*** *These preconditions include important minutiae of Maven > deployments. Make sure you read the [OSSRH Guide] and the [Sonatype GPG > blog post][GPG].* Releases involve releasing to Sonatype's managed repository which backs the maven central repository. To push bits to sonatype requires: 1. an account on oss.sonatype.org 2. permission for that account to push to your groupId 3. a pgp certificate (via gnupg) with a published public key 4. a [${HOME}/.m2/settings.xml][settings.xml] file containing the credentials for the account created in step #1. **NOTE**: change the ID for the tag in the example to `sonatype-nexus-staging`. The administrative steps above are all documented in Sonatype's [OSSRH Guide]. The GPG instructions particular to this process can be found in this [Sonatype GPG blog entry][GPG]. > ***Notes***: > * *If you don't set up the `settings.xml` correctly on the machine you're using, > you'll get an error that looks like this:* > ```shell > Failed to transfer file: https://oss.sonatype.org/service/local/staging/deploy/maven2/net/sf/jung/jung-parent/2.1/jung-parent-2.1.pom. Return code is: 401, ReasonPhrase: Unauthorized. > ``` > * *As of this writing (March 2016) the default GPG installation on OS X will give you a > binary called `gpg2` rather than `gpg`. This will cause the deploy script to fail. > You should create a symbolic link called `gpg` (using `ln -s`) that points to `gpg2`.* ### Create a release branch First check out the main project's master branch, and create a branch on which to do the release work (to avoid clobbering anything on the master branch): ```shell git clone git@github.com:jrtom/jung.git jung_release cd jung_release git checkout -b release_2_1_branch mvn verify ``` This generates a new branch, and does a full build to ensure that what is currently at the tip of the branch is sound. ### Update versions #### Increment SNAPSHOT dependency versions Do a quick check of the dependency versions to ensure that the project is not relying on -SNAPSHOT dependencies. Since the project manages versions in a `properties` section in the parent pom, the following is a useful tool: ```shell mvn -N versions:display-property-updates ``` Version properties will be generated and look like this: ``` ... [INFO] ------------------------------------------------------------------------ [INFO] Building jung-parent 2.1-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- versions-maven-plugin:2.2:display-property-updates (default-cli) @ jung-parent --- [INFO] artifact junit:junit: checking for updates from central [INFO] artifact com.google.guava:guava: checking for updates from central [INFO] [INFO] The following version properties are referencing the newest available version: [INFO] ${guava.version} ............................................... 19.0 [INFO] The following version property updates are available: [INFO] ${junit.version} ...................................... 3.8.1 -> 4.12 ... ``` For release, it's best to avoid updating older versions at the last minute, as this requires more testing and investigation than one typically does at release. But releases are gated on any -SNAPSHOT dependencies, so these should be incremented. > ***Note:*** *If there is enough dependency lag, the release should be abandoned > and dependencies should be incremented as a normal part of development.* #### Update the project's version. Update the versions of the project, like so (changing version numbers): ```shell mvn versions:set versions:commit -DnewVersion=2.1 git commit -a ``` This will set all versions of projects connected in sections from the parent pom - in short, all the parts of the project will be set to be (and depend on) `2.1`. ### Tag the release The release tags simply follow the format `jung-` so simply do this: ```shell git tag jung-2.1 ``` ### Build and deploy the release to sonatype A convenience script exists to properly run a standard `mvn deploy` run (which pushes built artifacts to the staging repository). It also activates the release profile which ensures that the GnuPG plugin is run, signing the binaries per Sonatype's requirements, adds in the generation of -javadoc and -sources jars, etc. It's parameter is the label for your GnuPG key which can be seen by running `gpg --list-keys` which supplies output similar to the following: ``` pub 2048D/E4382034 2014-12-16 uid Some User (Maven Deployments) ``` > More detail about GPG and Sonatype repositories [in this blog post][GPG] Given the above example, you would then run: ```shell tools/mvn-deploy.sh E4382034 ``` ... and the script will kick off the maven job, pausing when it first needs to sign binaries to ask for your GnuPG certificate passphrase (if any). It then pushes the binaries and signatures up to sonatype's staging repository. > ***Note:*** *Having out-of-date versions of Maven plugins can cause unexpected > errors in the build/deploy process, including failure to find local binaries, > and apparent compilation errors. In case of bizarre failures, update the > plugins to the [latest versions](https://maven.apache.org/plugins/) and try again.* ### Verify the release on sonatype Log in to `oss.sonatype.org` and select "Staging repositories". In the main window, scroll to the botton where a staging repository named roughly after the groupId will appear. > ***Note:*** *while this can be inspected, Sonatype performs several checks > automatically when going through the release lifecycle, so generally it is > not necessary to further inspect this staging repo.* Select the repository. You can check to ensure it is the correct repository by descending the tree in the lower info window. If you are convinced it is the correct one, click on the `close` button (in the upper menu bar) and optionally enter a message (which will be included in any notifications people have set up on that repository). Wait about 60 seconds or so and refresh. If successful, the `release` button will be visible. #### What if it goes wrong? If sonatype's analysis has rejected the release, you can check the information in the lower info window to see what went wrong. Failed analyzes will show in red, and the problem should be remedied and step #3 (Tag the release) should be re-attempted with `tag -f jung-` once the fixes have been committed. Then subsequent steps repeated. ### Release the bits on oss.sonatype.org to the public repo Assuming sonatype's validation was successful, press the `release` button, fill in the optional message, and the repository will be released and automatically dropped once its contents have been copied out to the master repository. At this point, the maven artifact(s) will be available for consumption by maven builds within a few minutes (though it will not be present on for about an hour). ### Push the tag to github Since the release was committed to the maven repository, the exact project state used to generate that should be marked. To push the above-mentioned tag to github, just do the standard git command: ```shell git push --tags ``` This will create a new [release](https://github.com/jrtom/jung/releases) on GitHub. ## Post-release Create a CL/commit that updates the versions from (for instance) `2.1-SNAPSHOT` to the next development version (typically `2.2-SNAPSHOT`). This commit should also contain any changes that were necessary to release the project which need to be persisted (any upgraded dependencies, etc.) > ***Note:*** *Generally do not merge this directly into github as that will disrupt > the standard MOE sync. It can either be created as a github pull-request and > the `moe github_pull` command will turn it into a CL, or it can be created > in a normal internal CL. The change can then by synced-out in the MOE run.* Once the release is done, and the tag is pushed, the branch can be safely deleted. [GPG]: http://blog.sonatype.com/2010/01/how-to-generate-pgp-signatures-with-maven [OSSRH Guide]: http://central.sonatype.org/pages/ossrh-guide.html [settings.xml]: https://books.sonatype.com/nexus-book/reference/_adding_credentials_to_your_maven_settings.html jung-jung-2.1.1/jung-algorithms/000077500000000000000000000000001276402340000165275ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/assembly.xml000066400000000000000000000006611276402340000210730ustar00rootroot00000000000000 dependencies tar.gz false / / false runtime jung-jung-2.1.1/jung-algorithms/pom.xml000066400000000000000000000020511276402340000200420ustar00rootroot00000000000000 4.0.0 net.sf.jung jung-parent 2.1.1 jung-algorithms JUNG - Algorithms Algorithms for the JUNG project net.sf.jung jung-api ${project.version} net.sf.jung jung-graph-impl ${project.version} test junit junit test jung-jung-2.1.1/jung-algorithms/src/000077500000000000000000000000001276402340000173165ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/000077500000000000000000000000001276402340000202425ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/000077500000000000000000000000001276402340000211635ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/000077500000000000000000000000001276402340000217405ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/000077500000000000000000000000001276402340000225205ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/000077500000000000000000000000001276402340000232765ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/000077500000000000000000000000001276402340000242415ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/000077500000000000000000000000001276402340000264125ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/blockmodel/000077500000000000000000000000001276402340000305255ustar00rootroot00000000000000StructurallyEquivalent.java000066400000000000000000000124671276402340000360760ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/blockmodel/* * Copyright (c) 2004, The JUNG Authors * * All rights reserved. * Created on Jan 28, 2004 * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.blockmodel; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Pair; /** * Identifies sets of structurally equivalent vertices in a graph. Vertices * i and j are structurally equivalent iff the set of i's * neighbors is identical to the set of j's neighbors, with the * exception of i and j themselves. This algorithm finds all * sets of equivalent vertices in O(V^2) time. * *

You can extend this class to have a different definition of equivalence (by * overriding isStructurallyEquivalent), and may give it hints for * accelerating the process by overriding canPossiblyCompare. * (For example, in a bipartite graph, canPossiblyCompare may * return false for vertices in * different partitions. This function should be fast.) * * @author Danyel Fisher */ public class StructurallyEquivalent implements Function, VertexPartition> { public VertexPartition apply(Graph g) { Set> vertex_pairs = getEquivalentPairs(g); Set> rv = new HashSet>(); Map> intermediate = new HashMap>(); for (Pair p : vertex_pairs) { Set res = intermediate.get(p.getFirst()); if (res == null) res = intermediate.get(p.getSecond()); if (res == null) // we haven't seen this one before res = new HashSet(); res.add(p.getFirst()); res.add(p.getSecond()); intermediate.put(p.getFirst(), res); intermediate.put(p.getSecond(), res); } rv.addAll(intermediate.values()); // pick up the vertices which don't appear in intermediate; they are // singletons (equivalence classes of size 1) Collection singletons = new ArrayList(g.getVertices()); singletons.removeAll(intermediate.keySet()); for (V v : singletons) { Set v_set = Collections.singleton(v); intermediate.put(v, v_set); rv.add(v_set); } return new VertexPartition(g, intermediate, rv); } /** * For each vertex pair v, v1 in G, checks whether v and v1 are fully * equivalent: meaning that they connect to the exact same vertices. (Is * this regular equivalence, or whathaveyou?) * * @param g the graph whose equivalent pairs are to be generated * @return a Set of Pairs of vertices, where all the vertices in the inner * Pairs are equivalent. */ protected Set> getEquivalentPairs(Graph g) { Set> rv = new HashSet>(); Set alreadyEquivalent = new HashSet(); List l = new ArrayList(g.getVertices()); for (V v1 : l) { if (alreadyEquivalent.contains(v1)) continue; for (Iterator iterator = l.listIterator(l.indexOf(v1) + 1); iterator.hasNext();) { V v2 = iterator.next(); if (alreadyEquivalent.contains(v2)) continue; if (!canBeEquivalent(v1, v2)) continue; if (isStructurallyEquivalent(g, v1, v2)) { Pair p = new Pair(v1, v2); alreadyEquivalent.add(v2); rv.add(p); } } } return rv; } /** * @param g the graph in which the structural equivalence comparison is to take place * @param v1 the vertex to check for structural equivalence to v2 * @param v2 the vertex to check for structural equivalence to v1 * @return {@code true} if {@code v1}'s predecessors/successors are equal to * {@code v2}'s predecessors/successors */ protected boolean isStructurallyEquivalent(Graph g, V v1, V v2) { if( g.degree(v1) != g.degree(v2)) { return false; } Set n1 = new HashSet(g.getPredecessors(v1)); n1.remove(v2); n1.remove(v1); Set n2 = new HashSet(g.getPredecessors(v2)); n2.remove(v1); n2.remove(v2); Set o1 = new HashSet(g.getSuccessors(v1)); Set o2 = new HashSet(g.getSuccessors(v2)); o1.remove(v1); o1.remove(v2); o2.remove(v1); o2.remove(v2); // this neglects self-loops and directed edges from 1 to other boolean b = (n1.equals(n2) && o1.equals(o2)); if (!b) return b; // if there's a directed edge v1->v2 then there's a directed edge v2->v1 b &= ( g.isSuccessor(v1, v2) == g.isSuccessor(v2, v1)); // self-loop check b &= ( g.isSuccessor(v1, v1) == g.isSuccessor(v2, v2)); return b; } /** * This is a space for optimizations. For example, for a bipartite graph, * vertices from different partitions cannot possibly be equivalent. * * @param v1 the first vertex to compare * @param v2 the second vertex to compare * @return {@code true} if the vertices can be equivalent */ protected boolean canBeEquivalent(V v1, V v2) { return true; } } VertexPartition.java000066400000000000000000000076521276402340000344720ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/blockmodel/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * Created on Feb 3, 2004 */ package edu.uci.ics.jung.algorithms.blockmodel; import java.util.*; import edu.uci.ics.jung.graph.Graph; /** * Maintains information about a vertex partition of a graph. * This can be built from a map from vertices to vertex sets * or from a collection of (disjoint) vertex sets, * such as those created by various clustering methods. */ public class VertexPartition { private Map> vertex_partition_map; private Collection> vertex_sets; private Graph graph; /** * Creates an instance based on the specified graph and mapping from vertices * to vertex sets, and generates a set of partitions based on this mapping. * @param g the graph over which the vertex partition is defined * @param partition_map the mapping from vertices to vertex sets (partitions) */ public VertexPartition(Graph g, Map> partition_map) { this.vertex_partition_map = Collections.unmodifiableMap(partition_map); this.graph = g; } /** * Creates an instance based on the specified graph, vertex-set mapping, * and set of disjoint vertex sets. The vertex-set mapping and vertex * partitions must be consistent; that is, the mapping must reflect the * division of vertices into partitions, and each vertex must appear in * exactly one partition. * @param g the graph over which the vertex partition is defined * @param partition_map the mapping from vertices to vertex sets (partitions) * @param vertex_sets the set of disjoint vertex sets */ public VertexPartition(Graph g, Map> partition_map, Collection> vertex_sets) { this.vertex_partition_map = Collections.unmodifiableMap(partition_map); this.vertex_sets = vertex_sets; this.graph = g; } /** * Creates an instance based on the specified graph and set of disjoint vertex sets, * and generates a vertex-to-partition map based on these sets. * @param g the graph over which the vertex partition is defined * @param vertex_sets the set of disjoint vertex sets */ public VertexPartition(Graph g, Collection> vertex_sets) { this.vertex_sets = vertex_sets; this.graph = g; } /** * Returns the graph on which the partition is defined. * @return the graph on which the partition is defined */ public Graph getGraph() { return graph; } /** * Returns a map from each vertex in the input graph to its partition. * This map is generated if it does not already exist. * @return a map from each vertex in the input graph to a vertex set */ public Map> getVertexToPartitionMap() { if (vertex_partition_map == null) { this.vertex_partition_map = new HashMap>(); for (Set set : this.vertex_sets) for (V v : set) this.vertex_partition_map.put(v, set); } return vertex_partition_map; } /** * Returns a collection of vertex sets, where each vertex in the * input graph is in exactly one set. * This collection is generated based on the vertex-to-partition map * if it does not already exist. * @return a collection of vertex sets such that each vertex in the * instance's graph is in exactly one set */ public Collection> getVertexPartitions() { if (vertex_sets == null) { this.vertex_sets = new HashSet>(); this.vertex_sets.addAll(vertex_partition_map.values()); } return vertex_sets; } /** * @return the number of partitions. */ public int numPartitions() { return vertex_sets.size(); } @Override public String toString() { return "Partitions: " + vertex_partition_map; } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/blockmodel/package.html000066400000000000000000000015021276402340000330040ustar00rootroot00000000000000 Support for establishing and maintaining graph element equivalence (such as in blockmodeling).

In blockmodeling, groups of vertices are clustered together by similarity (as if members of a "block" appearing on the diagonal of the graph's adjacency matrix).

This support currently includes:

  • VertexPartition: A class that maintains information on a division of the vertices of a graph into disjoint sets.
  • StructurallyEquivalent: An algorithm that finds sets of vertices that are structurally equivalent.

jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/000077500000000000000000000000001276402340000300735ustar00rootroot00000000000000BicomponentClusterer.java000066400000000000000000000151311276402340000350260ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.cluster; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.Stack; import com.google.common.base.Function; import edu.uci.ics.jung.graph.UndirectedGraph; /** * Finds all biconnected components (bicomponents) of an undirected graph. * A graph is a biconnected component if * at least 2 vertices must be removed in order to disconnect the graph. (Graphs * consisting of one vertex, or of two connected vertices, are also biconnected.) Biconnected * components of three or more vertices have the property that every pair of vertices in the component * are connected by two or more vertex-disjoint paths. *

* Running time: O(|V| + |E|) where |V| is the number of vertices and |E| is the number of edges * @see "Depth first search and linear graph algorithms by R. E. Tarjan (1972), SIAM J. Comp." * * @author Joshua O'Madadhain */ public class BicomponentClusterer implements Function, Set>> { protected Map dfs_num; protected Map high; protected Map parents; protected Stack stack; protected int converse_depth; /** * Constructs a new bicomponent finder */ public BicomponentClusterer() { } /** * Extracts the bicomponents from the graph. * @param theGraph the graph whose bicomponents are to be extracted * @return the ClusterSet of bicomponents */ public Set> apply(UndirectedGraph theGraph) { Set> bicomponents = new LinkedHashSet>(); if (theGraph.getVertices().isEmpty()) return bicomponents; // initialize DFS number for each vertex to 0 dfs_num = new HashMap(); for (V v : theGraph.getVertices()) { dfs_num.put(v, 0); } for (V v : theGraph.getVertices()) { if (dfs_num.get(v).intValue() == 0) // if we haven't hit this vertex yet... { high = new HashMap(); stack = new Stack(); parents = new HashMap(); converse_depth = theGraph.getVertexCount(); // find the biconnected components for this subgraph, starting from v findBiconnectedComponents(theGraph, v, bicomponents); // if we only visited one vertex, this method won't have // ID'd it as a biconnected component, so mark it as one if (theGraph.getVertexCount() - converse_depth == 1) { Set s = new HashSet(); s.add(v); bicomponents.add(s); } } } return bicomponents; } /** *

Stores, in bicomponents, all the biconnected * components that are reachable from v. * *

The algorithm basically proceeds as follows: do a depth-first * traversal starting from v, marking each vertex with * a value that indicates the order in which it was encountered (dfs_num), * and with * a value that indicates the highest point in the DFS tree that is known * to be reachable from this vertex using non-DFS edges (high). (Since it * is measured on non-DFS edges, "high" tells you how far back in the DFS * tree you can reach by two distinct paths, hence biconnectivity.) * Each time a new vertex w is encountered, push the edge just traversed * on a stack, and call this method recursively. If w.high is no greater than * v.dfs_num, then the contents of the stack down to (v,w) is a * biconnected component (and v is an articulation point, that is, a * component boundary). In either case, set v.high to max(v.high, w.high), * and continue. If w has already been encountered but is * not v's parent, set v.high max(v.high, w.dfs_num) and continue. * *

(In case anyone cares, the version of this algorithm on p. 224 of * Udi Manber's "Introduction to Algorithms: A Creative Approach" seems to be * wrong: the stack should be initialized outside this method, * (v,w) should only be put on the stack if w hasn't been seen already, * and there's no real benefit to putting v on the stack separately: just * check for (v,w) on the stack rather than v. Had I known this, I could * have saved myself a few days. JRTOM) * * @param g the graph to check for biconnected components * @param v the starting place for searching for biconnected components * @param bicomponents storage for the biconnected components found by this algorithm */ protected void findBiconnectedComponents(UndirectedGraph g, V v, Set> bicomponents) { int v_dfs_num = converse_depth; dfs_num.put(v, v_dfs_num); converse_depth--; high.put(v, v_dfs_num); for (V w : g.getNeighbors(v)) { int w_dfs_num = dfs_num.get(w).intValue();//get(w, dfs_num); E vw = g.findEdge(v,w); if (w_dfs_num == 0) // w hasn't yet been visited { parents.put(w, v); // v is w's parent in the DFS tree stack.push(vw); findBiconnectedComponents(g, w, bicomponents); int w_high = high.get(w).intValue();//get(w, high); if (w_high <= v_dfs_num) { // v disconnects w from the rest of the graph, // i.e., v is an articulation point // thus, everything between the top of the stack and // v is part of a single biconnected component Set bicomponent = new HashSet(); E e; do { e = stack.pop(); bicomponent.addAll(g.getIncidentVertices(e)); } while (e != vw); bicomponents.add(bicomponent); } high.put(v, Math.max(w_high, high.get(v).intValue())); } else if (w != parents.get(v)) // (v,w) is a back or a forward edge high.put(v, Math.max(w_dfs_num, high.get(v).intValue())); } } } EdgeBetweennessClusterer.java000066400000000000000000000075111276402340000356230ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.cluster; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.scoring.BetweennessCentrality; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Pair; /** * An algorithm for computing clusters (community structure) in graphs based on edge betweenness. * The betweenness of an edge is defined as the extent to which that edge lies along * shortest paths between all pairs of nodes. * * This algorithm works by iteratively following the 2 step process: *

    *
  • Compute edge betweenness for all edges in current graph *
  • Remove edge with highest betweenness *
*

* Running time is: O(kmn) where k is the number of edges to remove, m is the total number of edges, and * n is the total number of vertices. For very sparse graphs the running time is closer to O(kn^2) and for * graphs with strong community structure, the complexity is even lower. *

* This algorithm is a slight modification of the algorithm discussed below in that the number of edges * to be removed is parameterized. * @author Scott White * @author Tom Nelson (converted to jung2) * @see "Community structure in social and biological networks by Michelle Girvan and Mark Newman" */ public class EdgeBetweennessClusterer implements Function,Set>> { private int mNumEdgesToRemove; private Map> edges_removed; /** * Constructs a new clusterer for the specified graph. * @param numEdgesToRemove the number of edges to be progressively removed from the graph */ public EdgeBetweennessClusterer(int numEdgesToRemove) { mNumEdgesToRemove = numEdgesToRemove; edges_removed = new LinkedHashMap>(); } /** * Finds the set of clusters which have the strongest "community structure". * The more edges removed the smaller and more cohesive the clusters. * @param graph the graph */ public Set> apply(Graph graph) { if (mNumEdgesToRemove < 0 || mNumEdgesToRemove > graph.getEdgeCount()) { throw new IllegalArgumentException("Invalid number of edges passed in."); } edges_removed.clear(); for (int k=0;k bc = new BetweennessCentrality(graph); E to_remove = null; double score = 0; for (E e : graph.getEdges()) if (bc.getEdgeScore(e) > score) { to_remove = e; score = bc.getEdgeScore(e); } edges_removed.put(to_remove, graph.getEndpoints(to_remove)); graph.removeEdge(to_remove); } WeakComponentClusterer wcSearch = new WeakComponentClusterer(); Set> clusterSet = wcSearch.apply(graph); for (Map.Entry> entry : edges_removed.entrySet()) { Pair endpoints = entry.getValue(); graph.addEdge(entry.getKey(), endpoints.getFirst(), endpoints.getSecond()); } return clusterSet; } /** * Retrieves the list of all edges that were removed * (assuming extract(...) was previously called). * The edges returned * are stored in order in which they were removed. * * @return the edges in the original graph */ public List getEdgesRemoved() { return new ArrayList(edges_removed.keySet()); } } VoltageClusterer.java000066400000000000000000000320731276402340000341560ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/* * Copyright (c) 2004, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 12, 2004 */ package edu.uci.ics.jung.algorithms.cluster; import edu.uci.ics.jung.algorithms.scoring.VoltageScorer; import edu.uci.ics.jung.algorithms.util.DiscreteDistribution; import edu.uci.ics.jung.algorithms.util.KMeansClusterer; import edu.uci.ics.jung.algorithms.util.KMeansClusterer.NotEnoughClustersException; import edu.uci.ics.jung.graph.Graph; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; /** *

Clusters vertices of a Graph based on their ranks as * calculated by VoltageScorer. This algorithm is based on, * but not identical with, the method described in the paper below. * The primary difference is that Wu and Huberman assume a priori that the clusters * are of approximately the same size, and therefore use a more complex * method than k-means (which is used here) for determining cluster * membership based on co-occurrence data. * *

The algorithm proceeds as follows: *

    *
  • first, generate a set of candidate clusters as follows: *
      *
    • pick (widely separated) vertex pair, run VoltageScorer *
    • group the vertices in two clusters according to their voltages *
    • store resulting candidate clusters *
    *
  • second, generate k-1 clusters as follows: *
      *
    • pick a vertex v as a cluster 'seed' *
      (Wu/Huberman: most frequent vertex in candidate clusters) *
    • calculate co-occurrence over all candidate clusters of v with each other * vertex *
    • separate co-occurrence counts into high/low; * high vertices constitute a cluster *
    • remove v's vertices from candidate clusters; continue *
    *
  • finally, remaining unassigned vertices are assigned to the kth ("garbage") * cluster. *
* *

NOTE: Depending on how the co-occurrence data splits the data into * clusters, the number of clusters returned by this algorithm may be less than the * number of clusters requested. The number of clusters will never be more than * the number requested, however. * * @author Joshua O'Madadhain * @see "'Finding communities in linear time: a physics approach', Fang Wu and Bernardo Huberman, http://www.hpl.hp.com/research/idl/papers/linear/" * @see VoltageScorer * @see KMeansClusterer */ public class VoltageClusterer { protected int num_candidates; protected KMeansClusterer kmc; protected Random rand; protected Graph g; /** * Creates an instance of a VoltageCluster with the specified parameters. * These are mostly parameters that are passed directly to VoltageScorer * and KMeansClusterer. * * @param g the graph whose vertices are to be clustered * @param num_candidates the number of candidate clusters to create */ public VoltageClusterer(Graph g, int num_candidates) { if (num_candidates < 1) throw new IllegalArgumentException("must generate >=1 candidates"); this.num_candidates = num_candidates; this.kmc = new KMeansClusterer(); rand = new Random(); this.g = g; } protected void setRandomSeed(int random_seed) { rand = new Random(random_seed); } /** * @param v the vertex whose community we wish to discover * @return a community (cluster) centered around v. */ public Collection> getCommunity(V v) { return cluster_internal(v, 2); } /** * Clusters the vertices of g into * num_clusters clusters, based on their connectivity. * @param num_clusters the number of clusters to identify * @return a collection of clusters (sets of vertices) */ public Collection> cluster(int num_clusters) { return cluster_internal(null, num_clusters); } /** * Does the work of getCommunity and cluster. * @param origin the vertex around which clustering is to be done * @param num_clusters the (maximum) number of clusters to find * @return a collection of clusters (sets of vertices) */ protected Collection> cluster_internal(V origin, int num_clusters) { // generate candidate clusters // repeat the following 'samples' times: // * pick (widely separated) vertex pair, run VoltageScorer // * use k-means to identify 2 communities in ranked graph // * store resulting candidate communities ArrayList v_array = new ArrayList(g.getVertices()); LinkedList> candidates = new LinkedList>(); for (int j = 0; j < num_candidates; j++) { V source; if (origin == null) source = v_array.get((int)(rand.nextDouble() * v_array.size())); else source = origin; V target = null; do { target = v_array.get((int)(rand.nextDouble() * v_array.size())); } while (source == target); VoltageScorer vs = new VoltageScorer(g, source, target); vs.evaluate(); Map voltage_ranks = new HashMap(); for (V v : g.getVertices()) voltage_ranks.put(v, new double[] {vs.getVertexScore(v)}); // addOneCandidateCluster(candidates, voltage_ranks); addTwoCandidateClusters(candidates, voltage_ranks); } // repeat the following k-1 times: // * pick a vertex v as a cluster seed // (Wu/Huberman: most frequent vertex in candidates) // * calculate co-occurrence (in candidate clusters) // of this vertex with all others // * use k-means to separate co-occurrence counts into high/low; // high vertices are a cluster // * remove v's vertices from candidate clusters Collection> clusters = new LinkedList>(); Set remaining = new HashSet(g.getVertices()); List seed_candidates = getSeedCandidates(candidates); int seed_index = 0; for (int j = 0; j < (num_clusters - 1); j++) { if (remaining.isEmpty()) break; V seed; if (seed_index == 0 && origin != null) seed = origin; else { do { seed = seed_candidates.get(seed_index++); } while (!remaining.contains(seed)); } Map occur_counts = getObjectCounts(candidates, seed); if (occur_counts.size() < 2) break; // now that we have the counts, cluster them... try { Collection> high_low = kmc.cluster(occur_counts, 2); // ...get the cluster with the highest-valued centroid... Iterator> h_iter = high_low.iterator(); Map cluster1 = h_iter.next(); Map cluster2 = h_iter.next(); double[] centroid1 = DiscreteDistribution.mean(cluster1.values()); double[] centroid2 = DiscreteDistribution.mean(cluster2.values()); Set new_cluster; if (centroid1[0] >= centroid2[0]) new_cluster = cluster1.keySet(); else new_cluster = cluster2.keySet(); // ...remove the elements of new_cluster from each candidate... for (Set cluster : candidates) cluster.removeAll(new_cluster); clusters.add(new_cluster); remaining.removeAll(new_cluster); } catch (NotEnoughClustersException nece) { // all remaining vertices are in the same cluster break; } } // identify remaining vertices (if any) as a 'garbage' cluster if (!remaining.isEmpty()) clusters.add(remaining); return clusters; } /** * Do k-means with three intervals and pick the smaller two clusters * (presumed to be on the ends); this is closer to the Wu-Huberman method. * @param candidates the list of clusters to populate * @param voltage_ranks the voltage values for each vertex */ protected void addTwoCandidateClusters(LinkedList> candidates, Map voltage_ranks) { try { List> clusters = new ArrayList>(kmc.cluster(voltage_ranks, 3)); boolean b01 = clusters.get(0).size() > clusters.get(1).size(); boolean b02 = clusters.get(0).size() > clusters.get(2).size(); boolean b12 = clusters.get(1).size() > clusters.get(2).size(); if (b01 && b02) { candidates.add(clusters.get(1).keySet()); candidates.add(clusters.get(2).keySet()); } else if (!b01 && b12) { candidates.add(clusters.get(0).keySet()); candidates.add(clusters.get(2).keySet()); } else if (!b02 && !b12) { candidates.add(clusters.get(0).keySet()); candidates.add(clusters.get(1).keySet()); } } catch (NotEnoughClustersException e) { // no valid candidates, continue } } /** * alternative to addTwoCandidateClusters(): cluster vertices by voltages into 2 clusters. * We only consider the smaller of the two clusters returned * by k-means to be a 'true' cluster candidate; the other is a garbage cluster. * @param candidates the list of clusters to populate * @param voltage_ranks the voltage values for each vertex */ protected void addOneCandidateCluster(LinkedList> candidates, Map voltage_ranks) { try { List> clusters; clusters = new ArrayList>(kmc.cluster(voltage_ranks, 2)); if (clusters.get(0).size() < clusters.get(1).size()) candidates.add(clusters.get(0).keySet()); else candidates.add(clusters.get(1).keySet()); } catch (NotEnoughClustersException e) { // no valid candidates, continue } } /** * Returns a list of cluster seeds, ranked in decreasing order * of number of appearances in the specified collection of candidate * clusters. * @param candidates the set of candidate clusters * @return a set of cluster seeds */ protected List getSeedCandidates(Collection> candidates) { final Map occur_counts = getObjectCounts(candidates, null); ArrayList occurrences = new ArrayList(occur_counts.keySet()); Collections.sort(occurrences, new MapValueArrayComparator(occur_counts)); // System.out.println("occurrences: "); for (int i = 0; i < occurrences.size(); i++) System.out.println(occur_counts.get(occurrences.get(i))[0]); return occurrences; } protected Map getObjectCounts(Collection> candidates, V seed) { Map occur_counts = new HashMap(); for (V v : g.getVertices()) occur_counts.put(v, new double[]{0}); for (Set candidate : candidates) { if (seed == null) System.out.println(candidate.size()); if (seed == null || candidate.contains(seed)) { for (V element : candidate) { double[] count = occur_counts.get(element); count[0]++; } } } if (seed == null) { System.out.println("occur_counts size: " + occur_counts.size()); for (V v : occur_counts.keySet()) System.out.println(occur_counts.get(v)[0]); } return occur_counts; } protected class MapValueArrayComparator implements Comparator { private Map map; protected MapValueArrayComparator(Map map) { this.map = map; } public int compare(V o1, V o2) { double[] count0 = map.get(o1); double[] count1 = map.get(o2); if (count0[0] < count1[0]) return 1; else if (count0[0] > count1[0]) return -1; return 0; } } } WeakComponentClusterer.java000066400000000000000000000045121276402340000353240ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.cluster; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.Queue; import java.util.Set; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Graph; /** * Finds all weak components in a graph as sets of vertex sets. A weak component is defined as * a maximal subgraph in which all pairs of vertices in the subgraph are reachable from one * another in the underlying undirected subgraph. *

This implementation identifies components as sets of vertex sets. * To create the induced graphs from any or all of these vertex sets, * see algorithms.filters.FilterUtils. *

* Running time: O(|V| + |E|) where |V| is the number of vertices and |E| is the number of edges. * @author Scott White */ public class WeakComponentClusterer implements Function, Set>> { /** * Extracts the weak components from a graph. * @param graph the graph whose weak components are to be extracted * @return the list of weak components */ public Set> apply(Graph graph) { Set> clusterSet = new HashSet>(); HashSet unvisitedVertices = new HashSet(graph.getVertices()); while (!unvisitedVertices.isEmpty()) { Set cluster = new HashSet(); V root = unvisitedVertices.iterator().next(); unvisitedVertices.remove(root); cluster.add(root); Queue queue = new LinkedList(); queue.add(root); while (!queue.isEmpty()) { V currentVertex = queue.remove(); Collection neighbors = graph.getNeighbors(currentVertex); for(V neighbor : neighbors) { if (unvisitedVertices.contains(neighbor)) { queue.add(neighbor); unvisitedVertices.remove(neighbor); cluster.add(neighbor); } } } clusterSet.add(cluster); } return clusterSet; } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/package.html000066400000000000000000000021521276402340000323540ustar00rootroot00000000000000 Mechanisms for identifying clusters in graphs. Where these clusters define disjoint sets of vertices, they may be used to define a VertexPartition for more convenient manipulation of the vertex/set relationships. Current clustering algorithms include:

  • BicomponentClusterer: finds all subsets of vertices for which at least 2 vertices must be removed in order to disconnect the induced subgraphs.
  • EdgeBetweennessClusterer: identifies vertex clusters by removing the edges of the highest 'betweenness' scores (see the importance/scoring package).
  • VoltageClusterer: Clusters vertices based on their ranks as calculated by VoltageRanker.
  • WeakComponentVertexClusterer: Clusters vertices based on their membership in weakly connected components of a graph.
jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/000077500000000000000000000000001276402340000300625ustar00rootroot00000000000000EdgePredicateFilter.java000066400000000000000000000036711276402340000345100ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/* * Created on May 19, 2008 * * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.filters; import com.google.common.base.Predicate; import edu.uci.ics.jung.graph.Graph; /** * Transforms the input graph into one which contains only those edges * that pass the specified Predicate. The filtered graph * is a copy of the original graph (same type, uses the same vertex and * edge objects). All vertices from the original graph * are copied into the new graph (even if they are not incident to any * edges in the new graph). * * @author Joshua O'Madadhain */ public class EdgePredicateFilter implements Filter { protected Predicate edge_pred; /** * Creates an instance based on the specified edge Predicate. * @param edge_pred the predicate that specifies which edges to add to the filtered graph */ public EdgePredicateFilter(Predicate edge_pred) { this.edge_pred = edge_pred; } @SuppressWarnings("unchecked") public Graph apply(Graph g) { Graph filtered; try { filtered = g.getClass().newInstance(); } catch (InstantiationException e) { throw new RuntimeException("Unable to create copy of existing graph: ", e); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to create copy of existing graph: ", e); } for (V v : g.getVertices()) filtered.addVertex(v); for (E e : g.getEdges()) { if (edge_pred.apply(e)) filtered.addEdge(e, g.getIncidentVertices(e)); } return filtered; } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/Filter.java000066400000000000000000000013201276402340000321460ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.filters; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Graph; /** * An interface for classes that return a subset of the input Graph * as a Graph. The Graph returned may be either a * new graph or a view into an existing graph; the documentation for the filter * must specify which. * * @author danyelf */ public interface Filter extends Function, Graph>{ } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/FilterUtils.java000066400000000000000000000060671276402340000332040ustar00rootroot00000000000000/** * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Jun 7, 2008 * */ package edu.uci.ics.jung.algorithms.filters; import java.util.ArrayList; import java.util.Collection; import edu.uci.ics.jung.graph.Hypergraph; /** * Utility methods relating to filtering. */ public class FilterUtils { /** * Creates the induced subgraph from graph whose vertex set * is equal to vertices. The graph returned has * vertices as its vertex set, and includes all edges from * graph which are incident only to elements of * vertices. * * @param the vertex type * @param the edge type * @param the graph type * @param vertices the subset of graph's vertices around * which the subgraph is to be constructed * @param graph the graph whose subgraph is to be constructed * @return the subgraph induced by vertices * @throws IllegalArgumentException if any vertex in * vertices is not in graph */ @SuppressWarnings("unchecked") public static > G createInducedSubgraph(Collection vertices, G graph) { G subgraph = null; try { subgraph = (G)graph.getClass().newInstance(); for (V v : vertices) { if (!graph.containsVertex(v)) throw new IllegalArgumentException("Vertex " + v + " is not an element of " + graph); subgraph.addVertex(v); } for (E e : graph.getEdges()) { Collection incident = graph.getIncidentVertices(e); if (vertices.containsAll(incident)) subgraph.addEdge(e, incident, graph.getEdgeType(e)); } } catch (InstantiationException e) { throw new RuntimeException("Unable to create copy of existing graph: ", e); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to create copy of existing graph: ", e); } return subgraph; } /** * Creates the induced subgraphs of graph associated with each * element of vertex_collections. * Note that these vertex collections need not be disjoint. * @param the vertex type * @param the edge type * @param the graph type * @param vertex_collections the collections of vertex collections to be * used to induce the subgraphs * @param graph the graph whose subgraphs are to be created * @return the induced subgraphs of graph associated with each * element of vertex_collections */ public static > Collection createAllInducedSubgraphs(Collection> vertex_collections, G graph) { Collection subgraphs = new ArrayList(); for (Collection vertex_set : vertex_collections) subgraphs.add(createInducedSubgraph(vertex_set, graph)); return subgraphs; } } KNeighborhoodFilter.java000066400000000000000000000102071276402340000345360ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * Created on Dec 26, 2001 * */ package edu.uci.ics.jung.algorithms.filters; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import edu.uci.ics.jung.algorithms.filters.Filter; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Pair; /** * A filter used to extract the k-neighborhood around one or more root node(s). * The k-neighborhood is defined as the subgraph induced by the set of * vertices that are k or fewer hops (unweighted shortest-path distance) * away from the root node. * * @author Danyel Fisher */ public class KNeighborhoodFilter implements Filter { /** * The type of edge to follow for defining the neighborhood. */ public static enum EdgeType { IN_OUT, IN, OUT } private Set rootNodes; private int radiusK; private EdgeType edgeType; /** * Constructs a new instance of the filter. * @param rootNodes the set of root nodes * @param radiusK the neighborhood radius around the root set * @param edgeType 0 for in/out edges, 1 for in-edges, 2 for out-edges */ public KNeighborhoodFilter(Set rootNodes, int radiusK, EdgeType edgeType) { this.rootNodes = rootNodes; this.radiusK = radiusK; this.edgeType = edgeType; } /** * Constructs a new instance of the filter. * @param rootNode the root node * @param radiusK the neighborhood radius around the root set * @param edgeType 0 for in/out edges, 1 for in-edges, 2 for out-edges */ public KNeighborhoodFilter(V rootNode, int radiusK, EdgeType edgeType) { this.rootNodes = new HashSet(); this.rootNodes.add(rootNode); this.radiusK = radiusK; this.edgeType = edgeType; } /** * Constructs an unassembled graph containing the k-neighborhood around the root node(s). */ @SuppressWarnings("unchecked") public Graph apply(Graph graph) { // generate a Set of Vertices we want // add all to the UG int currentDepth = 0; List currentVertices = new ArrayList(); Set visitedVertices = new HashSet(); Set visitedEdges = new HashSet(); Set acceptedVertices = new HashSet(); //Copy, mark, and add all the root nodes to the new subgraph for (V currentRoot : rootNodes) { visitedVertices.add(currentRoot); acceptedVertices.add(currentRoot); currentVertices.add(currentRoot); } ArrayList newVertices = null; //Use BFS to locate the neighborhood around the root nodes within distance k while (currentDepth < radiusK) { newVertices = new ArrayList(); for (V currentVertex : currentVertices) { Collection edges = null; switch (edgeType) { case IN_OUT : edges = graph.getIncidentEdges(currentVertex); break; case IN : edges = graph.getInEdges(currentVertex); break; case OUT : edges = graph.getOutEdges(currentVertex); break; } for (E currentEdge : edges) { V currentNeighbor = graph.getOpposite(currentVertex, currentEdge); if (!visitedEdges.contains(currentEdge)) { visitedEdges.add(currentEdge); if (!visitedVertices.contains(currentNeighbor)) { visitedVertices.add(currentNeighbor); acceptedVertices.add(currentNeighbor); newVertices.add(currentNeighbor); } } } } currentVertices = newVertices; currentDepth++; } Graph ug = null; try { ug = graph.getClass().newInstance(); for(E edge : graph.getEdges()) { Pair endpoints = graph.getEndpoints(edge); if(acceptedVertices.containsAll(endpoints)) { ug.addEdge(edge, endpoints.getFirst(), endpoints.getSecond()); } } } catch (InstantiationException e) { throw new RuntimeException("Unable to create copy of existing graph: ", e); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to create copy of existing graph: ", e); } return ug; } } VertexPredicateFilter.java000066400000000000000000000042111276402340000351100ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/* * Created on May 19, 2008 * * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.filters; import java.util.Collection; import com.google.common.base.Predicate; import edu.uci.ics.jung.graph.Graph; /** * Transforms the input graph into one which contains only those vertices * that pass the specified Predicate. The filtered graph * is a copy of the original graph (same type, uses the same vertex and * edge objects). Only those edges whose entire incident vertex collection * passes the predicate are copied into the new graph. * * @author Joshua O'Madadhain */ public class VertexPredicateFilter implements Filter { protected Predicate vertex_pred; /** * Creates an instance based on the specified vertex Predicate. * @param vertex_pred the predicate that specifies which vertices to add to the filtered graph */ public VertexPredicateFilter(Predicate vertex_pred) { this.vertex_pred = vertex_pred; } @SuppressWarnings("unchecked") public Graph apply(Graph g) { Graph filtered; try { filtered = g.getClass().newInstance(); } catch (InstantiationException e) { throw new RuntimeException("Unable to create copy of existing graph: ", e); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to create copy of existing graph: ", e); } for (V v : g.getVertices()) if (vertex_pred.apply(v)) filtered.addVertex(v); Collection filtered_vertices = filtered.getVertices(); for (E e : g.getEdges()) { Collection incident = g.getIncidentVertices(e); if (filtered_vertices.containsAll(incident)) filtered.addEdge(e, incident); } return filtered; } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/package.html000066400000000000000000000013521276402340000323440ustar00rootroot00000000000000 Filtering mechanisms that produce subgraphs of an original graph. Currently includes:
  • Filter: an interface for graph filters
  • {Edge,Vertex}PredicateFilter: graph filters that return the induced subgraph according to the specified edge or vertex Predicate, respectively.
  • KNeighborhoodFilter: a filter that returns the subgraph induced by vertices within (unweighted) distance k of a specified vertex.
jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/flows/000077500000000000000000000000001276402340000275445ustar00rootroot00000000000000EdmondsKarpMaxFlow.java000066400000000000000000000257321276402340000340460ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/flows/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.flows; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import com.google.common.base.Function; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.util.IterativeProcess; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.util.EdgeType; /** * Implements the Edmonds-Karp maximum flow algorithm for solving the maximum flow problem. * After the algorithm is executed, * the input {@code Map} is populated with a {@code Number} for each edge that indicates * the flow along that edge. *

* An example of using this algorithm is as follows: *

 * EdmondsKarpMaxFlow ek = new EdmondsKarpMaxFlow(graph, source, sink, edge_capacities, edge_flows, 
 * edge_factory);
 * ek.evaluate(); // This instructs the class to compute the max flow
 * 
* * @see "Introduction to Algorithms by Cormen, Leiserson, Rivest, and Stein." * @see "Network Flows by Ahuja, Magnanti, and Orlin." * @see "Theoretical improvements in algorithmic efficiency for network flow problems by Edmonds and Karp, 1972." * @author Scott White, adapted to jung2 by Tom Nelson */ public class EdmondsKarpMaxFlow extends IterativeProcess { private DirectedGraph mFlowGraph; private DirectedGraph mOriginalGraph; private V source; private V target; private int mMaxFlow; private Set mSourcePartitionNodes; private Set mSinkPartitionNodes; private Set mMinCutEdges; private Map residualCapacityMap = new HashMap(); private Map parentMap = new HashMap(); private Map parentCapacityMap = new HashMap(); private Function edgeCapacityTransformer; private Map edgeFlowMap; private Supplier edgeFactory; /** * Constructs a new instance of the algorithm solver for a given graph, source, and sink. * Source and sink vertices must be elements of the specified graph, and must be * distinct. * @param directedGraph the flow graph * @param source the source vertex * @param sink the sink vertex * @param edgeCapacityTransformer the Function that gets the capacity for each edge. * @param edgeFlowMap the map where the solver will place the value of the flow for each edge * @param edgeFactory used to create new edge instances for backEdges */ @SuppressWarnings("unchecked") public EdmondsKarpMaxFlow(DirectedGraph directedGraph, V source, V sink, Function edgeCapacityTransformer, Map edgeFlowMap, Supplier edgeFactory) { if(directedGraph.getVertices().contains(source) == false || directedGraph.getVertices().contains(sink) == false) { throw new IllegalArgumentException("source and sink vertices must be elements of the specified graph"); } if (source.equals(sink)) { throw new IllegalArgumentException("source and sink vertices must be distinct"); } mOriginalGraph = directedGraph; this.source = source; this.target = sink; this.edgeFlowMap = edgeFlowMap; this.edgeCapacityTransformer = edgeCapacityTransformer; this.edgeFactory = edgeFactory; try { mFlowGraph = directedGraph.getClass().newInstance(); for(E e : mOriginalGraph.getEdges()) { mFlowGraph.addEdge(e, mOriginalGraph.getSource(e), mOriginalGraph.getDest(e), EdgeType.DIRECTED); } for(V v : mOriginalGraph.getVertices()) { mFlowGraph.addVertex(v); } } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } mMaxFlow = 0; mSinkPartitionNodes = new HashSet(); mSourcePartitionNodes = new HashSet(); mMinCutEdges = new HashSet(); } private void clearParentValues() { parentMap.clear(); parentCapacityMap.clear(); parentCapacityMap.put(source, Integer.MAX_VALUE); parentMap.put(source, source); } protected boolean hasAugmentingPath() { mSinkPartitionNodes.clear(); mSourcePartitionNodes.clear(); mSinkPartitionNodes.addAll(mFlowGraph.getVertices()); Set visitedEdgesMap = new HashSet(); Queue queue = new LinkedList(); queue.add(source); while (!queue.isEmpty()) { V currentVertex = queue.remove(); mSinkPartitionNodes.remove(currentVertex); mSourcePartitionNodes.add(currentVertex); Number currentCapacity = parentCapacityMap.get(currentVertex); Collection neighboringEdges = mFlowGraph.getOutEdges(currentVertex); for (E neighboringEdge : neighboringEdges) { V neighboringVertex = mFlowGraph.getDest(neighboringEdge); Number residualCapacity = residualCapacityMap.get(neighboringEdge); if (residualCapacity.intValue() <= 0 || visitedEdgesMap.contains(neighboringEdge)) continue; V neighborsParent = parentMap.get(neighboringVertex); Number neighborCapacity = parentCapacityMap.get(neighboringVertex); int newCapacity = Math.min(residualCapacity.intValue(),currentCapacity.intValue()); if ((neighborsParent == null) || newCapacity > neighborCapacity.intValue()) { parentMap.put(neighboringVertex, currentVertex); parentCapacityMap.put(neighboringVertex, new Integer(newCapacity)); visitedEdgesMap.add(neighboringEdge); if (neighboringVertex != target) { queue.add(neighboringVertex); } } } } boolean hasAugmentingPath = false; Number targetsParentCapacity = parentCapacityMap.get(target); if (targetsParentCapacity != null && targetsParentCapacity.intValue() > 0) { updateResidualCapacities(); hasAugmentingPath = true; } clearParentValues(); return hasAugmentingPath; } @Override public void step() { while (hasAugmentingPath()) { } computeMinCut(); // return 0; } private void computeMinCut() { for (E e : mOriginalGraph.getEdges()) { V source = mOriginalGraph.getSource(e); V destination = mOriginalGraph.getDest(e); if (mSinkPartitionNodes.contains(source) && mSinkPartitionNodes.contains(destination)) { continue; } if (mSourcePartitionNodes.contains(source) && mSourcePartitionNodes.contains(destination)) { continue; } if (mSinkPartitionNodes.contains(source) && mSourcePartitionNodes.contains(destination)) { continue; } mMinCutEdges.add(e); } } /** * @return the value of the maximum flow from the source to the sink. */ public int getMaxFlow() { return mMaxFlow; } /** * @return the nodes which share the same partition (as defined by the min-cut edges) * as the sink node. */ public Set getNodesInSinkPartition() { return mSinkPartitionNodes; } /** * @return the nodes which share the same partition (as defined by the min-cut edges) * as the source node. */ public Set getNodesInSourcePartition() { return mSourcePartitionNodes; } /** * @return the edges in the minimum cut. */ public Set getMinCutEdges() { return mMinCutEdges; } /** * @return the graph for which the maximum flow is calculated. */ public DirectedGraph getFlowGraph() { return mFlowGraph; } @Override protected void initializeIterations() { parentCapacityMap.put(source, Integer.MAX_VALUE); parentMap.put(source, source); List edgeList = new ArrayList(mFlowGraph.getEdges()); for (int eIdx=0;eIdx< edgeList.size();eIdx++) { E edge = edgeList.get(eIdx); Number capacity = edgeCapacityTransformer.apply(edge); if (capacity == null) { throw new IllegalArgumentException("Edge capacities must be provided in Function passed to constructor"); } residualCapacityMap.put(edge, capacity); V source = mFlowGraph.getSource(edge); V destination = mFlowGraph.getDest(edge); if(mFlowGraph.isPredecessor(source, destination) == false) { E backEdge = edgeFactory.get(); mFlowGraph.addEdge(backEdge, destination, source, EdgeType.DIRECTED); residualCapacityMap.put(backEdge, 0); } } } @Override protected void finalizeIterations() { for (E currentEdge : mFlowGraph.getEdges()) { Number capacity = edgeCapacityTransformer.apply(currentEdge); Number residualCapacity = residualCapacityMap.get(currentEdge); if (capacity != null) { Integer flowValue = new Integer(capacity.intValue()-residualCapacity.intValue()); this.edgeFlowMap.put(currentEdge, flowValue); } } Set backEdges = new HashSet(); for (E currentEdge: mFlowGraph.getEdges()) { if (edgeCapacityTransformer.apply(currentEdge) == null) { backEdges.add(currentEdge); } else { residualCapacityMap.remove(currentEdge); } } for(E e : backEdges) { mFlowGraph.removeEdge(e); } } private void updateResidualCapacities() { Number augmentingPathCapacity = parentCapacityMap.get(target); mMaxFlow += augmentingPathCapacity.intValue(); V currentVertex = target; V parentVertex = null; while ((parentVertex = parentMap.get(currentVertex)) != currentVertex) { E currentEdge = mFlowGraph.findEdge(parentVertex, currentVertex); Number residualCapacity = residualCapacityMap.get(currentEdge); residualCapacity = residualCapacity.intValue() - augmentingPathCapacity.intValue(); residualCapacityMap.put(currentEdge, residualCapacity); E backEdge = mFlowGraph.findEdge(currentVertex, parentVertex); residualCapacity = residualCapacityMap.get(backEdge); residualCapacity = residualCapacity.intValue() + augmentingPathCapacity.intValue(); residualCapacityMap.put(backEdge, residualCapacity); currentVertex = parentVertex; } } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/flows/package.html000066400000000000000000000005441276402340000320300ustar00rootroot00000000000000 Methods for calculating properties relating to network flows (such as max flow/min cut). jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/000077500000000000000000000000001276402340000305635ustar00rootroot00000000000000EvolvingGraphGenerator.java000066400000000000000000000014301276402340000357670ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.generators; /** * An interface for algorithms that generate graphs that evolve iteratively. * @author Scott White */ public interface EvolvingGraphGenerator extends GraphGenerator { /** * Instructs the algorithm to evolve the graph N steps. * @param numSteps number of steps to iterate from the current state */ void evolveGraph(int numSteps); /** * Retrieves the total number of steps elapsed. * @return number of elapsed steps */ int numIterations(); } GraphGenerator.java000066400000000000000000000007771276402340000342720ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.generators; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.Graph; /** * An interface for algorithms that generate graphs. * @author Scott White */ public interface GraphGenerator extends Supplier>{ } Lattice2DGenerator.java000066400000000000000000000154161276402340000350000ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/* * Copyright (c) 2009, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.generators; import java.util.ArrayList; import java.util.List; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.EdgeType; /** * Simple generator of an m x n lattice where each vertex * is incident with each of its neighbors (to the left, right, up, and down). * May be toroidal, in which case the vertices on the edges are connected to * their counterparts on the opposite edges as well. * *

If the graph Supplier supplied has a default edge type of {@code EdgeType.DIRECTED}, * then edges will be created in both directions between adjacent vertices. * * @author Joshua O'Madadhain */ public class Lattice2DGenerator implements GraphGenerator { protected int row_count; protected int col_count; protected boolean is_toroidal; protected boolean is_directed; protected Supplier> graph_factory; protected Supplier vertex_factory; protected Supplier edge_factory; private List v_array; /** * Constructs a generator of square lattices of size {@code latticeSize} * with the specified parameters. * * @param graph_factory used to create the {@code Graph} for the lattice * @param vertex_factory used to create the lattice vertices * @param edge_factory used to create the lattice edges * @param latticeSize the number of rows and columns of the lattice * @param isToroidal if true, the created lattice wraps from top to bottom and left to right */ public Lattice2DGenerator(Supplier> graph_factory, Supplier vertex_factory, Supplier edge_factory, int latticeSize, boolean isToroidal) { this(graph_factory, vertex_factory, edge_factory, latticeSize, latticeSize, isToroidal); } /** * Creates a generator of {@code row_count} x {@code col_count} lattices * with the specified parameters. * * @param graph_factory used to create the {@code Graph} for the lattice * @param vertex_factory used to create the lattice vertices * @param edge_factory used to create the lattice edges * @param row_count the number of rows in the lattice * @param col_count the number of columns in the lattice * @param isToroidal if true, the created lattice wraps from top to bottom and left to right */ public Lattice2DGenerator(Supplier> graph_factory, Supplier vertex_factory, Supplier edge_factory, int row_count, int col_count, boolean isToroidal) { if (row_count < 2 || col_count < 2) { throw new IllegalArgumentException("Row and column counts must each be at least 2."); } this.row_count = row_count; this.col_count = col_count; this.is_toroidal = isToroidal; this.graph_factory = graph_factory; this.vertex_factory = vertex_factory; this.edge_factory = edge_factory; this.is_directed = (graph_factory.get().getDefaultEdgeType() == EdgeType.DIRECTED); } /** * Generates a graph based on the constructor-specified settings. * * @return the generated graph */ public Graph get() { int vertex_count = row_count * col_count; Graph graph = graph_factory.get(); v_array = new ArrayList(vertex_count); for (int i = 0; i < vertex_count; i++) { V v = vertex_factory.get(); graph.addVertex(v); v_array.add(i, v); } int start = is_toroidal ? 0 : 1; int end_row = is_toroidal ? row_count : row_count - 1; int end_col = is_toroidal ? col_count : col_count - 1; // fill in edges // down for (int i = 0; i < end_row; i++) for (int j = 0; j < col_count; j++) graph.addEdge(edge_factory.get(), getVertex(i,j), getVertex(i+1, j)); // right for (int i = 0; i < row_count; i++) for (int j = 0; j < end_col; j++) graph.addEdge(edge_factory.get(), getVertex(i,j), getVertex(i, j+1)); // if the graph is directed, fill in the edges going the other direction... if (graph.getDefaultEdgeType() == EdgeType.DIRECTED) { // up for (int i = start; i < row_count; i++) for (int j = 0; j < col_count; j++) graph.addEdge(edge_factory.get(), getVertex(i,j), getVertex(i-1, j)); // left for (int i = 0; i < row_count; i++) for (int j = start; j < col_count; j++) graph.addEdge(edge_factory.get(), getVertex(i,j), getVertex(i, j-1)); } return graph; } /** * Returns the number of edges found in a lattice of this generator's specifications. * (This is useful for subclasses that may modify the generated graphs to add more edges.) * * @return the number of edges that this generator will generate */ public int getGridEdgeCount() { int boundary_adjustment = (is_toroidal ? 0 : 1); int vertical_edge_count = col_count * (row_count - boundary_adjustment); int horizontal_edge_count = row_count * (col_count - boundary_adjustment); return (vertical_edge_count + horizontal_edge_count) * (is_directed ? 2 : 1); } protected int getIndex(int i, int j) { return ((mod(i, row_count)) * col_count) + (mod(j, col_count)); } protected int mod(int i, int modulus) { int i_mod = i % modulus; return i_mod >= 0 ? i_mod : i_mod + modulus; } /** * @param i row index into the lattice * @param j column index into the lattice * @return the vertex at position ({@code i mod row_count, j mod col_count}) */ protected V getVertex(int i, int j) { return v_array.get(getIndex(i, j)); } /** * @param i row index into the lattice * @return the {@code i}th vertex (counting row-wise) */ protected V getVertex(int i) { return v_array.get(i); } /** * @param i index of the vertex whose row we want * @return the row in which the vertex with index {@code i} is found */ protected int getRow(int i) { return i / col_count; } /** * @param i index of the vertex whose column we want * @return the column in which the vertex with index {@code i} is found */ protected int getCol(int i) { return i % col_count; } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/package.html000066400000000000000000000005251276402340000330460ustar00rootroot00000000000000 Methods for generating new (often random) graphs with various properties. jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/000077500000000000000000000000001276402340000320435ustar00rootroot00000000000000BarabasiAlbertGenerator.java000066400000000000000000000236471276402340000373500ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.generators.random; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.generators.EvolvingGraphGenerator; import edu.uci.ics.jung.algorithms.util.WeightedChoice; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.MultiGraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** *

* Simple evolving scale-free random graph generator. At each time step, a new * vertex is created and is connected to existing vertices according to the * principle of "preferential attachment", whereby vertices with higher degree * have a higher probability of being selected for attachment. * *

* At a given timestep, the probability p of creating an edge * between an existing vertex v and the newly added vertex is * *

 * p = (degree(v) + 1) / (|E| + |V|);
 * 
* *

* where |E| and |V| are, respectively, the number of * edges and vertices currently in the network (counting neither the new vertex * nor the other edges that are being attached to it). * *

* Note that the formula specified in the original paper (cited below) was * *

 * p = degree(v) / |E|
 * 
* * *

* However, this would have meant that the probability of attachment for any * existing isolated vertex would be 0. This version uses Lagrangian smoothing * to give each existing vertex a positive attachment probability. * *

* The graph created may be either directed or undirected (controlled by a * constructor parameter); the default is undirected. If the graph is specified * to be directed, then the edges added will be directed from the newly added * vertex u to the existing vertex v, with probability proportional to the * indegree of v (number of edges directed towards v). If the graph is specified * to be undirected, then the (undirected) edges added will connect u to v, with * probability proportional to the degree of v. * *

* The parallel constructor parameter specifies whether parallel * edges may be created. * * @see "A.-L. Barabasi and R. Albert, Emergence of scaling in random networks, Science 286, 1999." * @author Scott White * @author Joshua O'Madadhain * @author Tom Nelson - adapted to jung2 * @author James Marchant */ public class BarabasiAlbertGenerator implements EvolvingGraphGenerator { private Graph mGraph = null; private int mNumEdgesToAttachPerStep; private int mElapsedTimeSteps; private Random mRandom; protected List vertex_index; protected int init_vertices; protected Map index_vertex; protected Supplier> graphFactory; protected Supplier vertexFactory; protected Supplier edgeFactory; /** * Constructs a new instance of the generator. * * @param graphFactory * factory for graphs of the appropriate type * @param vertexFactory * factory for vertices of the appropriate type * @param edgeFactory * factory for edges of the appropriate type * @param init_vertices * number of unconnected 'seed' vertices that the graph should * start with * @param numEdgesToAttach * the number of edges that should be attached from the new * vertex to pre-existing vertices at each time step * @param seed * random number seed * @param seedVertices * storage for the seed vertices that this graph creates */ // TODO: seedVertices is a bizarre way of exposing that information, // refactor public BarabasiAlbertGenerator(Supplier> graphFactory, Supplier vertexFactory, Supplier edgeFactory, int init_vertices, int numEdgesToAttach, int seed, Set seedVertices) { Preconditions.checkArgument(init_vertices > 0, "Number of initial unconnected 'seed' vertices must be positive"); Preconditions.checkArgument(numEdgesToAttach > 0, "Number of edges to attach at each time step must be positive"); Preconditions.checkArgument(numEdgesToAttach <= init_vertices, "Number of edges to attach at each time step must less than or equal to the number of initial vertices"); mNumEdgesToAttachPerStep = numEdgesToAttach; mRandom = new Random(seed); this.graphFactory = graphFactory; this.vertexFactory = vertexFactory; this.edgeFactory = edgeFactory; this.init_vertices = init_vertices; initialize(seedVertices); } /** * Constructs a new instance of the generator, whose output will be an * undirected graph, and which will use the current time as a seed for the * random number generation. * * @param graphFactory * factory for graphs of the appropriate type * @param vertexFactory * factory for vertices of the appropriate type * @param edgeFactory * factory for edges of the appropriate type * @param init_vertices * number of vertices that the graph should start with * @param numEdgesToAttach * the number of edges that should be attached from the new * vertex to pre-existing vertices at each time step * @param seedVertices * storage for the seed vertices that this graph creates */ public BarabasiAlbertGenerator(Supplier> graphFactory, Supplier vertexFactory, Supplier edgeFactory, int init_vertices, int numEdgesToAttach, Set seedVertices) { this(graphFactory, vertexFactory, edgeFactory, init_vertices, numEdgesToAttach, (int) System.currentTimeMillis(), seedVertices); } private void initialize(Set seedVertices) { mGraph = graphFactory.get(); vertex_index = new ArrayList(2 * init_vertices); index_vertex = new HashMap(2 * init_vertices); for (int i = 0; i < init_vertices; i++) { V v = vertexFactory.get(); mGraph.addVertex(v); vertex_index.add(v); index_vertex.put(v, i); seedVertices.add(v); } mElapsedTimeSteps = 0; } public void evolveGraph(int numTimeSteps) { for (int i = 0; i < numTimeSteps; i++) { evolveGraph(); mElapsedTimeSteps++; } } private void evolveGraph() { Collection preexistingNodes = mGraph.getVertices(); V newVertex = vertexFactory.get(); mGraph.addVertex(newVertex); // generate and store the new edges; don't add them to the graph // yet because we don't want to bias the degree calculations // (all new edges in a timestep should be added in parallel) Set> added_pairs = createRandomEdges(preexistingNodes, newVertex, mNumEdgesToAttachPerStep); for (Pair pair : added_pairs) { V v1 = pair.getFirst(); V v2 = pair.getSecond(); if (mGraph.getDefaultEdgeType() != EdgeType.UNDIRECTED || !mGraph.isNeighbor(v1, v2)) mGraph.addEdge(edgeFactory.get(), pair); } // now that we're done attaching edges to this new vertex, // add it to the index vertex_index.add(newVertex); index_vertex.put(newVertex, new Integer(vertex_index.size() - 1)); } private Set> createRandomEdges(Collection preexistingNodes, V newVertex, int numEdges) { Set> added_pairs = new HashSet>(numEdges * 3); /* Generate the probability distribution */ Map item_weights = new HashMap(); for (V v : preexistingNodes) { /* * as preexistingNodes is a view onto the vertex set, it will * contain the new node. In the construction of Barabasi-Albert, * there should be no self-loops. */ if (v == newVertex) continue; double degree; double denominator; /* * Attachment probability is dependent on whether the graph is * directed or undirected. * * Subtract 1 from numVertices because we don't want to count * newVertex (which has already been added to the graph, but not to * vertex_index). */ if (mGraph.getDefaultEdgeType() == EdgeType.UNDIRECTED) { degree = mGraph.degree(v); denominator = (2 * mGraph.getEdgeCount()) + mGraph.getVertexCount() - 1; } else { degree = mGraph.inDegree(v); denominator = mGraph.getEdgeCount() + mGraph.getVertexCount() - 1; } double prob = (degree + 1) / denominator; item_weights.put(v, prob); } WeightedChoice nodeProbabilities = new WeightedChoice(item_weights, mRandom); for (int i = 0; i < numEdges; i++) { createRandomEdge(preexistingNodes, newVertex, added_pairs, nodeProbabilities); } return added_pairs; } private void createRandomEdge(Collection preexistingNodes, V newVertex, Set> added_pairs, WeightedChoice weightedProbabilities) { V attach_point; boolean created_edge = false; Pair endpoints; do { attach_point = weightedProbabilities.nextItem(); endpoints = new Pair(newVertex, attach_point); /* * If parallel edges are not allowed, skip attach_point if * already exists; note that because of * the way the new node's edges are added, we only need to check the * list of candidate edges for duplicates. */ if (!(mGraph instanceof MultiGraph)) { if (added_pairs.contains(endpoints)) continue; if (mGraph.getDefaultEdgeType() == EdgeType.UNDIRECTED && added_pairs.contains(new Pair(attach_point, newVertex))) continue; } created_edge = true; } while (!created_edge); added_pairs.add(endpoints); if (mGraph.getDefaultEdgeType() == EdgeType.UNDIRECTED) { added_pairs.add(new Pair(attach_point, newVertex)); } } public int numIterations() { return mElapsedTimeSteps; } public Graph get() { return mGraph; } } EppsteinPowerLawGenerator.java000066400000000000000000000105571276402340000377560ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.generators.random; import java.util.ArrayList; import java.util.List; import java.util.Random; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.generators.GraphGenerator; import edu.uci.ics.jung.graph.Graph; /** * Graph generator that generates undirected graphs with power-law degree distributions. * @author Scott White * @see "A Steady State Model for Graph Power Law by David Eppstein and Joseph Wang" */ public class EppsteinPowerLawGenerator implements GraphGenerator { private int mNumVertices; private int mNumEdges; private int mNumIterations; private double mMaxDegree; private Random mRandom; private Supplier> graphFactory; private Supplier vertexFactory; private Supplier edgeFactory; /** * Creates an instance with the specified factories and specifications. * @param graphFactory the Supplier to use to generate the graph * @param vertexFactory the Supplier to use to create vertices * @param edgeFactory the Supplier to use to create edges * @param numVertices the number of vertices for the generated graph * @param numEdges the number of edges the generated graph will have, should be Theta(numVertices) * @param r the number of iterations to use; the larger the value the better the graph's degree * distribution will approximate a power-law */ public EppsteinPowerLawGenerator(Supplier> graphFactory, Supplier vertexFactory, Supplier edgeFactory, int numVertices, int numEdges, int r) { this.graphFactory = graphFactory; this.vertexFactory = vertexFactory; this.edgeFactory = edgeFactory; mNumVertices = numVertices; mNumEdges = numEdges; mNumIterations = r; mRandom = new Random(); } protected Graph initializeGraph() { Graph graph = null; graph = graphFactory.get(); for(int i=0; i vertices = new ArrayList(graph.getVertices()); while (graph.getEdgeCount() < mNumEdges) { V u = vertices.get((int) (mRandom.nextDouble() * mNumVertices)); V v = vertices.get((int) (mRandom.nextDouble() * mNumVertices)); if (!graph.isSuccessor(v,u)) { graph.addEdge(edgeFactory.get(), u, v); } } double maxDegree = 0; for (V v : graph.getVertices()) { maxDegree = Math.max(graph.degree(v),maxDegree); } mMaxDegree = maxDegree; //(maxDegree+1)*(maxDegree)/2; return graph; } /** * Generates a graph whose degree distribution approximates a power-law. * @return the generated graph */ public Graph get() { Graph graph = initializeGraph(); List vertices = new ArrayList(graph.getVertices()); for (int rIdx = 0; rIdx < mNumIterations; rIdx++) { V v = null; int degree = 0; do { v = vertices.get((int) (mRandom.nextDouble() * mNumVertices)); degree = graph.degree(v); } while (degree == 0); List edges = new ArrayList(graph.getIncidentEdges(v)); E randomExistingEdge = edges.get((int) (mRandom.nextDouble()*degree)); // FIXME: look at email thread on a more efficient RNG for arbitrary distributions V x = vertices.get((int) (mRandom.nextDouble() * mNumVertices)); V y = null; do { y = vertices.get((int) (mRandom.nextDouble() * mNumVertices)); } while (mRandom.nextDouble() > ((graph.degree(y)+1)/mMaxDegree)); if (!graph.isSuccessor(y,x) && x != y) { graph.removeEdge(randomExistingEdge); graph.addEdge(edgeFactory.get(), x, y); } } return graph; } /** * Sets the seed for the random number generator. * @param seed input to the random number generator. */ public void setSeed(long seed) { mRandom.setSeed(seed); } } ErdosRenyiGenerator.java000066400000000000000000000057561276402340000365760ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.generators.random; import java.util.ArrayList; import java.util.List; import java.util.Random; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.generators.GraphGenerator; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedGraph; /** * Generates a random graph using the Erdos-Renyi binomial model * (each pair of vertices is connected with probability p). * * @author William Giordano, Scott White, Joshua O'Madadhain */ public class ErdosRenyiGenerator implements GraphGenerator { private int mNumVertices; private double mEdgeConnectionProbability; private Random mRandom; Supplier> graphFactory; Supplier vertexFactory; Supplier edgeFactory; /** * * @param graphFactory factory for graphs of the appropriate type * @param vertexFactory factory for vertices of the appropriate type * @param edgeFactory factory for edges of the appropriate type * @param numVertices number of vertices graph should have * @param p Connection's probability between 2 vertices */ public ErdosRenyiGenerator(Supplier> graphFactory, Supplier vertexFactory, Supplier edgeFactory, int numVertices,double p) { if (numVertices <= 0) { throw new IllegalArgumentException("A positive # of vertices must be specified."); } mNumVertices = numVertices; if (p < 0 || p > 1) { throw new IllegalArgumentException("p must be between 0 and 1."); } this.graphFactory = graphFactory; this.vertexFactory = vertexFactory; this.edgeFactory = edgeFactory; mEdgeConnectionProbability = p; mRandom = new Random(); } /** * Returns a graph in which each pair of vertices is connected by * an undirected edge with the probability specified by the constructor. */ public Graph get() { UndirectedGraph g = graphFactory.get(); for(int i=0; i list = new ArrayList(g.getVertices()); for (int i = 0; i < mNumVertices-1; i++) { V v_i = list.get(i); for (int j = i+1; j < mNumVertices; j++) { V v_j = list.get(j); if (mRandom.nextDouble() < mEdgeConnectionProbability) { g.addEdge(edgeFactory.get(), v_i, v_j); } } } return g; } /** * Sets the seed of the internal random number generator to {@code seed}. * Enables consistent behavior. * * @param seed the seed to use for the internal random number generator */ public void setSeed(long seed) { mRandom.setSeed(seed); } } KleinbergSmallWorldGenerator.java000066400000000000000000000163621276402340000404110ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random package edu.uci.ics.jung.algorithms.generators.random; /* * Copyright (c) 2009, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ import java.util.HashMap; import java.util.Map; import java.util.Random; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.generators.Lattice2DGenerator; import edu.uci.ics.jung.algorithms.util.WeightedChoice; import edu.uci.ics.jung.graph.Graph; /** * Graph generator that produces a random graph with small world properties. * The underlying model is an mxn (optionally toroidal) lattice. Each node u * has four local connections, one to each of its neighbors, and * in addition 1+ long range connections to some node v where v is chosen randomly according to * probability proportional to d^-alpha where d is the lattice distance between u and v and alpha * is the clustering exponent. * * @see "Navigation in a small world J. Kleinberg, Nature 406(2000), 845." * @author Joshua O'Madadhain */ public class KleinbergSmallWorldGenerator extends Lattice2DGenerator { private double clustering_exponent; private Random random; private int num_connections = 1; /** * Creates an instance with the specified parameters, whose underlying lattice is (a) of size * {@code latticeSize} x {@code latticeSize}, and (b) toroidal. * @param graphFactory factory for graphs of the appropriate type * @param vertexFactory factory for vertices of the appropriate type * @param edgeFactory factory for edges of the appropriate type * @param latticeSize the number of rows and columns of the underlying lattice * @param clusteringExponent the clustering exponent */ public KleinbergSmallWorldGenerator(Supplier> graphFactory, Supplier vertexFactory, Supplier edgeFactory, int latticeSize, double clusteringExponent) { this(graphFactory, vertexFactory, edgeFactory, latticeSize, latticeSize, clusteringExponent); } /** * Creates an instance with the specified parameters, whose underlying lattice is toroidal. * @param graphFactory factory for graphs of the appropriate type * @param vertexFactory factory for vertices of the appropriate type * @param edgeFactory factory for edges of the appropriate type * @param row_count number of rows of the underlying lattice * @param col_count number of columns of the underlying lattice * @param clusteringExponent the clustering exponent */ public KleinbergSmallWorldGenerator(Supplier> graphFactory, Supplier vertexFactory, Supplier edgeFactory, int row_count, int col_count, double clusteringExponent) { super(graphFactory, vertexFactory, edgeFactory, row_count, col_count, true); clustering_exponent = clusteringExponent; initialize(); } /** * Creates an instance with the specified parameters. * @param graphFactory factory for graphs of the appropriate type * @param vertexFactory factory for vertices of the appropriate type * @param edgeFactory factory for edges of the appropriate type * @param row_count number of rows of the underlying lattice * @param col_count number of columns of the underlying lattice * @param clusteringExponent the clustering exponent * @param isToroidal whether the underlying lattice is toroidal */ public KleinbergSmallWorldGenerator(Supplier> graphFactory, Supplier vertexFactory, Supplier edgeFactory, int row_count, int col_count, double clusteringExponent, boolean isToroidal) { super(graphFactory, vertexFactory, edgeFactory, row_count, col_count, isToroidal); clustering_exponent = clusteringExponent; initialize(); } private void initialize() { this.random = new Random(); } /** * Sets the {@code Random} instance used by this instance. Useful for * unit testing. * @param random the {@code Random} instance for this class to use */ public void setRandom(Random random) { this.random = random; } /** * Sets the seed of the internal random number generator. May be used to provide repeatable * experiments. * @param seed the random seed that this class's random number generator is to use */ public void setRandomSeed(long seed) { random.setSeed(seed); } /** * Sets the number of new 'small-world' connections (outgoing edges) to be added to each vertex. * @param num_connections the number of outgoing small-world edges to add to each vertex */ public void setConnectionCount(int num_connections) { if (num_connections <= 0) { throw new IllegalArgumentException("Number of new connections per vertex must be >= 1"); } this.num_connections = num_connections; } /** * @return the number of new 'small-world' connections that will originate at each vertex */ public int getConnectionCount() { return this.num_connections; } /** * Generates a random small world network according to the parameters given * @return a random small world graph */ @Override public Graph get() { Graph graph = super.get(); // TODO: For toroidal graphs, we can make this more clever by pre-creating the WeightedChoice object // and using the output as an offset to the current vertex location. WeightedChoice weighted_choice; // Add long range connections for (int i = 0; i < graph.getVertexCount(); i++) { V source = getVertex(i); int row = getRow(i); int col = getCol(i); int row_offset = row < row_count/2 ? -row_count : row_count; int col_offset = col < col_count/2 ? -col_count : col_count; Map vertex_weights = new HashMap(); for (int j = 0; j < row_count; j++) { for (int k = 0; k < col_count; k++) { if (j == row && k == col) continue; int v_dist = Math.abs(j - row); int h_dist = Math.abs(k - col); if (is_toroidal) { v_dist = Math.min(v_dist, Math.abs(j - row+row_offset)); h_dist = Math.min(h_dist, Math.abs(k - col+col_offset)); } int distance = v_dist + h_dist; if (distance < 2) continue; else vertex_weights.put(getVertex(j,k), (float)Math.pow(distance, -clustering_exponent)); } } for (int j = 0; j < this.num_connections; j++) { weighted_choice = new WeightedChoice(vertex_weights, random); V target = weighted_choice.nextItem(); graph.addEdge(edge_factory.get(), source, target); } } return graph; } } MixedRandomGraphGenerator.java000066400000000000000000000055421276402340000376750ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * Created on Jul 2, 2003 * */ package edu.uci.ics.jung.algorithms.generators.random; import java.util.Map; import java.util.Set; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.EdgeType; /** * Generates a mixed-mode random graph (with random edge weights) based on the output of * BarabasiAlbertGenerator. * Primarily intended for providing a heterogeneous sample graph for visualization testing, etc. */ public class MixedRandomGraphGenerator { /** * Returns a random mixed-mode graph. Starts with a randomly generated * Barabasi-Albert (preferential attachment) generator * (4 initial vertices, 3 edges added at each step, and num_vertices - 4 evolution steps). * Then takes the resultant graph, replaces random undirected edges with directed * edges, and assigns random weights to each edge. * @param the vertex type * @param the edge type * @param graphFactory factory for graphs of the appropriate type * @param vertexFactory factory for vertices of the appropriate type * @param edgeFactory factory for edges of the appropriate type * @param edge_weights storage for the edge weights that this generator creates * @param num_vertices number of vertices to generate * @param seedVertices storage for the seed vertices that this generator creates * @return the generated graph */ public static Graph generateMixedRandomGraph( Supplier> graphFactory, Supplier vertexFactory, Supplier edgeFactory, Map edge_weights, int num_vertices, Set seedVertices) { int seed = (int)(Math.random() * 10000); BarabasiAlbertGenerator bag = new BarabasiAlbertGenerator(graphFactory, vertexFactory, edgeFactory, 4, 3, //false, parallel, seed, seedVertices); bag.evolveGraph(num_vertices - 4); Graph ug = bag.get(); Graph g = graphFactory.get(); for(V v : ug.getVertices()) { g.addVertex(v); } // randomly replace some of the edges by directed edges to // get a mixed-mode graph, add random weights for(E e : ug.getEdges()) { V v1 = ug.getEndpoints(e).getFirst(); V v2 = ug.getEndpoints(e).getSecond(); E me = edgeFactory.get(); g.addEdge(me, v1, v2, Math.random() < .5 ? EdgeType.DIRECTED : EdgeType.UNDIRECTED); edge_weights.put(me, Math.random()); } return g; } } package.html000066400000000000000000000015301276402340000342440ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random Methods for generating random graphs with various properties. These include:

  • BarabasiAlbertGenerator: scale-free graphs using the preferential attachment heuristic.
  • EppsteinPowerLawGenerator: graphs whose degree distribution approximates a power law
  • ErdosRenyiGenerator: graphs for which edges are created with a specified probability
  • MixedRandomGraphGenerator: takes the output of BarabasiAlbertGenerator and perturbs it to generate a mixed-mode analog with both directed and undirected edges.
  • jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/000077500000000000000000000000001276402340000305535ustar00rootroot00000000000000AbstractRanker.java000066400000000000000000000310601276402340000342450ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.importance; import java.text.DecimalFormat; import java.text.Format; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import edu.uci.ics.jung.algorithms.util.IterativeProcess; import edu.uci.ics.jung.graph.Graph; /** * Abstract class for algorithms that rank nodes or edges by some "importance" metric. Provides a common set of * services such as: *
      *
    • storing rank scores
    • *
    • getters and setters for rank scores
    • *
    • computing default edge weights
    • *
    • normalizing default or user-provided edge transition weights
    • *
    • normalizing rank scores
    • *
    • automatic cleanup of decorations
    • *
    • creation of Ranking list
    • *
    • print rankings in sorted order by rank
    • *
    *

    * By default, all rank scores are removed from the vertices (or edges) being ranked. * @author Scott White */ public abstract class AbstractRanker extends IterativeProcess { private Graph mGraph; private List> mRankings; private boolean mRemoveRankScoresOnFinalize; private boolean mRankNodes; private boolean mRankEdges; private boolean mNormalizeRankings; protected LoadingCache> vertexRankScores = CacheBuilder.newBuilder().build(new CacheLoader>() { public Map load(Object o) { return new HashMap(); } }); protected LoadingCache> edgeRankScores = CacheBuilder.newBuilder().build(new CacheLoader>() { public Map load(Object o) { return new HashMap(); } }); private Map edgeWeights = new HashMap(); protected void initialize(Graph graph, boolean isNodeRanker, boolean isEdgeRanker) { if (!isNodeRanker && !isEdgeRanker) throw new IllegalArgumentException("Must rank edges, vertices, or both"); mGraph = graph; mRemoveRankScoresOnFinalize = true; mNormalizeRankings = true; mRankNodes = isNodeRanker; mRankEdges = isEdgeRanker; } /** * @return all rankScores */ public Map> getVertexRankScores() { return vertexRankScores.asMap(); } public Map> getEdgeRankScores() { return edgeRankScores.asMap(); } /** * @param key the rank score key whose scores are to be retrieved * @return the rank scores for the specified key */ public Map getVertexRankScores(Object key) { return vertexRankScores.getUnchecked(key); } public Map getEdgeRankScores(Object key) { return edgeRankScores.getUnchecked(key); } protected Collection getVertices() { return mGraph.getVertices(); } protected int getVertexCount() { return mGraph.getVertexCount(); } protected Graph getGraph() { return mGraph; } @Override public void reset() { } /** * @return true if this ranker ranks nodes, and * false otherwise. */ public boolean isRankingNodes() { return mRankNodes; } /** * @return true if this ranker ranks edges, and * false otherwise. */ public boolean isRankingEdges() { return mRankEdges; } /** * Instructs the ranker whether or not it should remove the rank scores from the nodes (or edges) once the ranks * have been computed. * @param removeRankScoresOnFinalize true if the rank scores are to be removed, false otherwise */ public void setRemoveRankScoresOnFinalize(boolean removeRankScoresOnFinalize) { this.mRemoveRankScoresOnFinalize = removeRankScoresOnFinalize; } protected void onFinalize(Object e) {} /** * The user datum key used to store the rank score. * @return the key */ abstract public Object getRankScoreKey(); @SuppressWarnings("unchecked") @Override protected void finalizeIterations() { List> sortedRankings = new ArrayList>(); int id = 1; if (mRankNodes) { for (V currentVertex : getVertices()) { Ranking ranking = new Ranking(id,getVertexRankScore(currentVertex),currentVertex); sortedRankings.add(ranking); if (mRemoveRankScoresOnFinalize) { this.vertexRankScores.getUnchecked(getRankScoreKey()).remove(currentVertex); } id++; onFinalize(currentVertex); } } if (mRankEdges) { for (E currentEdge : mGraph.getEdges()) { Ranking ranking = new Ranking(id,getEdgeRankScore(currentEdge),currentEdge); sortedRankings.add(ranking); if (mRemoveRankScoresOnFinalize) { this.edgeRankScores.getUnchecked(getRankScoreKey()).remove(currentEdge); } id++; onFinalize(currentEdge); } } mRankings = sortedRankings; Collections.sort(mRankings); } /** * Retrieves the list of ranking instances in descending sorted order by rank score * If the algorithm is ranking edges, the instances will be of type EdgeRanking, otherwise * if the algorithm is ranking nodes the instances will be of type NodeRanking * @return the list of rankings */ public List> getRankings() { return mRankings; } /** * Return a list of the top k rank scores. * @param topKRankings the value of k to use * @return list of rank scores */ public List getRankScores(int topKRankings) { List scores = new ArrayList(); int count=1; for (Ranking currentRanking : getRankings()) { if (count > topKRankings) { return scores; } scores.add(currentRanking.rankScore); count++; } return scores; } /** * Given a node, returns the corresponding rank score. This is a default * implementation of getRankScore which assumes the decorations are of type MutableDouble. * This method only returns legal values if setRemoveRankScoresOnFinalize(false) was called * prior to evaluate(). * * @param v the node whose rank score is to be returned. * @return the rank score value */ public double getVertexRankScore(V v) { Number rankScore = vertexRankScores.getUnchecked(getRankScoreKey()).get(v); if (rankScore != null) { return rankScore.doubleValue(); } else { throw new RuntimeException("setRemoveRankScoresOnFinalize(false) must be called before evaluate()."); } } public double getVertexRankScore(V v, Object key) { return vertexRankScores.getUnchecked(key).get(v).doubleValue(); } public double getEdgeRankScore(E e) { Number rankScore = edgeRankScores.getUnchecked(getRankScoreKey()).get(e); if (rankScore != null) { return rankScore.doubleValue(); } else { throw new RuntimeException("setRemoveRankScoresOnFinalize(false) must be called before evaluate()."); } } public double getEdgeRankScore(E e, Object key) { return edgeRankScores.getUnchecked(key).get(e).doubleValue(); } protected void setVertexRankScore(V v, double rankValue, Object key) { vertexRankScores.getUnchecked(key).put(v, rankValue); } protected void setEdgeRankScore(E e, double rankValue, Object key) { edgeRankScores.getUnchecked(key).put(e, rankValue); } protected void setVertexRankScore(V v, double rankValue) { setVertexRankScore(v,rankValue, getRankScoreKey()); } protected void setEdgeRankScore(E e, double rankValue) { setEdgeRankScore(e, rankValue, getRankScoreKey()); } protected void removeVertexRankScore(V v, Object key) { vertexRankScores.getUnchecked(key).remove(v); } protected void removeEdgeRankScore(E e, Object key) { edgeRankScores.getUnchecked(key).remove(e); } protected void removeVertexRankScore(V v) { vertexRankScores.getUnchecked(getRankScoreKey()).remove(v); } protected void removeEdgeRankScore(E e) { edgeRankScores.getUnchecked(getRankScoreKey()).remove(e); } protected double getEdgeWeight(E e) { return edgeWeights.get(e).doubleValue(); } protected void setEdgeWeight(E e, double weight) { edgeWeights.put(e, weight); } public void setEdgeWeights(Map edgeWeights) { this.edgeWeights = edgeWeights; } /** * @return the edgeWeights */ public Map getEdgeWeights() { return edgeWeights; } protected void assignDefaultEdgeTransitionWeights() { for (V currentVertex : getVertices()) { Collection outgoingEdges = mGraph.getOutEdges(currentVertex); double numOutEdges = outgoingEdges.size(); for (E currentEdge : outgoingEdges) { setEdgeWeight(currentEdge,1.0/numOutEdges); } } } protected void normalizeEdgeTransitionWeights() { for (V currentVertex : getVertices()) { Collection outgoingEdges = mGraph.getOutEdges(currentVertex); double totalEdgeWeight = 0; for (E currentEdge : outgoingEdges) { totalEdgeWeight += getEdgeWeight(currentEdge); } for (E currentEdge : outgoingEdges) { setEdgeWeight(currentEdge,getEdgeWeight(currentEdge)/totalEdgeWeight); } } } protected void normalizeRankings() { if (!mNormalizeRankings) { return; } double totalWeight = 0; for (V currentVertex : getVertices()) { totalWeight += getVertexRankScore(currentVertex); } for (V currentVertex : getVertices()) { setVertexRankScore(currentVertex,getVertexRankScore(currentVertex)/totalWeight); } } /** * Print the rankings to standard out in descending order of rank score * @param verbose if true, include information about the actual rank order as well as * the original position of the vertex before it was ranked * @param printScore if true, include the actual value of the rank score */ public void printRankings(boolean verbose,boolean printScore) { double total = 0; Format formatter = new DecimalFormat("#0.#######"); int rank = 1; for (Ranking currentRanking : getRankings()) { double rankScore = currentRanking.rankScore; if (verbose) { System.out.print("Rank " + rank + ": "); if (printScore) { System.out.print(formatter.format(rankScore)); } System.out.print("\tVertex Id: " + currentRanking.originalPos); System.out.print(" (" + currentRanking.getRanked() + ")"); System.out.println(); } else { System.out.print(rank + "\t"); if (printScore) { System.out.print(formatter.format(rankScore)); } System.out.println("\t" + currentRanking.originalPos); } total += rankScore; rank++; } if (verbose) { System.out.println("Total: " + formatter.format(total)); } } /** * Allows the user to specify whether or not s/he wants the rankings to be normalized. * In some cases, this will have no effect since the algorithm doesn't allow normalization * as an option * @param normalizeRankings {@code true} iff the ranking are to be normalized */ public void setNormalizeRankings(boolean normalizeRankings) { mNormalizeRankings = normalizeRankings; } } BetweennessCentrality.java000066400000000000000000000137051276402340000356660ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.importance; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Stack; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedGraph; /** * Computes betweenness centrality for each vertex and edge in the graph. The result is that each vertex * and edge has a UserData element of type MutableDouble whose key is 'centrality.BetweennessCentrality'. * Note: Many social network researchers like to normalize the betweenness values by dividing the values by * (n-1)(n-2)/2. The values given here are unnormalized.

    * * A simple example of usage is: *

     * BetweennessCentrality ranker = new BetweennessCentrality(someGraph);
     * ranker.evaluate();
     * ranker.printRankings();
     * 
    * * Running time is: O(n^2 + nm). * @see "Ulrik Brandes: A Faster Algorithm for Betweenness Centrality. Journal of Mathematical Sociology 25(2):163-177, 2001." * @author Scott White * @author Tom Nelson converted to jung2 */ public class BetweennessCentrality extends AbstractRanker { public static final String CENTRALITY = "centrality.BetweennessCentrality"; /** * Constructor which initializes the algorithm * @param g the graph whose nodes are to be analyzed */ public BetweennessCentrality(Graph g) { initialize(g, true, true); } public BetweennessCentrality(Graph g, boolean rankNodes) { initialize(g, rankNodes, true); } public BetweennessCentrality(Graph g, boolean rankNodes, boolean rankEdges) { initialize(g, rankNodes, rankEdges); } protected void computeBetweenness(Graph graph) { Map decorator = new HashMap(); Map bcVertexDecorator = vertexRankScores.getUnchecked(getRankScoreKey()); bcVertexDecorator.clear(); Map bcEdgeDecorator = edgeRankScores.getUnchecked(getRankScoreKey()); bcEdgeDecorator.clear(); Collection vertices = graph.getVertices(); for (V s : vertices) { initializeData(graph,decorator); decorator.get(s).numSPs = 1; decorator.get(s).distance = 0; Stack stack = new Stack(); Queue queue = new LinkedList(); queue.add(s); while (!queue.isEmpty()) { V v = queue.remove(); stack.push(v); for(V w : getGraph().getSuccessors(v)) { if (decorator.get(w).distance < 0) { queue.add(w); decorator.get(w).distance = decorator.get(v).distance + 1; } if (decorator.get(w).distance == decorator.get(v).distance + 1) { decorator.get(w).numSPs += decorator.get(v).numSPs; decorator.get(w).predecessors.add(v); } } } while (!stack.isEmpty()) { V w = stack.pop(); for (V v : decorator.get(w).predecessors) { double partialDependency = (decorator.get(v).numSPs / decorator.get(w).numSPs); partialDependency *= (1.0 + decorator.get(w).dependency); decorator.get(v).dependency += partialDependency; E currentEdge = getGraph().findEdge(v, w); double edgeValue = bcEdgeDecorator.get(currentEdge).doubleValue(); edgeValue += partialDependency; bcEdgeDecorator.put(currentEdge, edgeValue); } if (w != s) { double bcValue = bcVertexDecorator.get(w).doubleValue(); bcValue += decorator.get(w).dependency; bcVertexDecorator.put(w, bcValue); } } } if(graph instanceof UndirectedGraph) { for (V v : vertices) { double bcValue = bcVertexDecorator.get(v).doubleValue(); bcValue /= 2.0; bcVertexDecorator.put(v, bcValue); } for (E e : graph.getEdges()) { double bcValue = bcEdgeDecorator.get(e).doubleValue(); bcValue /= 2.0; bcEdgeDecorator.put(e, bcValue); } } for (V vertex : vertices) { decorator.remove(vertex); } } private void initializeData(Graph g, Map decorator) { for (V vertex : g.getVertices()) { Map bcVertexDecorator = vertexRankScores.getUnchecked(getRankScoreKey()); if(bcVertexDecorator.containsKey(vertex) == false) { bcVertexDecorator.put(vertex, 0.0); } decorator.put(vertex, new BetweennessData()); } for (E e : g.getEdges()) { Map bcEdgeDecorator = edgeRankScores.getUnchecked(getRankScoreKey()); if(bcEdgeDecorator.containsKey(e) == false) { bcEdgeDecorator.put(e, 0.0); } } } /** * the user datum key used to store the rank scores * @return the key */ @Override public String getRankScoreKey() { return CENTRALITY; } @Override public void step() { computeBetweenness(getGraph()); } class BetweennessData { double distance; double numSPs; List predecessors; double dependency; BetweennessData() { distance = -1; numSPs = 0; predecessors = new ArrayList(); dependency = 0; } } } KStepMarkov.java000066400000000000000000000110071276402340000335440ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.importance; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; import edu.uci.ics.jung.graph.DirectedGraph; /** * Algorithm variant of PageRankWithPriors that computes the importance of a node based upon taking fixed-length random * walks out from the root set and then computing the stationary probability of being at each node. Specifically, it computes * the relative probability that the markov chain will spend at any particular node, given that it start in the root * set and ends after k steps. *

    * A simple example of usage is: *

     * KStepMarkov ranker = new KStepMarkov(someGraph,rootSet,6,null);
     * ranker.evaluate();
     * ranker.printRankings();
     * 
    *

    * * @author Scott White * @author Tom Nelson - adapter to jung2 * @see "Algorithms for Estimating Relative Importance in Graphs by Scott White and Padhraic Smyth, 2003" */ public class KStepMarkov extends RelativeAuthorityRanker { public final static String RANK_SCORE = "jung.algorithms.importance.KStepMarkovExperimental.RankScore"; private final static String CURRENT_RANK = "jung.algorithms.importance.KStepMarkovExperimental.CurrentRank"; private int mNumSteps; HashMap mPreviousRankingsMap; /** * Construct the algorihm instance and initializes the algorithm. * @param graph the graph to be analyzed * @param priors the set of root nodes * @param k positive integer parameter which controls the relative tradeoff between a distribution "biased" towards * R and the steady-state distribution which is independent of where the Markov-process started. Generally values * between 4-8 are reasonable * @param edgeWeights the weight for each edge */ public KStepMarkov(DirectedGraph graph, Set priors, int k, Map edgeWeights) { super.initialize(graph,true,false); mNumSteps = k; setPriors(priors); initializeRankings(); if (edgeWeights == null) { assignDefaultEdgeTransitionWeights(); } else { setEdgeWeights(edgeWeights); } normalizeEdgeTransitionWeights(); } /** * The user datum key used to store the rank scores. * @return the key */ @Override public String getRankScoreKey() { return RANK_SCORE; } protected void incrementRankScore(V v, double rankValue) { double value = getVertexRankScore(v, RANK_SCORE); value += rankValue; setVertexRankScore(v, value, RANK_SCORE); } protected double getCurrentRankScore(V v) { return getVertexRankScore(v, CURRENT_RANK); } protected void setCurrentRankScore(V v, double rankValue) { setVertexRankScore(v, rankValue, CURRENT_RANK); } protected void initializeRankings() { mPreviousRankingsMap = new HashMap(); for (V v : getVertices()) { Set priors = getPriors(); double numPriors = priors.size(); if (getPriors().contains(v)) { setVertexRankScore(v, 1.0/ numPriors); setCurrentRankScore(v, 1.0/ numPriors); mPreviousRankingsMap.put(v,1.0/numPriors); } else { setVertexRankScore(v, 0); setCurrentRankScore(v, 0); mPreviousRankingsMap.put(v, 0); } } } @Override public void step() { for (int i=0;i incomingEdges = getGraph().getInEdges(v); double currentPageRankSum = 0; for (E e : incomingEdges) { double currentWeight = getEdgeWeight(e); currentPageRankSum += mPreviousRankingsMap.get(getGraph().getOpposite(v,e)).doubleValue()*currentWeight; } setCurrentRankScore(v,currentPageRankSum); } } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/Ranking.java000066400000000000000000000041321276402340000330070ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.importance; /** * Abstract data container for ranking objects. Stores common data relevant to both node and edge rankings, namely, * the original position of the instance in the list and the actual ranking score. * @author Scott White */ @SuppressWarnings("rawtypes") public class Ranking implements Comparable { /** * The original (0-indexed) position of the instance being ranked */ public int originalPos; /** * The actual rank score (normally between 0 and 1) */ public double rankScore; /** * what is being ranked */ private V ranked; /** * Constructor which allows values to be set on construction * @param originalPos The original (0-indexed) position of the instance being ranked * @param rankScore The actual rank score (normally between 0 and 1) * @param ranked the vertex being ranked */ public Ranking(int originalPos, double rankScore, V ranked) { this.originalPos = originalPos; this.rankScore = rankScore; this.ranked = ranked; } /** * Compares two ranking based on the rank score. * @param other The other ranking * @return -1 if the other ranking is higher, 0 if they are equal, and 1 if this ranking is higher */ public int compareTo(Object other) { @SuppressWarnings("unchecked") Ranking otherRanking = (Ranking) other; return Double.compare(otherRanking.rankScore,rankScore); } /** * Returns the rank score as a string. * @return the stringified rank score */ @Override public String toString() { return String.valueOf(rankScore); } /** * @return the ranked element */ public V getRanked() { return ranked; } /** * @param ranked the ranked to set */ public void setRanked(V ranked) { this.ranked = ranked; } } RelativeAuthorityRanker.java000066400000000000000000000040211276402340000361630ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.importance; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * This class provides basic infrastructure for relative authority algorithms that compute the importance of nodes * relative to one or more root nodes. The services provided are: *

      *
    • The set of root nodes (priors) is stored and maintained
    • *
    • Getters and setters for the prior rank score are provided
    • *
    * * @author Scott White */ public abstract class RelativeAuthorityRanker extends AbstractRanker { private Set mPriors; /** * The default key used for the user datum key corresponding to prior rank scores. */ protected Map priorRankScoreMap = new HashMap(); /** * Cleans up all of the prior rank scores on finalize. */ @Override protected void finalizeIterations() { super.finalizeIterations(); priorRankScoreMap.clear(); } /** * Retrieves the value of the prior rank score. * @param v the root node (prior) * @return the prior rank score */ protected double getPriorRankScore(V v) { return priorRankScoreMap.get(v).doubleValue(); } /** * Allows the user to specify a value to set for the prior rank score * @param v the root node (prior) * @param value the score to set to */ public void setPriorRankScore(V v, double value) { this.priorRankScoreMap.put(v, value); } /** * Retrieves the set of priors. * @return the set of root nodes (priors) */ protected Set getPriors() { return mPriors; } /** * Specifies which vertices are root nodes (priors). * @param priors the root nodes */ protected void setPriors(Set priors) { mPriors = priors; } } WeightedNIPaths.java000066400000000000000000000162201276402340000343270ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.importance; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.DirectedGraph; /** * This algorithm measures the importance of nodes based upon both the number and length of disjoint paths that lead * to a given node from each of the nodes in the root set. Specifically the formula for measuring the importance of a * node is given by: I(t|R) = sum_i=1_|P(r,t)|_{alpha^|p_i|} where alpha is the path decay coefficient, p_i is path i * and P(r,t) is a set of maximum-sized node-disjoint paths from r to t. *

    * This algorithm uses heuristic breadth-first search to try and find the maximum-sized set of node-disjoint paths * between two nodes. As such, it is not guaranteed to give exact answers. *

    * A simple example of usage is: *

     * WeightedNIPaths ranker = new WeightedNIPaths(someGraph,2.0,6,rootSet);
     * ranker.evaluate();
     * ranker.printRankings();
     * 
    * * @author Scott White * @see "Algorithms for Estimating Relative Importance in Graphs by Scott White and Padhraic Smyth, 2003" */ public class WeightedNIPaths extends AbstractRanker { public final static String WEIGHTED_NIPATHS_KEY = "jung.algorithms.importance.WEIGHTED_NIPATHS_KEY"; private double mAlpha; private int mMaxDepth; private Set mPriors; private Map pathIndices = new HashMap(); private Map roots = new HashMap(); private Map> pathsSeenMap = new HashMap>(); private Supplier vertexFactory; private Supplier edgeFactory; /** * Constructs and initializes the algorithm. * @param graph the graph whose nodes are being measured for their importance * @param vertexFactory used to generate instances of V * @param edgeFactory used to generate instances of E * @param alpha the path decay coefficient (≥1); 2 is recommended * @param maxDepth the maximal depth to search out from the root set * @param priors the root set (starting vertices) */ public WeightedNIPaths(DirectedGraph graph, Supplier vertexFactory, Supplier edgeFactory, double alpha, int maxDepth, Set priors) { super.initialize(graph, true,false); this.vertexFactory = vertexFactory; this.edgeFactory = edgeFactory; mAlpha = alpha; mMaxDepth = maxDepth; mPriors = priors; for (V v : graph.getVertices()) { super.setVertexRankScore(v, 0.0); } } protected void incrementRankScore(V v, double rankValue) { setVertexRankScore(v, getVertexRankScore(v) + rankValue); } protected void computeWeightedPathsFromSource(V root, int depth) { int pathIdx = 1; for (E e : getGraph().getOutEdges(root)) { this.pathIndices.put(e, pathIdx); this.roots.put(e, root); newVertexEncountered(pathIdx, getGraph().getEndpoints(e).getSecond(), root); pathIdx++; } List edges = new ArrayList(); V virtualNode = vertexFactory.get(); getGraph().addVertex(virtualNode); E virtualSinkEdge = edgeFactory.get(); getGraph().addEdge(virtualSinkEdge, virtualNode, root); edges.add(virtualSinkEdge); int currentDepth = 0; while (currentDepth <= depth) { double currentWeight = Math.pow(mAlpha, -1.0 * currentDepth); for (E currentEdge : edges) { incrementRankScore(getGraph().getEndpoints(currentEdge).getSecond(),// currentWeight); } if ((currentDepth == depth) || (edges.size() == 0)) break; List newEdges = new ArrayList(); for (E currentSourceEdge : edges) { //Iterator sourceEdgeIt = edges.iterator(); sourceEdgeIt.hasNext();) { Number sourcePathIndex = this.pathIndices.get(currentSourceEdge); // from the currentSourceEdge, get its opposite end // then iterate over the out edges of that opposite end V newDestVertex = getGraph().getEndpoints(currentSourceEdge).getSecond(); Collection outs = getGraph().getOutEdges(newDestVertex); for (E currentDestEdge : outs) { V destEdgeRoot = this.roots.get(currentDestEdge); V destEdgeDest = getGraph().getEndpoints(currentDestEdge).getSecond(); if (currentSourceEdge == virtualSinkEdge) { newEdges.add(currentDestEdge); continue; } if (destEdgeRoot == root) { continue; } if (destEdgeDest == getGraph().getEndpoints(currentSourceEdge).getFirst()) {//currentSourceEdge.getSource()) { continue; } Set pathsSeen = this.pathsSeenMap.get(destEdgeDest); if (pathsSeen == null) { newVertexEncountered(sourcePathIndex.intValue(), destEdgeDest, root); } else if (roots.get(destEdgeDest) != root) { roots.put(destEdgeDest,root); pathsSeen.clear(); pathsSeen.add(sourcePathIndex); } else if (!pathsSeen.contains(sourcePathIndex)) { pathsSeen.add(sourcePathIndex); } else { continue; } this.pathIndices.put(currentDestEdge, sourcePathIndex); this.roots.put(currentDestEdge, root); newEdges.add(currentDestEdge); } } edges = newEdges; currentDepth++; } getGraph().removeVertex(virtualNode); } private void newVertexEncountered(int sourcePathIndex, V dest, V root) { Set pathsSeen = new HashSet(); pathsSeen.add(sourcePathIndex); this.pathsSeenMap.put(dest, pathsSeen); roots.put(dest, root); } @Override public void step() { for (V v : mPriors) { computeWeightedPathsFromSource(v, mMaxDepth); } normalizeRankings(); // return 0; } /** * Given a node, returns the corresponding rank score. This implementation of getRankScore assumes * the decoration representing the rank score is of type MutableDouble. * @return the rank score for this node */ @Override public String getRankScoreKey() { return WEIGHTED_NIPATHS_KEY; } @Override protected void onFinalize(Object udc) { pathIndices.remove(udc); roots.remove(udc); pathsSeenMap.remove(udc); } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/000077500000000000000000000000001276402340000277275ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/AbstractLayout.java000066400000000000000000000212431276402340000335350ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 7, 2003 * */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.awt.geom.Point2D; import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.Set; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Preconditions; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import edu.uci.ics.jung.graph.Graph; /** * Abstract class for implementations of {@code Layout}. It handles some of the * basic functions: storing coordinates, maintaining the dimensions, initializing * the locations, maintaining locked vertices. * * @author Danyel Fisher, Scott White * @author Tom Nelson - converted to jung2 * @param the vertex type * @param the edge type */ abstract public class AbstractLayout implements Layout { /** * A set of vertices that are fixed in place and not affected by the layout algorithm */ private Set dontmove = new HashSet(); protected Dimension size; protected Graph graph; protected boolean initialized; protected LoadingCache locations = CacheBuilder.newBuilder().build(new CacheLoader() { public Point2D load(V vertex) { return new Point2D.Double(); } }); /** * Creates an instance for {@code graph} which does not initialize the vertex locations. * * @param graph the graph on which the layout algorithm is to operate */ protected AbstractLayout(Graph graph) { if (graph == null) { throw new IllegalArgumentException("Graph must be non-null"); } this.graph = graph; } /** * Creates an instance for {@code graph} which initializes the vertex locations * using {@code initializer}. * * @param graph the graph on which the layout algorithm is to operate * @param initializer specifies the starting positions of the vertices */ protected AbstractLayout(Graph graph, Function initializer) { this.graph = graph; Function chain = Functions.compose( new Function(){ public Point2D apply(Point2D p) { return (Point2D)p.clone(); }}, initializer ); this.locations = CacheBuilder.newBuilder().build(CacheLoader.from(chain)); initialized = true; } /** * Creates an instance for {@code graph} which sets the size of the layout to {@code size}. * * @param graph the graph on which the layout algorithm is to operate * @param size the dimensions of the region in which the layout algorithm will place vertices */ protected AbstractLayout(Graph graph, Dimension size) { this.graph = graph; this.size = size; } /** * Creates an instance for {@code graph} which initializes the vertex locations * using {@code initializer} and sets the size of the layout to {@code size}. * * @param graph the graph on which the layout algorithm is to operate * @param initializer specifies the starting positions of the vertices * @param size the dimensions of the region in which the layout algorithm will place vertices */ protected AbstractLayout(Graph graph, Function initializer, Dimension size) { this.graph = graph; Function chain = Functions.compose( new Function(){ public Point2D apply(Point2D p) { return (Point2D)p.clone(); }}, initializer ); this.locations = CacheBuilder.newBuilder().build(CacheLoader.from(chain)); this.size = size; } public void setGraph(Graph graph) { this.graph = graph; if(size != null && graph != null) { initialize(); } } /** * When a visualization is resized, it presumably wants to fix the * locations of the vertices and possibly to reinitialize its data. The * current method calls initializeLocations followed by initialize_local. */ public void setSize(Dimension size) { if(size != null && graph != null) { Dimension oldSize = this.size; this.size = size; initialize(); if(oldSize != null) { adjustLocations(oldSize, size); } } } private void adjustLocations(Dimension oldSize, Dimension size) { int xOffset = (size.width - oldSize.width) / 2; int yOffset = (size.height - oldSize.height) / 2; // now, move each vertex to be at the new screen center while(true) { try { for(V v : getGraph().getVertices()) { offsetVertex(v, xOffset, yOffset); } break; } catch(ConcurrentModificationException cme) { } } } public boolean isLocked(V v) { return dontmove.contains(v); } public void setInitializer(Function initializer) { if(this.equals(initializer)) { throw new IllegalArgumentException("Layout cannot be initialized with itself"); } Function chain = Functions.compose( new Function(){ public Point2D apply(Point2D p) { return (Point2D)p.clone(); }}, initializer ); this.locations = CacheBuilder.newBuilder().build(CacheLoader.from(chain)); initialized = true; } /** * Returns the current size of the visualization space, accoring to the * last call to resize(). * * @return the current size of the screen */ public Dimension getSize() { return size; } /** * Returns the Coordinates object that stores the vertex' x and y location. * * @param v * A Vertex that is a part of the Graph being visualized. * @return A Coordinates object with x and y locations. */ private Point2D getCoordinates(V v) { return locations.getUnchecked(v); } public Point2D apply(V v) { return getCoordinates(v); } /** * Returns the x coordinate of the vertex from the Coordinates object. * in most cases you will be better off calling transform(v). * * @param v the vertex whose x coordinate is to be returned * @return the x coordinate of {@code v} */ public double getX(V v) { Preconditions.checkNotNull(getCoordinates(v), "Cannot getX for an unmapped vertex "+v); return getCoordinates(v).getX(); } /** * Returns the y coordinate of the vertex from the Coordinates object. * In most cases you will be better off calling transform(v). * * @param v the vertex whose y coordinate is to be returned * @return the y coordinate of {@code v} */ public double getY(V v) { Preconditions.checkNotNull(getCoordinates(v), "Cannot getY for an unmapped vertex "+v); return getCoordinates(v).getY(); } /** * @param v the vertex whose coordinates are to be offset * @param xOffset the change to apply to this vertex's x coordinate * @param yOffset the change to apply to this vertex's y coordinate */ protected void offsetVertex(V v, double xOffset, double yOffset) { Point2D c = getCoordinates(v); c.setLocation(c.getX()+xOffset, c.getY()+yOffset); setLocation(v, c); } /** * @return the graph that this layout operates on */ public Graph getGraph() { return graph; } /** * Forcibly moves a vertex to the (x,y) location by setting its x and y * locations to the specified location. Does not add the vertex to the * "dontmove" list, and (in the default implementation) does not make any * adjustments to the rest of the graph. * @param picked the vertex whose location is being set * @param x the x coordinate of the location to set * @param y the y coordinate of the location to set */ public void setLocation(V picked, double x, double y) { Point2D coord = getCoordinates(picked); coord.setLocation(x, y); } public void setLocation(V picked, Point2D p) { Point2D coord = getCoordinates(picked); coord.setLocation(p); } /** * Locks {@code v} in place if {@code state} is {@code true}, otherwise unlocks it. * @param v the vertex whose position is to be (un)locked * @param state {@code true} if the vertex is to be locked, {@code false} if to be unlocked */ public void lock(V v, boolean state) { if(state == true) dontmove.add(v); else dontmove.remove(v); } /** * @param lock {@code true} to lock all vertices in place, {@code false} to unlock all vertices */ public void lock(boolean lock) { for(V v : graph.getVertices()) { lock(v, lock); } } } AggregateLayout.java000066400000000000000000000143221276402340000336010ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * * */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.util.HashMap; import java.util.Map; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.util.IterativeContext; import edu.uci.ics.jung.graph.Graph; /** * A {@code Layout} implementation that combines * multiple other layouts so that they may be manipulated * as one layout. The relaxer thread will step each layout * in sequence. * * @author Tom Nelson - tomnelson@dev.java.net * * @param the vertex type * @param the edge type */ public class AggregateLayout implements Layout, IterativeContext { protected Layout delegate; protected Map,Point2D> layouts = new HashMap,Point2D>(); /** * Creates an instance backed by the specified {@code delegate}. * @param delegate the layout to which this instance is delegating */ public AggregateLayout(Layout delegate) { this.delegate = delegate; } /** * @return the delegate */ public Layout getDelegate() { return delegate; } /** * @param delegate the delegate to set */ public void setDelegate(Layout delegate) { this.delegate = delegate; } /** * Adds the passed layout as a sublayout, and specifies * the center of where this sublayout should appear. * @param layout the layout algorithm to use as a sublayout * @param center the center of the coordinates for the sublayout */ public void put(Layout layout, Point2D center) { layouts.put(layout,center); } /** * @param layout the layout whose center is to be returned * @return the center of the passed layout */ public Point2D get(Layout layout) { return layouts.get(layout); } /** * Removes {@code layout} from this instance. * @param layout the layout to remove */ public void remove(Layout layout) { layouts.remove(layout); } /** * Removes all layouts from this instance. */ public void removeAll() { layouts.clear(); } public Graph getGraph() { return delegate.getGraph(); } public Dimension getSize() { return delegate.getSize(); } public void initialize() { delegate.initialize(); for(Layout layout : layouts.keySet()) { layout.initialize(); } } /** * @param v the vertex whose locked state is to be returned * @return true if v is locked in any of the layouts, and false otherwise */ public boolean isLocked(V v) { for(Layout layout : layouts.keySet()) { if (layout.isLocked(v)) { return true; } } return delegate.isLocked(v); } /** * Locks this vertex in the main layout and in any sublayouts whose graph contains * this vertex. * @param v the vertex whose locked state is to be set * @param state {@code true} if the vertex is to be locked, and {@code false} if unlocked */ public void lock(V v, boolean state) { for(Layout layout : layouts.keySet()) { if(layout.getGraph().getVertices().contains(v)) { layout.lock(v, state); } } delegate.lock(v, state); } public void reset() { for(Layout layout : layouts.keySet()) { layout.reset(); } delegate.reset(); } public void setGraph(Graph graph) { delegate.setGraph(graph); } public void setInitializer(Function initializer) { delegate.setInitializer(initializer); } public void setLocation(V v, Point2D location) { boolean wasInSublayout = false; for(Layout layout : layouts.keySet()) { if(layout.getGraph().getVertices().contains(v)) { Point2D center = layouts.get(layout); // transform by the layout itself, but offset to the // center of the sublayout Dimension d = layout.getSize(); AffineTransform at = AffineTransform.getTranslateInstance(-center.getX()+d.width/2,-center.getY()+d.height/2); Point2D localLocation = at.transform(location, null); layout.setLocation(v, localLocation); wasInSublayout = true; } } if(wasInSublayout == false && getGraph().getVertices().contains(v)) { delegate.setLocation(v, location); } } public void setSize(Dimension d) { delegate.setSize(d); } /** * @return a map from each {@code Layout} instance to its center point. */ public Map,Point2D> getLayouts() { return layouts; } /** * Returns the location of the vertex. The location is specified first * by the sublayouts, and then by the base layout if no sublayouts operate * on this vertex. * @return the location of the vertex */ public Point2D apply(V v) { boolean wasInSublayout = false; for(Layout layout : layouts.keySet()) { if(layout.getGraph().getVertices().contains(v)) { wasInSublayout = true; Point2D center = layouts.get(layout); // transform by the layout itself, but offset to the // center of the sublayout Dimension d = layout.getSize(); AffineTransform at = AffineTransform.getTranslateInstance(center.getX()-d.width/2, center.getY()-d.height/2); return at.transform(layout.apply(v),null); } } if(wasInSublayout == false) { return delegate.apply(v); } return null; } /** * @return {@code true} iff the delegate layout and all sublayouts are done */ public boolean done() { for (Layout layout : layouts.keySet()) { if (layout instanceof IterativeContext) { if (! ((IterativeContext) layout).done() ) { return false; } } } if(delegate instanceof IterativeContext) { return ((IterativeContext)delegate).done(); } return true; } /** * Call step on any sublayout that is also an IterativeContext and is not done */ public void step() { for(Layout layout : layouts.keySet()) { if(layout instanceof IterativeContext) { IterativeContext context = (IterativeContext)layout; if(context.done() == false) { context.step(); } } } if(delegate instanceof IterativeContext) { IterativeContext context = (IterativeContext)delegate; if(context.done() == false) { context.step(); } } } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/BalloonLayout.java000066400000000000000000000101571276402340000333620ustar00rootroot00000000000000/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 9, 2005 */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import edu.uci.ics.jung.graph.Forest; import edu.uci.ics.jung.graph.util.TreeUtils; /** * A {@code Layout} implementation that assigns positions to {@code Tree} or * {@code Forest} vertices using associations with nested circles ("balloons"). * A balloon is nested inside another balloon if the first balloon's subtree * is a subtree of the second balloon's subtree. * * @author Tom Nelson * */ public class BalloonLayout extends TreeLayout { protected LoadingCache polarLocations = CacheBuilder.newBuilder().build(new CacheLoader() { public PolarPoint load(V vertex) { return new PolarPoint(); } }); protected Map radii = new HashMap(); /** * Creates an instance based on the input forest. * @param g the forest on which this layout will operate */ public BalloonLayout(Forest g) { super(g); } protected void setRootPolars() { List roots = TreeUtils.getRoots(graph); if(roots.size() == 1) { // its a Tree V root = roots.get(0); setRootPolar(root); setPolars(new ArrayList(graph.getChildren(root)), getCenter(), getSize().width/2); } else if (roots.size() > 1) { // its a Forest setPolars(roots, getCenter(), getSize().width/2); } } protected void setRootPolar(V root) { PolarPoint pp = new PolarPoint(0,0); Point2D p = getCenter(); polarLocations.put(root, pp); locations.put(root, p); } protected void setPolars(List kids, Point2D parentLocation, double parentRadius) { int childCount = kids.size(); if(childCount == 0) return; // handle the 1-child case with 0 limit on angle. double angle = Math.max(0, Math.PI / 2 * (1 - 2.0/childCount)); double childRadius = parentRadius*Math.cos(angle) / (1 + Math.cos(angle)); double radius = parentRadius - childRadius; double rand = Math.random(); for(int i=0; i< childCount; i++) { V child = kids.get(i); double theta = i* 2*Math.PI/childCount + rand; radii.put(child, childRadius); PolarPoint pp = new PolarPoint(theta, radius); polarLocations.put(child, pp); Point2D p = PolarPoint.polarToCartesian(pp); p.setLocation(p.getX()+parentLocation.getX(), p.getY()+parentLocation.getY()); locations.put(child, p); setPolars(new ArrayList(graph.getChildren(child)), p, childRadius); } } @Override public void setSize(Dimension size) { this.size = size; setRootPolars(); } /** * @param v the vertex whose center is to be returned * @return the coordinates of {@code v}'s parent, or the center of this layout's area if it's a root. */ public Point2D getCenter(V v) { V parent = graph.getParent(v); if(parent == null) { return getCenter(); } return locations.getUnchecked(parent); } @Override public void setLocation(V v, Point2D location) { Point2D c = getCenter(v); Point2D pv = new Point2D.Double(location.getX()-c.getX(),location.getY()-c.getY()); PolarPoint newLocation = PolarPoint.cartesianToPolar(pv); polarLocations.getUnchecked(v).setLocation(newLocation); Point2D center = getCenter(v); pv.setLocation(pv.getX()+center.getX(), pv.getY()+center.getY()); locations.put(v, pv); } @Override public Point2D apply(V v) { return locations.getUnchecked(v); } /** * @return the radii */ public Map getRadii() { return radii; } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/CircleLayout.java000066400000000000000000000067351276402340000332040ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * Created on Dec 4, 2003 */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import edu.uci.ics.jung.graph.Graph; /** * A {@code Layout} implementation that positions vertices equally spaced on a regular circle. * * @author Masanori Harada */ public class CircleLayout extends AbstractLayout { private double radius; private List vertex_ordered_list; protected LoadingCache circleVertexDatas = CacheBuilder.newBuilder().build(new CacheLoader() { public CircleVertexData load(V vertex) { return new CircleVertexData(); } }); public CircleLayout(Graph g) { super(g); } /** * @return the radius of the circle. */ public double getRadius() { return radius; } /** * Sets the radius of the circle. Must be called before {@code initialize()} is called. * @param radius the radius of the circle */ public void setRadius(double radius) { this.radius = radius; } /** * Sets the order of the vertices in the layout according to the ordering * specified by {@code comparator}. * @param comparator the comparator to use to order the vertices */ public void setVertexOrder(Comparator comparator) { if (vertex_ordered_list == null) vertex_ordered_list = new ArrayList(getGraph().getVertices()); Collections.sort(vertex_ordered_list, comparator); } /** * Sets the order of the vertices in the layout according to the ordering * of {@code vertex_list}. * @param vertex_list a list specifying the ordering of the vertices */ public void setVertexOrder(List vertex_list) { if (!vertex_list.containsAll(getGraph().getVertices())) throw new IllegalArgumentException("Supplied list must include " + "all vertices of the graph"); this.vertex_ordered_list = vertex_list; } public void reset() { initialize(); } public void initialize() { Dimension d = getSize(); if (d != null) { if (vertex_ordered_list == null) setVertexOrder(new ArrayList(getGraph().getVertices())); double height = d.getHeight(); double width = d.getWidth(); if (radius <= 0) { radius = 0.45 * (height < width ? height : width); } int i = 0; for (V v : vertex_ordered_list) { Point2D coord = apply(v); double angle = (2 * Math.PI * i) / vertex_ordered_list.size(); coord.setLocation(Math.cos(angle) * radius + width / 2, Math.sin(angle) * radius + height / 2); CircleVertexData data = getCircleData(v); data.setAngle(angle); i++; } } } protected CircleVertexData getCircleData(V v) { return circleVertexDatas.getUnchecked(v); } protected static class CircleVertexData { private double angle; protected double getAngle() { return angle; } protected void setAngle(double angle) { this.angle = angle; } @Override public String toString() { return "CircleVertexData: angle=" + angle; } } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/DAGLayout.java000066400000000000000000000235271276402340000323740ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Dec 4, 2003 */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.awt.geom.Point2D; import java.util.HashMap; import java.util.Map; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Pair; /** * An implementation of {@code Layout} suitable for tree-like directed * acyclic graphs. Parts of it will probably not terminate if the graph is * cyclic! The layout will result in directed edges pointing generally upwards. * Any vertices with no successors are considered to be level 0, and tend * towards the top of the layout. Any vertex has a level one greater than the * maximum level of all its successors. * * * @author John Yesberg */ public class DAGLayout extends SpringLayout { /** * Each vertex has a minimumLevel. Any vertex with no successors has * minimumLevel of zero. The minimumLevel of any vertex must be strictly * greater than the minimumLevel of its parents. (Vertex A is a parent of * Vertex B iff there is an edge from B to A.) Typically, a vertex will * have a minimumLevel which is one greater than the minimumLevel of its * parent's. However, if the vertex has two parents, its minimumLevel will * be one greater than the maximum of the parents'. We need to calculate * the minimumLevel for each vertex. When we layout the graph, vertices * cannot be drawn any higher than the minimumLevel. The graphHeight of a * graph is the greatest minimumLevel that is used. We will modify the * SpringLayout calculations so that nodes cannot move above their assigned * minimumLevel. */ private Map minLevels = new HashMap(); // Simpler than the "pair" technique. static int graphHeight; static int numRoots; final double SPACEFACTOR = 1.3; // How much space do we allow for additional floating at the bottom. final double LEVELATTRACTIONRATE = 0.8; /** * A bunch of parameters to help work out when to stop quivering. * * If the MeanSquareVel(ocity) ever gets below the MSV_THRESHOLD, then we * will start a final cool-down phase of COOL_DOWN_INCREMENT increments. If * the MeanSquareVel ever exceeds the threshold, we will exit the cool down * phase, and continue looking for another opportunity. */ final double MSV_THRESHOLD = 10.0; double meanSquareVel; boolean stoppingIncrements = false; int incrementsLeft; final int COOL_DOWN_INCREMENTS = 200; public DAGLayout(Graph g) { super(g); } /** * Calculates the level of each vertex in the graph. Level 0 is * allocated to each vertex with no successors. Level n+1 is allocated to * any vertex whose successors' maximum level is n. */ public void setRoot() { numRoots = 0; Graph g = getGraph(); for(V v : g.getVertices()) { if (g.getSuccessors(v).isEmpty()) { setRoot(v); numRoots++; } } } /** * Set vertex v to be level 0. * @param v the vertex to set as root */ public void setRoot(V v) { minLevels.put(v, new Integer(0)); // set all the levels. propagateMinimumLevel(v); } /** * A recursive method for allocating the level for each vertex. Ensures * that all predecessors of v have a level which is at least one greater * than the level of v. * * @param v the vertex whose minimum level is to be calculated */ public void propagateMinimumLevel(V v) { int level = minLevels.get(v).intValue(); for(V child : getGraph().getPredecessors(v)) { int oldLevel, newLevel; Number o = minLevels.get(child); if (o != null) oldLevel = o.intValue(); else oldLevel = 0; newLevel = Math.max(oldLevel, level + 1); minLevels.put(child, new Integer(newLevel)); if (newLevel > graphHeight) graphHeight = newLevel; propagateMinimumLevel(child); } } /** * Sets a random location for a vertex within the dimensions of the space. * * @param v the vertex whose position is to be set * @param coord the coordinates of the vertex once the position has been set * @param d the dimensions of the space */ private void initializeLocation( V v, Point2D coord, Dimension d) { int level = minLevels.get(v).intValue(); int minY = (int) (level * d.getHeight() / (graphHeight * SPACEFACTOR)); double x = Math.random() * d.getWidth(); double y = Math.random() * (d.getHeight() - minY) + minY; coord.setLocation(x,y); } @Override public void setSize(Dimension size) { super.setSize(size); for(V v : getGraph().getVertices()) { initializeLocation(v,apply(v),getSize()); } } /** * Had to override this one as well, to ensure that setRoot() is called. */ @Override public void initialize() { super.initialize(); setRoot(); } /** * Override the moveNodes() method from SpringLayout. The only change we * need to make is to make sure that nodes don't float higher than the minY * coordinate, as calculated by their minimumLevel. */ @Override protected void moveNodes() { // Dimension d = currentSize; double oldMSV = meanSquareVel; meanSquareVel = 0; synchronized (getSize()) { for(V v : getGraph().getVertices()) { if (isLocked(v)) continue; SpringLayout.SpringVertexData vd = springVertexData.getUnchecked(v); Point2D xyd = apply(v); int width = getSize().width; int height = getSize().height; // (JY addition: three lines are new) int level = minLevels.get(v).intValue(); int minY = (int) (level * height / (graphHeight * SPACEFACTOR)); int maxY = level == 0 ? (int) (height / (graphHeight * SPACEFACTOR * 2)) : height; // JY added 2* - double the sideways repulsion. vd.dx += 2 * vd.repulsiondx + vd.edgedx; vd.dy += vd.repulsiondy + vd.edgedy; // JY Addition: Attract the vertex towards it's minimumLevel // height. double delta = xyd.getY() - minY; vd.dy -= delta * LEVELATTRACTIONRATE; if (level == 0) vd.dy -= delta * LEVELATTRACTIONRATE; // twice as much at the top. // JY addition: meanSquareVel += (vd.dx * vd.dx + vd.dy * vd.dy); // keeps nodes from moving any faster than 5 per time unit xyd.setLocation(xyd.getX()+Math.max(-5, Math.min(5, vd.dx)) , xyd.getY()+Math.max(-5, Math.min(5, vd.dy)) ); if (xyd.getX() < 0) { xyd.setLocation(0, xyd.getY()); } else if (xyd.getX() > width) { xyd.setLocation(width, xyd.getY()); } // (JY addition: These two lines replaced 0 with minY) if (xyd.getY() < minY) { xyd.setLocation(xyd.getX(), minY); // (JY addition: replace height with maxY) } else if (xyd.getY() > maxY) { xyd.setLocation(xyd.getX(), maxY); } // (JY addition: if there's only one root, anchor it in the // middle-top of the screen) if (numRoots == 1 && level == 0) { xyd.setLocation(width/2, xyd.getY()); } } } //System.out.println("MeanSquareAccel="+meanSquareVel); if (!stoppingIncrements && Math.abs(meanSquareVel - oldMSV) < MSV_THRESHOLD) { stoppingIncrements = true; incrementsLeft = COOL_DOWN_INCREMENTS; } else if ( stoppingIncrements && Math.abs(meanSquareVel - oldMSV) <= MSV_THRESHOLD) { incrementsLeft--; if (incrementsLeft <= 0) incrementsLeft = 0; } } /** * Override incrementsAreDone so that we can eventually stop. */ @Override public boolean done() { if (stoppingIncrements && incrementsLeft == 0) return true; else return false; } /** * Override forceMove so that if someone moves a node, we can re-layout * everything. * @param picked the vertex whose location is to be set * @param x the x coordinate of the location to set * @param y the y coordinate of the location to set */ @Override public void setLocation(V picked, double x, double y) { Point2D coord = apply(picked); coord.setLocation(x,y); stoppingIncrements = false; } /** * Override forceMove so that if someone moves a node, we can re-layout * everything. * @param picked the vertex whose location is to be set * @param p the location to set */ @Override public void setLocation(V picked, Point2D p) { setLocation(picked, p.getX(), p.getY()); } /** * Overridden relaxEdges. This one reduces the effect of edges between * greatly different levels. * */ @Override protected void relaxEdges() { for(E e : getGraph().getEdges()) { Pair endpoints = getGraph().getEndpoints(e); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); Point2D p1 = apply(v1); Point2D p2 = apply(v2); double vx = p1.getX() - p2.getX(); double vy = p1.getY() - p2.getY(); double len = Math.sqrt(vx * vx + vy * vy); // JY addition. int level1 = minLevels.get(v1).intValue(); int level2 = minLevels.get(v2).intValue(); double desiredLen = lengthFunction.apply(e); // round from zero, if needed [zero would be Bad.]. len = (len == 0) ? .0001 : len; // force factor: optimal length minus actual length, // is made smaller as the current actual length gets larger. // why? // System.out.println("Desired : " + getLength( e )); double f = force_multiplier * (desiredLen - len) / len; f = f * Math.pow(stretch / 100.0, (getGraph().degree(v1) + getGraph().degree(v2) -2)); // JY addition. If this is an edge which stretches a long way, // don't be so concerned about it. if (level1 != level2) f = f / Math.pow(Math.abs(level2 - level1), 1.5); // the actual movement distance 'dx' is the force multiplied by the // distance to go. double dx = f * vx; double dy = f * vy; SpringVertexData v1D, v2D; v1D = springVertexData.getUnchecked(v1); v2D = springVertexData.getUnchecked(v2); v1D.edgedx += dx; v1D.edgedy += dy; v2D.edgedx += -dx; v2D.edgedy += -dy; } } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/FRLayout.java000066400000000000000000000225261276402340000323060ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.awt.geom.Point2D; import java.util.ConcurrentModificationException; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer; import edu.uci.ics.jung.algorithms.util.IterativeContext; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Pair; /** * Implements the Fruchterman-Reingold force-directed algorithm for node layout. * *

    Behavior is determined by the following settable parameters: *

      *
    • attraction multiplier: how much edges try to keep their vertices together *
    • repulsion multiplier: how much vertices try to push each other apart *
    • maximum iterations: how many iterations this algorithm will use before stopping *
    * Each of the first two defaults to 0.75; the maximum number of iterations defaults to 700. * * @see "Fruchterman and Reingold, 'Graph Drawing by Force-directed Placement'" * @see "http://i11www.ilkd.uni-karlsruhe.de/teaching/SS_04/visualisierung/papers/fruchterman91graph.pdf" * @author Scott White, Yan-Biao Boey, Danyel Fisher */ public class FRLayout extends AbstractLayout implements IterativeContext { private double forceConstant; private double temperature; private int currentIteration; private int mMaxIterations = 700; protected LoadingCache frVertexData = CacheBuilder.newBuilder().build(new CacheLoader() { public FRVertexData load(V vertex) { return new FRVertexData(); } }); private double attraction_multiplier = 0.75; private double attraction_constant; private double repulsion_multiplier = 0.75; private double repulsion_constant; private double max_dimension; public FRLayout(Graph g) { super(g); } public FRLayout(Graph g, Dimension d) { super(g, new RandomLocationTransformer(d), d); initialize(); max_dimension = Math.max(d.height, d.width); } @Override public void setSize(Dimension size) { if(initialized == false) { setInitializer(new RandomLocationTransformer(size)); } super.setSize(size); max_dimension = Math.max(size.height, size.width); } public void setAttractionMultiplier(double attraction) { this.attraction_multiplier = attraction; } public void setRepulsionMultiplier(double repulsion) { this.repulsion_multiplier = repulsion; } public void reset() { doInit(); } public void initialize() { doInit(); } private void doInit() { Graph graph = getGraph(); Dimension d = getSize(); if(graph != null && d != null) { currentIteration = 0; temperature = d.getWidth() / 10; forceConstant = Math .sqrt(d.getHeight() * d.getWidth() / graph.getVertexCount()); attraction_constant = attraction_multiplier * forceConstant; repulsion_constant = repulsion_multiplier * forceConstant; } } private double EPSILON = 0.000001D; /** * Moves the iteration forward one notch, calculation attraction and * repulsion between vertices and edges and cooling the temperature. */ public synchronized void step() { currentIteration++; /** * Calculate repulsion */ while(true) { try { for(V v1 : getGraph().getVertices()) { calcRepulsion(v1); } break; } catch(ConcurrentModificationException cme) {} } /** * Calculate attraction */ while(true) { try { for(E e : getGraph().getEdges()) { calcAttraction(e); } break; } catch(ConcurrentModificationException cme) {} } while(true) { try { for(V v : getGraph().getVertices()) { if (isLocked(v)) continue; calcPositions(v); } break; } catch(ConcurrentModificationException cme) {} } cool(); } protected synchronized void calcPositions(V v) { FRVertexData fvd = getFRData(v); if(fvd == null) return; Point2D xyd = apply(v); double deltaLength = Math.max(EPSILON, fvd.norm()); double newXDisp = fvd.getX() / deltaLength * Math.min(deltaLength, temperature); if (Double.isNaN(newXDisp)) { throw new IllegalArgumentException( "Unexpected mathematical result in FRLayout:calcPositions [xdisp]"); } double newYDisp = fvd.getY() / deltaLength * Math.min(deltaLength, temperature); xyd.setLocation(xyd.getX()+newXDisp, xyd.getY()+newYDisp); double borderWidth = getSize().getWidth() / 50.0; double newXPos = xyd.getX(); if (newXPos < borderWidth) { newXPos = borderWidth + Math.random() * borderWidth * 2.0; } else if (newXPos > (getSize().getWidth() - borderWidth)) { newXPos = getSize().getWidth() - borderWidth - Math.random() * borderWidth * 2.0; } double newYPos = xyd.getY(); if (newYPos < borderWidth) { newYPos = borderWidth + Math.random() * borderWidth * 2.0; } else if (newYPos > (getSize().getHeight() - borderWidth)) { newYPos = getSize().getHeight() - borderWidth - Math.random() * borderWidth * 2.0; } xyd.setLocation(newXPos, newYPos); } protected void calcAttraction(E e) { Pair endpoints = getGraph().getEndpoints(e); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); boolean v1_locked = isLocked(v1); boolean v2_locked = isLocked(v2); if(v1_locked && v2_locked) { // both locked, do nothing return; } Point2D p1 = apply(v1); Point2D p2 = apply(v2); if(p1 == null || p2 == null) return; double xDelta = p1.getX() - p2.getX(); double yDelta = p1.getY() - p2.getY(); double deltaLength = Math.max(EPSILON, Math.sqrt((xDelta * xDelta) + (yDelta * yDelta))); double force = (deltaLength * deltaLength) / attraction_constant; if (Double.isNaN(force)) { throw new IllegalArgumentException( "Unexpected mathematical result in FRLayout:calcPositions [force]"); } double dx = (xDelta / deltaLength) * force; double dy = (yDelta / deltaLength) * force; if(v1_locked == false) { FRVertexData fvd1 = getFRData(v1); fvd1.offset(-dx, -dy); } if(v2_locked == false) { FRVertexData fvd2 = getFRData(v2); fvd2.offset(dx, dy); } } protected void calcRepulsion(V v1) { FRVertexData fvd1 = getFRData(v1); if(fvd1 == null) return; fvd1.setLocation(0, 0); try { for(V v2 : getGraph().getVertices()) { // if (isLocked(v2)) continue; if (v1 != v2) { Point2D p1 = apply(v1); Point2D p2 = apply(v2); if(p1 == null || p2 == null) continue; double xDelta = p1.getX() - p2.getX(); double yDelta = p1.getY() - p2.getY(); double deltaLength = Math.max(EPSILON, Math .sqrt((xDelta * xDelta) + (yDelta * yDelta))); double force = (repulsion_constant * repulsion_constant) / deltaLength; if (Double.isNaN(force)) { throw new RuntimeException( "Unexpected mathematical result in FRLayout:calcPositions [repulsion]"); } fvd1.offset((xDelta / deltaLength) * force, (yDelta / deltaLength) * force); } } } catch(ConcurrentModificationException cme) { calcRepulsion(v1); } } private void cool() { temperature *= (1.0 - currentIteration / (double) mMaxIterations); } public void setMaxIterations(int maxIterations) { mMaxIterations = maxIterations; } protected FRVertexData getFRData(V v) { return frVertexData.getUnchecked(v); } /** * @return true */ public boolean isIncremental() { return true; } /** * @return true once the current iteration has passed the maximum count. */ public boolean done() { if (currentIteration > mMaxIterations || temperature < 1.0/max_dimension) { return true; } return false; } @SuppressWarnings("serial") protected static class FRVertexData extends Point2D.Double { protected void offset(double x, double y) { this.x += x; this.y += y; } protected double norm() { return Math.sqrt(x*x + y*y); } } }jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/FRLayout2.java000066400000000000000000000237411276402340000323700ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ConcurrentModificationException; import com.google.common.base.Preconditions; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer; import edu.uci.ics.jung.algorithms.util.IterativeContext; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Pair; /** * Implements the Fruchterman-Reingold force-directed algorithm for node layout. * This is an experimental attempt at optimizing {@code FRLayout}; if it is successful * it will be folded back into {@code FRLayout} (and this class will disappear). * *

    Behavior is determined by the following settable parameters: *

      *
    • attraction multiplier: how much edges try to keep their vertices together *
    • repulsion multiplier: how much vertices try to push each other apart *
    • maximum iterations: how many iterations this algorithm will use before stopping *
    * Each of the first two defaults to 0.75; the maximum number of iterations defaults to 700. * * @see "Fruchterman and Reingold, 'Graph Drawing by Force-directed Placement'" * @see "http://i11www.ilkd.uni-karlsruhe.de/teaching/SS_04/visualisierung/papers/fruchterman91graph.pdf" * * @author Tom Nelson * @author Scott White, Yan-Biao Boey, Danyel Fisher */ public class FRLayout2 extends AbstractLayout implements IterativeContext { private double forceConstant; private double temperature; private int currentIteration; private int maxIterations = 700; protected LoadingCache frVertexData = CacheBuilder.newBuilder().build(new CacheLoader() { public Point2D load(V vertex) { return new Point2D.Double(); } }); private double attraction_multiplier = 0.75; private double attraction_constant; private double repulsion_multiplier = 0.75; private double repulsion_constant; private double max_dimension; private Rectangle2D innerBounds = new Rectangle2D.Double(); private boolean checked = false; public FRLayout2(Graph g) { super(g); } public FRLayout2(Graph g, Dimension d) { super(g, new RandomLocationTransformer(d), d); max_dimension = Math.max(d.height, d.width); initialize(); } @Override public void setSize(Dimension size) { if(initialized == false) setInitializer(new RandomLocationTransformer(size)); super.setSize(size); double t = size.width/50.0; innerBounds.setFrameFromDiagonal(t,t,size.width-t,size.height-t); max_dimension = Math.max(size.height, size.width); } public void setAttractionMultiplier(double attraction) { this.attraction_multiplier = attraction; } public void setRepulsionMultiplier(double repulsion) { this.repulsion_multiplier = repulsion; } public void reset() { doInit(); } public void initialize() { doInit(); } private void doInit() { Graph graph = getGraph(); Dimension d = getSize(); if(graph != null && d != null) { currentIteration = 0; temperature = d.getWidth() / 10; forceConstant = Math .sqrt(d.getHeight() * d.getWidth() / graph.getVertexCount()); attraction_constant = attraction_multiplier * forceConstant; repulsion_constant = repulsion_multiplier * forceConstant; } } private double EPSILON = 0.000001D; /** * Moves the iteration forward one notch, calculation attraction and * repulsion between vertices and edges and cooling the temperature. */ public synchronized void step() { currentIteration++; /** * Calculate repulsion */ while(true) { try { for(V v1 : getGraph().getVertices()) { calcRepulsion(v1); } break; } catch(ConcurrentModificationException cme) {} } /** * Calculate attraction */ while(true) { try { for(E e : getGraph().getEdges()) { calcAttraction(e); } break; } catch(ConcurrentModificationException cme) {} } while(true) { try { for(V v : getGraph().getVertices()) { if (isLocked(v)) continue; calcPositions(v); } break; } catch(ConcurrentModificationException cme) {} } cool(); } protected synchronized void calcPositions(V v) { Point2D fvd = this.frVertexData.getUnchecked(v); if(fvd == null) return; Point2D xyd = apply(v); double deltaLength = Math.max(EPSILON, Math.sqrt(fvd.getX()*fvd.getX()+fvd.getY()*fvd.getY())); double newXDisp = fvd.getX() / deltaLength * Math.min(deltaLength, temperature); Preconditions.checkState(!Double.isNaN(newXDisp), "Unexpected mathematical result in FRLayout:calcPositions [xdisp]"); double newYDisp = fvd.getY() / deltaLength * Math.min(deltaLength, temperature); double newX = xyd.getX()+Math.max(-5, Math.min(5,newXDisp)); double newY = xyd.getY()+Math.max(-5, Math.min(5,newYDisp)); newX = Math.max(innerBounds.getMinX(), Math.min(newX, innerBounds.getMaxX())); newY = Math.max(innerBounds.getMinY(), Math.min(newY, innerBounds.getMaxY())); xyd.setLocation(newX, newY); } protected void calcAttraction(E e) { Pair endpoints = getGraph().getEndpoints(e); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); boolean v1_locked = isLocked(v1); boolean v2_locked = isLocked(v2); if(v1_locked && v2_locked) { // both locked, do nothing return; } Point2D p1 = apply(v1); Point2D p2 = apply(v2); if(p1 == null || p2 == null) return; double xDelta = p1.getX() - p2.getX(); double yDelta = p1.getY() - p2.getY(); double deltaLength = Math.max(EPSILON, p1.distance(p2)); double force = deltaLength / attraction_constant; Preconditions.checkState(!Double.isNaN(force), "Unexpected mathematical result in FRLayout:calcPositions [force]"); double dx = xDelta * force; double dy = yDelta * force; Point2D fvd1 = frVertexData.getUnchecked(v1); Point2D fvd2 = frVertexData.getUnchecked(v2); if(v2_locked) { // double the offset for v1, as v2 will not be moving in // the opposite direction fvd1.setLocation(fvd1.getX()-2*dx, fvd1.getY()-2*dy); } else { fvd1.setLocation(fvd1.getX()-dx, fvd1.getY()-dy); } if(v1_locked) { // double the offset for v2, as v1 will not be moving in // the opposite direction fvd2.setLocation(fvd2.getX()+2*dx, fvd2.getY()+2*dy); } else { fvd2.setLocation(fvd2.getX()+dx, fvd2.getY()+dy); } } protected void calcRepulsion(V v1) { Point2D fvd1 = frVertexData.getUnchecked(v1); if(fvd1 == null) return; fvd1.setLocation(0, 0); boolean v1_locked = isLocked(v1); try { for(V v2 : getGraph().getVertices()) { boolean v2_locked = isLocked(v2); if (v1_locked && v2_locked) continue; if (v1 != v2) { Point2D p1 = apply(v1); Point2D p2 = apply(v2); if(p1 == null || p2 == null) continue; double xDelta = p1.getX() - p2.getX(); double yDelta = p1.getY() - p2.getY(); double deltaLength = Math.max(EPSILON, p1.distanceSq(p2)); double force = (repulsion_constant * repulsion_constant);// / deltaLength; double forceOverDeltaLength = force / deltaLength; Preconditions.checkState(!Double.isNaN(force), "Unexpected mathematical result in FRLayout:calcPositions [repulsion]"); if(v2_locked) { // double the offset for v1, as v2 will not be moving in // the opposite direction fvd1.setLocation(fvd1.getX()+2 * xDelta * forceOverDeltaLength, fvd1.getY()+ 2 * yDelta * forceOverDeltaLength); } else { fvd1.setLocation(fvd1.getX()+xDelta * forceOverDeltaLength, fvd1.getY()+yDelta * forceOverDeltaLength); } } } } catch(ConcurrentModificationException cme) { calcRepulsion(v1); } } private void cool() { temperature *= (1.0 - currentIteration / (double) maxIterations); } public void setMaxIterations(int maxIterations) { this.maxIterations = maxIterations; } /** * @return true */ public boolean isIncremental() { return true; } /** * @return true once the current iteration has passed the maximum count. */ public boolean done() { if (currentIteration > maxIterations || temperature < 1.0/max_dimension) { if (!checked) { checked = true; } return true; } return false; } } GraphElementAccessor.java000066400000000000000000000032021276402340000345460ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * * Created on Apr 12, 2005 */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Shape; import java.util.Collection; /** * Interface for coordinate-based selection of graph components. * @author Tom Nelson * @author Joshua O'Madadhain */ public interface GraphElementAccessor { /** * Returns the vertex, if any, associated with (x, y). * * @param layout the layout instance that records the positions for all vertices * @param x the x coordinate of the pick point * @param y the y coordinate of the pick point * @return the vertex associated with (x, y) */ V getVertex(Layout layout, double x, double y); /** * @param layout the layout instance that records the positions for all vertices * @param rectangle the region in which the returned vertices are located * @return the vertices whose locations given by {@code layout} * are contained within {@code rectangle} */ Collection getVertices(Layout layout, Shape rectangle); /** * @param layout the context in which the location is defined * @param x the x coordinate of the location * @param y the y coordinate of the location * @return an edge which is associated with the location {@code (x,y)} * as given by {@code layout}, generally by reference to the edge's endpoints */ E getEdge(Layout layout, double x, double y); }jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/ISOMLayout.java000066400000000000000000000134561276402340000325500ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.layout; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.List; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer; import edu.uci.ics.jung.algorithms.util.IterativeContext; import edu.uci.ics.jung.graph.Graph; /** * Implements a self-organizing map layout algorithm, based on Meyer's * self-organizing graph methods. * * @author Yan Biao Boey */ public class ISOMLayout extends AbstractLayout implements IterativeContext { protected LoadingCache isomVertexData = CacheBuilder.newBuilder().build(new CacheLoader() { public ISOMVertexData load(V vertex) { return new ISOMVertexData(); } }); private int maxEpoch; private int epoch; private int radiusConstantTime; private int radius; private int minRadius; private double adaption; private double initialAdaption; private double minAdaption; protected GraphElementAccessor elementAccessor = new RadiusGraphElementAccessor(); private double coolingFactor; private List queue = new ArrayList(); private String status = null; /** * @return the current number of epochs and execution status, as a string. */ public String getStatus() { return status; } public ISOMLayout(Graph g) { super(g); } public void initialize() { setInitializer(new RandomLocationTransformer(getSize())); maxEpoch = 2000; epoch = 1; radiusConstantTime = 100; radius = 5; minRadius = 1; initialAdaption = 90.0D / 100.0D; adaption = initialAdaption; minAdaption = 0; //factor = 0; //Will be set later on coolingFactor = 2; //temperature = 0.03; //initialJumpRadius = 100; //jumpRadius = initialJumpRadius; //delay = 100; } /** * Advances the current positions of the graph elements. */ public void step() { status = "epoch: " + epoch + "; "; if (epoch < maxEpoch) { adjust(); updateParameters(); status += " status: running"; } else { status += "adaption: " + adaption + "; "; status += "status: done"; // done = true; } } private synchronized void adjust() { //Generate random position in graph space Point2D tempXYD = new Point2D.Double(); // creates a new XY data location tempXYD.setLocation(10 + Math.random() * getSize().getWidth(), 10 + Math.random() * getSize().getHeight()); //Get closest vertex to random position V winner = elementAccessor.getVertex(this, tempXYD.getX(), tempXYD.getY()); while(true) { try { for(V v : getGraph().getVertices()) { ISOMVertexData ivd = getISOMVertexData(v); ivd.distance = 0; ivd.visited = false; } break; } catch(ConcurrentModificationException cme) {} } adjustVertex(winner, tempXYD); } private synchronized void updateParameters() { epoch++; double factor = Math.exp(-1 * coolingFactor * (1.0 * epoch / maxEpoch)); adaption = Math.max(minAdaption, factor * initialAdaption); //jumpRadius = (int) factor * jumpRadius; //temperature = factor * temperature; if ((radius > minRadius) && (epoch % radiusConstantTime == 0)) { radius--; } } private synchronized void adjustVertex(V v, Point2D tempXYD) { queue.clear(); ISOMVertexData ivd = getISOMVertexData(v); ivd.distance = 0; ivd.visited = true; queue.add(v); V current; while (!queue.isEmpty()) { current = queue.remove(0); ISOMVertexData currData = getISOMVertexData(current); Point2D currXYData = apply(current); double dx = tempXYD.getX() - currXYData.getX(); double dy = tempXYD.getY() - currXYData.getY(); double factor = adaption / Math.pow(2, currData.distance); currXYData.setLocation(currXYData.getX()+(factor*dx), currXYData.getY()+(factor*dy)); if (currData.distance < radius) { Collection s = getGraph().getNeighbors(current); while(true) { try { for(V child : s) { ISOMVertexData childData = getISOMVertexData(child); if (childData != null && !childData.visited) { childData.visited = true; childData.distance = currData.distance + 1; queue.add(child); } } break; } catch(ConcurrentModificationException cme) {} } } } } protected ISOMVertexData getISOMVertexData(V v) { return isomVertexData.getUnchecked(v); } /** * This one is an incremental visualization. * @return true is the layout algorithm is incremental, false otherwise */ public boolean isIncremental() { return true; } /** * Returns true if the vertex positions are no longer being * updated. Currently ISOMLayout stops updating vertex * positions after a certain number of iterations have taken place. * @return true if the vertex position updates have stopped, * false otherwise */ public boolean done() { return epoch >= maxEpoch; } protected static class ISOMVertexData { int distance; boolean visited; protected ISOMVertexData() { distance = 0; visited = false; } } /** * Resets the layout iteration count to 0, which allows the layout algorithm to * continue updating vertex positions. */ public void reset() { epoch = 0; } }jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/KKLayout.java000066400000000000000000000265421276402340000323060ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.layout; /* * This source is under the same license with JUNG. * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ import java.awt.Dimension; import java.awt.geom.Point2D; import java.util.ConcurrentModificationException; import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer; import edu.uci.ics.jung.algorithms.shortestpath.Distance; import edu.uci.ics.jung.algorithms.shortestpath.DistanceStatistics; import edu.uci.ics.jung.algorithms.shortestpath.UnweightedShortestPath; import edu.uci.ics.jung.algorithms.util.IterativeContext; import edu.uci.ics.jung.graph.Graph; /** * Implements the Kamada-Kawai algorithm for node layout. * Does not respect filter calls, and sometimes crashes when the view changes to it. * * @see "Tomihisa Kamada and Satoru Kawai: An algorithm for drawing general indirect graphs. Information Processing Letters 31(1):7-15, 1989" * @see "Tomihisa Kamada: On visualization of abstract objects and relations. Ph.D. dissertation, Dept. of Information Science, Univ. of Tokyo, Dec. 1988." * * @author Masanori Harada */ public class KKLayout extends AbstractLayout implements IterativeContext { private double EPSILON = 0.1d; private int currentIteration; private int maxIterations = 2000; private String status = "KKLayout"; private double L; // the ideal length of an edge private double K = 1; // arbitrary const number private double[][] dm; // distance matrix private boolean adjustForGravity = true; private boolean exchangeVertices = true; private V[] vertices; private Point2D[] xydata; /** * Retrieves graph distances between vertices of the visible graph */ protected Distance distance; /** * The diameter of the visible graph. In other words, the maximum over all pairs * of vertices of the length of the shortest path between a and bf the visible graph. */ protected double diameter; /** * A multiplicative factor which partly specifies the "preferred" length of an edge (L). */ private double length_factor = 0.9; /** * A multiplicative factor which specifies the fraction of the graph's diameter to be * used as the inter-vertex distance between disconnected vertices. */ private double disconnected_multiplier = 0.5; public KKLayout(Graph g) { this(g, new UnweightedShortestPath(g)); } /** * Creates an instance for the specified graph and distance metric. * @param g the graph on which the layout algorithm is to operate * @param distance specifies the distance between pairs of vertices */ public KKLayout(Graph g, Distance distance){ super(g); this.distance = distance; } /** * @param length_factor a multiplicative factor which partially specifies * the preferred length of an edge */ public void setLengthFactor(double length_factor){ this.length_factor = length_factor; } /** * @param disconnected_multiplier a multiplicative factor that specifies the fraction of the * graph's diameter to be used as the inter-vertex distance between disconnected vertices */ public void setDisconnectedDistanceMultiplier(double disconnected_multiplier){ this.disconnected_multiplier = disconnected_multiplier; } /** * @return a string with information about the current status of the algorithm. */ public String getStatus() { return status + this.getSize(); } public void setMaxIterations(int maxIterations) { this.maxIterations = maxIterations; } /** * @return true */ public boolean isIncremental() { return true; } /** * @return true if the current iteration has passed the maximum count. */ public boolean done() { if (currentIteration > maxIterations) { return true; } return false; } @SuppressWarnings("unchecked") public void initialize() { currentIteration = 0; if(graph != null && size != null) { double height = size.getHeight(); double width = size.getWidth(); int n = graph.getVertexCount(); dm = new double[n][n]; vertices = (V[])graph.getVertices().toArray(); xydata = new Point2D[n]; // assign IDs to all visible vertices while(true) { try { int index = 0; for(V v : graph.getVertices()) { Point2D xyd = apply(v); vertices[index] = v; xydata[index] = xyd; index++; } break; } catch(ConcurrentModificationException cme) {} } diameter = DistanceStatistics.diameter(graph, distance, true); double L0 = Math.min(height, width); L = (L0 / diameter) * length_factor; // length_factor used to be hardcoded to 0.9 //L = 0.75 * Math.sqrt(height * width / n); for (int i = 0; i < n - 1; i++) { for (int j = i + 1; j < n; j++) { Number d_ij = distance.getDistance(vertices[i], vertices[j]); Number d_ji = distance.getDistance(vertices[j], vertices[i]); double dist = diameter * disconnected_multiplier; if (d_ij != null) dist = Math.min(d_ij.doubleValue(), dist); if (d_ji != null) dist = Math.min(d_ji.doubleValue(), dist); dm[i][j] = dm[j][i] = dist; } } } } public void step() { try { currentIteration++; double energy = calcEnergy(); status = "Kamada-Kawai V=" + getGraph().getVertexCount() + "(" + getGraph().getVertexCount() + ")" + " IT: " + currentIteration + " E=" + energy ; int n = getGraph().getVertexCount(); if (n == 0) return; double maxDeltaM = 0; int pm = -1; // the node having max deltaM for (int i = 0; i < n; i++) { if (isLocked(vertices[i])) continue; double deltam = calcDeltaM(i); if (maxDeltaM < deltam) { maxDeltaM = deltam; pm = i; } } if (pm == -1) return; for (int i = 0; i < 100; i++) { double[] dxy = calcDeltaXY(pm); xydata[pm].setLocation(xydata[pm].getX()+dxy[0], xydata[pm].getY()+dxy[1]); double deltam = calcDeltaM(pm); if (deltam < EPSILON) break; } if (adjustForGravity) adjustForGravity(); if (exchangeVertices && maxDeltaM < EPSILON) { energy = calcEnergy(); for (int i = 0; i < n - 1; i++) { if (isLocked(vertices[i])) continue; for (int j = i + 1; j < n; j++) { if (isLocked(vertices[j])) continue; double xenergy = calcEnergyIfExchanged(i, j); if (energy > xenergy) { double sx = xydata[i].getX(); double sy = xydata[i].getY(); xydata[i].setLocation(xydata[j]); xydata[j].setLocation(sx, sy); return; } } } } } finally { // fireStateChanged(); } } /** * Shift all vertices so that the center of gravity is located at * the center of the screen. */ public void adjustForGravity() { Dimension d = getSize(); double height = d.getHeight(); double width = d.getWidth(); double gx = 0; double gy = 0; for (int i = 0; i < xydata.length; i++) { gx += xydata[i].getX(); gy += xydata[i].getY(); } gx /= xydata.length; gy /= xydata.length; double diffx = width / 2 - gx; double diffy = height / 2 - gy; for (int i = 0; i < xydata.length; i++) { xydata[i].setLocation(xydata[i].getX()+diffx, xydata[i].getY()+diffy); } } @Override public void setSize(Dimension size) { if(initialized == false) setInitializer(new RandomLocationTransformer(size)); super.setSize(size); } public void setAdjustForGravity(boolean on) { adjustForGravity = on; } public boolean getAdjustForGravity() { return adjustForGravity; } /** * Enable or disable the local minimum escape technique by * exchanging vertices. * @param on iff the local minimum escape technique is to be enabled */ public void setExchangeVertices(boolean on) { exchangeVertices = on; } public boolean getExchangeVertices() { return exchangeVertices; } /** * Determines a step to new position of the vertex m. */ private double[] calcDeltaXY(int m) { double dE_dxm = 0; double dE_dym = 0; double d2E_d2xm = 0; double d2E_dxmdym = 0; double d2E_dymdxm = 0; double d2E_d2ym = 0; for (int i = 0; i < vertices.length; i++) { if (i != m) { double dist = dm[m][i]; double l_mi = L * dist; double k_mi = K / (dist * dist); double dx = xydata[m].getX() - xydata[i].getX(); double dy = xydata[m].getY() - xydata[i].getY(); double d = Math.sqrt(dx * dx + dy * dy); double ddd = d * d * d; dE_dxm += k_mi * (1 - l_mi / d) * dx; dE_dym += k_mi * (1 - l_mi / d) * dy; d2E_d2xm += k_mi * (1 - l_mi * dy * dy / ddd); d2E_dxmdym += k_mi * l_mi * dx * dy / ddd; d2E_d2ym += k_mi * (1 - l_mi * dx * dx / ddd); } } // d2E_dymdxm equals to d2E_dxmdym. d2E_dymdxm = d2E_dxmdym; double denomi = d2E_d2xm * d2E_d2ym - d2E_dxmdym * d2E_dymdxm; double deltaX = (d2E_dxmdym * dE_dym - d2E_d2ym * dE_dxm) / denomi; double deltaY = (d2E_dymdxm * dE_dxm - d2E_d2xm * dE_dym) / denomi; return new double[]{deltaX, deltaY}; } /** * Calculates the gradient of energy function at the vertex m. */ private double calcDeltaM(int m) { double dEdxm = 0; double dEdym = 0; for (int i = 0; i < vertices.length; i++) { if (i != m) { double dist = dm[m][i]; double l_mi = L * dist; double k_mi = K / (dist * dist); double dx = xydata[m].getX() - xydata[i].getX(); double dy = xydata[m].getY() - xydata[i].getY(); double d = Math.sqrt(dx * dx + dy * dy); double common = k_mi * (1 - l_mi / d); dEdxm += common * dx; dEdym += common * dy; } } return Math.sqrt(dEdxm * dEdxm + dEdym * dEdym); } /** * Calculates the energy function E. */ private double calcEnergy() { double energy = 0; for (int i = 0; i < vertices.length - 1; i++) { for (int j = i + 1; j < vertices.length; j++) { double dist = dm[i][j]; double l_ij = L * dist; double k_ij = K / (dist * dist); double dx = xydata[i].getX() - xydata[j].getX(); double dy = xydata[i].getY() - xydata[j].getY(); double d = Math.sqrt(dx * dx + dy * dy); energy += k_ij / 2 * (dx * dx + dy * dy + l_ij * l_ij - 2 * l_ij * d); } } return energy; } /** * Calculates the energy function E as if positions of the * specified vertices are exchanged. */ private double calcEnergyIfExchanged(int p, int q) { if (p >= q) throw new RuntimeException("p should be < q"); double energy = 0; // < 0 for (int i = 0; i < vertices.length - 1; i++) { for (int j = i + 1; j < vertices.length; j++) { int ii = i; int jj = j; if (i == p) ii = q; if (j == q) jj = p; double dist = dm[i][j]; double l_ij = L * dist; double k_ij = K / (dist * dist); double dx = xydata[ii].getX() - xydata[jj].getX(); double dy = xydata[ii].getY() - xydata[jj].getY(); double d = Math.sqrt(dx * dx + dy * dy); energy += k_ij / 2 * (dx * dx + dy * dy + l_ij * l_ij - 2 * l_ij * d); } } return energy; } public void reset() { currentIteration = 0; } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/Layout.java000066400000000000000000000044061276402340000320530ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.awt.geom.Point2D; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Graph; /** * A generalized interface is a mechanism for returning (x,y) coordinates * from vertices. In general, most of these methods are used to both control and * get information from the layout algorithm. *

    * @author danyelf * @author tom nelson */ public interface Layout extends Function { /** * Initializes fields in the node that may not have * been set during the constructor. Must be called before * the iterations begin. */ void initialize(); /** * @param initializer a function that specifies initial locations for all vertices */ void setInitializer(Function initializer); /** * @param graph the graph that this algorithm is to operate on */ void setGraph(Graph graph); /** * @return the graph that this Layout refers to */ Graph getGraph(); void reset(); /** * @param d the space to use to lay out this graph */ void setSize(Dimension d); /** * @return the current size of the visualization's space */ Dimension getSize(); /** * Locks or unlocks the specified vertex. Locking the vertex fixes it at its current position, * so that it will not be affected by the layout algorithm. Unlocking it allows the layout * algorithm to change the vertex's position. * * @param v the vertex to lock/unlock * @param state {@code true} to lock the vertex, {@code false} to unlock it */ void lock(V v, boolean state); /** * @param v the vertex whose locked state is being queried * @return true if the position of vertex v is locked */ boolean isLocked(V v); /** * Changes the layout coordinates of {@code v} to {@code location}. * @param v the vertex whose location is to be specified * @param location the coordinates of the specified location */ void setLocation(V v, Point2D location); } LayoutDecorator.java000066400000000000000000000045571276402340000336460ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 23, 2005 */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.awt.geom.Point2D; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.util.IterativeContext; import edu.uci.ics.jung.graph.Graph; /** * a pure decorator for the Layout interface. Intended to be overridden * to provide specific behavior decoration * * @author Tom Nelson * */ public abstract class LayoutDecorator implements Layout, IterativeContext { protected Layout delegate; /** * Creates an instance backed by the specified {@code delegate}. * @param delegate the layout to which this instance is delegating */ public LayoutDecorator(Layout delegate) { this.delegate = delegate; } /** * @return the backing (delegate) layout. */ public Layout getDelegate() { return delegate; } public void setDelegate(Layout delegate) { this.delegate = delegate; } public void step() { if(delegate instanceof IterativeContext) { ((IterativeContext)delegate).step(); } } public void initialize() { delegate.initialize(); } public void setInitializer(Function initializer) { delegate.setInitializer(initializer); } public void setLocation(V v, Point2D location) { delegate.setLocation(v, location); } public Dimension getSize() { return delegate.getSize(); } public Graph getGraph() { return delegate.getGraph(); } public Point2D transform(V v) { return delegate.apply(v); } public boolean done() { if(delegate instanceof IterativeContext) { return ((IterativeContext)delegate).done(); } return true; } public void lock(V v, boolean state) { delegate.lock(v, state); } public boolean isLocked(V v) { return delegate.isLocked(v); } public void setSize(Dimension d) { delegate.setSize(d); } public void reset() { delegate.reset(); } public void setGraph(Graph graph) { delegate.setGraph(graph); } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/PolarPoint.java000066400000000000000000000055271276402340000326720ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.layout; import java.awt.geom.Point2D; /** * Represents a point in polar coordinates: distance and angle from the origin. * Includes conversions between polar and Cartesian * coordinates (Point2D). * * @author Tom Nelson - tomnelson@dev.java.net */ public class PolarPoint { double theta; double radius; /** * Creates a new instance with radius and angle each 0. */ public PolarPoint() { this(0,0); } /** * Creates a new instance with the specified radius and angle. * @param theta the angle of the point to create * @param radius the distance from the origin of the point to create */ public PolarPoint(double theta, double radius) { this.theta = theta; this.radius = radius; } /** * @return the angle for this point */ public double getTheta() { return theta; } /** * @return the radius for this point */ public double getRadius() { return radius; } public void setTheta(double theta) { this.theta = theta; } public void setRadius(double radius) { this.radius = radius; } /** * @param polar the input location to convert * @return the result of converting polar to Cartesian coordinates. */ public static Point2D polarToCartesian(PolarPoint polar) { return polarToCartesian(polar.getTheta(), polar.getRadius()); } /** * @param theta the angle of the input location * @param radius the distance from the origin of the input location * @return the result of converting (theta, radius) to Cartesian coordinates. */ public static Point2D polarToCartesian(double theta, double radius) { return new Point2D.Double(radius*Math.cos(theta), radius*Math.sin(theta)); } /** * @param point the input location * @return the result of converting point to polar coordinates. */ public static PolarPoint cartesianToPolar(Point2D point) { return cartesianToPolar(point.getX(), point.getY()); } /** * @param x the x coordinate of the input location * @param y the y coordinate of the input location * @return the result of converting (x, y) to polar coordinates. */ public static PolarPoint cartesianToPolar(double x, double y) { double theta = Math.atan2(y,x); double radius = Math.sqrt(x*x+y*y); return new PolarPoint(theta, radius); } @Override public String toString() { return "PolarPoint[" + radius + "," + theta +"]"; } /** * Sets the angle and radius of this point to those of {@code p}. * @param p the point whose location is copied into this instance */ public void setLocation(PolarPoint p) { this.theta = p.getTheta(); this.radius = p.getRadius(); } }RadialTreeLayout.java000066400000000000000000000060161276402340000337300ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 9, 2005 */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.awt.geom.Point2D; import java.util.HashMap; import java.util.Map; import edu.uci.ics.jung.graph.Forest; /** * A radial layout for Tree or Forest graphs. * * @author Tom Nelson * */ public class RadialTreeLayout extends TreeLayout { protected Map polarLocations; public RadialTreeLayout(Forest g) { this(g, DEFAULT_DISTX, DEFAULT_DISTY); } public RadialTreeLayout(Forest g, int distx) { this(g, distx, DEFAULT_DISTY); } public RadialTreeLayout(Forest g, int distx, int disty) { super(g, distx, disty); } @Override protected void buildTree() { super.buildTree(); this.polarLocations = new HashMap(); setRadialLocations(); } @Override public void setSize(Dimension size) { this.size = size; buildTree(); } @Override protected void setCurrentPositionFor(V vertex) { locations.getUnchecked(vertex).setLocation(m_currentPoint); } @Override public void setLocation(V v, Point2D location) { Point2D c = getCenter(); Point2D pv = new Point2D.Double(location.getX() - c.getX(), location.getY() - c.getY()); PolarPoint newLocation = PolarPoint.cartesianToPolar(pv); PolarPoint currentLocation = polarLocations.get(v); if (currentLocation == null) polarLocations.put(v, newLocation); else currentLocation.setLocation(newLocation); } /** * @return a map from vertices to their locations in polar coordinates. */ public Map getPolarLocations() { return polarLocations; } @Override public Point2D apply(V v) { PolarPoint pp = polarLocations.get(v); double centerX = getSize().getWidth()/2; double centerY = getSize().getHeight()/2; Point2D cartesian = PolarPoint.polarToCartesian(pp); cartesian.setLocation(cartesian.getX()+centerX,cartesian.getY()+centerY); return cartesian; } private Point2D getMaxXY() { double maxx = 0; double maxy = 0; for(Point2D p : locations.asMap().values()) { maxx = Math.max(maxx, p.getX()); maxy = Math.max(maxy, p.getY()); } return new Point2D.Double(maxx,maxy); } private void setRadialLocations() { Point2D max = getMaxXY(); double maxx = max.getX(); double maxy = max.getY(); maxx = Math.max(maxx, size.width); double theta = 2*Math.PI/maxx; double deltaRadius = size.width/2/maxy; for(Map.Entry entry : locations.asMap().entrySet()) { V v = entry.getKey(); Point2D p = entry.getValue(); PolarPoint polarPoint = new PolarPoint(p.getX()*theta, (p.getY() - this.distY)*deltaRadius); polarLocations.put(v, polarPoint); } } } RadiusGraphElementAccessor.java000066400000000000000000000153261276402340000357300ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * * Created on Apr 12, 2005 */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Shape; import java.awt.geom.Point2D; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import edu.uci.ics.jung.graph.Graph; /** * Simple implementation of PickSupport that returns the vertex or edge * that is closest to the specified location. This implementation * provides the same picking options that were available in * previous versions of AbstractLayout. * *

    No element will be returned that is farther away than the specified * maximum distance. * * @author Tom Nelson * @author Joshua O'Madadhain */ public class RadiusGraphElementAccessor implements GraphElementAccessor { protected double maxDistance; /** * Creates an instance with an effectively infinite default maximum distance. */ public RadiusGraphElementAccessor() { this(Math.sqrt(Double.MAX_VALUE - 1000)); } /** * Creates an instance with the specified default maximum distance. * @param maxDistance the maximum distance at which any element can be from a specified location * and still be returned */ public RadiusGraphElementAccessor(double maxDistance) { this.maxDistance = maxDistance; } /** * Gets the vertex nearest to the location of the (x,y) location selected, * within a distance of maxDistance. Iterates through all * visible vertices and checks their distance from the click. Override this * method to provide a more efficient implementation. * * @param layout the context in which the location is defined * @param x the x coordinate of the location * @param y the y coordinate of the location * @return a vertex which is associated with the location {@code (x,y)} * as given by {@code layout} */ public V getVertex(Layout layout, double x, double y) { return getVertex(layout, x, y, this.maxDistance); } /** * Gets the vertex nearest to the location of the (x,y) location selected, * within a distance of {@code maxDistance}. Iterates through all * visible vertices and checks their distance from the location. Override this * method to provide a more efficient implementation. * * @param layout the context in which the location is defined * @param x the x coordinate of the location * @param y the y coordinate of the location * @param maxDistance the maximum distance at which any element can be from a specified location * and still be returned * @return a vertex which is associated with the location {@code (x,y)} * as given by {@code layout} */ public V getVertex(Layout layout, double x, double y, double maxDistance) { double minDistance = maxDistance * maxDistance; V closest = null; while(true) { try { for(V v : layout.getGraph().getVertices()) { Point2D p = layout.apply(v); double dx = p.getX() - x; double dy = p.getY() - y; double dist = dx * dx + dy * dy; if (dist < minDistance) { minDistance = dist; closest = v; } } break; } catch(ConcurrentModificationException cme) {} } return closest; } public Collection getVertices(Layout layout, Shape rectangle) { Set pickedVertices = new HashSet(); while(true) { try { for(V v : layout.getGraph().getVertices()) { Point2D p = layout.apply(v); if(rectangle.contains(p)) { pickedVertices.add(v); } } break; } catch(ConcurrentModificationException cme) {} } return pickedVertices; } public E getEdge(Layout layout, double x, double y) { return getEdge(layout, x, y, this.maxDistance); } /** * Gets the vertex nearest to the location of the (x,y) location selected, * whose endpoints are < {@code maxDistance}. Iterates through all * visible vertices and checks their distance from the location. Override this * method to provide a more efficient implementation. * * @param layout the context in which the location is defined * @param x the x coordinate of the location * @param y the y coordinate of the location * @param maxDistance the maximum distance at which any element can be from a specified location * and still be returned * @return an edge which is associated with the location {@code (x,y)} * as given by {@code layout} */ public E getEdge(Layout layout, double x, double y, double maxDistance) { double minDistance = maxDistance * maxDistance; E closest = null; while(true) { try { for(E e : layout.getGraph().getEdges()) { // Could replace all this set stuff with getFrom_internal() etc. Graph graph = layout.getGraph(); Collection vertices = graph.getIncidentVertices(e); Iterator vertexIterator = vertices.iterator(); V v1 = vertexIterator.next(); V v2 = vertexIterator.next(); // Get coords Point2D p1 = layout.apply(v1); Point2D p2 = layout.apply(v2); double x1 = p1.getX(); double y1 = p1.getY(); double x2 = p2.getX(); double y2 = p2.getY(); // Calculate location on line closest to (x,y) // First, check that v1 and v2 are not coincident. if (x1 == x2 && y1 == y2) continue; double b = ((y - y1) * (y2 - y1) + (x - x1) * (x2 - x1)) / ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); // double distance2; // square of the distance if (b <= 0) distance2 = (x - x1) * (x - x1) + (y - y1) * (y - y1); else if (b >= 1) distance2 = (x - x2) * (x - x2) + (y - y2) * (y - y2); else { double x3 = x1 + b * (x2 - x1); double y3 = y1 + b * (y2 - y1); distance2 = (x - x3) * (x - x3) + (y - y3) * (y - y3); } if (distance2 < minDistance) { minDistance = distance2; closest = e; } } break; } catch(ConcurrentModificationException cme) {} } return closest; } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/SpringLayout.java000066400000000000000000000246331276402340000332420ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.geom.Point2D; import java.util.ConcurrentModificationException; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer; import edu.uci.ics.jung.algorithms.util.IterativeContext; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Pair; /** * The SpringLayout package represents a visualization of a set of nodes. The * SpringLayout, which is initialized with a Graph, assigns X/Y locations to * each node. When called relax(), the SpringLayout moves the * visualization forward one step. * * @author Danyel Fisher * @author Joshua O'Madadhain */ public class SpringLayout extends AbstractLayout implements IterativeContext { protected double stretch = 0.70; protected Function lengthFunction; protected int repulsion_range_sq = 100 * 100; protected double force_multiplier = 1.0 / 3.0; protected LoadingCache springVertexData = CacheBuilder.newBuilder().build(new CacheLoader() { public SpringVertexData load(V vertex) { return new SpringVertexData(); } }); // protected Map springVertexData = // new MapMaker().makeComputingMap(new Function(){ // public SpringVertexData apply(V arg0) { // return new SpringVertexData(); // }}); /** * Constructor for a SpringLayout for a raw graph with associated * dimension--the input knows how big the graph is. Defaults to the unit * length function. * @param g the graph on which the layout algorithm is to operate */ @SuppressWarnings("unchecked") public SpringLayout(Graph g) { this(g, (Function)Functions.constant(30)); } /** * Constructor for a SpringLayout for a raw graph with associated component. * * @param g the graph on which the layout algorithm is to operate * @param length_function provides a length for each edge */ public SpringLayout(Graph g, Function length_function) { super(g); this.lengthFunction = length_function; } /** * @return the current value for the stretch parameter */ public double getStretch() { return stretch; } @Override public void setSize(Dimension size) { if(initialized == false) setInitializer(new RandomLocationTransformer(size)); super.setSize(size); } /** *

    Sets the stretch parameter for this instance. This value * specifies how much the degrees of an edge's incident vertices * should influence how easily the endpoints of that edge * can move (that is, that edge's tendency to change its length). * *

    The default value is 0.70. Positive values less than 1 cause * high-degree vertices to move less than low-degree vertices, and * values > 1 cause high-degree vertices to move more than * low-degree vertices. Negative values will have unpredictable * and inconsistent results. * @param stretch the stretch parameter */ public void setStretch(double stretch) { this.stretch = stretch; } public int getRepulsionRange() { return (int)(Math.sqrt(repulsion_range_sq)); } /** * Sets the node repulsion range (in drawing area units) for this instance. * Outside this range, nodes do not repel each other. The default value * is 100. Negative values are treated as their positive equivalents. * @param range the maximum repulsion range */ public void setRepulsionRange(int range) { this.repulsion_range_sq = range * range; } public double getForceMultiplier() { return force_multiplier; } /** * Sets the force multiplier for this instance. This value is used to * specify how strongly an edge "wants" to be its default length * (higher values indicate a greater attraction for the default length), * which affects how much its endpoints move at each timestep. * The default value is 1/3. A value of 0 turns off any attempt by the * layout to cause edges to conform to the default length. Negative * values cause long edges to get longer and short edges to get shorter; use * at your own risk. * @param force an energy field created by all living things that binds the galaxy together */ public void setForceMultiplier(double force) { this.force_multiplier = force; } public void initialize() { } /** * Relaxation step. Moves all nodes a smidge. */ public void step() { try { for(V v : getGraph().getVertices()) { SpringVertexData svd = springVertexData.getUnchecked(v); if (svd == null) { continue; } svd.dx /= 4; svd.dy /= 4; svd.edgedx = svd.edgedy = 0; svd.repulsiondx = svd.repulsiondy = 0; } } catch(ConcurrentModificationException cme) { step(); } relaxEdges(); calculateRepulsion(); moveNodes(); } protected void relaxEdges() { try { for(E e : getGraph().getEdges()) { Pair endpoints = getGraph().getEndpoints(e); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); Point2D p1 = apply(v1); Point2D p2 = apply(v2); if(p1 == null || p2 == null) continue; double vx = p1.getX() - p2.getX(); double vy = p1.getY() - p2.getY(); double len = Math.sqrt(vx * vx + vy * vy); double desiredLen = lengthFunction.apply(e); // round from zero, if needed [zero would be Bad.]. len = (len == 0) ? .0001 : len; double f = force_multiplier * (desiredLen - len) / len; f = f * Math.pow(stretch, (getGraph().degree(v1) + getGraph().degree(v2) - 2)); // the actual movement distance 'dx' is the force multiplied by the // distance to go. double dx = f * vx; double dy = f * vy; SpringVertexData v1D, v2D; v1D = springVertexData.getUnchecked(v1); v2D = springVertexData.getUnchecked(v2); v1D.edgedx += dx; v1D.edgedy += dy; v2D.edgedx += -dx; v2D.edgedy += -dy; } } catch(ConcurrentModificationException cme) { relaxEdges(); } } protected void calculateRepulsion() { try { for (V v : getGraph().getVertices()) { if (isLocked(v)) continue; SpringVertexData svd = springVertexData.getUnchecked(v); if(svd == null) continue; double dx = 0, dy = 0; for (V v2 : getGraph().getVertices()) { if (v == v2) continue; Point2D p = apply(v); Point2D p2 = apply(v2); if(p == null || p2 == null) continue; double vx = p.getX() - p2.getX(); double vy = p.getY() - p2.getY(); double distanceSq = p.distanceSq(p2); if (distanceSq == 0) { dx += Math.random(); dy += Math.random(); } else if (distanceSq < repulsion_range_sq) { double factor = 1; dx += factor * vx / distanceSq; dy += factor * vy / distanceSq; } } double dlen = dx * dx + dy * dy; if (dlen > 0) { dlen = Math.sqrt(dlen) / 2; svd.repulsiondx += dx / dlen; svd.repulsiondy += dy / dlen; } } } catch(ConcurrentModificationException cme) { calculateRepulsion(); } } protected void moveNodes() { synchronized (getSize()) { try { for (V v : getGraph().getVertices()) { if (isLocked(v)) continue; SpringVertexData vd = springVertexData.getUnchecked(v); if(vd == null) continue; Point2D xyd = apply(v); vd.dx += vd.repulsiondx + vd.edgedx; vd.dy += vd.repulsiondy + vd.edgedy; // keeps nodes from moving any faster than 5 per time unit xyd.setLocation(xyd.getX()+Math.max(-5, Math.min(5, vd.dx)), xyd.getY()+Math.max(-5, Math.min(5, vd.dy))); Dimension d = getSize(); int width = d.width; int height = d.height; if (xyd.getX() < 0) { xyd.setLocation(0, xyd.getY()); } else if (xyd.getX() > width) { xyd.setLocation(width, xyd.getY()); } if (xyd.getY() < 0) { xyd.setLocation(xyd.getX(), 0); } else if (xyd.getY() > height) { xyd.setLocation(xyd.getX(), height); } } } catch(ConcurrentModificationException cme) { moveNodes(); } } } protected static class SpringVertexData { protected double edgedx; protected double edgedy; protected double repulsiondx; protected double repulsiondy; /** movement speed, x */ protected double dx; /** movement speed, y */ protected double dy; } /** * Used for changing the size of the layout in response to a component's size. */ public class SpringDimensionChecker extends ComponentAdapter { @Override public void componentResized(ComponentEvent e) { setSize(e.getComponent().getSize()); } } /** * @return true */ public boolean isIncremental() { return true; } /** * @return false */ public boolean done() { return false; } /** * No effect. */ public void reset() { } }jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/SpringLayout2.java000066400000000000000000000111731276402340000333170ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.awt.geom.Point2D; import java.util.ConcurrentModificationException; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Graph; /** * The SpringLayout package represents a visualization of a set of nodes. The * SpringLayout, which is initialized with a Graph, assigns X/Y locations to * each node. When called relax(), the SpringLayout moves the * visualization forward one step. * * * * @author Danyel Fisher * @author Joshua O'Madadhain */ public class SpringLayout2 extends SpringLayout { protected int currentIteration; protected int averageCounter; protected int loopCountMax = 4; protected boolean done; protected Point2D averageDelta = new Point2D.Double(); /** * Constructor for a SpringLayout for a raw graph with associated * dimension--the input knows how big the graph is. Defaults to the unit * length function. * @param g the graph on which the layout algorithm is to operate */ public SpringLayout2(Graph g) { super(g); } /** * Constructor for a SpringLayout for a raw graph with associated component. * * @param g the {@code Graph} to lay out * @param length_function provides a length for each edge */ public SpringLayout2(Graph g, Function length_function) { super(g, length_function); } /** * Relaxation step. Moves all nodes a smidge. */ @Override public void step() { super.step(); currentIteration++; testAverageDeltas(); } private void testAverageDeltas() { double dx = this.averageDelta.getX(); double dy = this.averageDelta.getY(); if(Math.abs(dx) < .001 && Math.abs(dy) < .001) { done = true; System.err.println("done, dx="+dx+", dy="+dy); } if(currentIteration > loopCountMax) { this.averageDelta.setLocation(0,0); averageCounter = 0; currentIteration = 0; } } @Override protected void moveNodes() { synchronized (getSize()) { try { for (V v : getGraph().getVertices()) { if (isLocked(v)) continue; SpringVertexData vd = springVertexData.getUnchecked(v); if(vd == null) continue; Point2D xyd = apply(v); vd.dx += vd.repulsiondx + vd.edgedx; vd.dy += vd.repulsiondy + vd.edgedy; // int currentCount = currentIteration % this.loopCountMax; // System.err.println(averageCounter+" --- vd.dx="+vd.dx+", vd.dy="+vd.dy); // System.err.println("averageDelta was "+averageDelta); averageDelta.setLocation( ((averageDelta.getX() * averageCounter) + vd.dx) / (averageCounter+1), ((averageDelta.getY() * averageCounter) + vd.dy) / (averageCounter+1) ); // System.err.println("averageDelta now "+averageDelta); // System.err.println(); averageCounter++; // keeps nodes from moving any faster than 5 per time unit xyd.setLocation(xyd.getX()+Math.max(-5, Math.min(5, vd.dx)), xyd.getY()+Math.max(-5, Math.min(5, vd.dy))); Dimension d = getSize(); int width = d.width; int height = d.height; if (xyd.getX() < 0) { xyd.setLocation(0, xyd.getY());// setX(0); } else if (xyd.getX() > width) { xyd.setLocation(width, xyd.getY()); //setX(width); } if (xyd.getY() < 0) { xyd.setLocation(xyd.getX(),0);//setY(0); } else if (xyd.getY() > height) { xyd.setLocation(xyd.getX(), height); //setY(height); } } } catch(ConcurrentModificationException cme) { moveNodes(); } } } @Override public boolean done() { return done; } }jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/StaticLayout.java000066400000000000000000000025141276402340000332210ustar00rootroot00000000000000/* * Created on Jul 21, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.awt.geom.Point2D; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Graph; /** * StaticLayout places the vertices in the locations specified by its initializer, * and has no other behavior. * Vertex locations can be placed in a {@code Map} and then supplied to * this layout as follows: {@code Function vertexLocations = Functions.forMap(map);} * @author Tom Nelson - tomnelson@dev.java.net */ public class StaticLayout extends AbstractLayout { public StaticLayout(Graph graph, Function initializer, Dimension size) { super(graph, initializer, size); } public StaticLayout(Graph graph, Function initializer) { super(graph, initializer); } public StaticLayout(Graph graph) { super(graph); } public StaticLayout(Graph graph, Dimension size) { super(graph, size); } public void initialize() {} public void reset() {} } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/TreeLayout.java000066400000000000000000000154271276402340000327000ustar00rootroot00000000000000/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 9, 2005 */ package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.awt.Point; import java.awt.geom.Point2D; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.google.common.base.Function; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import edu.uci.ics.jung.graph.Forest; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.TreeUtils; /** * @author Karlheinz Toni * @author Tom Nelson - converted to jung2 */ public class TreeLayout implements Layout { protected Dimension size = new Dimension(600,600); protected Forest graph; protected Map basePositions = new HashMap(); protected LoadingCache locations = CacheBuilder.newBuilder().build(new CacheLoader() { public Point2D load(V vertex) { return new Point2D.Double(); } }); protected transient Set alreadyDone = new HashSet(); /** * The default horizontal vertex spacing. Initialized to 50. */ public static int DEFAULT_DISTX = 50; /** * The default vertical vertex spacing. Initialized to 50. */ public static int DEFAULT_DISTY = 50; /** * The horizontal vertex spacing. Defaults to {@code DEFAULT_XDIST}. */ protected int distX = 50; /** * The vertical vertex spacing. Defaults to {@code DEFAULT_YDIST}. */ protected int distY = 50; protected transient Point m_currentPoint = new Point(); /** * Creates an instance for the specified graph with default X and Y distances. * @param g the graph on which the layout algorithm is to operate */ public TreeLayout(Forest g) { this(g, DEFAULT_DISTX, DEFAULT_DISTY); } /** * Creates an instance for the specified graph and X distance with * default Y distance. * @param g the graph on which the layout algorithm is to operate * @param distx the horizontal spacing between adjacent siblings */ public TreeLayout(Forest g, int distx) { this(g, distx, DEFAULT_DISTY); } /** * Creates an instance for the specified graph, X distance, and Y distance. * @param g the graph on which the layout algorithm is to operate * @param distx the horizontal spacing between adjacent siblings * @param disty the vertical spacing between adjacent siblings */ public TreeLayout(Forest g, int distx, int disty) { if (g == null) throw new IllegalArgumentException("Graph must be non-null"); if (distx < 1 || disty < 1) throw new IllegalArgumentException("X and Y distances must each be positive"); this.graph = g; this.distX = distx; this.distY = disty; buildTree(); } protected void buildTree() { this.m_currentPoint = new Point(0, 20); Collection roots = TreeUtils.getRoots(graph); if (roots.size() > 0 && graph != null) { calculateDimensionX(roots); for(V v : roots) { calculateDimensionX(v); m_currentPoint.x += this.basePositions.get(v)/2 + this.distX; buildTree(v, this.m_currentPoint.x); } } } protected void buildTree(V v, int x) { if (alreadyDone.add(v)) { //go one level further down this.m_currentPoint.y += this.distY; this.m_currentPoint.x = x; this.setCurrentPositionFor(v); int sizeXofCurrent = basePositions.get(v); int lastX = x - sizeXofCurrent / 2; int sizeXofChild; int startXofChild; for (V element : graph.getSuccessors(v)) { sizeXofChild = this.basePositions.get(element); startXofChild = lastX + sizeXofChild / 2; buildTree(element, startXofChild); lastX = lastX + sizeXofChild + distX; } this.m_currentPoint.y -= this.distY; } } private int calculateDimensionX(V v) { int size = 0; int childrenNum = graph.getSuccessors(v).size(); if (childrenNum != 0) { for (V element : graph.getSuccessors(v)) { size += calculateDimensionX(element) + distX; } } size = Math.max(0, size - distX); basePositions.put(v, size); return size; } private int calculateDimensionX(Collection roots) { int size = 0; for(V v : roots) { int childrenNum = graph.getSuccessors(v).size(); if (childrenNum != 0) { for (V element : graph.getSuccessors(v)) { size += calculateDimensionX(element) + distX; } } size = Math.max(0, size - distX); basePositions.put(v, size); } return size; } /** * This method is not supported by this class. The size of the layout * is determined by the topology of the tree, and by the horizontal * and vertical spacing (optionally set by the constructor). */ public void setSize(Dimension size) { throw new UnsupportedOperationException("Size of TreeLayout is set" + " by vertex spacing in constructor"); } protected void setCurrentPositionFor(V vertex) { int x = m_currentPoint.x; int y = m_currentPoint.y; if(x < 0) size.width -= x; if(x > size.width-distX) size.width = x + distX; if(y < 0) size.height -= y; if(y > size.height-distY) size.height = y + distY; locations.getUnchecked(vertex).setLocation(m_currentPoint); } public Graph getGraph() { return graph; } public Dimension getSize() { return size; } public void initialize() { } public boolean isLocked(V v) { return false; } public void lock(V v, boolean state) { } public void reset() { } public void setGraph(Graph graph) { if(graph instanceof Forest) { this.graph = (Forest)graph; buildTree(); } else { throw new IllegalArgumentException("graph must be a Forest"); } } public void setInitializer(Function initializer) { } /** * @return the center of this layout's area. */ public Point2D getCenter() { return new Point2D.Double(size.getWidth()/2,size.getHeight()/2); } public void setLocation(V v, Point2D location) { locations.getUnchecked(v).setLocation(location); } public Point2D apply(V v) { return locations.getUnchecked(v); } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/package.html000066400000000000000000000027261276402340000322170ustar00rootroot00000000000000 Algorithms for assigning 2D coordinates (typically used for graph visualizations) to vertices. Current layout algorithms include:

    • Layout, AbstractLayout: interface and abstract class defining the Layout contract and handling some common implementation details
    • AggregateLayout: allows multiple layouts to be combined and manipulated as one layout
    • BalloonLayout: places vertices on nested circles (trees/forests only)
    • CircleLayout: places vertices on a circle
    • DAGLayout: places vertices in a hierarchy (directed acyclic graphs only)
    • FRLayout: Fruchterman-Reingold algorithm (force-directed)
    • ISOMLayout: self-organizing map layout
    • KKLayout: Kamada-Kawai algorithm (tries to maintain specified distances)
    • RadialTreeLayout: places vertices on concentric circles (trees only)
    • SpringLayout: simple force-directed layout
    • StaticLayout: places vertices at user-specified locations
    • TreeLayout: simple tree/forest layout
    Rendering and other aspects of visualization are handled in the visualization package. jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/000077500000000000000000000000001276402340000307045ustar00rootroot00000000000000RandomLocationTransformer.java000066400000000000000000000035261276402340000366320ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/* * Created on Jul 19, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.layout.util; import java.awt.Dimension; import java.awt.geom.Point2D; import java.util.Date; import java.util.Random; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.layout.StaticLayout; /** * Provides a random vertex location within the bounds of the Dimension property. * This provides a random location for unmapped vertices * the first time they are accessed. * *

    Note: the generated values are not cached, so apply() will generate a new random * location for the passed vertex every time it is called. If you want a consistent value, * wrap this layout's generated values in a {@link StaticLayout} instance. * * @author Tom Nelson * * @param the vertex type */ public class RandomLocationTransformer implements Function { Dimension d; Random random; /** * Creates an instance with the specified size which uses the current time * as the random seed. * @param d the size of the layout area */ public RandomLocationTransformer(Dimension d) { this(d, new Date().getTime()); } /** * Creates an instance with the specified dimension and random seed. * @param d the size of the layout area * @param seed the seed for the internal random number generator */ public RandomLocationTransformer(final Dimension d, long seed) { this.d = d; this.random = new Random(seed); } public Point2D apply(V v) { return new Point2D.Double(random.nextDouble() * d.width, random.nextDouble() * d.height); } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/Relaxer.java000066400000000000000000000013121276402340000331460ustar00rootroot00000000000000package edu.uci.ics.jung.algorithms.layout.util; /** * Interface for operating the relax iterations on a layout. * * @author Tom Nelson - tomnelson@dev.java.net * */ public interface Relaxer { /** * Execute a loop of steps in a new Thread, * firing an event after each step. */ void relax(); /** * Execute a loop of steps in the calling * thread, firing no events. */ void prerelax(); /** * Make the relaxer thread wait. */ void pause(); /** * Make the relaxer thread resume. * */ void resume(); /** * Set flags to stop the relaxer thread. */ void stop(); /** * @param i the sleep time between iterations, in milliseconds */ void setSleepTime(long i); } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/VisRunner.java000066400000000000000000000064361276402340000335130ustar00rootroot00000000000000/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * */ package edu.uci.ics.jung.algorithms.layout.util; import edu.uci.ics.jung.algorithms.util.IterativeContext; /** * * Implementation of a relaxer thread for layouts. * Extracted from the {@code VisualizationModel} in previous * versions of JUNG. * * @author Tom Nelson - tomnelson@dev.java.net * */ public class VisRunner implements Relaxer, Runnable { protected boolean running; protected IterativeContext process; protected boolean stop; protected boolean manualSuspend; protected Thread thread; /** * how long the relaxer thread pauses between iteration loops. */ protected long sleepTime = 100L; /** * Creates an instance for the specified process. * @param process the process (generally a layout) for which this instance is created */ public VisRunner(IterativeContext process) { this.process = process; } /** * @return the relaxerThreadSleepTime */ public long getSleepTime() { return sleepTime; } /** * @param sleepTime the sleep time to set for this thread */ public void setSleepTime(long sleepTime) { this.sleepTime = sleepTime; } public void prerelax() { manualSuspend = true; long timeNow = System.currentTimeMillis(); while (System.currentTimeMillis() - timeNow < 500 && !process.done()) { process.step(); } manualSuspend = false; } public void pause() { manualSuspend = true; } public void relax() { // in case its running stop(); stop = false; thread = new Thread(this); thread.setPriority(Thread.MIN_PRIORITY); thread.start(); } /** * Used for synchronization. */ public Object pauseObject = new String("PAUSE OBJECT"); public void resume() { manualSuspend = false; if(running == false) { prerelax(); relax(); } else { synchronized(pauseObject) { pauseObject.notifyAll(); } } } public synchronized void stop() { if(thread != null) { manualSuspend = false; stop = true; // interrupt the relaxer, in case it is paused or sleeping // this should ensure that visRunnerIsRunning gets set to false try { thread.interrupt(); } catch(Exception ex) { // the applet security manager may have prevented this. // just sleep for a second to let the thread stop on its own try { Thread.sleep(1000); } catch(InterruptedException ie) {} // ignore } synchronized (pauseObject) { pauseObject.notifyAll(); } } } public void run() { running = true; try { while (!process.done() && !stop) { synchronized (pauseObject) { while (manualSuspend && !stop) { try { pauseObject.wait(); } catch (InterruptedException e) { // ignore } } } process.step(); if (stop) return; try { Thread.sleep(sleepTime); } catch (InterruptedException ie) { // ignore } } } finally { running = false; } } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/package.html000066400000000000000000000004731276402340000331710ustar00rootroot00000000000000 Utility classes for updating layout positions. jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/000077500000000000000000000000001276402340000300605ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/Metrics.java000066400000000000000000000051401276402340000323310ustar00rootroot00000000000000/** * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Jun 7, 2008 * */ package edu.uci.ics.jung.algorithms.metrics; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import edu.uci.ics.jung.graph.Graph; /** * A class consisting of static methods for calculating graph metrics. */ public class Metrics { /** * Returns a Map of vertices to their clustering coefficients. * The clustering coefficient cc(v) of a vertex v is defined as follows: *

      *
    • degree(v) == {0,1}: 0 *
    • degree(v) == n, n >= 2: given S, the set of neighbors * of v: cc(v) = (the sum over all w in S of the number of * other elements of w that are neighbors of w) / ((|S| * (|S| - 1) / 2). * Less formally, the fraction of v's neighbors that are also * neighbors of each other. *
    *

    Note: This algorithm treats its argument as an undirected graph; * edge direction is ignored. * @param graph the graph whose clustering coefficients are to be calculated * @param the vertex type * @param the edge type * @return the clustering coefficient for each vertex * @see "The structure and function of complex networks, M.E.J. Newman, aps.arxiv.org/abs/cond-mat/0303516" */ public static Map clusteringCoefficients(Graph graph) { Map coefficients = new HashMap(); for (V v : graph.getVertices()) { int n = graph.getNeighborCount(v); if (n < 2) coefficients.put(v, new Double(0)); else { // how many of v's neighbors are connected to each other? ArrayList neighbors = new ArrayList(graph.getNeighbors(v)); double edge_count = 0; for (int i = 0; i < n; i++) { V w = neighbors.get(i); for (int j = i+1; j < n; j++ ) { V x = neighbors.get(j); edge_count += graph.isNeighbor(w, x) ? 1 : 0; } } double possible_edges = (n * (n - 1))/2.0; coefficients.put(v, new Double(edge_count / possible_edges)); } } return coefficients; } } StructuralHoles.java000066400000000000000000000270161276402340000340150ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/* * Created on Sep 19, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.metrics; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Graph; /** * Calculates some of the measures from Burt's text "Structural Holes: * The Social Structure of Competition". * *

    Notes: *

      *
    • Each of these measures assumes that each edge has an associated * non-null weight whose value is accessed through the specified * Transformer instance. *
    • Nonexistent edges are treated as edges with weight 0 for purposes * of edge weight calculations. *
    * *

    Based on code donated by Jasper Voskuilen and * Diederik van Liere of the Department of Information and Decision Sciences * at Erasmus University. * * @author Joshua O'Madadhain * @author Jasper Voskuilen * @see "Ronald Burt, Structural Holes: The Social Structure of Competition" * @author Tom Nelson - converted to jung2 */ public class StructuralHoles { protected Function edge_weight; protected Graph g; /** * @param graph the graph for which the metrics are to be calculated * @param nev the edge weights */ public StructuralHoles(Graph graph, Function nev) { this.g = graph; this.edge_weight = nev; } /** * Burt's measure of the effective size of a vertex's network. Essentially, the * number of neighbors minus the average degree of those in v's neighbor set, * not counting ties to v. Formally: *

         * effectiveSize(v) = v.degree() - (sum_{u in N(v)} sum_{w in N(u), w !=u,v} p(v,w)*m(u,w))
         * 
    * where *
      *
    • N(a) = a.getNeighbors() *
    • p(v,w) = normalized mutual edge weight of v and w *
    • m(u,w) = maximum-scaled mutual edge weight of u and w *
    * @param v the vertex whose properties are being measured * @return the effective size of the vertex's network * * @see #normalizedMutualEdgeWeight(Object, Object) * @see #maxScaledMutualEdgeWeight(Object, Object) */ public double effectiveSize(V v) { double result = g.degree(v); for(V u : g.getNeighbors(v)) { for(V w : g.getNeighbors(u)) { if (w != v && w != u) result -= normalizedMutualEdgeWeight(v,w) * maxScaledMutualEdgeWeight(u,w); } } return result; } /** * Returns the effective size of v divided by the number of * alters in v's network. (In other words, * effectiveSize(v) / v.degree().) * If v.degree() == 0, returns 0. * * @param v the vertex whose properties are being measured * @return the effective size of the vertex divided by its degree */ public double efficiency(V v) { double degree = g.degree(v); if (degree == 0) return 0; else return effectiveSize(v) / degree; } /** * Burt's constraint measure (equation 2.4, page 55 of Burt, 1992). Essentially a * measure of the extent to which v is invested in people who are invested in * other of v's alters (neighbors). The "constraint" is characterized * by a lack of primary holes around each neighbor. Formally: *
         * constraint(v) = sum_{w in MP(v), w != v} localConstraint(v,w)
         * 
    * where MP(v) is the subset of v's neighbors that are both predecessors and successors of v. * @see #localConstraint(Object, Object) * * @param v the vertex whose properties are being measured * @return the constraint of the vertex */ public double constraint(V v) { double result = 0; for(V w : g.getSuccessors(v)) { if (v != w && g.isPredecessor(v,w)) { result += localConstraint(v, w); } } return result; } /** * Calculates the hierarchy value for a given vertex. Returns NaN when * v's degree is 0, and 1 when v's degree is 1. * Formally: *
         * hierarchy(v) = (sum_{v in N(v), w != v} s(v,w) * log(s(v,w))}) / (v.degree() * Math.log(v.degree()) 
         * 
    * where *
      *
    • N(v) = v.getNeighbors() *
    • s(v,w) = localConstraint(v,w) / (aggregateConstraint(v) / v.degree()) *
    * @see #localConstraint(Object, Object) * @see #aggregateConstraint(Object) * * @param v the vertex whose properties are being measured * @return the hierarchy value for a given vertex */ public double hierarchy(V v) { double v_degree = g.degree(v); if (v_degree == 0) return Double.NaN; if (v_degree == 1) return 1; double v_constraint = aggregateConstraint(v); double numerator = 0; for (V w : g.getNeighbors(v)) { if (v != w) { double sl_constraint = localConstraint(v, w) / (v_constraint / v_degree); numerator += sl_constraint * Math.log(sl_constraint); } } return numerator / (v_degree * Math.log(v_degree)); } /** * Returns the local constraint on v1 from a lack of primary holes * around its neighbor v2. * Based on Burt's equation 2.4. Formally: *
         * localConstraint(v1, v2) = ( p(v1,v2) + ( sum_{w in N(v)} p(v1,w) * p(w, v2) ) )^2
         * 
    * where *
      *
    • N(v) = v.getNeighbors() *
    • p(v,w) = normalized mutual edge weight of v and w *
    * @param v1 the first vertex whose local constraint is desired * @param v2 the second vertex whose local constraint is desired * @return the local constraint on (v1, v2) * @see #normalizedMutualEdgeWeight(Object, Object) */ public double localConstraint(V v1, V v2) { double nmew_vw = normalizedMutualEdgeWeight(v1, v2); double inner_result = 0; for (V w : g.getNeighbors(v1)) { inner_result += normalizedMutualEdgeWeight(v1,w) * normalizedMutualEdgeWeight(w,v2); } return (nmew_vw + inner_result) * (nmew_vw + inner_result); } /** * The aggregate constraint on v. Based on Burt's equation 2.7. * Formally: *
         * aggregateConstraint(v) = sum_{w in N(v)} localConstraint(v,w) * O(w)
         * 
    * where *
      *
    • N(v) = v.getNeighbors() *
    • O(w) = organizationalMeasure(w) *
    * * @param v the vertex whose properties are being measured * @return the aggregate constraint on v */ public double aggregateConstraint(V v) { double result = 0; for (V w : g.getNeighbors(v)) { result += localConstraint(v, w) * organizationalMeasure(g, w); } return result; } /** * A measure of the organization of individuals within the subgraph * centered on v. Burt's text suggests that this is * in some sense a measure of how "replaceable" v is by * some other element of this subgraph. Should be a number in the * closed interval [0,1]. * *

    This implementation returns 1. Users may wish to override this * method in order to define their own behavior. * @param g the subgraph centered on v * @param v the vertex whose properties are being measured * @return 1.0 (in this implementation) */ protected double organizationalMeasure(Graph g, V v) { return 1.0; } /** * Returns the proportion of v1's network time and energy invested * in the relationship with v2. Formally: *

         * normalizedMutualEdgeWeight(a,b) = mutual_weight(a,b) / (sum_c mutual_weight(a,c))
         * 
    * Returns 0 if either numerator or denominator = 0, or if v1 == v2. * @see #mutualWeight(Object, Object) * @param v1 the first vertex of the pair whose property is being measured * @param v2 the second vertex of the pair whose property is being measured * @return the normalized mutual edge weight between v1 and v2 */ protected double normalizedMutualEdgeWeight(V v1, V v2) { if (v1 == v2) return 0; double numerator = mutualWeight(v1, v2); if (numerator == 0) return 0; double denominator = 0; for (V v : g.getNeighbors(v1)) { denominator += mutualWeight(v1, v); } if (denominator == 0) return 0; return numerator / denominator; } /** * Returns the weight of the edge from v1 to v2 * plus the weight of the edge from v2 to v1; * if either edge does not exist, it is treated as an edge with weight 0. * Undirected edges are treated as two antiparallel directed edges (that * is, if there is one undirected edge with weight w connecting * v1 to v2, the value returned is 2w). * Ignores parallel edges; if there are any such, one is chosen at random. * Throws NullPointerException if either edge is * present but not assigned a weight by the constructor-specified * NumberEdgeValue. * * @param v1 the first vertex of the pair whose property is being measured * @param v2 the second vertex of the pair whose property is being measured * @return the weights of the edges {@code} and {@code } */ protected double mutualWeight(V v1, V v2) { E e12 = g.findEdge(v1,v2); E e21 = g.findEdge(v2,v1); double w12 = (e12 != null ? edge_weight.apply(e12).doubleValue() : 0); double w21 = (e21 != null ? edge_weight.apply(e21).doubleValue() : 0); return w12 + w21; } /** * The marginal strength of v1's relation with contact v2. * Formally: *
         * normalized_mutual_weight = mutual_weight(a,b) / (max_c mutual_weight(a,c))
         * 
    * Returns 0 if either numerator or denominator is 0, or if v1 == v2. * * @param v1 the first vertex of the pair whose property is being measured * @param v2 the second vertex of the pair whose property is being measured * @return the marginal strength of v1's relation with v2 * * @see #mutualWeight(Object, Object) */ protected double maxScaledMutualEdgeWeight(V v1, V v2) { if (v1 == v2) return 0; double numerator = mutualWeight(v1, v2); if (numerator == 0) return 0; double denominator = 0; for (V w : g.getNeighbors(v1)) { if (v2 != w) denominator = Math.max(numerator, mutualWeight(v1, w)); } if (denominator == 0) return 0; return numerator / denominator; } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/TriadicCensus.java000066400000000000000000000165541276402340000334760ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.metrics; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.Graph; /** * TriadicCensus is a standard social network tool that counts, for each of the * different possible configurations of three vertices, the number of times * that that configuration occurs in the given graph. * This may then be compared to the set of expected counts for this particular * graph or to an expected sample. This is often used in p* modeling. *

    * To use this class, *

     * long[] triad_counts = TriadicCensus(dg);
     * 
    * where dg is a DirectedGraph. * ith element of the array (for i in [1,16]) is the number of * occurrences of the corresponding triad type. * (The 0th element is not meaningful; this array is effectively 1-based.) * To get the name of the ith triad (e.g. "003"), * look at the global constant array c.TRIAD_NAMES[i] *

    * Triads are named as * (number of pairs that are mutually tied) * (number of pairs that are one-way tied) * (number of non-tied pairs) * in the triple. Since there are be only three pairs, there is a finite * set of these possible triads. *

    * In fact, there are exactly 16, conventionally sorted by the number of * realized edges in the triad: * * * * * * * * * * * * * * * * * * * *
    Descriptions of the different types of triads
    Number Configuration Notes
    1003The empty triad
    2012
    3102
    4021D"Down": the directed edges point away
    5021U"Up": the directed edges meet
    6021C"Circle": one in, one out
    7111D"Down": 021D but one edge is mutual
    8111U"Up": 021U but one edge is mutual
    9030T"Transitive": two point to the same vertex
    10030C"Circle": A→B→C→A
    11201
    12120D"Down": 021D but the third edge is mutual
    13120U"Up": 021U but the third edge is mutual
    14120C"Circle": 021C but the third edge is mutual
    15210
    16300The complete
    *

    * This implementation takes O( m ), m is the number of edges in the graph. *
    * It is based on * * A subquadratic triad census algorithm for large sparse networks * with small maximum degree * Vladimir Batagelj and Andrej Mrvar, University of Ljubljana * Published in Social Networks. * @author Danyel Fisher * @author Tom Nelson - converted to jung2 * */ public class TriadicCensus { // NOTE THAT THIS RETURNS STANDARD 1-16 COUNT! // and their types public static final String[] TRIAD_NAMES = { "N/A", "003", "012", "102", "021D", "021U", "021C", "111D", "111U", "030T", "030C", "201", "120D", "120U", "120C", "210", "300" }; public static final int MAX_TRIADS = TRIAD_NAMES.length; /** * Returns an array whose ith element (for i in [1,16]) is the number of * occurrences of the corresponding triad type in g. * (The 0th element is not meaningful; this array is effectively 1-based.) * * @param g the graph whose properties are being measured * @param the vertex type * @param the edge type * @return an array encoding the number of occurrences of each triad type */ public static long[] getCounts(DirectedGraph g) { long[] count = new long[MAX_TRIADS]; List id = new ArrayList(g.getVertices()); // apply algorithm to each edge, one at at time for (int i_v = 0; i_v < g.getVertexCount(); i_v++) { V v = id.get(i_v); for(V u : g.getNeighbors(v)) { int triType = -1; if (id.indexOf(u) <= i_v) continue; Set neighbors = new HashSet(g.getNeighbors(u)); neighbors.addAll(g.getNeighbors(v)); neighbors.remove(u); neighbors.remove(v); if (g.isSuccessor(v,u) && g.isSuccessor(u,v)) { triType = 3; } else { triType = 2; } count[triType] += g.getVertexCount() - neighbors.size() - 2; for (V w : neighbors) { if (shouldCount(g, id, u, v, w)) { count [ triType ( triCode(g, u, v, w) ) ] ++; } } } } int sum = 0; for (int i = 2; i <= 16; i++) { sum += count[i]; } int n = g.getVertexCount(); count[1] = n * (n-1) * (n-2) / 6 - sum; return count; } /** * This is the core of the technique in the paper. Returns an int from 0 to * 63 which encodes the presence of all possible links between u, v, and w * as bit flags: WU = 32, UW = 16, WV = 8, VW = 4, UV = 2, VU = 1 * * @param g the graph for which the calculation is being made * @param u a vertex in g * @param v a vertex in g * @param w a vertex in g * @param the vertex type * @param the edge type * @return an int encoding the presence of all links between u, v, and w */ public static int triCode(Graph g, V u, V v, V w) { int i = 0; i += link(g, v, u ) ? 1 : 0; i += link(g, u, v ) ? 2 : 0; i += link(g, v, w ) ? 4 : 0; i += link(g, w, v ) ? 8 : 0; i += link(g, u, w ) ? 16 : 0; i += link(g, w, u ) ? 32 : 0; return i; } protected static boolean link(Graph g, V a, V b) { return g.isPredecessor(b, a); } /** * @param triCode the code returned by {@code triCode()} * @return the string code associated with the numeric type */ public static int triType( int triCode ) { return codeToType[ triCode ]; } /** * For debugging purposes, this is copied straight out of the paper which * means that they refer to triad types 1-16. */ protected static final int[] codeToType = { 1, 2, 2, 3, 2, 4, 6, 8, 2, 6, 5, 7, 3, 8, 7, 11, 2, 6, 4, 8, 5, 9, 9, 13, 6, 10, 9, 14, 7, 14, 12, 15, 2, 5, 6, 7, 6, 9, 10, 14, 4, 9, 9, 12, 8, 13, 14, 15, 3, 7, 8, 11, 7, 12, 14, 15, 8, 14, 13, 15, 11, 15, 15, 16 }; /** * Return true iff this ordering is canonical and therefore we should build statistics for it. * * @param g the graph whose properties are being examined * @param id a list of the vertices in g; used to assign an index to each * @param u a vertex in g * @param v a vertex in g * @param w a vertex in g * @param the vertex type * @param the edge type * @return true if index(u) < index(w), or if index(v) < index(w) < index(u) * and v doesn't link to w; false otherwise */ protected static boolean shouldCount(Graph g, List id, V u, V v, V w) { int i_u = id.indexOf(u); int i_w = id.indexOf(w); if (i_u < i_w) return true; int i_v = id.indexOf(v); if ((i_v < i_w) && (i_w < i_u) && (!g.isNeighbor(w,v))) return true; return false; } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/package.html000066400000000000000000000010661276402340000323440ustar00rootroot00000000000000 Specialized measures for graph properties. These currently include:

    • StructuralHoles: calculates some of Burt's 'structural holes' measures (e.g. efficiency, hierarchy, constraint).
    • TriadicCensus: returns counts for each triad type found in a graph.
    jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/package.html000066400000000000000000000030351276402340000306740ustar00rootroot00000000000000

    Algorithms for graphs and networks.

    These algorithms are divided into categories as follows:

    • blockmodel: dividing graph elements (typically vertices) into equivalence classes, generally by topological properties (e.g. structural equivalence)
    • cluster: identifying coherent (not necessarily disjoint) groups of elements (e.g. weakly connected components, edge betweenness clustering)
    • filters: removing parts of a graph according to specified criteria
    • flows: calculating properties relating to network flows (e.g. max flow/min cut)
    • generators: creating graphs with certain properties
    • importance (deprecated): assigning values to vertices/edges based on topological properties
    • layout: arrangement of graph elements, generally for visualization
    • metrics: calculating structural properties (triad census, structural holes)
    • scoring: assigning values (denoting significance, influence, centrality, etc.) to vertices/edges based on topological properties, e.g. PageRank, HITS, betweenness centrality (replaces "importance", above)
    • shortestpath: calculation of shortest paths between vertices
    • util: low-level utility classes used in a variety of algorithms
    jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/000077500000000000000000000000001276402340000300565ustar00rootroot00000000000000AbstractIterativeScorer.java000066400000000000000000000265701276402340000354520ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/* * Created on Jul 6, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import java.util.HashMap; import java.util.Map; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.scoring.util.DelegateToEdgeTransformer; import edu.uci.ics.jung.algorithms.scoring.util.VEPair; import edu.uci.ics.jung.algorithms.util.IterativeContext; import edu.uci.ics.jung.graph.Hypergraph; /** * An abstract class for algorithms that assign scores to vertices based on iterative methods. * Generally, any (concrete) subclass will function by creating an instance, and then either calling * evaluate (if the user wants to iterate until the algorithms is 'done') or * repeatedly call step (if the user wants to observe the values at each step). */ public abstract class AbstractIterativeScorer implements IterativeContext, VertexScorer { /** * Maximum number of iterations to use before terminating. Defaults to 100. */ protected int max_iterations; /** * Minimum change from one step to the next; if all changes are ≤ tolerance, * no further updates will occur. * Defaults to 0.001. */ protected double tolerance; /** * The graph on which the calculations are to be made. */ protected Hypergraph graph; /** * The total number of iterations used so far. */ protected int total_iterations; /** * The edge weights used by this algorithm. */ protected Function, ? extends Number> edge_weights; /** * Indicates whether the output and current values are in a 'swapped' state. * Intended for internal use only. */ protected boolean output_reversed; /** * The map in which the output values are stored. */ private Map output; /** * The map in which the current values are stored. */ private Map current_values; /** * A flag representing whether this instance tolerates disconnected graphs. * Instances that do not accept disconnected graphs may have unexpected behavior * on disconnected graphs; they are not guaranteed to do an explicit check. * Defaults to true. */ private boolean accept_disconnected_graph; protected boolean hyperedges_are_self_loops = false; /** * Sets the output value for this vertex. * @param v the vertex whose output value is to be set * @param value the value to set */ protected void setOutputValue(V v, T value) { output.put(v, value); } /** * Gets the output value for this vertex. * @param v the vertex whose output value is to be retrieved * @return the output value for this vertex */ protected T getOutputValue(V v) { return output.get(v); } /** * Gets the current value for this vertex * @param v the vertex whose current value is to be retrieved * @return the current value for this vertex */ protected T getCurrentValue(V v) { return current_values.get(v); } /** * Sets the current value for this vertex. * @param v the vertex whose current value is to be set * @param value the current value to set */ protected void setCurrentValue(V v, T value) { current_values.put(v, value); } /** * The largest change seen so far among all vertex scores. */ protected double max_delta; /** * Creates an instance for the specified graph and edge weights. * @param g the graph for which the instance is to be created * @param edge_weights the edge weights for this instance */ public AbstractIterativeScorer(Hypergraph g, Function edge_weights) { this.graph = g; this.max_iterations = 100; this.tolerance = 0.001; this.accept_disconnected_graph = true; setEdgeWeights(edge_weights); } /** * Creates an instance for the specified graph g. * NOTE: This constructor does not set the internal * edge_weights variable. If this variable is used by * the subclass which invoked this constructor, it must be initialized * by that subclass. * @param g the graph for which the instance is to be created */ public AbstractIterativeScorer(Hypergraph g) { this.graph = g; this.max_iterations = 100; this.tolerance = 0.001; this.accept_disconnected_graph = true; } /** * Initializes the internal state for this instance. */ protected void initialize() { this.total_iterations = 0; this.max_delta = Double.MIN_VALUE; this.output_reversed = true; this.current_values = new HashMap(); this.output = new HashMap(); } /** * Steps through this scoring algorithm until a termination condition is reached. */ public void evaluate() { do step(); while (!done()); } /** * Returns true if the total number of iterations is greater than or equal to * max_iterations * or if the maximum value change observed is less than tolerance. */ public boolean done() { return total_iterations >= max_iterations || max_delta < tolerance; } /** * Performs one step of this algorithm; updates the state (value) for each vertex. */ public void step() { swapOutputForCurrent(); for (V v : graph.getVertices()) { double diff = update(v); updateMaxDelta(v, diff); } total_iterations++; afterStep(); } /** * */ protected void swapOutputForCurrent() { Map tmp = output; output = current_values; current_values = tmp; output_reversed = !output_reversed; } /** * Updates the value for v. * @param v the vertex whose value is to be updated * @return the updated value */ protected abstract double update(V v); protected void updateMaxDelta(V v, double diff) { max_delta = Math.max(max_delta, diff); } protected void afterStep() {} public T getVertexScore(V v) { if (!graph.containsVertex(v)) throw new IllegalArgumentException("Vertex " + v + " not an element of this graph"); return output.get(v); } /** * Returns the maximum number of iterations that this instance will use. * @return the maximum number of iterations that evaluate will use * prior to terminating */ public int getMaxIterations() { return max_iterations; } /** * Returns the number of iterations that this instance has used so far. * @return the number of iterations that this instance has used so far */ public int getIterations() { return total_iterations; } /** * Sets the maximum number of times that evaluate will call step. * @param max_iterations the maximum */ public void setMaxIterations(int max_iterations) { this.max_iterations = max_iterations; } /** * Gets the size of the largest change (difference between the current and previous values) * for any vertex that can be tolerated. Once all changes are less than this value, * evaluate will terminate. * @return the size of the largest change that evaluate() will permit */ public double getTolerance() { return tolerance; } /** * Sets the size of the largest change (difference between the current and previous values) * for any vertex that can be tolerated. * @param tolerance the size of the largest change that evaluate() will permit */ public void setTolerance(double tolerance) { this.tolerance = tolerance; } /** * Returns the Function that this instance uses to associate edge weights with each edge. * @return the Function that associates an edge weight with each edge */ public Function, ? extends Number> getEdgeWeights() { return edge_weights; } /** * Sets the Function that this instance uses to associate edge weights with each edge * @param edge_weights the Function to use to associate an edge weight with each edge * @see edu.uci.ics.jung.algorithms.scoring.util.UniformDegreeWeight */ public void setEdgeWeights(Function edge_weights) { this.edge_weights = new DelegateToEdgeTransformer(edge_weights); } /** * Gets the edge weight for e in the context of its (incident) vertex v. * @param v the vertex incident to e as a context in which the edge weight is to be calculated * @param e the edge whose weight is to be returned * @return the edge weight for e in the context of its (incident) vertex v */ protected Number getEdgeWeight(V v, E e) { return edge_weights.apply(new VEPair(v,e)); } /** * Collects the 'potential' from v (its current value) if it has no outgoing edges; this * can then be redistributed among the other vertices as a means of normalization. * @param v the vertex whose potential is being collected */ protected void collectDisappearingPotential(V v) {} /** * Specifies whether this instance should accept vertices with no outgoing edges. * @param accept true if this instance should accept vertices with no outgoing edges, false otherwise */ public void acceptDisconnectedGraph(boolean accept) { this.accept_disconnected_graph = accept; } /** * Returns true if this instance accepts vertices with no outgoing edges, and false otherwise. * @return true if this instance accepts vertices with no outgoing edges, otherwise false */ public boolean isDisconnectedGraphOK() { return this.accept_disconnected_graph; } /** * Specifies whether hyperedges are to be treated as self-loops. If they * are, then potential will flow along a hyperedge a vertex to itself, * just as it does to all other vertices incident to that hyperedge. * @param arg if {@code true}, hyperedges are treated as self-loops */ public void setHyperedgesAreSelfLoops(boolean arg) { this.hyperedges_are_self_loops = arg; } /** * Returns the effective number of vertices incident to this edge. If * the graph is a binary relation or if hyperedges are treated as self-loops, * the value returned is {@code graph.getIncidentCount(e)}; otherwise it is * {@code graph.getIncidentCount(e) - 1}. * @param e the edge whose incident edge count is requested * @return the edge count, adjusted based on how hyperedges are treated */ protected int getAdjustedIncidentCount(E e) { return graph.getIncidentCount(e) - (hyperedges_are_self_loops ? 0 : 1); } } AbstractIterativeScorerWithPriors.java000066400000000000000000000072711276402340000375020ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/* * Created on Jul 14, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Hypergraph; /** * An abstract class for iterative random-walk-based vertex scoring algorithms * that have a * fixed probability, for each vertex, of 'jumping' to that vertex at each * step in the algorithm (rather than following a link out of that vertex). * * @param the vertex type * @param the edge type * @param the score type */ public abstract class AbstractIterativeScorerWithPriors extends AbstractIterativeScorer implements VertexScorer { /** * The prior probability of each vertex being visited on a given * 'jump' (non-link-following) step. */ protected Function vertex_priors; /** * The probability of making a 'jump' at each step. */ protected double alpha; /** * Creates an instance for the specified graph, edge weights, vertex * priors, and jump probability. * @param g the graph whose vertices are to be assigned scores * @param edge_weights the edge weights to use in the score assignment * @param vertex_priors the prior probabilities of each vertex being 'jumped' to * @param alpha the probability of making a 'jump' at each step */ public AbstractIterativeScorerWithPriors(Hypergraph g, Function edge_weights, Function vertex_priors, double alpha) { super(g, edge_weights); this.vertex_priors = vertex_priors; this.alpha = alpha; initialize(); } /** * Creates an instance for the specified graph, vertex priors, and jump * probability, with edge weights specified by the subclass. * @param g the graph whose vertices are to be assigned scores * @param vertex_priors the prior probabilities of each vertex being 'jumped' to * @param alpha the probability of making a 'jump' at each step */ public AbstractIterativeScorerWithPriors(Hypergraph g, Function vertex_priors, double alpha) { super(g); this.vertex_priors = vertex_priors; this.alpha = alpha; initialize(); } /** * Initializes the state of this instance. */ @Override public void initialize() { super.initialize(); // initialize output values to priors // (output and current are swapped before each step(), so current will // have priors when update()s start happening) for (V v : graph.getVertices()) setOutputValue(v, getVertexPrior(v)); } /** * Returns the prior probability for v. * @param v the vertex whose prior probability is being queried * @return the prior probability for v */ protected S getVertexPrior(V v) { return vertex_priors.apply(v); } /** * Returns a Function which maps each vertex to its prior probability. * @return a Function which maps each vertex to its prior probability */ public Function getVertexPriors() { return vertex_priors; } /** * Returns the probability of making a 'jump' (non-link-following step). * @return the probability of making a 'jump' (non-link-following step) */ public double getAlpha() { return alpha; } } BarycenterScorer.java000066400000000000000000000032411276402340000341160ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/* * Created on Jul 12, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.shortestpath.Distance; import edu.uci.ics.jung.graph.Hypergraph; /** * Assigns scores to each vertex according to the sum of its distances to all other vertices. */ public class BarycenterScorer extends DistanceCentralityScorer { /** * Creates an instance with the specified graph and distance metric. * @param graph the input graph * @param distance the distance metric to use */ public BarycenterScorer(Hypergraph graph, Distance distance) { super(graph, distance, false); } /** * Creates an instance with the specified graph and edge weights. * Will generate a Distance metric internally based on the edge weights. * @param graph the input graph * @param edge_weights the edge weights to use to calculate vertex/vertex distances */ public BarycenterScorer(Hypergraph graph, Function edge_weights) { super(graph, edge_weights, false); } /** * Creates an instance with the specified graph. * Will generate a Distance metric internally assuming that the * graph is unweighted. * @param graph the input graph */ public BarycenterScorer(Hypergraph graph) { super(graph, false); } } BetweennessCentrality.java000066400000000000000000000301071276402340000351640ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/** * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Sep 16, 2008 * */ package edu.uci.ics.jung.algorithms.scoring; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Stack; import com.google.common.base.Function; import com.google.common.base.Functions; import edu.uci.ics.jung.algorithms.util.MapBinaryHeap; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedGraph; /** * Computes betweenness centrality for each vertex and edge in the graph. * * @see "Ulrik Brandes: A Faster Algorithm for Betweenness Centrality. Journal of Mathematical Sociology 25(2):163-177, 2001." */ public class BetweennessCentrality implements VertexScorer, EdgeScorer { protected Graph graph; protected Map vertex_scores; protected Map edge_scores; protected Map vertex_data; /** * Calculates betweenness scores based on the all-pairs unweighted shortest paths * in the graph. * @param graph the graph for which the scores are to be calculated */ public BetweennessCentrality(Graph graph) { initialize(graph); computeBetweenness(new LinkedList(), Functions.constant(1)); } /** * Calculates betweenness scores based on the all-pairs weighted shortest paths in the * graph. * *

    NOTE: This version of the algorithm may not work correctly on all graphs; we're still * working out the bugs. Use at your own risk. * @param graph the graph for which the scores are to be calculated * @param edge_weights the edge weights to be used in the path length calculations */ public BetweennessCentrality(Graph graph, Function edge_weights) { // reject negative-weight edges up front for (E e : graph.getEdges()) { double e_weight = edge_weights.apply(e).doubleValue(); if (e_weight < 0) throw new IllegalArgumentException(String.format( "Weight for edge '%s' is < 0: %d", e, e_weight)); } initialize(graph); computeBetweenness(new MapBinaryHeap(new BetweennessComparator()), edge_weights); } protected void initialize(Graph graph) { this.graph = graph; this.vertex_scores = new HashMap(); this.edge_scores = new HashMap(); this.vertex_data = new HashMap(); for (V v : graph.getVertices()) this.vertex_scores.put(v, 0.0); for (E e : graph.getEdges()) this.edge_scores.put(e, 0.0); } protected void computeBetweenness(Queue queue, Function edge_weights) { for (V v : graph.getVertices()) { // initialize the betweenness data for this new vertex for (V s : graph.getVertices()) this.vertex_data.put(s, new BetweennessData()); // if (v.equals(new Integer(0))) // System.out.println("pause"); vertex_data.get(v).numSPs = 1; vertex_data.get(v).distance = 0; Stack stack = new Stack(); // Buffer queue = new UnboundedFifoBuffer(); // queue.add(v); queue.offer(v); while (!queue.isEmpty()) { // V w = queue.remove(); V w = queue.poll(); stack.push(w); BetweennessData w_data = vertex_data.get(w); for (E e : graph.getOutEdges(w)) { // TODO (jrtom): change this to getOtherVertices(w, e) V x = graph.getOpposite(w, e); if (x.equals(w)) continue; double wx_weight = edge_weights.apply(e).doubleValue(); // for(V x : graph.getSuccessors(w)) // { // if (x.equals(w)) // continue; // FIXME: the other problem is that I need to // keep putting the neighbors of things we've just // discovered in the queue, if they're undiscovered or // at greater distance. // FIXME: this is the problem, right here, I think: // need to update position in queue if distance changes // (which can only happen with weighted edges). // for each outgoing edge e from w, get other end x // if x not already visited (dist x < 0) // set x's distance to w's dist + edge weight // add x to queue; pri in queue is x's dist // if w's dist + edge weight < x's dist // update x's dist // update x in queue (MapBinaryHeap) // clear x's incoming edge list // if w's dist + edge weight = x's dist // add e to x's incoming edge list BetweennessData x_data = vertex_data.get(x); double x_potential_dist = w_data.distance + wx_weight; if (x_data.distance < 0) { // queue.add(x); // vertex_data.get(x).distance = vertex_data.get(w).distance + 1; x_data.distance = x_potential_dist; queue.offer(x); } // note: // (1) this can only happen with weighted edges // (2) x's SP count and incoming edges are updated below if (x_data.distance > x_potential_dist) { x_data.distance = x_potential_dist; // invalidate previously identified incoming edges // (we have a new shortest path distance to x) x_data.incomingEdges.clear(); // update x's position in queue ((MapBinaryHeap)queue).update(x); } // if (vertex_data.get(x).distance == vertex_data.get(w).distance + 1) // // if (x_data.distance == x_potential_dist) // { // x_data.numSPs += w_data.numSPs; //// vertex_data.get(x).predecessors.add(w); // x_data.incomingEdges.add(e); // } } for (E e: graph.getOutEdges(w)) { V x = graph.getOpposite(w, e); if (x.equals(w)) continue; double e_weight = edge_weights.apply(e).doubleValue(); BetweennessData x_data = vertex_data.get(x); double x_potential_dist = w_data.distance + e_weight; if (x_data.distance == x_potential_dist) { x_data.numSPs += w_data.numSPs; // vertex_data.get(x).predecessors.add(w); x_data.incomingEdges.add(e); } } } while (!stack.isEmpty()) { V x = stack.pop(); // for (V w : vertex_data.get(x).predecessors) for (E e : vertex_data.get(x).incomingEdges) { V w = graph.getOpposite(x, e); double partialDependency = vertex_data.get(w).numSPs / vertex_data.get(x).numSPs * (1.0 + vertex_data.get(x).dependency); vertex_data.get(w).dependency += partialDependency; // E w_x = graph.findEdge(w, x); // double w_x_score = edge_scores.get(w_x).doubleValue(); // w_x_score += partialDependency; // edge_scores.put(w_x, w_x_score); double e_score = edge_scores.get(e).doubleValue(); edge_scores.put(e, e_score + partialDependency); } if (!x.equals(v)) { double x_score = vertex_scores.get(x).doubleValue(); x_score += vertex_data.get(x).dependency; vertex_scores.put(x, x_score); } } } if(graph instanceof UndirectedGraph) { for (V v : graph.getVertices()) { double v_score = vertex_scores.get(v).doubleValue(); v_score /= 2.0; vertex_scores.put(v, v_score); } for (E e : graph.getEdges()) { double e_score = edge_scores.get(e).doubleValue(); e_score /= 2.0; edge_scores.put(e, e_score); } } vertex_data.clear(); } // protected void computeWeightedBetweenness(Function edge_weights) // { // for (V v : graph.getVertices()) // { // // initialize the betweenness data for this new vertex // for (V s : graph.getVertices()) // this.vertex_data.put(s, new BetweennessData()); // vertex_data.get(v).numSPs = 1; // vertex_data.get(v).distance = 0; // // Stack stack = new Stack(); //// Buffer queue = new UnboundedFifoBuffer(); // SortedSet pqueue = new TreeSet(new BetweennessComparator()); //// queue.add(v); // pqueue.add(v); // //// while (!queue.isEmpty()) // while (!pqueue.isEmpty()) // { //// V w = queue.remove(); // V w = pqueue.first(); // pqueue.remove(w); // stack.push(w); // //// for(V x : graph.getSuccessors(w)) // for (E e : graph.getOutEdges(w)) // { // // TODO (jrtom): change this to getOtherVertices(w, e) // V x = graph.getOpposite(w, e); // if (x.equals(w)) // continue; // double e_weight = edge_weights.transform(e).doubleValue(); // // if (vertex_data.get(x).distance < 0) // { //// queue.add(x); // pqueue.add(v); //// vertex_data.get(x).distance = vertex_data.get(w).distance + 1; // vertex_data.get(x).distance = // vertex_data.get(w).distance + e_weight; // } // //// if (vertex_data.get(x).distance == vertex_data.get(w).distance + 1) // if (vertex_data.get(x).distance == // vertex_data.get(w).distance + e_weight) // { // vertex_data.get(x).numSPs += vertex_data.get(w).numSPs; // vertex_data.get(x).predecessors.add(w); // } // } // } // updateScores(v, stack); // } // // if(graph instanceof UndirectedGraph) // adjustUndirectedScores(); // // vertex_data.clear(); // } public Double getVertexScore(V v) { return vertex_scores.get(v); } public Double getEdgeScore(E e) { return edge_scores.get(e); } private class BetweennessData { double distance; double numSPs; // List predecessors; List incomingEdges; double dependency; BetweennessData() { distance = -1; numSPs = 0; // predecessors = new ArrayList(); incomingEdges = new ArrayList(); dependency = 0; } @Override public String toString() { return "[d:" + distance + ", sp:" + numSPs + ", p:" + incomingEdges + ", d:" + dependency + "]\n"; // ", p:" + predecessors + ", d:" + dependency + "]\n"; } } private class BetweennessComparator implements Comparator { public int compare(V v1, V v2) { return vertex_data.get(v1).distance > vertex_data.get(v2).distance ? 1 : -1; } } } ClosenessCentrality.java000066400000000000000000000031351276402340000346410ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/* * Created on Jul 12, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.shortestpath.Distance; import edu.uci.ics.jung.graph.Hypergraph; /** * Assigns scores to each vertex based on the mean distance to each other vertex. * * @author Joshua O'Madadhain */ public class ClosenessCentrality extends DistanceCentralityScorer { /** * Creates an instance using the specified vertex/vertex distance metric. * @param graph the input * @param distance the vertex/vertex distance metric. */ public ClosenessCentrality(Hypergraph graph, Distance distance) { super(graph, distance, true); } /** * Creates an instance which measures distance using the specified edge weights. * @param graph the input graph * @param edge_weights the edge weights to be used to determine vertex/vertex distances */ public ClosenessCentrality(Hypergraph graph, Function edge_weights) { super(graph, edge_weights, true); } /** * Creates an instance which measures distance on the graph without edge weights. * @param graph the graph whose vertices' centrality scores will be calculated */ public ClosenessCentrality(Hypergraph graph) { super(graph, true); } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/DegreeScorer.java000066400000000000000000000017501276402340000332750ustar00rootroot00000000000000/* * Created on Jul 6, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import edu.uci.ics.jung.graph.Hypergraph; /** * Assigns a score to each vertex equal to its degree. * * @param the vertex type */ public class DegreeScorer implements VertexScorer { /** * The graph for which scores are to be generated. */ protected Hypergraph graph; /** * Creates an instance for the specified graph. * @param graph the input graph */ public DegreeScorer(Hypergraph graph) { this.graph = graph; } /** * Returns the degree of the vertex. * @return the degree of the vertex */ public Integer getVertexScore(V v) { return graph.degree(v); } } DistanceCentralityScorer.java000066400000000000000000000212571276402340000356200ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/* * Created on Jul 10, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import java.util.HashMap; import java.util.Map; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.shortestpath.DijkstraDistance; import edu.uci.ics.jung.algorithms.shortestpath.Distance; import edu.uci.ics.jung.algorithms.shortestpath.UnweightedShortestPath; import edu.uci.ics.jung.graph.Hypergraph; /** * Assigns scores to vertices based on their distances to each other vertex * in the graph. * * This class optionally normalizes its results based on the value of its * 'averaging' constructor parameter. If it is true, * then the value returned for vertex v is 1 / (_average_ distance from v to all other vertices); * this is sometimes called closeness centrality. * If it is false, then the value returned is 1 / (_total_ distance from * v to all other vertices); this is sometimes referred to as barycenter centrality. * (If the average/total distance is 0, the value returned is {@code Double.POSITIVE_INFINITY}.) * * @see BarycenterScorer * @see ClosenessCentrality */ public class DistanceCentralityScorer implements VertexScorer { /** * The graph on which the vertex scores are to be calculated. */ protected Hypergraph graph; /** * The metric to use for specifying the distance between pairs of vertices. */ protected Distance distance; /** * The cache for the output results. Null encodes "not yet calculated", * < 0 encodes "no such distance exists". */ protected Map output; /** * Specifies whether the values returned are the sum of the v-distances * or the mean v-distance. */ protected boolean averaging; /** * Specifies whether, for a vertex v with missing (null) distances, * v's score should ignore the missing values or be set to 'null'. * Defaults to 'true'. */ protected boolean ignore_missing; /** * Specifies whether the values returned should ignore self-distances * (distances from v to itself). * Defaults to 'true'. */ protected boolean ignore_self_distances; /** * Creates an instance with the specified graph, distance metric, and * averaging behavior. * * @param graph The graph on which the vertex scores are to be calculated. * @param distance The metric to use for specifying the distance between * pairs of vertices. * @param averaging Specifies whether the values returned is the sum of all * v-distances or the mean v-distance. * @param ignore_missing Specifies whether scores for missing distances * are to ignore missing distances or be set to null. * @param ignore_self_distances Specifies whether distances from a vertex * to itself should be included in its score. */ public DistanceCentralityScorer(Hypergraph graph, Distance distance, boolean averaging, boolean ignore_missing, boolean ignore_self_distances) { this.graph = graph; this.distance = distance; this.averaging = averaging; this.ignore_missing = ignore_missing; this.ignore_self_distances = ignore_self_distances; this.output = new HashMap(); } /** * Equivalent to this(graph, distance, averaging, true, true). * * @param graph The graph on which the vertex scores are to be calculated. * @param distance The metric to use for specifying the distance between * pairs of vertices. * @param averaging Specifies whether the values returned is the sum of all * v-distances or the mean v-distance. */ public DistanceCentralityScorer(Hypergraph graph, Distance distance, boolean averaging) { this(graph, distance, averaging, true, true); } /** * Creates an instance with the specified graph and averaging behavior * whose vertex distances are calculated based on the specified edge * weights. * * @param graph The graph on which the vertex scores are to be * calculated. * @param edge_weights The edge weights to use for specifying the distance * between pairs of vertices. * @param averaging Specifies whether the values returned is the sum of * all v-distances or the mean v-distance. * @param ignore_missing Specifies whether scores for missing distances * are to ignore missing distances or be set to null. * @param ignore_self_distances Specifies whether distances from a vertex * to itself should be included in its score. */ public DistanceCentralityScorer(Hypergraph graph, Function edge_weights, boolean averaging, boolean ignore_missing, boolean ignore_self_distances) { this(graph, new DijkstraDistance(graph, edge_weights), averaging, ignore_missing, ignore_self_distances); } /** * Equivalent to this(graph, edge_weights, averaging, true, true). * @param graph The graph on which the vertex scores are to be * calculated. * @param edge_weights The edge weights to use for specifying the distance * between pairs of vertices. * @param averaging Specifies whether the values returned is the sum of * all v-distances or the mean v-distance. */ public DistanceCentralityScorer(Hypergraph graph, Function edge_weights, boolean averaging) { this(graph, new DijkstraDistance(graph, edge_weights), averaging, true, true); } /** * Creates an instance with the specified graph and averaging behavior * whose vertex distances are calculated on the unweighted graph. * * @param graph The graph on which the vertex scores are to be * calculated. * @param averaging Specifies whether the values returned is the sum of * all v-distances or the mean v-distance. * @param ignore_missing Specifies whether scores for missing distances * are to ignore missing distances or be set to null. * @param ignore_self_distances Specifies whether distances from a vertex * to itself should be included in its score. */ public DistanceCentralityScorer(Hypergraph graph, boolean averaging, boolean ignore_missing, boolean ignore_self_distances) { this(graph, new UnweightedShortestPath(graph), averaging, ignore_missing, ignore_self_distances); } /** * Equivalent to this(graph, averaging, true, true). * @param graph The graph on which the vertex scores are to be * calculated. * @param averaging Specifies whether the values returned is the sum of * all v-distances or the mean v-distance. */ public DistanceCentralityScorer(Hypergraph graph, boolean averaging) { this(graph, new UnweightedShortestPath(graph), averaging, true, true); } /** * Calculates the score for the specified vertex. Returns {@code null} if * there are missing distances and such are not ignored by this instance. */ public Double getVertexScore(V v) { Double value = output.get(v); if (value != null) { if (value < 0) return null; return value; } Map v_distances = new HashMap(distance.getDistanceMap(v)); if (ignore_self_distances) v_distances.remove(v); // if we don't ignore missing distances and there aren't enough // distances, output null (shortcut) if (!ignore_missing) { int num_dests = graph.getVertexCount() - (ignore_self_distances ? 1 : 0); if (v_distances.size() != num_dests) { output.put(v, -1.0); return null; } } Double sum = 0.0; for (V w : graph.getVertices()) { if (w.equals(v) && ignore_self_distances) continue; Number w_distance = v_distances.get(w); if (w_distance == null) if (ignore_missing) continue; else { output.put(v, -1.0); return null; } else sum += w_distance.doubleValue(); } value = sum; if (averaging) value /= v_distances.size(); double score = value == 0 ? Double.POSITIVE_INFINITY : 1.0 / value; output.put(v, score); return score; } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/EdgeScorer.java000066400000000000000000000011631276402340000327440ustar00rootroot00000000000000/* * Created on Jul 6, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; /** * An interface for algorithms that assign scores to edges. * * @param the edge type * @param the score type */ public interface EdgeScorer { /** * @param e the edge whose score is requested * @return the algorithm's score for this edge */ public S getEdgeScore(E e); } EigenvectorCentrality.java000066400000000000000000000032321276402340000351530ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/* * Created on Jul 12, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Hypergraph; /** * Calculates eigenvector centrality for each vertex in the graph. * The 'eigenvector centrality' for a vertex is defined as the fraction of * time that a random walk(er) will spend at that vertex over an infinite * time horizon. * Assumes that the graph is strongly connected. */ public class EigenvectorCentrality extends PageRank { /** * Creates an instance with the specified graph and edge weights. * The outgoing edge weights for each edge must sum to 1. * (See UniformDegreeWeight for one way to handle this for * undirected graphs.) * @param graph the graph for which the centrality is to be calculated * @param edge_weights the edge weights */ public EigenvectorCentrality(Hypergraph graph, Function edge_weights) { super(graph, edge_weights, 0); acceptDisconnectedGraph(false); } /** * Creates an instance with the specified graph and default edge weights. * (Default edge weights: UniformDegreeWeight.) * @param graph the graph for which the centrality is to be calculated. */ public EigenvectorCentrality(Hypergraph graph) { super(graph, 0); acceptDisconnectedGraph(false); } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/HITS.java000066400000000000000000000123231276402340000314710ustar00rootroot00000000000000/* * Created on Jul 15, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.scoring.util.ScoringUtils; import edu.uci.ics.jung.graph.Graph; /** * Assigns hub and authority scores to each vertex depending on the topology of * the network. The essential idea is that a vertex is a hub to the extent * that it links to authoritative vertices, and is an authority to the extent * that it links to 'hub' vertices. * *

    The classic HITS algorithm essentially proceeds as follows: *

     * assign equal initial hub and authority values to each vertex
     * repeat
     *   for each vertex w:
     *     w.hub = sum over successors x of x.authority
     *     w.authority = sum over predecessors v of v.hub
     *   normalize hub and authority scores so that the sum of the squares of each = 1
     * until scores converge
     * 
    * * HITS is somewhat different from random walk/eigenvector-based algorithms * such as PageRank in that: *
      *
    • there are two mutually recursive scores being calculated, rather than * a single value *
    • the edge weights are effectively all 1, i.e., they can't be interpreted * as transition probabilities. This means that the more inlinks and outlinks * that a vertex has, the better, since adding an inlink (or outlink) does * not dilute the influence of the other inlinks (or outlinks) as in * random walk-based algorithms. *
    • the scores cannot be interpreted as posterior probabilities (due to the different * normalization) *
    * * This implementation has the classic behavior by default. However, it has * been generalized somewhat so that it can act in a more "PageRank-like" fashion: *
      *
    • this implementation has an optional 'random jump probability' parameter analogous * to the 'alpha' parameter used by PageRank. Varying this value between 0 and 1 * allows the user to vary between the classic HITS behavior and one in which the * scores are smoothed to a uniform distribution. * The default value for this parameter is 0 (no random jumps possible). *
    • the edge weights can be set to anything the user likes, and in * particular they can be set up (e.g. using UniformDegreeWeight) * so that the weights of the relevant edges incident to a vertex sum to 1. *
    • The vertex score normalization has been factored into its own method * so that it can be overridden by a subclass. Thus, for example, * since the vertices' values are set to sum to 1 initially, if the weights of the * relevant edges incident to a vertex sum to 1, then the vertices' values * will continue to sum to 1 if the "sum-of-squares" normalization code * is overridden to a no-op. (Other normalization methods may also be employed.) *
    * * @param the vertex type * @param the edge type * * @see "'Authoritative sources in a hyperlinked environment' by Jon Kleinberg, 1997" */ public class HITS extends HITSWithPriors { /** * Creates an instance for the specified graph, edge weights, and alpha * (random jump probability) parameter. * @param g the input graph * @param edge_weights the weights to use for each edge * @param alpha the probability of a hub giving some authority to all vertices, * and of an authority increasing the score of all hubs (not just those connected * via links) */ public HITS(Graph g, Function edge_weights, double alpha) { super(g, edge_weights, ScoringUtils.getHITSUniformRootPrior(g.getVertices()), alpha); } /** * Creates an instance for the specified graph and alpha (random jump probability) * parameter. The edge weights are all set to 1. * @param g the input graph * @param alpha the probability of a hub giving some authority to all vertices, * and of an authority increasing the score of all hubs (not just those connected * via links) */ public HITS(Graph g, double alpha) { super(g, ScoringUtils.getHITSUniformRootPrior(g.getVertices()), alpha); } /** * Creates an instance for the specified graph. The edge weights are all set to 1 * and alpha is set to 0. * @param g the input graph */ public HITS(Graph g) { this(g, 0.0); } /** * Maintains hub and authority score information for a vertex. */ public static class Scores { /** * The hub score for a vertex. */ public double hub; /** * The authority score for a vertex. */ public double authority; /** * Creates an instance with the specified hub and authority score. * @param hub the hub score * @param authority the authority score */ public Scores(double hub, double authority) { this.hub = hub; this.authority = authority; } @Override public String toString() { return String.format("[h:%.4f,a:%.4f]", this.hub, this.authority); } } } HITSWithPriors.java000066400000000000000000000157261276402340000334570ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/* * Created on Jul 14, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import com.google.common.base.Function; import com.google.common.base.Functions; import edu.uci.ics.jung.graph.Hypergraph; /** * A generalization of HITS that permits non-uniformly-distributed random jumps. * The 'vertex_priors' (that is, prior probabilities for each vertex) may be * thought of as the fraction of the total 'potential' (hub or authority score) * that is assigned to that vertex out of the portion that is assigned according * to random jumps. * * @see "Algorithms for Estimating Relative Importance in Graphs by Scott White and Padhraic Smyth, 2003" */ public class HITSWithPriors extends AbstractIterativeScorerWithPriors { /** * The sum of the potential, at each step, associated with vertices with no outedges (authority) * or no inedges (hub). */ protected HITS.Scores disappearing_potential; /** * Creates an instance for the specified graph, edge weights, vertex prior probabilities, * and random jump probability (alpha). * @param g the input graph * @param edge_weights the edge weights * @param vertex_priors the prior probability for each vertex * @param alpha the probability of a random jump at each step */ public HITSWithPriors(Hypergraph g, Function edge_weights, Function vertex_priors, double alpha) { super(g, edge_weights, vertex_priors, alpha); disappearing_potential = new HITS.Scores(0,0); } /** * Creates an instance for the specified graph, vertex priors, and random * jump probability (alpha). The edge weights default to 1.0. * @param g the input graph * @param vertex_priors the prior probability for each vertex * @param alpha the probability of a random jump at each step */ public HITSWithPriors(Hypergraph g, Function vertex_priors, double alpha) { super(g, Functions.constant(1.0), vertex_priors, alpha); disappearing_potential = new HITS.Scores(0,0); } /** * Updates the value for this vertex. */ @Override protected double update(V v) { collectDisappearingPotential(v); double v_auth = 0; for (E e : graph.getInEdges(v)) { int incident_count = getAdjustedIncidentCount(e); for (V w : graph.getIncidentVertices(e)) { if (!w.equals(v) || hyperedges_are_self_loops) v_auth += (getCurrentValue(w).hub * getEdgeWeight(w,e).doubleValue() / incident_count); } // V w = graph.getOpposite(v, e); // auth += (getCurrentValue(w).hub * getEdgeWeight(w, e).doubleValue()); } double v_hub = 0; for (E e : graph.getOutEdges(v)) { int incident_count = getAdjustedIncidentCount(e); for (V w : graph.getIncidentVertices(e)) { if (!w.equals(v) || hyperedges_are_self_loops) v_hub += (getCurrentValue(w).authority * getEdgeWeight(w,e).doubleValue() / incident_count); } // V x = graph.getOpposite(v,e); // hub += (getCurrentValue(x).authority * getEdgeWeight(x, e).doubleValue()); } // modify total_input according to alpha if (alpha > 0) { v_auth = v_auth * (1 - alpha) + getVertexPrior(v).authority * alpha; v_hub = v_hub * (1 - alpha) + getVertexPrior(v).hub * alpha; } setOutputValue(v, new HITS.Scores(v_hub, v_auth)); return Math.max(Math.abs(getCurrentValue(v).hub - v_hub), Math.abs(getCurrentValue(v).authority - v_auth)); } /** * Code which is executed after each step. In this case, deals with the * 'disappearing potential', normalizes the scores, and then calls * super.afterStep(). * @see #collectDisappearingPotential(Object) */ @Override protected void afterStep() { if (disappearing_potential.hub > 0 || disappearing_potential.authority > 0) { for (V v : graph.getVertices()) { double new_hub = getOutputValue(v).hub + (1 - alpha) * (disappearing_potential.hub * getVertexPrior(v).hub); double new_auth = getOutputValue(v).authority + (1 - alpha) * (disappearing_potential.authority * getVertexPrior(v).authority); setOutputValue(v, new HITS.Scores(new_hub, new_auth)); } disappearing_potential.hub = 0; disappearing_potential.authority = 0; } normalizeScores(); super.afterStep(); } /** * Normalizes scores so that sum of their squares = 1. * This method may be overridden so as to yield different * normalizations. */ protected void normalizeScores() { double hub_ssum = 0; double auth_ssum = 0; for (V v : graph.getVertices()) { double hub_val = getOutputValue(v).hub; double auth_val = getOutputValue(v).authority; hub_ssum += (hub_val * hub_val); auth_ssum += (auth_val * auth_val); } hub_ssum = Math.sqrt(hub_ssum); auth_ssum = Math.sqrt(auth_ssum); for (V v : graph.getVertices()) { HITS.Scores values = getOutputValue(v); setOutputValue(v, new HITS.Scores( values.hub / hub_ssum, values.authority / auth_ssum)); } } /** * Collects the "disappearing potential" associated with vertices that have either * no incoming edges, no outgoing edges, or both. Vertices that have no incoming edges * do not directly contribute to the hub scores of other vertices; similarly, vertices * that have no outgoing edges do not directly contribute to the authority scores of * other vertices. These values are collected at each step and then distributed across all vertices * as a part of the normalization process. (This process is not required for, and does * not affect, the 'sum-of-squares'-style normalization.) */ @Override protected void collectDisappearingPotential(V v) { if (graph.outDegree(v) == 0) { if (isDisconnectedGraphOK()) disappearing_potential.hub += getCurrentValue(v).authority; else throw new IllegalArgumentException("Outdegree of " + v + " must be > 0"); } if (graph.inDegree(v) == 0) { if (isDisconnectedGraphOK()) disappearing_potential.authority += getCurrentValue(v).hub; else throw new IllegalArgumentException("Indegree of " + v + " must be > 0"); } } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/KStepMarkov.java000066400000000000000000000130271276402340000331320ustar00rootroot00000000000000/** * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Aug 22, 2008 * */ package edu.uci.ics.jung.algorithms.scoring; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.scoring.util.ScoringUtils; import edu.uci.ics.jung.graph.Hypergraph; /** * A special case of {@code PageRankWithPriors} in which the final scores * represent a probability distribution over position assuming a random (Markovian) * walk of exactly k steps, based on the initial distribution specified by the priors. * *

    NOTE: The version of {@code KStepMarkov} in {@code algorithms.importance} * (and in JUNG 1.x) is believed to be incorrect: rather than returning * a score which represents a probability distribution over position assuming * a k-step random walk, it returns a score which represents the sum over all steps * of the probability for each step. If you want that behavior, set the * 'cumulative' flag as follows before calling {@code evaluate()}: *

     *     KStepMarkov ksm = new KStepMarkov(...);
     *     ksm.setCumulative(true);
     *     ksm.evaluate();
     * 
    * * By default, the 'cumulative' flag is set to false. * * NOTE: THIS CLASS IS NOT YET COMPLETE. USE AT YOUR OWN RISK. (The original behavior * is captured by the version still available in {@code algorithms.importance}.) * * @see "Algorithms for Estimating Relative Importance in Graphs by Scott White and Padhraic Smyth, 2003" * @see PageRank * @see PageRankWithPriors */ public class KStepMarkov extends PageRankWithPriors { private boolean cumulative; /** * Creates an instance based on the specified graph, edge weights, vertex * priors (initial scores), and number of steps to take. * @param graph the input graph * @param edge_weights the edge weights (transition probabilities) * @param vertex_priors the initial probability distribution (score assignment) * @param steps the number of times that {@code step()} will be called by {@code evaluate} */ public KStepMarkov(Hypergraph graph, Function edge_weights, Function vertex_priors, int steps) { super(graph, edge_weights, vertex_priors, 0); initialize(steps); } /** * Creates an instance based on the specified graph, vertex * priors (initial scores), and number of steps to take. The edge * weights (transition probabilities) are set to default values (a uniform * distribution over all outgoing edges). * @param graph the input graph * @param vertex_priors the initial probability distribution (score assignment) * @param steps the number of times that {@code step()} will be called by {@code evaluate} */ public KStepMarkov(Hypergraph graph, Function vertex_priors, int steps) { super(graph, vertex_priors, 0); initialize(steps); } /** * Creates an instance based on the specified graph and number of steps to * take. The edge weights (transition probabilities) and vertex initial scores * (prior probabilities) are set to default values (a uniform * distribution over all outgoing edges, and a uniform distribution over * all vertices, respectively). * @param graph the input graph * @param steps the number of times that {@code step()} will be called by {@code evaluate} */ public KStepMarkov(Hypergraph graph, int steps) { super(graph, ScoringUtils.getUniformRootPrior(graph.getVertices()), 0); initialize(steps); } private void initialize(int steps) { this.acceptDisconnectedGraph(false); if (steps <= 0) throw new IllegalArgumentException("Number of steps must be > 0"); this.max_iterations = steps; this.tolerance = -1.0; this.cumulative = false; } /** * Specifies whether this instance should assign a score to each vertex * based on the sum over all steps of the probability for each step. * See the class-level documentation for details. * @param cumulative true if this instance should assign a cumulative score to each vertex */ public void setCumulative(boolean cumulative) { this.cumulative = cumulative; } /** * Updates the value for this vertex. Called by step(). */ @Override public double update(V v) { if (!cumulative) return super.update(v); collectDisappearingPotential(v); double v_input = 0; for (E e : graph.getInEdges(v)) { // For graphs, the code below is equivalent to // V w = graph.getOpposite(v, e); // total_input += (getCurrentValue(w) * getEdgeWeight(w,e).doubleValue()); // For hypergraphs, this divides the potential coming from w // by the number of vertices in the connecting edge e. int incident_count = getAdjustedIncidentCount(e); for (V w : graph.getIncidentVertices(e)) { if (!w.equals(v) || hyperedges_are_self_loops) v_input += (getCurrentValue(w) * getEdgeWeight(w,e).doubleValue() / incident_count); } } // modify total_input according to alpha double new_value = alpha > 0 ? v_input * (1 - alpha) + getVertexPrior(v) * alpha : v_input; setOutputValue(v, new_value + getCurrentValue(v)); // FIXME: DO WE NEED TO CHANGE HOW DISAPPEARING IS COUNTED? NORMALIZE? return Math.abs(getCurrentValue(v) - new_value); } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/PageRank.java000066400000000000000000000061701276402340000324150ustar00rootroot00000000000000/* * Created on Jul 12, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.scoring.util.ScoringUtils; import edu.uci.ics.jung.graph.Hypergraph; /** * Assigns scores to each vertex according to the PageRank algorithm. * *

    PageRank is an eigenvector-based algorithm. The score for a given vertex may be thought of * as the fraction of time spent 'visiting' that vertex (measured over all time) * in a random walk over the vertices (following outgoing edges from each vertex). * PageRank modifies this random walk by adding to the model a probability (specified as 'alpha' * in the constructor) of jumping to any vertex. If alpha is 0, this is equivalent to the * eigenvector centrality algorithm; if alpha is 1, all vertices will receive the same score * (1/|V|). Thus, alpha acts as a sort of score smoothing parameter. * *

    The original algorithm assumed that, for a given vertex, the probability of following any * outgoing edge was the same; this is the default if edge weights are not specified. * This implementation generalizes the original by permitting * the user to specify edge weights; in order to maintain the original semantics, however, * the weights on the outgoing edges for a given vertex must represent transition probabilities; * that is, they must sum to 1. * *

    If a vertex has no outgoing edges, then the probability of taking a random jump from that * vertex is (by default) effectively 1. If the user wishes to instead throw an exception when this happens, * call acceptDisconnectedGraph(false) on this instance. * *

    Typical values for alpha (according to the original paper) are in the range [0.1, 0.2] * but may be any value between 0 and 1 inclusive. * * @see "The Anatomy of a Large-Scale Hypertextual Web Search Engine by L. Page and S. Brin, 1999" */ public class PageRank extends PageRankWithPriors { /** * Creates an instance for the specified graph, edge weights, and random jump probability. * @param graph the input graph * @param edge_weight the edge weights (transition probabilities) * @param alpha the probability of taking a random jump to an arbitrary vertex */ public PageRank(Hypergraph graph, Function edge_weight, double alpha) { super(graph, edge_weight, ScoringUtils.getUniformRootPrior(graph.getVertices()), alpha); } /** * Creates an instance for the specified graph and random jump probability; the probability * of following any outgoing edge from a given vertex is the same. * @param graph the input graph * @param alpha the probability of taking a random jump to an arbitrary vertex */ public PageRank(Hypergraph graph, double alpha) { super(graph, ScoringUtils.getUniformRootPrior(graph.getVertices()), alpha); } } PageRankWithPriors.java000066400000000000000000000120671276402340000343730ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/* * Created on Jul 6, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.scoring.util.UniformDegreeWeight; import edu.uci.ics.jung.graph.Hypergraph; /** * A generalization of PageRank that permits non-uniformly-distributed random jumps. * The 'vertex_priors' (that is, prior probabilities for each vertex) may be * thought of as the fraction of the total 'potential' that is assigned to that * vertex at each step out of the portion that is assigned according * to random jumps (this portion is specified by 'alpha'). * * @see "Algorithms for Estimating Relative Importance in Graphs by Scott White and Padhraic Smyth, 2003" * @see PageRank */ public class PageRankWithPriors extends AbstractIterativeScorerWithPriors { /** * Maintains the amount of potential associated with vertices with no out-edges. */ protected double disappearing_potential = 0.0; /** * Creates an instance with the specified graph, edge weights, vertex priors, and * 'random jump' probability (alpha). * @param graph the input graph * @param edge_weights the edge weights, denoting transition probabilities from source to destination * @param vertex_priors the prior probabilities for each vertex * @param alpha the probability of executing a 'random jump' at each step */ public PageRankWithPriors(Hypergraph graph, Function edge_weights, Function vertex_priors, double alpha) { super(graph, edge_weights, vertex_priors, alpha); } /** * Creates an instance with the specified graph, vertex priors, and * 'random jump' probability (alpha). The outgoing edge weights for each * vertex will be equal and sum to 1. * @param graph the input graph * @param vertex_priors the prior probabilities for each vertex * @param alpha the probability of executing a 'random jump' at each step */ public PageRankWithPriors(Hypergraph graph, Function vertex_priors, double alpha) { super(graph, vertex_priors, alpha); this.edge_weights = new UniformDegreeWeight(graph); } /** * Updates the value for this vertex. Called by step(). */ @Override public double update(V v) { collectDisappearingPotential(v); double v_input = 0; for (E e : graph.getInEdges(v)) { // For graphs, the code below is equivalent to // V w = graph.getOpposite(v, e); // total_input += (getCurrentValue(w) * getEdgeWeight(w,e).doubleValue()); // For hypergraphs, this divides the potential coming from w // by the number of vertices in the connecting edge e. int incident_count = getAdjustedIncidentCount(e); for (V w : graph.getIncidentVertices(e)) { if (!w.equals(v) || hyperedges_are_self_loops) v_input += (getCurrentValue(w) * getEdgeWeight(w,e).doubleValue() / incident_count); } } // modify total_input according to alpha double new_value = alpha > 0 ? v_input * (1 - alpha) + getVertexPrior(v) * alpha : v_input; setOutputValue(v, new_value); return Math.abs(getCurrentValue(v) - new_value); } /** * Cleans up after each step. In this case that involves allocating the disappearing * potential (thus maintaining normalization of the scores) according to the vertex * probability priors, and then calling * super.afterStep. */ @Override protected void afterStep() { // distribute disappearing potential according to priors if (disappearing_potential > 0) { for (V v : graph.getVertices()) { setOutputValue(v, getOutputValue(v) + (1 - alpha) * (disappearing_potential * getVertexPrior(v))); } disappearing_potential = 0; } super.afterStep(); } /** * Collects the "disappearing potential" associated with vertices that have * no outgoing edges. Vertices that have no outgoing edges do not directly * contribute to the scores of other vertices. These values are collected * at each step and then distributed across all vertices * as a part of the normalization process. */ @Override protected void collectDisappearingPotential(V v) { if (graph.outDegree(v) == 0) { if (isDisconnectedGraphOK()) disappearing_potential += getCurrentValue(v); else throw new IllegalArgumentException("Outdegree of " + v + " must be > 0"); } } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/VertexScorer.java000066400000000000000000000012001276402340000333450ustar00rootroot00000000000000/* * Created on Jul 6, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; /** * An interface for algorithms that assign scores to vertices. * * @param the vertex type * @param the score type */ public interface VertexScorer { /** * @param v the vertex whose score is requested * @return the algorithm's score for this vertex */ public S getVertexScore(V v); } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/VoltageScorer.java000066400000000000000000000216611276402340000335060ustar00rootroot00000000000000/* * Created on Jul 15, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.scoring.util.UniformDegreeWeight; import edu.uci.ics.jung.graph.Hypergraph; /** * Assigns scores to vertices according to their 'voltage' in an approximate * solution to the Kirchoff equations. This is accomplished by tying "source" * vertices to specified positive voltages, "sink" vertices to 0 V, and * iteratively updating the voltage of each other vertex to the (weighted) * average of the voltages of its neighbors. * *

    The resultant voltages will all be in the range [0, max] * where max is the largest voltage of any source vertex (in the * absence of negative source voltages; see below). * *

    A few notes about this algorithm's interpretation of the graph data: *

      *
    • Higher edge weights are interpreted as indicative of greater * influence/effect than lower edge weights. *
    • Negative edge weights (and negative "source" voltages) invalidate * the interpretation of the resultant values as voltages. However, this * algorithm will not reject graphs with negative edge weights or source voltages. *
    • Parallel edges are equivalent to a single edge whose weight is the * sum of the weights on the parallel edges. *
    • Current flows along undirected edges in both directions, * but only flows along directed edges in the direction of the edge. *
    * */ public class VoltageScorer extends AbstractIterativeScorer implements VertexScorer { protected Map source_voltages; protected Collection sinks; /** * Creates an instance with the specified graph, edge weights, source voltages, * and sinks. * @param g the input graph * @param edge_weights the edge weights, representing conductivity * @param source_voltages the (fixed) voltage for each source * @param sinks the vertices whose voltages are tied to 0 */ public VoltageScorer(Hypergraph g, Function edge_weights, Map source_voltages, Collection sinks) { super(g, edge_weights); this.source_voltages = source_voltages; this.sinks = sinks; initialize(); } /** * Creates an instance with the specified graph, edge weights, source vertices * (each of whose 'voltages' are tied to 1), and sinks. * @param g the input graph * @param edge_weights the edge weights, representing conductivity * @param sources the vertices whose voltages are tied to 1 * @param sinks the vertices whose voltages are tied to 0 */ public VoltageScorer(Hypergraph g, Function edge_weights, Collection sources, Collection sinks) { super(g, edge_weights); Map unit_voltages = new HashMap(); for(V v : sources) unit_voltages.put(v, new Double(1.0)); this.source_voltages = unit_voltages; this.sinks = sinks; initialize(); } /** * Creates an instance with the specified graph, source vertices * (each of whose 'voltages' are tied to 1), and sinks. * The outgoing edges for each vertex are assigned * weights that sum to 1. * @param g the input graph * @param sources the vertices whose voltages are tied to 1 * @param sinks the vertices whose voltages are tied to 0 */ public VoltageScorer(Hypergraph g, Collection sources, Collection sinks) { super(g); Map unit_voltages = new HashMap(); for(V v : sources) unit_voltages.put(v, new Double(1.0)); this.source_voltages = unit_voltages; this.sinks = sinks; initialize(); } /** * Creates an instance with the specified graph, source voltages, * and sinks. The outgoing edges for each vertex are assigned * weights that sum to 1. * @param g the input graph * @param source_voltages the (fixed) voltage for each source * @param sinks the vertices whose voltages are tied to 0 */ public VoltageScorer(Hypergraph g, Map source_voltages, Collection sinks) { super(g); this.source_voltages = source_voltages; this.sinks = sinks; this.edge_weights = new UniformDegreeWeight(g); initialize(); } /** * Creates an instance with the specified graph, edge weights, source, and * sink. The source vertex voltage is tied to 1. * @param g the input graph * @param edge_weights the edge weights, representing conductivity * @param source the vertex whose voltage is tied to 1 * @param sink the vertex whose voltage is tied to 0 */ public VoltageScorer(Hypergraph g, Function edge_weights, V source, V sink) { this(g, edge_weights, Collections.singletonMap(source, 1.0), Collections.singletonList(sink)); initialize(); } /** * Creates an instance with the specified graph, edge weights, source, and * sink. The source vertex voltage is tied to 1. * The outgoing edges for each vertex are assigned * weights that sum to 1. * @param g the input graph * @param source the vertex whose voltage is tied to 1 * @param sink the vertex whose voltage is tied to 0 */ public VoltageScorer(Hypergraph g, V source, V sink) { this(g, Collections.singletonMap(source, 1.0), Collections.singletonList(sink)); initialize(); } /** * Initializes the state of this instance. */ @Override public void initialize() { super.initialize(); // sanity check if (source_voltages.isEmpty() || sinks.isEmpty()) throw new IllegalArgumentException("Both sources and sinks (grounds) must be defined"); if (source_voltages.size() + sinks.size() > graph.getVertexCount()) throw new IllegalArgumentException("Source/sink sets overlap, or contain vertices not in graph"); for (Map.Entry entry : source_voltages.entrySet()) { V v = entry.getKey(); if (sinks.contains(v)) throw new IllegalArgumentException("Vertex " + v + " is incorrectly specified as both source and sink"); double value = entry.getValue().doubleValue(); if (value <= 0) throw new IllegalArgumentException("Source vertex " + v + " has negative voltage"); } // set up initial voltages for (V v : graph.getVertices()) { if (source_voltages.containsKey(v)) setOutputValue(v, source_voltages.get(v).doubleValue()); else setOutputValue(v, 0.0); } } /** * @see edu.uci.ics.jung.algorithms.scoring.AbstractIterativeScorer#update(Object) */ @Override public double update(V v) { // if it's a voltage source or sink, we're done Number source_volts = source_voltages.get(v); if (source_volts != null) { setOutputValue(v, source_volts.doubleValue()); return 0.0; } if (sinks.contains(v)) { setOutputValue(v, 0.0); return 0.0; } Collection edges = graph.getInEdges(v); double voltage_sum = 0; double weight_sum = 0; for (E e: edges) { int incident_count = getAdjustedIncidentCount(e); for (V w : graph.getIncidentVertices(e)) { if (!w.equals(v) || hyperedges_are_self_loops) { double weight = getEdgeWeight(w,e).doubleValue() / incident_count; voltage_sum += getCurrentValue(w).doubleValue() * weight; weight_sum += weight; } } // V w = graph.getOpposite(v, e); // double weight = getEdgeWeight(w,e).doubleValue(); // voltage_sum += getCurrentValue(w).doubleValue() * weight; // weight_sum += weight; } // if either is 0, new value is 0 if (voltage_sum == 0 || weight_sum == 0) { setOutputValue(v, 0.0); return getCurrentValue(v).doubleValue(); } setOutputValue(v, voltage_sum / weight_sum); return Math.abs(getCurrentValue(v).doubleValue() - voltage_sum / weight_sum); } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/package.html000066400000000000000000000030471276402340000323430ustar00rootroot00000000000000 Mechanisms for assigning values (denoting significance, influence, centrality, etc.) to graph elements based on topological properties. These include:
    • BarycenterScorer: assigns a score to each vertex according to the sum of the distances to all other vertices
    • ClosenessCentrality: assigns a score to each vertex based on the mean distance to each other vertex
    • DegreeScorer: assigns a score to each vertex based on its degree
    • EigenvectorCentrality: assigns vertex scores based on long-term probabilities of random walks passing through the vertex at time t
    • PageRank: like EigenvectorCentrality, but with a constant probability of the random walk restarting at a uniform-randomly chosen vertex
    • PageRankWithPriors: like PageRank, but with a constant probability of the random walk restarting at a vertex drawn from an arbitrary distribution
    • HITS: assigns hubs-and-authorities scores to vertices based on complementary random walk processes
    • HITSWithPriors: analogous to HITS (see PageRankWithPriors)
    • VoltageScorer: assigns scores to vertices based on simulated current flow along edges
    jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/000077500000000000000000000000001276402340000310335ustar00rootroot00000000000000DelegateToEdgeTransformer.java000066400000000000000000000025611276402340000366500ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/* * Created on Jul 11, 2008 * * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring.util; import com.google.common.base.Function; /** * A {@code Transformer}. Mainly useful for technical reasons inside * AbstractIterativeScorer; in essence it allows the edge weight instance * variable to be of type VEPair,W even if the edge weight * Transformer only operates on edges. */ public class DelegateToEdgeTransformer implements Function,Number> { /** * The Function to which this instance delegates its function. */ protected Function delegate; /** * Creates an instance with the specified delegate Function. * @param delegate the Function to which this instance will delegate */ public DelegateToEdgeTransformer(Function delegate) { this.delegate = delegate; } /** * @see Function#apply(Object) */ public Number apply(VEPair arg0) { return delegate.apply(arg0.getE()); } } ScoringUtils.java000066400000000000000000000045151276402340000342510ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/* * Created on Jul 12, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring.util; import java.util.Collection; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.scoring.HITS; /** * Methods for assigning values (to be interpreted as prior probabilities) to vertices in the context * of random-walk-based scoring algorithms. */ public class ScoringUtils { /** * Assigns a probability of 1/roots.size() to each of the elements of roots. * @param the vertex type * @param roots the vertices to be assigned nonzero prior probabilities * @return a Function assigning a uniform prior to each element in {@code roots} */ public static Function getUniformRootPrior(Collection roots) { final Collection inner_roots = roots; Function distribution = new Function() { public Double apply(V input) { if (inner_roots.contains(input)) return new Double(1.0 / inner_roots.size()); else return 0.0; } }; return distribution; } /** * Returns a Function that hub and authority values of 1/roots.size() to each * element of roots. * @param the vertex type * @param roots the vertices to be assigned nonzero scores * @return a Function that assigns uniform prior hub/authority probabilities to each root */ public static Function getHITSUniformRootPrior(Collection roots) { final Collection inner_roots = roots; Function distribution = new Function() { public HITS.Scores apply(V input) { if (inner_roots.contains(input)) return new HITS.Scores(1.0 / inner_roots.size(), 1.0 / inner_roots.size()); else return new HITS.Scores(0.0, 0.0); } }; return distribution; } } UniformDegreeWeight.java000066400000000000000000000030221276402340000355170ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/** * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Jul 14, 2008 * */ package edu.uci.ics.jung.algorithms.scoring.util; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.graph.util.EdgeType; /** * An edge weight function that assigns weights as uniform * transition probabilities. * For undirected edges, returns 1/degree(v) (where 'v' is the * vertex in the VEPair. * For directed edges, returns 1/outdegree(source(e)) (where 'e' * is the edge in the VEPair). * Throws an IllegalArgumentException if the input * edge is neither EdgeType.UNDIRECTED nor EdgeType.DIRECTED. * */ public class UniformDegreeWeight implements Function, Double> { private Hypergraph graph; /** * @param graph the graph for which an instance is being created */ public UniformDegreeWeight(Hypergraph graph) { this.graph = graph; } public Double apply(VEPair ve_pair) { E e = ve_pair.getE(); V v = ve_pair.getV(); EdgeType edge_type = graph.getEdgeType(e); if (edge_type == EdgeType.UNDIRECTED) return 1.0 / graph.degree(v); if (edge_type == EdgeType.DIRECTED) return 1.0 / graph.outDegree(graph.getSource(e)); throw new IllegalArgumentException("can't handle edge type: " + edge_type); } } UniformInOut.java000066400000000000000000000030141276402340000342130ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/* * Created on Jul 11, 2008 * * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring.util; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.EdgeType; /** * Assigns weights to directed edges (the edge of the vertex/edge pair) depending on * whether the vertex is the edge's source or its destination. * If the vertex v is the edge's source, assigns 1/outdegree(v). * Otherwise, assigns 1/indegree(w). * Throws IllegalArgumentException if the edge is not directed. */ public class UniformInOut implements Function, Double> { /** * The graph for which the edge weights are defined. */ protected Graph graph; /** * Creates an instance for the specified graph. * @param graph the graph for which the edge weights will be defined */ public UniformInOut(Graph graph) { this.graph = graph; } public Double apply(VEPair ve_pair) { V v = ve_pair.getV(); E e = ve_pair.getE(); if (graph.getEdgeType(e) != EdgeType.DIRECTED) throw new IllegalArgumentException("This Function only" + " operates on directed edges"); return 1.0 / (graph.isSource(v, e) ? graph.outDegree(v) : graph.inDegree(v)); } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/VEPair.java000066400000000000000000000023501276402340000330240ustar00rootroot00000000000000/* * Created on Jul 8, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring.util; /** * Convenience class for associating a vertex and an edge. Used, for example, * in contexts in which it is necessary to know the origin for an edge traversal * (that is, the direction in which an (undirected) edge is being traversed). * * @param the vertex type * @param the edge type */ public class VEPair { private V v; private E e; /** * Creates an instance with the specified vertex and edge * @param v the vertex to add * @param e the edge to add */ public VEPair(V v, E e) { if (v == null || e == null) throw new IllegalArgumentException("elements must be non-null"); this.v = v; this.e = e; } /** * @return the vertex of this pair */ public V getV() { return v; } /** * @return the edge of this pair */ public E getE() { return e; } } VertexScoreTransformer.java000066400000000000000000000021621276402340000363140ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/* * Created on Jul 18, 2008 * * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring.util; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.scoring.VertexScorer; /** * A Function convenience wrapper around VertexScorer. */ public class VertexScoreTransformer implements Function { /** * The VertexScorer instance that provides the values returned by transform. */ protected VertexScorer vs; /** * Creates an instance based on the specified VertexScorer. * @param vs the VertexScorer which will retrieve the score for each vertex */ public VertexScoreTransformer(VertexScorer vs) { this.vs = vs; } /** * @param v the vertex whose score is being returned * @return the score for this vertex. */ public S apply(V v) { return vs.getVertexScore(v); } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/package.html000066400000000000000000000021731276402340000333170ustar00rootroot00000000000000 Utility functions for assigning scores to graph elements. These include:
    • EdgeWeight: interface for classes that associate numeric values with edges
    • ScoringUtils: methods for calculating transition probabilities for random-walk-based algorithms.
    • UniformOut: an edge weight function that assigns weights as uniform transition probabilities to all outgoing edges of a vertex.
    • UniformIncident: an edge weight function that assigns weights as uniform transition probabilities to all incident edges of a vertex (useful for undirected graphs).
    • VEPair: analogous to Pair but specifically containing an associated vertex and edge.
    • VertexEdgeWeight: a subtype of EdgeWeight that assigns edge weights with respect to a specified 'source' vertex.
    jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/000077500000000000000000000000001276402340000311425ustar00rootroot00000000000000BFSDistanceLabeler.java000066400000000000000000000134041276402340000353040ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.shortestpath; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import edu.uci.ics.jung.graph.Hypergraph; /** * Labels each node in the graph according to the BFS distance from the start node(s). If nodes are unreachable, then * they are assigned a distance of -1. * All nodes traversed at step k are marked as predecessors of their successors traversed at step k+1. *

    * Running time is: O(m) * @author Scott White */ public class BFSDistanceLabeler { private Map distanceDecorator = new HashMap(); private List mCurrentList; private Set mUnvisitedVertices; private List mVerticesInOrderVisited; private Map> mPredecessorMap; /** * Creates a new BFS labeler for the specified graph and root set * The distances are stored in the corresponding Vertex objects and are of type MutableInteger */ public BFSDistanceLabeler() { mPredecessorMap = new HashMap>(); } /** * Returns the list of vertices visited in order of traversal * @return the list of vertices */ public List getVerticesInOrderVisited() { return mVerticesInOrderVisited; } /** * Returns the set of all vertices that were not visited * @return the list of unvisited vertices */ public Set getUnvisitedVertices() { return mUnvisitedVertices; } /** * Given a vertex, returns the shortest distance from any node in the root set to v * @param g the graph in which the distances are to be measured * @param v the vertex whose distance is to be retrieved * @return the shortest distance from any node in the root set to v */ public int getDistance(Hypergraph g, V v) { if (!g.getVertices().contains(v)) { throw new IllegalArgumentException("Vertex is not contained in the graph."); } return distanceDecorator.get(v).intValue(); } /** * Returns set of predecessors of the given vertex * @param v the vertex whose predecessors are to be retrieved * @return the set of predecessors */ public Set getPredecessors(V v) { return mPredecessorMap.get(v); } protected void initialize(Hypergraph g, Set rootSet) { mVerticesInOrderVisited = new ArrayList(); mUnvisitedVertices = new HashSet(); for(V currentVertex : g.getVertices()) { mUnvisitedVertices.add(currentVertex); mPredecessorMap.put(currentVertex,new HashSet()); } mCurrentList = new ArrayList(); for(V v : rootSet) { distanceDecorator.put(v, new Integer(0)); mCurrentList.add(v); mUnvisitedVertices.remove(v); mVerticesInOrderVisited.add(v); } } private void addPredecessor(V predecessor,V sucessor) { HashSet predecessors = mPredecessorMap.get(sucessor); predecessors.add(predecessor); } /** * Computes the distances of all the node from the starting root nodes. If there is more than one root node * the minimum distance from each root node is used as the designated distance to a given node. Also keeps track * of the predecessors of each node traversed as well as the order of nodes traversed. * @param graph the graph to label * @param rootSet the set of starting vertices to traverse from */ public void labelDistances(Hypergraph graph, Set rootSet) { initialize(graph,rootSet); int distance = 1; while (true) { List newList = new ArrayList(); for(V currentVertex : mCurrentList) { if(graph.containsVertex(currentVertex)) { for(V next : graph.getSuccessors(currentVertex)) { visitNewVertex(currentVertex,next, distance, newList); } } } if (newList.size() == 0) break; mCurrentList = newList; distance++; } for(V v : mUnvisitedVertices) { distanceDecorator.put(v,new Integer(-1)); } } /** * Computes the distances of all the node from the specified root node. Also keeps track * of the predecessors of each node traversed as well as the order of nodes traversed. * @param graph the graph to label * @param root the single starting vertex to traverse from */ public void labelDistances(Hypergraph graph, V root) { labelDistances(graph, Collections.singleton(root)); } private void visitNewVertex(V predecessor, V neighbor, int distance, List newList) { if (mUnvisitedVertices.contains(neighbor)) { distanceDecorator.put(neighbor, new Integer(distance)); newList.add(neighbor); mVerticesInOrderVisited.add(neighbor); mUnvisitedVertices.remove(neighbor); } int predecessorDistance = distanceDecorator.get(predecessor).intValue(); int successorDistance = distanceDecorator.get(neighbor).intValue(); if (predecessorDistance < successorDistance) { addPredecessor(predecessor,neighbor); } } /** * Must be called after {@code labelDistances} in order to contain valid data. * @return a map from vertices to minimum distances from the original source(s) */ public Map getDistanceDecorator() { return distanceDecorator; } } DijkstraDistance.java000066400000000000000000000520101276402340000351520ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/* * Created on Jul 9, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.shortestpath; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import com.google.common.base.Function; import com.google.common.base.Functions; import edu.uci.ics.jung.algorithms.util.BasicMapEntry; import edu.uci.ics.jung.algorithms.util.MapBinaryHeap; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.Hypergraph; /** *

    Calculates distances in a specified graph, using * Dijkstra's single-source-shortest-path algorithm. All edge weights * in the graph must be nonnegative; if any edge with negative weight is * found in the course of calculating distances, an * IllegalArgumentException will be thrown. * (Note: this exception will only be thrown when such an edge would be * used to update a given tentative distance; * the algorithm does not check for negative-weight edges "up front".) * *

    Distances and partial results are optionally cached (by this instance) * for later reference. Thus, if the 10 closest vertices to a specified source * vertex are known, calculating the 20 closest vertices does not require * starting Dijkstra's algorithm over from scratch. * *

    Distances are stored as double-precision values. * If a vertex is not reachable from the specified source vertex, no * distance is stored. This is new behavior with version 1.4; * the previous behavior was to store a value of * Double.POSITIVE_INFINITY. This change gives the algorithm * an approximate complexity of O(kD log k), where k is either the number of * requested targets or the number of reachable vertices (whichever is smaller), * and D is the average degree of a vertex. * *

    The elements in the maps returned by getDistanceMap * are ordered (that is, returned * by the iterator) by nondecreasing distance from source. * *

    Users are cautioned that distances calculated should be assumed to * be invalidated by changes to the graph, and should invoke reset() * when appropriate so that the distances can be recalculated. * * @author Joshua O'Madadhain * @author Tom Nelson converted to jung2 */ public class DijkstraDistance implements Distance { protected Hypergraph g; protected Function nev; protected Map sourceMap; // a map of source vertices to an instance of SourceData protected boolean cached; protected double max_distance; protected int max_targets; /** *

    Creates an instance of DijkstraShortestPath for * the specified graph and the specified method of extracting weights * from edges, which caches results locally if and only if * cached is true. * * @param g the graph on which distances will be calculated * @param nev the class responsible for returning weights for edges * @param cached specifies whether the results are to be cached */ public DijkstraDistance(Hypergraph g, Function nev, boolean cached) { this.g = g; this.nev = nev; this.sourceMap = new HashMap(); this.cached = cached; this.max_distance = Double.POSITIVE_INFINITY; this.max_targets = Integer.MAX_VALUE; } /** *

    Creates an instance of DijkstraShortestPath for * the specified graph and the specified method of extracting weights * from edges, which caches results locally. * * @param g the graph on which distances will be calculated * @param nev the class responsible for returning weights for edges */ public DijkstraDistance(Hypergraph g, Function nev) { this(g, nev, true); } /** *

    Creates an instance of DijkstraShortestPath for * the specified unweighted graph (that is, all weights 1) which * caches results locally. * * @param g the graph on which distances will be calculated */ public DijkstraDistance(Graph g) { this(g, Functions.constant(1), true); } /** *

    Creates an instance of DijkstraShortestPath for * the specified unweighted graph (that is, all weights 1) which * caches results locally. * * @param g the graph on which distances will be calculated * @param cached specifies whether the results are to be cached */ public DijkstraDistance(Graph g, boolean cached) { this(g, Functions.constant(1), cached); } /** * Implements Dijkstra's single-source shortest-path algorithm for * weighted graphs. Uses a MapBinaryHeap as the priority queue, * which gives this algorithm a time complexity of O(m lg n) (m = # of edges, n = * # of vertices). * This algorithm will terminate when any of the following have occurred (in order * of priority): *

      *
    • the distance to the specified target (if any) has been found *
    • no more vertices are reachable *
    • the specified # of distances have been found, or the maximum distance * desired has been exceeded *
    • all distances have been found *
    * * @param source the vertex from which distances are to be measured * @param numDests the number of distances to measure * @param targets the set of vertices to which distances are to be measured * @return a mapping from vertex to the shortest distance from the source to each target */ protected LinkedHashMap singleSourceShortestPath(V source, Collection targets, int numDests) { SourceData sd = getSourceData(source); Set to_get = new HashSet(); if (targets != null) { to_get.addAll(targets); Set existing_dists = sd.distances.keySet(); for(V o : targets) { if (existing_dists.contains(o)) to_get.remove(o); } } // if we've exceeded the max distance or max # of distances we're willing to calculate, or // if we already have all the distances we need, // terminate if (sd.reached_max || (targets != null && to_get.isEmpty()) || (sd.distances.size() >= numDests)) { return sd.distances; } while (!sd.unknownVertices.isEmpty() && (sd.distances.size() < numDests || !to_get.isEmpty())) { Map.Entry p = sd.getNextVertex(); V v = p.getKey(); double v_dist = p.getValue().doubleValue(); to_get.remove(v); if (v_dist > this.max_distance) { // we're done; put this vertex back in so that we're not including // a distance beyond what we specified sd.restoreVertex(v, v_dist); sd.reached_max = true; break; } sd.dist_reached = v_dist; if (sd.distances.size() >= this.max_targets) { sd.reached_max = true; break; } for (E e : getEdgesToCheck(v) ) { for (V w : g.getIncidentVertices(e)) { if (!sd.distances.containsKey(w)) { double edge_weight = nev.apply(e).doubleValue(); if (edge_weight < 0) throw new IllegalArgumentException("Edges weights must be non-negative"); double new_dist = v_dist + edge_weight; if (!sd.estimatedDistances.containsKey(w)) { sd.createRecord(w, e, new_dist); } else { double w_dist = ((Double)sd.estimatedDistances.get(w)).doubleValue(); if (new_dist < w_dist) // update tentative distance & path for w sd.update(w, e, new_dist); } } } } } return sd.distances; } protected SourceData getSourceData(V source) { SourceData sd = sourceMap.get(source); if (sd == null) sd = new SourceData(source); return sd; } /** * Returns the set of edges incident to v that should be tested. * By default, this is the set of outgoing edges for instances of Graph, * the set of incident edges for instances of Hypergraph, * and is otherwise undefined. * @param v the vertex whose edges are to be checked * @return the set of edges incident to {@code v} that should be tested */ protected Collection getEdgesToCheck(V v) { if (g instanceof Graph) return ((Graph)g).getOutEdges(v); else return g.getIncidentEdges(v); } /** * Returns the length of a shortest path from the source to the target vertex, * or null if the target is not reachable from the source. * If either vertex is not in the graph for which this instance * was created, throws IllegalArgumentException. * * @param source the vertex from which the distance to {@code target} is to be measured * @param target the vertex to which the distance from {@code source} is to be measured * @return the distance between {@code source} and {@code target} * * @see #getDistanceMap(Object) * @see #getDistanceMap(Object,int) */ public Number getDistance(V source, V target) { if (g.containsVertex(target) == false) throw new IllegalArgumentException("Specified target vertex " + target + " is not part of graph " + g); if (g.containsVertex(source) == false) throw new IllegalArgumentException("Specified source vertex " + source + " is not part of graph " + g); Set targets = new HashSet(); targets.add(target); Map distanceMap = getDistanceMap(source, targets); return distanceMap.get(target); } /** * Returns a {@code Map} from each element {@code t} of {@code targets} to the * shortest-path distance from {@code source} to {@code t}. * @param source the vertex from which the distance to each target is to be measured * @param targets the vertices to which the distance from the source is to be measured * @return {@code Map} from each element of {@code targets} to its distance from {@code source} */ public Map getDistanceMap(V source, Collection targets) { if (g.containsVertex(source) == false) throw new IllegalArgumentException("Specified source vertex " + source + " is not part of graph " + g); if (targets.size() > max_targets) throw new IllegalArgumentException("size of target set exceeds maximum " + "number of targets allowed: " + this.max_targets); Map distanceMap = singleSourceShortestPath(source, targets, Math.min(g.getVertexCount(), max_targets)); if (!cached) reset(source); return distanceMap; } /** *

    Returns a LinkedHashMap which maps each vertex * in the graph (including the source vertex) * to its distance from the source vertex. * The map's iterator will return the elements in order of * increasing distance from source. * *

    The size of the map returned will be the number of * vertices reachable from source. * * @see #getDistanceMap(Object,int) * @see #getDistance(Object,Object) * @param source the vertex from which distances are measured * @return a mapping from each vertex in the graph to its distance from {@code source} */ public Map getDistanceMap(V source) { return getDistanceMap(source, Math.min(g.getVertexCount(), max_targets)); } /** *

    Returns a LinkedHashMap which maps each of the closest * numDist vertices to the source vertex * in the graph (including the source vertex) * to its distance from the source vertex. Throws * an IllegalArgumentException if source * is not in this instance's graph, or if numDests is * either less than 1 or greater than the number of vertices in the * graph. * *

    The size of the map returned will be the smaller of * numDests and the number of vertices reachable from * source. * * @see #getDistanceMap(Object) * @see #getDistance(Object,Object) * @param source the vertex from which distances are measured * @param numDests the number of vertices for which to measure distances * @return a mapping from the {@code numDests} vertices in the graph * closest to {@code source}, to their distance from {@code source} * */ public LinkedHashMap getDistanceMap(V source, int numDests) { if(g.getVertices().contains(source) == false) { throw new IllegalArgumentException("Specified source vertex " + source + " is not part of graph " + g); } if (numDests < 1 || numDests > g.getVertexCount()) throw new IllegalArgumentException("numDests must be >= 1 " + "and <= g.numVertices()"); if (numDests > max_targets) throw new IllegalArgumentException("numDests must be <= the maximum " + "number of targets allowed: " + this.max_targets); LinkedHashMap distanceMap = singleSourceShortestPath(source, null, numDests); if (!cached) reset(source); return distanceMap; } /** * Allows the user to specify the maximum distance that this instance will calculate. * Any vertices past this distance will effectively be unreachable from the source, in * the sense that the algorithm will not calculate the distance to any vertices which * are farther away than this distance. A negative value for max_dist * will ensure that no further distances are calculated. * *

    This can be useful for limiting the amount of time and space used by this algorithm * if the graph is very large. * *

    Note: if this instance has already calculated distances greater than max_dist, * and the results are cached, those results will still be valid and available; this limit * applies only to subsequent distance calculations. * * @param max_dist the maximum distance that this instance will calculate * * @see #setMaxTargets(int) */ public void setMaxDistance(double max_dist) { this.max_distance = max_dist; for (V v : sourceMap.keySet()) { SourceData sd = sourceMap.get(v); sd.reached_max = (this.max_distance <= sd.dist_reached) || (sd.distances.size() >= max_targets); } } /** * Allows the user to specify the maximum number of target vertices per source vertex * for which this instance will calculate distances. Once this threshold is reached, * any further vertices will effectively be unreachable from the source, in * the sense that the algorithm will not calculate the distance to any more vertices. * A negative value for max_targets will ensure that no further distances are calculated. * *

    This can be useful for limiting the amount of time and space used by this algorithm * if the graph is very large. * *

    Note: if this instance has already calculated distances to a greater number of * targets than max_targets, and the results are cached, those results * will still be valid and available; this limit applies only to subsequent distance * calculations. * * @param max_targets the maximum number of targets for which this instance will calculate * distances * * @see #setMaxDistance(double) */ public void setMaxTargets(int max_targets) { this.max_targets = max_targets; for (V v : sourceMap.keySet()) { SourceData sd = sourceMap.get(v); sd.reached_max = (this.max_distance <= sd.dist_reached) || (sd.distances.size() >= max_targets); } } /** * Clears all stored distances for this instance. * Should be called whenever the graph is modified (edge weights * changed or edges added/removed). If the user knows that * some currently calculated distances are unaffected by a * change, reset(V) may be appropriate instead. * * @see #reset(Object) */ public void reset() { sourceMap = new HashMap(); } /** * Specifies whether or not this instance of DijkstraShortestPath * should cache its results (final and partial) for future reference. * * @param enable true if the results are to be cached, and * false otherwise */ public void enableCaching(boolean enable) { this.cached = enable; } /** * Clears all stored distances for the specified source vertex * source. Should be called whenever the stored distances * from this vertex are invalidated by changes to the graph. * * @param source the vertex for which stored distances should be cleared * * @see #reset() */ public void reset(V source) { sourceMap.put(source, null); } /** * Compares according to distances, so that the BinaryHeap knows how to * order the tree. */ protected static class VertexComparator implements Comparator { private Map distances; protected VertexComparator(Map distances) { this.distances = distances; } public int compare(V o1, V o2) { return ((Double) distances.get(o1)).compareTo((Double) distances.get(o2)); } } /** * For a given source vertex, holds the estimated and final distances, * tentative and final assignments of incoming edges on the shortest path from * the source vertex, and a priority queue (ordered by estimated distance) * of the vertices for which distances are unknown. * * @author Joshua O'Madadhain */ protected class SourceData { protected LinkedHashMap distances; protected Map estimatedDistances; protected MapBinaryHeap unknownVertices; protected boolean reached_max = false; protected double dist_reached = 0; protected SourceData(V source) { distances = new LinkedHashMap(); estimatedDistances = new HashMap(); unknownVertices = new MapBinaryHeap(new VertexComparator(estimatedDistances)); sourceMap.put(source, this); // initialize priority queue estimatedDistances.put(source, new Double(0)); // distance from source to itself is 0 unknownVertices.add(source); reached_max = false; dist_reached = 0; } protected Map.Entry getNextVertex() { V v = unknownVertices.remove(); Double dist = (Double)estimatedDistances.remove(v); distances.put(v, dist); return new BasicMapEntry(v, dist); } protected void update(V dest, E tentative_edge, double new_dist) { estimatedDistances.put(dest, new_dist); unknownVertices.update(dest); } protected void createRecord(V w, E e, double new_dist) { estimatedDistances.put(w, new_dist); unknownVertices.add(w); } protected void restoreVertex(V v, double dist) { estimatedDistances.put(v, dist); unknownVertices.add(v); distances.remove(v); } } } DijkstraShortestPath.java000066400000000000000000000250101276402340000360500ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.shortestpath; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Graph; /** *

    Calculates distances and shortest paths using Dijkstra's * single-source-shortest-path algorithm. This is a lightweight * extension of DijkstraDistance that also stores * path information, so that the shortest paths can be reconstructed. * *

    The elements in the maps returned by * getIncomingEdgeMap are ordered (that is, returned * by the iterator) by nondecreasing distance from source. * * @author Joshua O'Madadhain * @author Tom Nelson converted to jung2 * @see DijkstraDistance */ public class DijkstraShortestPath extends DijkstraDistance implements ShortestPath { /** *

    Creates an instance of DijkstraShortestPath for * the specified graph and the specified method of extracting weights * from edges, which caches results locally if and only if * cached is true. * * @param g the graph on which distances will be calculated * @param nev the class responsible for returning weights for edges * @param cached specifies whether the results are to be cached */ public DijkstraShortestPath(Graph g, Function nev, boolean cached) { super(g, nev, cached); } /** *

    Creates an instance of DijkstraShortestPath for * the specified graph and the specified method of extracting weights * from edges, which caches results locally. * * @param g the graph on which distances will be calculated * @param nev the class responsible for returning weights for edges */ public DijkstraShortestPath(Graph g, Function nev) { super(g, nev); } /** *

    Creates an instance of DijkstraShortestPath for * the specified unweighted graph (that is, all weights 1) which * caches results locally. * * @param g the graph on which distances will be calculated */ public DijkstraShortestPath(Graph g) { super(g); } /** *

    Creates an instance of DijkstraShortestPath for * the specified unweighted graph (that is, all weights 1) which * caches results locally. * * @param g the graph on which distances will be calculated * @param cached specifies whether the results are to be cached */ public DijkstraShortestPath(Graph g, boolean cached) { super(g, cached); } @Override protected SourceData getSourceData(V source) { SourceData sd = sourceMap.get(source); if (sd == null) sd = new SourcePathData(source); return sd; } /** *

    Returns the last edge on a shortest path from source * to target, or null if target is not * reachable from source. * *

    If either vertex is not in the graph for which this instance * was created, throws IllegalArgumentException. * * @param source the vertex where the shortest path starts * @param target the vertex where the shortest path ends * @return the last edge on a shortest path from {@code source} to {@code target} * or null if {@code target} is not reachable from {@code source} */ public E getIncomingEdge(V source, V target) { if (!g.containsVertex(source)) throw new IllegalArgumentException("Specified source vertex " + source + " is not part of graph " + g); if (!g.containsVertex(target)) throw new IllegalArgumentException("Specified target vertex " + target + " is not part of graph " + g); Set targets = new HashSet(); targets.add(target); singleSourceShortestPath(source, targets, g.getVertexCount()); @SuppressWarnings("unchecked") Map incomingEdgeMap = ((SourcePathData)sourceMap.get(source)).incomingEdges; E incomingEdge = incomingEdgeMap.get(target); if (!cached) reset(source); return incomingEdge; } /** *

    Returns a LinkedHashMap which maps each vertex * in the graph (including the source vertex) * to the last edge on the shortest path from the * source vertex. * The map's iterator will return the elements in order of * increasing distance from source. * * @see DijkstraDistance#getDistanceMap(Object,int) * @see DijkstraDistance#getDistance(Object,Object) * @param source the vertex from which distances are measured */ public Map getIncomingEdgeMap(V source) { return getIncomingEdgeMap(source, g.getVertexCount()); } /** * Returns a List of the edges on the shortest path from * source to target, in order of their * occurrence on this path. * If either vertex is not in the graph for which this instance * was created, throws IllegalArgumentException. * * @param source the starting vertex for the path to generate * @param target the ending vertex for the path to generate * @return the edges on the shortest path from {@code source} to {@code target}, * in order of their occurrence */ public List getPath(V source, V target) { if(!g.containsVertex(source)) throw new IllegalArgumentException("Specified source vertex " + source + " is not part of graph " + g); if(!g.containsVertex(target)) throw new IllegalArgumentException("Specified target vertex " + target + " is not part of graph " + g); LinkedList path = new LinkedList(); // collect path data; must use internal method rather than // calling getIncomingEdge() because getIncomingEdge() may // wipe out results if results are not cached Set targets = new HashSet(); targets.add(target); singleSourceShortestPath(source, targets, g.getVertexCount()); @SuppressWarnings("unchecked") Map incomingEdges = ((SourcePathData)sourceMap.get(source)).incomingEdges; if (incomingEdges.isEmpty() || incomingEdges.get(target) == null) return path; V current = target; while (!current.equals(source)) { E incoming = incomingEdges.get(current); path.addFirst(incoming); current = ((Graph)g).getOpposite(current, incoming); } return path; } /** *

    Returns a LinkedHashMap which maps each of the closest * numDests vertices to the source vertex * in the graph (including the source vertex) * to the incoming edge along the path from that vertex. Throws * an IllegalArgumentException if source * is not in this instance's graph, or if numDests is * either less than 1 or greater than the number of vertices in the * graph. * * @see #getIncomingEdgeMap(Object) * @see #getPath(Object,Object) * @param source the vertex from which distances are measured * @param numDests the number of vertices for which to measure distances * @return a map from each of the closest {@code numDests} vertices * to the last edge on the shortest path to that vertex starting from {@code source} */ public LinkedHashMap getIncomingEdgeMap(V source, int numDests) { if (g.getVertices().contains(source) == false) throw new IllegalArgumentException("Specified source vertex " + source + " is not part of graph " + g); if (numDests < 1 || numDests > g.getVertexCount()) throw new IllegalArgumentException("numDests must be >= 1 " + "and <= g.numVertices()"); singleSourceShortestPath(source, null, numDests); @SuppressWarnings("unchecked") LinkedHashMap incomingEdgeMap = ((SourcePathData)sourceMap.get(source)).incomingEdges; if (!cached) reset(source); return incomingEdgeMap; } /** * For a given source vertex, holds the estimated and final distances, * tentative and final assignments of incoming edges on the shortest path from * the source vertex, and a priority queue (ordered by estimaed distance) * of the vertices for which distances are unknown. * * @author Joshua O'Madadhain */ protected class SourcePathData extends SourceData { protected Map tentativeIncomingEdges; protected LinkedHashMap incomingEdges; protected SourcePathData(V source) { super(source); incomingEdges = new LinkedHashMap(); tentativeIncomingEdges = new HashMap(); } @Override public void update(V dest, E tentative_edge, double new_dist) { super.update(dest, tentative_edge, new_dist); tentativeIncomingEdges.put(dest, tentative_edge); } @Override public Map.Entry getNextVertex() { Map.Entry p = super.getNextVertex(); V v = p.getKey(); E incoming = tentativeIncomingEdges.remove(v); incomingEdges.put(v, incoming); return p; } @Override public void restoreVertex(V v, double dist) { super.restoreVertex(v, dist); E incoming = incomingEdges.get(v); tentativeIncomingEdges.put(v, incoming); } @Override public void createRecord(V w, E e, double new_dist) { super.createRecord(w, e, new_dist); tentativeIncomingEdges.put(w, e); } } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/Distance.java000066400000000000000000000026511276402340000335430ustar00rootroot00000000000000/* * Created on Apr 2, 2004 * * Copyright (c) 2004, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.shortestpath; import java.util.Map; /** * An interface for classes which calculate the distance between * one vertex and another. * * @author Joshua O'Madadhain */ public interface Distance { /** * Returns the distance from the source vertex to the * target vertex. If target is not reachable from * source, returns null. * * @param source the vertex from which distance is to be measured * @param target the vertex to which distance is to be measured * @return the distance from {@code source} to {@code target} */ Number getDistance(V source, V target); /** * Returns a Map which maps each vertex in the graph (including * the source vertex) to its distance (represented as a Number) * from source. If any vertex is not reachable from * source, no distance is stored for that vertex. * * @param source the vertex from which distances are to be measured * @return a {@code Map} of the distances from {@code source} to other vertices in the graph */ Map getDistanceMap(V source); } DistanceStatistics.java000066400000000000000000000150751276402340000355430ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.shortestpath; import java.util.Collection; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.scoring.ClosenessCentrality; import edu.uci.ics.jung.algorithms.scoring.util.VertexScoreTransformer; import edu.uci.ics.jung.graph.Hypergraph; /** * Statistics relating to vertex-vertex distances in a graph. * *

    Formerly known as GraphStatistics in JUNG 1.x. * * @author Scott White * @author Joshua O'Madadhain */ public class DistanceStatistics { /** * For each vertex v in graph, * calculates the average shortest path length from v * to all other vertices in graph using the metric * specified by d, and returns the results in a * Map from vertices to Double values. * If there exists an ordered pair <u,v> * for which d.getDistance(u,v) returns null, * then the average distance value for u will be stored * as Double.POSITIVE_INFINITY). * *

    Does not include self-distances (path lengths from v * to v). * *

    To calculate the average distances, ignoring edge weights if any: *

         * Map distances = DistanceStatistics.averageDistances(g, new UnweightedShortestPath(g));
         * 
    * To calculate the average distances respecting edge weights: *
         * DijkstraShortestPath dsp = new DijkstraShortestPath(g, nev);
         * Map distances = DistanceStatistics.averageDistances(g, dsp);
         * 
    * where nev is an instance of Transformer that * is used to fetch the weight for each edge. * * @see edu.uci.ics.jung.algorithms.shortestpath.UnweightedShortestPath * @see edu.uci.ics.jung.algorithms.shortestpath.DijkstraDistance * * @param graph the graph for which distances are to be calculated * @param d the distance metric to use for the calculation * @param the vertex type * @param the edge type * @return a map from each vertex to the mean distance to each other (reachable) vertex */ public static Function averageDistances(Hypergraph graph, Distance d) { final ClosenessCentrality cc = new ClosenessCentrality(graph, d); return new VertexScoreTransformer(cc); } /** * For each vertex v in g, * calculates the average shortest path length from v * to all other vertices in g, ignoring edge weights. * @see #diameter(Hypergraph) * @see edu.uci.ics.jung.algorithms.scoring.ClosenessCentrality * * @param g the graph for which distances are to be calculated * @param the vertex type * @param the edge type * @return a map from each vertex to the mean distance to each other (reachable) vertex */ public static Function averageDistances(Hypergraph g) { final ClosenessCentrality cc = new ClosenessCentrality(g, new UnweightedShortestPath(g)); return new VertexScoreTransformer(cc); } /** * Returns the diameter of g using the metric * specified by d. The diameter is defined to be * the maximum, over all pairs of vertices u,v, * of the length of the shortest path from u to * v. If the graph is disconnected (that is, not * all pairs of vertices are reachable from one another), the * value returned will depend on use_max: * if use_max == true, the value returned * will be the the maximum shortest path length over all pairs of connected * vertices; otherwise it will be Double.POSITIVE_INFINITY. * * @param g the graph for which distances are to be calculated * @param d the distance metric to use for the calculation * @param use_max if {@code true}, return the maximum shortest path length for all graphs; * otherwise, return {@code Double.POSITIVE_INFINITY} for disconnected graphs * @param the vertex type * @param the edge type * @return the longest distance from any vertex to any other */ public static double diameter(Hypergraph g, Distance d, boolean use_max) { double diameter = 0; Collection vertices = g.getVertices(); for(V v : vertices) { for(V w : vertices) { if (v.equals(w) == false) // don't include self-distances { Number dist = d.getDistance(v, w); if (dist == null) { if (!use_max) return Double.POSITIVE_INFINITY; } else diameter = Math.max(diameter, dist.doubleValue()); } } } return diameter; } /** * Returns the diameter of g using the metric * specified by d. The diameter is defined to be * the maximum, over all pairs of vertices u,v, * of the length of the shortest path from u to * v, or Double.POSITIVE_INFINITY * if any of these distances do not exist. * @see #diameter(Hypergraph, Distance, boolean) * * @param g the graph for which distances are to be calculated * @param d the distance metric to use for the calculation * @param the vertex type * @param the edge type * @return the longest distance from any vertex to any other */ public static double diameter(Hypergraph g, Distance d) { return diameter(g, d, false); } /** * Returns the diameter of g, ignoring edge weights. * @see #diameter(Hypergraph, Distance, boolean) * * @param g the graph for which distances are to be calculated * @param the vertex type * @param the edge type * @return the longest distance from any vertex to any other */ public static double diameter(Hypergraph g) { return diameter(g, new UnweightedShortestPath(g)); } } MinimumSpanningForest.java000066400000000000000000000130011276402340000362150ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpathpackage edu.uci.ics.jung.algorithms.shortestpath; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.Forest; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * For the input Graph, creates a MinimumSpanningTree * using a variation of Prim's algorithm. * * @author Tom Nelson - tomnelson@dev.java.net * * @param the vertex type * @param the edge type */ public class MinimumSpanningForest { protected Graph graph; protected Forest forest; protected Function weights; /** * Creates a Forest from the supplied Graph and supplied Supplier, which * is used to create a new, empty Forest. If non-null, the supplied root * will be used as the root of the tree/forest. If the supplied root is * null, or not present in the Graph, then an arbitrary Graph vertex * will be selected as the root. * If the Minimum Spanning Tree does not include all vertices of the * Graph, then a leftover vertex is selected as a root, and another * tree is created. * @param graph the input graph * @param Supplier the Supplier to use to create the new forest * @param root the vertex of the graph to be used as the root of the forest * @param weights edge weights */ public MinimumSpanningForest(Graph graph, Supplier> Supplier, V root, Map weights) { this(graph, Supplier.get(), root, weights); } /** * Creates a minimum spanning forest from the supplied graph, populating the * supplied Forest, which must be empty. * If the supplied root is null, or not present in the Graph, * then an arbitrary Graph vertex will be selected as the root. * If the Minimum Spanning Tree does not include all vertices of the * Graph, then a leftover vertex is selected as a root, and another * tree is created * @param graph the Graph to find MST in * @param forest the Forest to populate. Must be empty * @param root first Tree root, may be null * @param weights edge weights, may be null */ public MinimumSpanningForest(Graph graph, Forest forest, V root, Map weights) { if(forest.getVertexCount() != 0) { throw new IllegalArgumentException("Supplied Forest must be empty"); } this.graph = graph; this.forest = forest; if(weights != null) { this.weights = Functions.forMap(weights); } Set unfinishedEdges = new HashSet(graph.getEdges()); if(graph.getVertices().contains(root)) { this.forest.addVertex(root); } updateForest(forest.getVertices(), unfinishedEdges); } /** * Creates a minimum spanning forest from the supplied graph, populating the * supplied Forest, which must be empty. * If the supplied root is null, or not present in the Graph, * then an arbitrary Graph vertex will be selected as the root. * If the Minimum Spanning Tree does not include all vertices of the * Graph, then a leftover vertex is selected as a root, and another * tree is created * @param graph the Graph to find MST in * @param forest the Forest to populate. Must be empty * @param root first Tree root, may be null */ @SuppressWarnings("unchecked") public MinimumSpanningForest(Graph graph, Forest forest, V root) { if(forest.getVertexCount() != 0) { throw new IllegalArgumentException("Supplied Forest must be empty"); } this.graph = graph; this.forest = forest; this.weights = (Function) Functions.constant(1.0); Set unfinishedEdges = new HashSet(graph.getEdges()); if(graph.getVertices().contains(root)) { this.forest.addVertex(root); } updateForest(forest.getVertices(), unfinishedEdges); } /** * @return the generated forest */ public Forest getForest() { return forest; } protected void updateForest(Collection tv, Collection unfinishedEdges) { double minCost = Double.MAX_VALUE; E nextEdge = null; V nextVertex = null; V currentVertex = null; for(E e : unfinishedEdges) { if(forest.getEdges().contains(e)) continue; // find the lowest cost edge, get its opposite endpoint, // and then update forest from its Successors Pair endpoints = graph.getEndpoints(e); V first = endpoints.getFirst(); V second = endpoints.getSecond(); if(tv.contains(first) == true && tv.contains(second) == false) { if(weights.apply(e) < minCost) { minCost = weights.apply(e); nextEdge = e; currentVertex = first; nextVertex = second; } } if(graph.getEdgeType(e) == EdgeType.UNDIRECTED && tv.contains(second) == true && tv.contains(first) == false) { if(weights.apply(e) < minCost) { minCost = weights.apply(e); nextEdge = e; currentVertex = second; nextVertex = first; } } } if(nextVertex != null && nextEdge != null) { unfinishedEdges.remove(nextEdge); forest.addEdge(nextEdge, currentVertex, nextVertex); updateForest(forest.getVertices(), unfinishedEdges); } Collection leftovers = new HashSet(graph.getVertices()); leftovers.removeAll(forest.getVertices()); if(leftovers.size() > 0) { V anotherRoot = leftovers.iterator().next(); forest.addVertex(anotherRoot); updateForest(forest.getVertices(), unfinishedEdges); } } } MinimumSpanningForest2.java000066400000000000000000000070011276402340000363020ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpathpackage edu.uci.ics.jung.algorithms.shortestpath; import java.util.Collection; import java.util.Set; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.cluster.WeakComponentClusterer; import edu.uci.ics.jung.algorithms.filters.FilterUtils; import edu.uci.ics.jung.graph.Forest; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.Tree; import edu.uci.ics.jung.graph.util.TreeUtils; /** * For the input Graph, creates a MinimumSpanningTree * using a variation of Prim's algorithm. * * @author Tom Nelson - tomnelson@dev.java.net * * @param the vertex type * @param the edge type */ @SuppressWarnings("unchecked") public class MinimumSpanningForest2 { protected Graph graph; protected Forest forest; protected Function weights = (Function)Functions.constant(1.0); /** * Create a Forest from the supplied Graph and supplied Supplier, which * is used to create a new, empty Forest. If non-null, the supplied root * will be used as the root of the tree/forest. If the supplied root is * null, or not present in the Graph, then an arbitary Graph vertex * will be selected as the root. * If the Minimum Spanning Tree does not include all vertices of the * Graph, then a leftover vertex is selected as a root, and another * tree is created * @param graph the graph for which the minimum spanning forest will be generated * @param supplier a factory for the type of forest to build * @param treeFactory a factory for the type of tree to build * @param weights edge weights; may be null */ public MinimumSpanningForest2(Graph graph, Supplier> supplier, Supplier> treeFactory, Function weights) { this(graph, supplier.get(), treeFactory, weights); } /** * Create a forest from the supplied graph, populating the * supplied Forest, which must be empty. * If the supplied root is null, or not present in the Graph, * then an arbitary Graph vertex will be selected as the root. * If the Minimum Spanning Tree does not include all vertices of the * Graph, then a leftover vertex is selected as a root, and another * tree is created * @param graph the Graph to find MST in * @param forest the Forest to populate. Must be empty * @param treeFactory a factory for the type of tree to build * @param weights edge weights, may be null */ public MinimumSpanningForest2(Graph graph, Forest forest, Supplier> treeFactory, Function weights) { if(forest.getVertexCount() != 0) { throw new IllegalArgumentException("Supplied Forest must be empty"); } this.graph = graph; this.forest = forest; if(weights != null) { this.weights = weights; } WeakComponentClusterer wcc = new WeakComponentClusterer(); Set> component_vertices = wcc.apply(graph); Collection> components = FilterUtils.createAllInducedSubgraphs(component_vertices, graph); for(Graph component : components) { PrimMinimumSpanningTree mst = new PrimMinimumSpanningTree(treeFactory, this.weights); Graph subTree = mst.apply(component); if(subTree instanceof Tree) { TreeUtils.addSubTree(forest, (Tree)subTree, null, null); } } } /** * @return the generated forest */ public Forest getForest() { return forest; } } PrimMinimumSpanningTree.java000066400000000000000000000067271276402340000365230ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpathpackage edu.uci.ics.jung.algorithms.shortestpath; import java.util.Collection; import java.util.HashSet; import java.util.Set; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Pair; /** * For the input Graph, creates a MinimumSpanningTree * using a variation of Prim's algorithm. * * @author Tom Nelson - tomnelson@dev.java.net * * @param the vertex type * @param the edge type */ public class PrimMinimumSpanningTree implements Function,Graph> { protected Supplier> treeFactory; protected Function weights; /** * Creates an instance which generates a minimum spanning tree assuming constant edge weights. * @param supplier used to create the tree instances */ public PrimMinimumSpanningTree(Supplier> supplier) { this(supplier, Functions.constant(1.0)); } /** * Creates an instance which generates a minimum spanning tree using the input edge weights. * @param supplier used to create the tree instances * @param weights the edge weights to use for defining the MST */ public PrimMinimumSpanningTree(Supplier> supplier, Function weights) { this.treeFactory = supplier; if(weights != null) { this.weights = weights; } } /** * @param graph the Graph to find MST in */ public Graph apply(Graph graph) { Set unfinishedEdges = new HashSet(graph.getEdges()); Graph tree = treeFactory.get(); V root = findRoot(graph); if(graph.getVertices().contains(root)) { tree.addVertex(root); } else if(graph.getVertexCount() > 0) { // pick an arbitrary vertex to make root tree.addVertex(graph.getVertices().iterator().next()); } updateTree(tree, graph, unfinishedEdges); return tree; } protected V findRoot(Graph graph) { for(V v : graph.getVertices()) { if(graph.getInEdges(v).size() == 0) { return v; } } // if there is no obvious root, pick any vertex if(graph.getVertexCount() > 0) { return graph.getVertices().iterator().next(); } // this graph has no vertices return null; } protected void updateTree(Graph tree, Graph graph, Collection unfinishedEdges) { Collection tv = tree.getVertices(); double minCost = Double.MAX_VALUE; E nextEdge = null; V nextVertex = null; V currentVertex = null; for(E e : unfinishedEdges) { if(tree.getEdges().contains(e)) continue; // find the lowest cost edge, get its opposite endpoint, // and then update forest from its Successors Pair endpoints = graph.getEndpoints(e); V first = endpoints.getFirst(); V second = endpoints.getSecond(); if((tv.contains(first) == true && tv.contains(second) == false)) { if(weights.apply(e) < minCost) { minCost = weights.apply(e); nextEdge = e; currentVertex = first; nextVertex = second; } } else if((tv.contains(second) == true && tv.contains(first) == false)) { if(weights.apply(e) < minCost) { minCost = weights.apply(e); nextEdge = e; currentVertex = second; nextVertex = first; } } } if(nextVertex != null && nextEdge != null) { unfinishedEdges.remove(nextEdge); tree.addEdge(nextEdge, currentVertex, nextVertex); updateTree(tree, graph, unfinishedEdges); } } } ShortestPath.java000066400000000000000000000014731276402340000343630ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Feb 12, 2004 */ package edu.uci.ics.jung.algorithms.shortestpath; import java.util.Map; /** * An interface for algorithms that calculate shortest paths. */ public interface ShortestPath { /** * Returns a map from vertices to the last edge on the shortest path to that vertex * starting from {@code source}. * * @param source the starting point for the shortest paths * @return a map from vertices to the last edge on the shortest path to that vertex * starting from {@code source} */ Map getIncomingEdgeMap(V source); } ShortestPathUtils.java000066400000000000000000000036751276402340000354120ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/* * Created on Jul 10, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.shortestpath; import java.util.LinkedList; import java.util.List; import java.util.Map; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Pair; /** * Utilities relating to the shortest paths in a graph. */ public class ShortestPathUtils { /** * Returns a List of the edges on the shortest path from * source to target, in order of their * occurrence on this path. * * @param graph the graph for which the shortest path is defined * @param sp holder of the shortest path information * @param source the vertex from which the shortest path is measured * @param target the vertex to which the shortest path is measured * @param the vertex type * @param the edge type * @return the edges on the shortest path from {@code source} to {@code target}, * in the order traversed */ public static List getPath(Graph graph, ShortestPath sp, V source, V target) { LinkedList path = new LinkedList(); Map incomingEdges = sp.getIncomingEdgeMap(source); if (incomingEdges.isEmpty() || incomingEdges.get(target) == null) return path; V current = target; while (!current.equals(source)) { E incoming = incomingEdges.get(current); path.addFirst(incoming); Pair endpoints = graph.getEndpoints(incoming); if(endpoints.getFirst().equals(current)) { current = endpoints.getSecond(); } else { current = endpoints.getFirst(); } } return path; } } UnweightedShortestPath.java000066400000000000000000000110061276402340000364000ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.shortestpath; import java.util.HashMap; import java.util.Map; import edu.uci.ics.jung.graph.Hypergraph; /** * Computes the shortest path distances for graphs whose edges are not weighted (using BFS). * * @author Scott White */ public class UnweightedShortestPath implements ShortestPath, Distance { private Map> mDistanceMap; private Map> mIncomingEdgeMap; private Hypergraph mGraph; private Map distances = new HashMap(); /** * Constructs and initializes algorithm * @param g the graph */ public UnweightedShortestPath(Hypergraph g) { mDistanceMap = new HashMap>(); mIncomingEdgeMap = new HashMap>(); mGraph = g; } /** * @see edu.uci.ics.jung.algorithms.shortestpath.Distance#getDistance(Object, Object) */ public Number getDistance(V source, V target) { Map sourceSPMap = getDistanceMap(source); return sourceSPMap.get(target); } /** * @see edu.uci.ics.jung.algorithms.shortestpath.Distance#getDistanceMap(Object) */ public Map getDistanceMap(V source) { Map sourceSPMap = mDistanceMap.get(source); if (sourceSPMap == null) { computeShortestPathsFromSource(source); sourceSPMap = mDistanceMap.get(source); } return sourceSPMap; } /** * @see edu.uci.ics.jung.algorithms.shortestpath.ShortestPath#getIncomingEdgeMap(Object) */ public Map getIncomingEdgeMap(V source) { Map sourceIEMap = mIncomingEdgeMap.get(source); if (sourceIEMap == null) { computeShortestPathsFromSource(source); sourceIEMap = mIncomingEdgeMap.get(source); } return sourceIEMap; } /** * Computes the shortest path distances from a given node to all other nodes. * @param source the source node */ private void computeShortestPathsFromSource(V source) { BFSDistanceLabeler labeler = new BFSDistanceLabeler(); labeler.labelDistances(mGraph, source); distances = labeler.getDistanceDecorator(); Map currentSourceSPMap = new HashMap(); Map currentSourceEdgeMap = new HashMap(); for(V vertex : mGraph.getVertices()) { Number distanceVal = distances.get(vertex); // BFSDistanceLabeler uses -1 to indicate unreachable vertices; // don't bother to store unreachable vertices if (distanceVal != null && distanceVal.intValue() >= 0) { currentSourceSPMap.put(vertex, distanceVal); int minDistance = distanceVal.intValue(); for(E incomingEdge : mGraph.getInEdges(vertex)) { for (V neighbor : mGraph.getIncidentVertices(incomingEdge)) { if (neighbor.equals(vertex)) continue; Number predDistanceVal = distances.get(neighbor); int pred_distance = predDistanceVal.intValue(); if (pred_distance < minDistance && pred_distance >= 0) { minDistance = predDistanceVal.intValue(); currentSourceEdgeMap.put(vertex, incomingEdge); } } } } } mDistanceMap.put(source, currentSourceSPMap); mIncomingEdgeMap.put(source, currentSourceEdgeMap); } /** * Clears all stored distances for this instance. * Should be called whenever the graph is modified (edge weights * changed or edges added/removed). If the user knows that * some currently calculated distances are unaffected by a * change, reset(V) may be appropriate instead. * * @see #reset(Object) */ public void reset() { mDistanceMap.clear(); mIncomingEdgeMap.clear(); } /** * Clears all stored distances for the specified source vertex * source. Should be called whenever the stored distances * from this vertex are invalidated by changes to the graph. * * @see #reset() * * @param v the vertex for which distances should be cleared */ public void reset(V v) { mDistanceMap.remove(v); mIncomingEdgeMap.remove(v); } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/package.html000066400000000000000000000021401276402340000334200ustar00rootroot00000000000000 Provides interfaces and classes for calculating (geodesic) distances and shortest paths. Currently includes:
    • DijkstraDistance: finds the distances from a specified source vertex to other vertices in a weighted graph with no negative cycles
    • DijkstraShortestPath: extends DijkstraDistance, also finds shortest paths
    • Distance: an interface for defining vertex-vertex distances
    • PrimMinimumSpanningTree: identifies the spanning tree for a graph of least total edge weight
    • ShortestPath: an interface for shortest-path algorithms
    • ShortestPathUtils: utility functions for manipulating shortest paths
    • UnweightedShortestPath: finds the distances from a specified source vertex to other vertices in an unweighted graph
    jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/000077500000000000000000000000001276402340000314605ustar00rootroot00000000000000DirectionTransformer.java000066400000000000000000000107571276402340000364210ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * Created on Apr 21, 2004 */ package edu.uci.ics.jung.algorithms.transformation; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** *

    Functions for transforming graphs into directed or undirected graphs. * * * @author Danyel Fisher * @author Joshua O'Madadhain */ public class DirectionTransformer { /** * Transforms graph (which may be of any directionality) * into an undirected graph. (This may be useful for * visualization tasks). * Specifically: *

      *
    • Vertices are copied from graph. *
    • Directed edges are 'converted' into a single new undirected edge in the new graph. *
    • Each undirected edge (if any) in graph is 'recreated' with a new undirected edge in the new * graph if create_new is true, or copied from graph otherwise. *
    * * @param graph the graph to be transformed * @param create_new specifies whether existing undirected edges are to be copied or recreated * @param graph_factory used to create the new graph object * @param edge_factory used to create new edges * @param the vertex type * @param the edge type * @return the transformed Graph */ public static UndirectedGraph toUndirected(Graph graph, Supplier> graph_factory, Supplier edge_factory, boolean create_new) { UndirectedGraph out = graph_factory.get(); for (V v : graph.getVertices()) out.addVertex(v); for (E e : graph.getEdges()) { Pair endpoints = graph.getEndpoints(e); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); E to_add; if (graph.getEdgeType(e) == EdgeType.DIRECTED || create_new) to_add = edge_factory.get(); else to_add = e; out.addEdge(to_add, v1, v2, EdgeType.UNDIRECTED); } return out; } /** * Transforms graph (which may be of any directionality) * into a directed graph. * Specifically: *
      *
    • Vertices are copied from graph. *
    • Undirected edges are 'converted' into two new antiparallel directed edges in the new graph. *
    • Each directed edge (if any) in graph is 'recreated' with a new edge in the new * graph if create_new is true, or copied from graph otherwise. *
    * * @param graph the graph to be transformed * @param create_new specifies whether existing directed edges are to be copied or recreated * @param graph_factory used to create the new graph object * @param edge_factory used to create new edges * @param the vertex type * @param the edge type * @return the transformed Graph */ public static Graph toDirected(Graph graph, Supplier> graph_factory, Supplier edge_factory, boolean create_new) { DirectedGraph out = graph_factory.get(); for (V v : graph.getVertices()) out.addVertex(v); for (E e : graph.getEdges()) { Pair endpoints = graph.getEndpoints(e); if (graph.getEdgeType(e) == EdgeType.UNDIRECTED) { V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); out.addEdge(edge_factory.get(), v1, v2, EdgeType.DIRECTED); out.addEdge(edge_factory.get(), v2, v1, EdgeType.DIRECTED); } else // if the edge is directed, just add it { V source = graph.getSource(e); V dest = graph.getDest(e); E to_add = create_new ? edge_factory.get() : e; out.addEdge(to_add, source, dest, EdgeType.DIRECTED); } } return out; } }FoldingTransformer.java000066400000000000000000000313201276402340000360500ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * Created on Apr 21, 2004 */ package edu.uci.ics.jung.algorithms.transformation; import java.util.ArrayList; import java.util.Collection; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.graph.KPartiteGraph; /** * Methods for creating a "folded" graph based on a k-partite graph or a * hypergraph. * *

    A "folded" graph is derived from a k-partite graph by identifying * a partition of vertices which will become the vertices of the new graph, copying * these vertices into the new graph, and then connecting those vertices whose * original analogues were connected indirectly through elements * of other partitions. * *

    A "folded" graph is derived from a hypergraph by creating vertices based on * either the vertices or the hyperedges of the original graph, and connecting * vertices in the new graph if their corresponding vertices/hyperedges share a * connection with a common hyperedge/vertex. * * @author Danyel Fisher * @author Joshua O'Madadhain */ public class FoldingTransformer { /** * Converts g into a unipartite graph whose vertex set is the * vertices of g's partition p. For vertices * a and b in this partition, the resultant * graph will include the edge (a,b) if the original graph * contains edges (a,c) and (c,b) for at least * one vertex c. * *

    The vertices of the new graph are the same as the vertices of the * appropriate partition in the old graph; the edges in the new graph are * created by the input edge Factory. * *

    If there is more than 1 such vertex c for a given pair * (a,b), the type of the output graph will determine whether * it will contain parallel edges or not. * *

    This function will not create self-loops. * * @param vertex type * @param input edge type * @param g input k-partite graph * @param p predicate specifying vertex partition * @param graph_factory Supplier used to create the output graph * @param edge_factory Supplier used to create the edges in the new graph * @return a copy of the input graph folded with respect to the input partition */ public static Graph foldKPartiteGraph(KPartiteGraph g, Predicate p, Supplier> graph_factory, Supplier edge_factory) { Graph newGraph = graph_factory.get(); // get vertices for the specified partition Collection vertices = g.getVertices(p); for (V v : vertices) { newGraph.addVertex(v); for (V s : g.getSuccessors(v)) { for (V t : g.getSuccessors(s)) { if (!vertices.contains(t) || t.equals(v)) continue; newGraph.addVertex(t); newGraph.addEdge(edge_factory.get(), v, t); } } } return newGraph; } /** * Converts g into a unipartite graph whose vertices are the * vertices of g's partition p, and whose edges * consist of collections of the intermediate vertices from other partitions. * For vertices * a and b in this partition, the resultant * graph will include the edge (a,b) if the original graph * contains edges (a,c) and (c,b) for at least * one vertex c. * *

    The vertices of the new graph are the same as the vertices of the * appropriate partition in the old graph; the edges in the new graph are * collections of the intermediate vertices c. * *

    This function will not create self-loops. * * @param vertex type * @param input edge type * @param g input k-partite graph * @param p predicate specifying vertex partition * @param graph_factory Supplier used to create the output graph * @return the result of folding g into unipartite graph whose vertices * are those of the p partition of g */ public static Graph> foldKPartiteGraph(KPartiteGraph g, Predicate p, Supplier>> graph_factory) { Graph> newGraph = graph_factory.get(); // get vertices for the specified partition, copy into new graph Collection vertices = g.getVertices(p); for (V v : vertices) { newGraph.addVertex(v); for (V s : g.getSuccessors(v)) { for (V t : g.getSuccessors(s)) { if (!vertices.contains(t) || t.equals(v)) continue; newGraph.addVertex(t); Collection v_coll = newGraph.findEdge(v, t); if (v_coll == null) { v_coll = new ArrayList(); newGraph.addEdge(v_coll, v, t); } v_coll.add(s); } } } return newGraph; } /** * Creates a Graph which is an edge-folded version of h, where * hyperedges are replaced by k-cliques in the output graph. * *

    The vertices of the new graph are the same objects as the vertices of * h, and a * is connected to b in the new graph if the corresponding vertices * in h are connected by a hyperedge. Thus, each hyperedge with * k vertices in h induces a k-clique in the new graph. * *

    The edges of the new graph consist of collections of each hyperedge that connected * the corresponding vertex pair in the original graph. * * @param vertex type * @param input edge type * @param h hypergraph to be folded * @param graph_factory Supplier used to generate the output graph * @return a copy of the input graph where hyperedges are replaced by cliques */ public static Graph> foldHypergraphEdges(Hypergraph h, Supplier>> graph_factory) { Graph> target = graph_factory.get(); for (V v : h.getVertices()) target.addVertex(v); for (E e : h.getEdges()) { ArrayList incident = new ArrayList(h.getIncidentVertices(e)); populateTarget(target, e, incident); } return target; } /** * Creates a Graph which is an edge-folded version of h, where * hyperedges are replaced by k-cliques in the output graph. * *

    The vertices of the new graph are the same objects as the vertices of * h, and a * is connected to b in the new graph if the corresponding vertices * in h are connected by a hyperedge. Thus, each hyperedge with * k vertices in h induces a k-clique in the new graph. * *

    The edges of the new graph are generated by the specified edge Supplier. * * @param vertex type * @param input edge type * @param h hypergraph to be folded * @param graph_factory Supplier used to generate the output graph * @param edge_factory Supplier used to create the new edges * @return a copy of the input graph where hyperedges are replaced by cliques */ public static Graph foldHypergraphEdges(Hypergraph h, Supplier> graph_factory, Supplier edge_factory) { Graph target = graph_factory.get(); for (V v : h.getVertices()) target.addVertex(v); for (E e : h.getEdges()) { ArrayList incident = new ArrayList(h.getIncidentVertices(e)); for (int i = 0; i < incident.size(); i++) for (int j = i+1; j < incident.size(); j++) target.addEdge(edge_factory.get(), incident.get(i), incident.get(j)); } return target; } /** * Creates a Graph which is a vertex-folded version of h, whose * vertices are the input's hyperedges and whose edges are induced by adjacent hyperedges * in the input. * *

    The vertices of the new graph are the same objects as the hyperedges of * h, and a * is connected to b in the new graph if the corresponding edges * in h have a vertex in common. Thus, each vertex incident to * k edges in h induces a k-clique in the new graph. * *

    The edges of the new graph are created by the specified Supplier. * * @param vertex type * @param input edge type * @param output edge type * @param h hypergraph to be folded * @param graph_factory Supplier used to generate the output graph * @param edge_factory Supplier used to generate the output edges * @return a transformation of the input graph whose vertices correspond to the input's hyperedges * and edges are induced by hyperedges sharing vertices in the input */ public static Graph foldHypergraphVertices(Hypergraph h, Supplier> graph_factory, Supplier edge_factory) { Graph target = graph_factory.get(); for (E e : h.getEdges()) target.addVertex(e); for (V v : h.getVertices()) { ArrayList incident = new ArrayList(h.getIncidentEdges(v)); for (int i = 0; i < incident.size(); i++) for (int j = i+1; j < incident.size(); j++) target.addEdge(edge_factory.get(), incident.get(i), incident.get(j)); } return target; } /** * Creates a Graph which is a vertex-folded version of h, whose * vertices are the input's hyperedges and whose edges are induced by adjacent hyperedges * in the input. * *

    The vertices of the new graph are the same objects as the hyperedges of * h, and a * is connected to b in the new graph if the corresponding edges * in h have a vertex in common. Thus, each vertex incident to * k edges in h induces a k-clique in the new graph. * *

    The edges of the new graph consist of collections of each vertex incident to * the corresponding hyperedge pair in the original graph. * * @param h hypergraph to be folded * @param graph_factory Supplier used to generate the output graph * @return a transformation of the input graph whose vertices correspond to the input's hyperedges * and edges are induced by hyperedges sharing vertices in the input */ public Graph> foldHypergraphVertices(Hypergraph h, Supplier>> graph_factory) { Graph> target = graph_factory.get(); for (E e : h.getEdges()) target.addVertex(e); for (V v : h.getVertices()) { ArrayList incident = new ArrayList(h.getIncidentEdges(v)); populateTarget(target, v, incident); } return target; } /** * @param target * @param e * @param incident */ private static void populateTarget(Graph> target, T e, ArrayList incident) { for (int i = 0; i < incident.size(); i++) { S v1 = incident.get(i); for (int j = i+1; j < incident.size(); j++) { S v2 = incident.get(j); Collection e_coll = target.findEdge(v1, v2); if (e_coll == null) { e_coll = new ArrayList(); target.addEdge(e_coll, v1, v2); } e_coll.add(e); } } } }VertexPartitionCollapser.java000066400000000000000000000077041276402340000372700ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.transformation; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.blockmodel.VertexPartition; import edu.uci.ics.jung.graph.Graph; /** * This class transforms a graph with a known vertex partitioning into a graph whose * vertices correspond to the input graph's partitions. Two vertices in the output graph * are connected if and only if there exists at least one edge between vertices in the * corresponding partitions of the input graph. If the output graph permits parallel edges, * there will be an edge connecting two vertices in the new graph for each such * edge connecting constituent vertices in the input graph. * *

    Concept based on Danyel Fisher's GraphCollapser in JUNG 1.x. * */ public class VertexPartitionCollapser { protected Supplier> graph_factory; protected Supplier vertex_factory; protected Supplier edge_factory; protected Map, CV> set_collapsedv; /** * Creates an instance with the specified graph and element factories. * @param vertex_factory used to construct the vertices of the new graph * @param edge_factory used to construct the edges of the new graph * @param graph_factory used to construct the new graph */ public VertexPartitionCollapser(Supplier> graph_factory, Supplier vertex_factory, Supplier edge_factory) { this.graph_factory = graph_factory; this.vertex_factory = vertex_factory; this.edge_factory = edge_factory; this.set_collapsedv = new HashMap, CV>(); } /** * Creates a new graph whose vertices correspond to the partitions of the supplied graph. * @param partitioning a vertex partition of a graph * @return a new graph whose vertices correspond to the partitions of the supplied graph */ public Graph collapseVertexPartitions(VertexPartition partitioning) { Graph original = partitioning.getGraph(); Graph collapsed = graph_factory.get(); // create vertices in new graph corresponding to equivalence sets in the original graph for (Set set : partitioning.getVertexPartitions()) { CV cv = vertex_factory.get(); collapsed.addVertex(vertex_factory.get()); set_collapsedv.put(set, cv); } // create edges in new graph corresponding to edges in original graph for (E e : original.getEdges()) { Collection incident = original.getIncidentVertices(e); Collection collapsed_vertices = new HashSet(); Map> vertex_partitions = partitioning.getVertexToPartitionMap(); // collect the collapsed vertices corresponding to the original incident vertices for (V v : incident) collapsed_vertices.add(set_collapsedv.get(vertex_partitions.get(v))); // if there's only one collapsed vertex, continue (no edges to create) if (collapsed_vertices.size() > 1) { CE ce = edge_factory.get(); collapsed.addEdge(ce, collapsed_vertices); } } return collapsed; } /** * @return a Function from vertex sets in the original graph to collapsed vertices * in the transformed graph. */ public Function, CV> getSetToCollapsedVertexTransformer() { return Functions.forMap(set_collapsedv); } } package.html000066400000000000000000000014401276402340000336610ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation Mechanisms for graph transformation. These currently include:

    • DirectionTransformer: generates graphs where input undirected edges have been converted to directed edges, or vice versa
    • FoldingTransformer: transforms k-partite graphs or hypergraphs into unipartite graphs
    • VertexPartitionCollapser: transforms a graph, given a partition of its vertices into disjoint sets, into a graph in which each of these disjoint sets has been 'collapsed' into a single new vertex.
    jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/000077500000000000000000000000001276402340000273675ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/BasicMapEntry.java000066400000000000000000000034301276402340000327330ustar00rootroot00000000000000package edu.uci.ics.jung.algorithms.util; import java.util.HashMap; import java.util.Map; /** * An simple minimal implementation of Map.Entry. * * @param the key type * @param the value type */ public class BasicMapEntry implements Map.Entry { final K key; V value; /** * @param k the key * @param v the value */ public BasicMapEntry(K k, V v) { value = v; key = k; } public K getKey() { return key; } public V getValue() { return value; } public V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } @Override public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; @SuppressWarnings("rawtypes") Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } @Override public int hashCode() { return (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); } @Override public String toString() { return getKey() + "=" + getValue(); } /** * This method is invoked whenever the value in an entry is * overwritten by an invocation of put(k,v) for a key k that's already * in the HashMap. */ void recordAccess(HashMap m) { } /** * This method is invoked whenever the entry is * removed from the table. */ void recordRemoval(HashMap m) { } } DiscreteDistribution.java000066400000000000000000000202031276402340000343120ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Feb 18, 2004 */ package edu.uci.ics.jung.algorithms.util; import java.util.Collection; import java.util.Iterator; import com.google.common.base.Preconditions; /** * A utility class for calculating properties of discrete distributions. * Generally, these distributions are represented as arrays of * double values, which are assumed to be normalized * such that the entries in a single array sum to 1. * * @author Joshua O'Madadhain */ public class DiscreteDistribution { /** * Returns the Kullback-Leibler divergence between the * two specified distributions, which must have the same * number of elements. This is defined as * the sum over all i of * dist[i] * Math.log(dist[i] / reference[i]). * Note that this value is not symmetric; see * symmetricKL for a symmetric variant. * @see #symmetricKL(double[], double[]) * @param dist the distribution whose divergence from {@code reference} is being measured * @param reference the reference distribution * @return sum_i of {@code dist[i] * Math.log(dist[i] / reference[i])} */ public static double KullbackLeibler(double[] dist, double[] reference) { double distance = 0; Preconditions.checkArgument(dist.length == reference.length, "input arrays must be of the same length"); for (int i = 0; i < dist.length; i++) { if (dist[i] > 0 && reference[i] > 0) distance += dist[i] * Math.log(dist[i] / reference[i]); } return distance; } /** * @param dist the distribution whose divergence from {@code reference} is being measured * @param reference the reference distribution * @return KullbackLeibler(dist, reference) + KullbackLeibler(reference, dist) * @see #KullbackLeibler(double[], double[]) */ public static double symmetricKL(double[] dist, double[] reference) { return KullbackLeibler(dist, reference) + KullbackLeibler(reference, dist); } /** * Returns the squared difference between the * two specified distributions, which must have the same * number of elements. This is defined as * the sum over all i of the square of * (dist[i] - reference[i]). * @param dist the distribution whose distance from {@code reference} is being measured * @param reference the reference distribution * @return sum_i {@code (dist[i] - reference[i])^2} */ public static double squaredError(double[] dist, double[] reference) { double error = 0; Preconditions.checkArgument(dist.length == reference.length, "input arrays must be of the same length"); for (int i = 0; i < dist.length; i++) { double difference = dist[i] - reference[i]; error += difference * difference; } return error; } /** * Returns the cosine distance between the two * specified distributions, which must have the same number * of elements. The distributions are treated as vectors * in dist.length-dimensional space. * Given the following definitions *
      *
    • v = the sum over all i of dist[i] * dist[i] *
    • w = the sum over all i of reference[i] * reference[i] *
    • vw = the sum over all i of dist[i] * reference[i] *
    * the value returned is defined as vw / (Math.sqrt(v) * Math.sqrt(w)). * @param dist the distribution whose distance from {@code reference} is being measured * @param reference the reference distribution * @return the cosine distance between {@code dist} and {@code reference}, considered as vectors */ public static double cosine(double[] dist, double[] reference) { double v_prod = 0; // dot product x*x double w_prod = 0; // dot product y*y double vw_prod = 0; // dot product x*y Preconditions.checkArgument(dist.length == reference.length, "input arrays must be of the same length"); for (int i = 0; i < dist.length; i++) { vw_prod += dist[i] * reference[i]; v_prod += dist[i] * dist[i]; w_prod += reference[i] * reference[i]; } // cosine distance between v and w return vw_prod / (Math.sqrt(v_prod) * Math.sqrt(w_prod)); } /** * Returns the entropy of this distribution. * High entropy indicates that the distribution is * close to uniform; low entropy indicates that the * distribution is close to a Dirac delta (i.e., if * the probability mass is concentrated at a single * point, this method returns 0). Entropy is defined as * the sum over all i of * -(dist[i] * Math.log(dist[i])) * * @param dist the distribution whose entropy is being measured * @return sum_i {@code -(dist[i] * Math.log(dist[i]))} */ public static double entropy(double[] dist) { double total = 0; for (int i = 0; i < dist.length; i++) { if (dist[i] > 0) total += dist[i] * Math.log(dist[i]); } return -total; } /** * Normalizes, with Lagrangian smoothing, the specified double * array, so that the values sum to 1 (i.e., can be treated as probabilities). * The effect of the Lagrangian smoothing is to ensure that all entries * are nonzero; effectively, a value of alpha is added to each * entry in the original array prior to normalization. * @param counts the array to be converted into a probability distribution * @param alpha the value to add to each entry prior to normalization */ public static void normalize(double[] counts, double alpha) { double total_count = 0; for (int i = 0; i < counts.length; i++) total_count += counts[i]; for (int i = 0; i < counts.length; i++) counts[i] = (counts[i] + alpha) / (total_count + counts.length * alpha); } /** * Returns the mean of the specified Collection of * distributions, which are assumed to be normalized arrays of * double values. * @see #mean(double[][]) * @param distributions the distributions whose mean is to be calculated * @return the mean of the distributions */ public static double[] mean(Collection distributions) { if (distributions.isEmpty()) throw new IllegalArgumentException("Distribution collection must be non-empty"); Iterator iter = distributions.iterator(); double[] first = iter.next(); double[][] d_array = new double[distributions.size()][first.length]; d_array[0] = first; for (int i = 1; i < d_array.length; i++) d_array[i] = iter.next(); return mean(d_array); } /** * Returns the mean of the specified array of distributions, * represented as normalized arrays of double values. * Will throw an "index out of bounds" exception if the * distribution arrays are not all of the same length. * @param distributions the distributions whose mean is to be calculated * @return the mean of the distributions */ public static double[] mean(double[][] distributions) { double[] d_mean = new double[distributions[0].length]; for (int j = 0; j < d_mean.length; j++) d_mean[j] = 0; for (int i = 0; i < distributions.length; i++) for (int j = 0; j < d_mean.length; j++) d_mean[j] += distributions[i][j] / distributions.length; return d_mean; } }jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/Indexer.java000066400000000000000000000036041276402340000316330ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.util; import java.util.Collection; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; /** * A class providing static methods useful for improving the * performance of graph algorithms. * * @author Tom Nelson * */ public class Indexer { /** * Returns a BiMap mapping each element of the collection to its * index as encountered while iterating over the collection. The purpose * of the index operation is to supply an O(1) replacement operation for the * O(n) indexOf(element) method of a List * @param the type of the collection elements * @param collection the collection whose indices are to be generated * @return a bidirectional map from collection elements to 0-based indices */ public static BiMap create(Collection collection) { return create(collection, 0); } /** * Returns a BiMap mapping each element of the collection to its * index as encountered while iterating over the collection. The purpose * of the index operation is to supply an O(1) replacement operation for the * O(n) indexOf(element) method of a List * @param the type of the collection elements * @param collection the collection whose indices are to be generated * @param start start index * @return a bidirectional map from collection elements to start-based indices */ public static BiMap create(Collection collection, int start) { BiMap map = HashBiMap.create(); int i=start; for(T t : collection) { map.put(t,i++); } return map; } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/IterativeContext.java000066400000000000000000000010431276402340000335310ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.util; /** * An interface for algorithms that proceed iteratively. * */ public interface IterativeContext { /** * Advances one step. */ void step(); /** * @return {@code true} if this iterative process is finished, and {@code false} otherwise. */ boolean done(); } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/IterativeProcess.java000066400000000000000000000100661276402340000335300ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.util; /** * Provides basic infrastructure for iterative algorithms. Services provided include: *
      *
    • storage of current and max iteration count
    • *
    • framework for initialization, iterative evaluation, and finalization
    • *
    • test for convergence
    • *
    • etc.
    • *
    *

    * Algorithms that subclass this class are typically used in the following way:
    *

     * FooAlgorithm foo = new FooAlgorithm(...)
     * foo.setMaximumIterations(100); //set up conditions
     * ...
     * foo.evaluate(); //key method which initiates iterative process
     * foo.getSomeResult();
     * 
    * * @author Scott White (originally written by Didier Besset) */ public abstract class IterativeProcess implements IterativeContext { /** * Number of iterations performed. */ private int iterations; /** * Maximum allowed number of iterations. */ private int maximumIterations = 50; /** * Desired precision. */ private double desiredPrecision = Double.MIN_VALUE; /** * Achieved precision. */ private double precision; /** * Generic constructor. */ public IterativeProcess() { } /** * Performs the iterative process. * Note: this method does not return anything because Java does not * allow mixing double, int, or objects */ public void evaluate() { iterations = 0; initializeIterations(); while (iterations++ < maximumIterations) { step(); precision = getPrecision(); if (hasConverged()) break; } finalizeIterations(); } /** * Evaluate the result of the current iteration. */ abstract public void step(); /** * Perform eventual clean-up operations * (must be implement by subclass when needed). */ protected void finalizeIterations() { } /** * @return the desired precision. */ public double getDesiredPrecision() { return desiredPrecision; } /** * @return the number of iterations performed. */ public int getIterations() { return iterations; } /** * @return the maximum allowed number of iterations. */ public int getMaximumIterations() { return maximumIterations; } /** * @return the attained precision. */ public double getPrecision() { return precision; } /** * @param precision the precision to set */ public void setPrecision(double precision) { this.precision = precision; } /** * * Check to see if the result has been attained. * @return boolean */ public boolean hasConverged() { return precision < desiredPrecision; } public boolean done() { return hasConverged(); } /** * Initializes internal parameters to start the iterative process. */ protected void initializeIterations() { } /** * */ public void reset() { } /** * @return double * @param epsilon double * @param x double */ public double relativePrecision(double epsilon, double x) { return x > desiredPrecision ? epsilon / x: epsilon; } /** * @param prec the desired precision. */ public void setDesiredPrecision(double prec) throws IllegalArgumentException { if (prec <= 0) throw new IllegalArgumentException("Non-positive precision: " + prec); desiredPrecision = prec; } /** * @param maxIter the maximum allowed number of iterations */ public void setMaximumIterations(int maxIter) throws IllegalArgumentException { if (maxIter < 1) throw new IllegalArgumentException("Non-positive maximum iteration: " + maxIter); maximumIterations = maxIter; } }jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/KMeansClusterer.java000066400000000000000000000215171276402340000333070ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * Created on Aug 9, 2004 * */ package edu.uci.ics.jung.algorithms.util; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.Set; /** * Groups items into a specified number of clusters, based on their proximity in * d-dimensional space, using the k-means algorithm. Calls to * cluster will terminate when either of the two following * conditions is true: *
      *
    • the number of iterations is > max_iterations *
    • none of the centroids has moved as much as convergence_threshold * since the previous iteration *
    * * @author Joshua O'Madadhain */ public class KMeansClusterer { protected int max_iterations; protected double convergence_threshold; protected Random rand; /** * Creates an instance which will terminate when either the maximum number of * iterations has been reached, or all changes are smaller than the convergence threshold. * @param max_iterations the maximum number of iterations to employ * @param convergence_threshold the smallest change we want to track */ public KMeansClusterer(int max_iterations, double convergence_threshold) { this.max_iterations = max_iterations; this.convergence_threshold = convergence_threshold; this.rand = new Random(); } /** * Creates an instance with max iterations of 100 and convergence threshold * of 0.001. */ public KMeansClusterer() { this(100, 0.001); } /** * @return the maximum number of iterations */ public int getMaxIterations() { return max_iterations; } /** * @param max_iterations the maximum number of iterations */ public void setMaxIterations(int max_iterations) { if (max_iterations < 0) throw new IllegalArgumentException("max iterations must be >= 0"); this.max_iterations = max_iterations; } /** * @return the convergence threshold */ public double getConvergenceThreshold() { return convergence_threshold; } /** * @param convergence_threshold the convergence threshold */ public void setConvergenceThreshold(double convergence_threshold) { if (convergence_threshold <= 0) throw new IllegalArgumentException("convergence threshold " + "must be > 0"); this.convergence_threshold = convergence_threshold; } /** * Returns a Collection of clusters, where each cluster is * represented as a Map of Objects to locations * in d-dimensional space. * @param object_locations a map of the items to cluster, to * double arrays that specify their locations in d-dimensional space. * @param num_clusters the number of clusters to create * @return a clustering of the input objects in d-dimensional space * @throws NotEnoughClustersException if {@code num_clusters} is larger than the number of * distinct points in object_locations */ @SuppressWarnings("unchecked") public Collection> cluster(Map object_locations, int num_clusters) { if (object_locations == null || object_locations.isEmpty()) throw new IllegalArgumentException("'objects' must be non-empty"); if (num_clusters < 2 || num_clusters > object_locations.size()) throw new IllegalArgumentException("number of clusters " + "must be >= 2 and <= number of objects (" + object_locations.size() + ")"); Set centroids = new HashSet(); Object[] obj_array = object_locations.keySet().toArray(); Set tried = new HashSet(); // create the specified number of clusters while (centroids.size() < num_clusters && tried.size() < object_locations.size()) { T o = (T)obj_array[(int)(rand.nextDouble() * obj_array.length)]; tried.add(o); double[] mean_value = object_locations.get(o); boolean duplicate = false; for (double[] cur : centroids) { if (Arrays.equals(mean_value, cur)) duplicate = true; } if (!duplicate) centroids.add(mean_value); } if (tried.size() >= object_locations.size()) throw new NotEnoughClustersException(); // put items in their initial clusters Map> clusterMap = assignToClusters(object_locations, centroids); // keep reconstituting clusters until either // (a) membership is stable, or // (b) number of iterations passes max_iterations, or // (c) max movement of any centroid is <= convergence_threshold int iterations = 0; double max_movement = Double.POSITIVE_INFINITY; while (iterations++ < max_iterations && max_movement > convergence_threshold) { max_movement = 0; Set new_centroids = new HashSet(); // calculate new mean for each cluster for (Map.Entry> entry : clusterMap.entrySet()) { double[] centroid = entry.getKey(); Map elements = entry.getValue(); ArrayList locations = new ArrayList(elements.values()); double[] mean = DiscreteDistribution.mean(locations); max_movement = Math.max(max_movement, Math.sqrt(DiscreteDistribution.squaredError(centroid, mean))); new_centroids.add(mean); } // TODO: check membership of clusters: have they changed? // regenerate cluster membership based on means clusterMap = assignToClusters(object_locations, new_centroids); } return clusterMap.values(); } /** * Assigns each object to the cluster whose centroid is closest to the * object. * @param object_locations a map of objects to locations * @param centroids the centroids of the clusters to be formed * @return a map of objects to assigned clusters */ protected Map> assignToClusters(Map object_locations, Set centroids) { Map> clusterMap = new HashMap>(); for (double[] centroid : centroids) clusterMap.put(centroid, new HashMap()); for (Map.Entry object_location : object_locations.entrySet()) { T object = object_location.getKey(); double[] location = object_location.getValue(); // find the cluster with the closest centroid Iterator c_iter = centroids.iterator(); double[] closest = c_iter.next(); double distance = DiscreteDistribution.squaredError(location, closest); while (c_iter.hasNext()) { double[] centroid = c_iter.next(); double dist_cur = DiscreteDistribution.squaredError(location, centroid); if (dist_cur < distance) { distance = dist_cur; closest = centroid; } } clusterMap.get(closest).put(object, location); } return clusterMap; } /** * Sets the seed used by the internal random number generator. * Enables consistent outputs. * @param random_seed the random seed to use */ public void setSeed(int random_seed) { this.rand = new Random(random_seed); } /** * An exception that indicates that the specified data points cannot be * clustered into the number of clusters requested by the user. * This will happen if and only if there are fewer distinct points than * requested clusters. (If there are fewer total data points than * requested clusters, IllegalArgumentException will be thrown.) * * @author Joshua O'Madadhain */ @SuppressWarnings("serial") public static class NotEnoughClustersException extends RuntimeException { @Override public String getMessage() { return "Not enough distinct points in the input data set to form " + "the requested number of clusters"; } } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/MapBinaryHeap.java000066400000000000000000000233261276402340000327200ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * * Created on Oct 29, 2003 */ package edu.uci.ics.jung.algorithms.util; import java.util.AbstractCollection; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Queue; import java.util.Vector; import com.google.common.collect.Iterators; /** * An array-based binary heap implementation of a priority queue, * which also provides * efficient update() and contains operations. * It contains extra infrastructure (a hash table) to keep track of the * position of each element in the array; thus, if the key value of an element * changes, it may be "resubmitted" to the heap via update * so that the heap can reposition it efficiently, as necessary. * * @author Joshua O'Madadhain */ public class MapBinaryHeap extends AbstractCollection implements Queue { private Vector heap = new Vector(); // holds the heap as an implicit binary tree private Map object_indices = new HashMap(); // maps each object in the heap to its index in the heap private Comparator comp; private final static int TOP = 0; // the index of the top of the heap /** * Creates a MapBinaryHeap whose heap ordering * is based on the ordering of the elements specified by comp. * @param comp the comparator to use to order elements in the heap */ public MapBinaryHeap(Comparator comp) { initialize(comp); } /** * Creates a MapBinaryHeap whose heap ordering * will be based on the natural ordering of the elements, * which must be Comparable. */ public MapBinaryHeap() { initialize(new ComparableComparator()); } /** * Creates a MapBinaryHeap based on the specified * collection whose heap ordering * will be based on the natural ordering of the elements, * which must be Comparable. * @param c the collection of {@code Comparable} elements to add to the heap */ public MapBinaryHeap(Collection c) { this(); addAll(c); } /** * Creates a MapBinaryHeap based on the specified collection * whose heap ordering * is based on the ordering of the elements specified by c. * @param c the collection of elements to add to the heap * @param comp the comparator to use for items in {@code c} */ public MapBinaryHeap(Collection c, Comparator comp) { this(comp); addAll(c); } private void initialize(Comparator comp) { this.comp = comp; clear(); } /** * @see Collection#clear() */ @Override public void clear() { object_indices.clear(); heap.clear(); } /** * Inserts o into this collection. */ @Override public boolean add(T o) { int i = heap.size(); // index 1 past the end of the heap heap.setSize(i+1); percolateUp(i, o); return true; } /** * Returns true if this collection contains no elements, and * false otherwise. */ @Override public boolean isEmpty() { return heap.isEmpty(); } /** * Returns the element at the top of the heap; does not * alter the heap. */ public T peek() { if (heap.size() > 0) return heap.elementAt(TOP); else return null; } /** * @return the size of this heap */ @Override public int size() { return heap.size(); } /** * Informs the heap that this object's internal key value has been * updated, and that its place in the heap may need to be shifted * (up or down). * @param o the object whose key value has been updated */ public void update(T o) { // Since we don't know whether the key value increased or // decreased, we just percolate up followed by percolating down; // one of the two will have no effect. int cur = object_indices.get(o).intValue(); // current index int new_idx = percolateUp(cur, o); percolateDown(new_idx); } @Override public boolean contains(Object o) { return object_indices.containsKey(o); } /** * Moves the element at position cur closer to * the bottom of the heap, or returns if no further motion is * necessary. Calls itself recursively if further motion is * possible. */ private void percolateDown(int cur) { int left = lChild(cur); int right = rChild(cur); int smallest; if ((left < heap.size()) && (comp.compare(heap.elementAt(left), heap.elementAt(cur)) < 0)) { smallest = left; } else { smallest = cur; } if ((right < heap.size()) && (comp.compare(heap.elementAt(right), heap.elementAt(smallest)) < 0)) { smallest = right; } if (cur != smallest) { swap(cur, smallest); percolateDown(smallest); } } /** * Moves the element o at position cur * as high as it can go in the heap. Returns the new position of the * element in the heap. */ private int percolateUp(int cur, T o) { int i = cur; while ((i > TOP) && (comp.compare(heap.elementAt(parent(i)), o) > 0)) { T parentElt = heap.elementAt(parent(i)); heap.setElementAt(parentElt, i); object_indices.put(parentElt, new Integer(i)); // reset index to i (new location) i = parent(i); } // place object in heap at appropriate place object_indices.put(o, new Integer(i)); heap.setElementAt(o, i); return i; } /** * Returns the index of the left child of the element at * index i of the heap. * @param i * @return the index of the left child of the element at * index i of the heap */ private int lChild(int i) { return (i<<1) + 1; } /** * Returns the index of the right child of the element at * index i of the heap. * @param i * @return the index of the right child of the element at * index i of the heap */ private int rChild(int i) { return (i<<1) + 2; } /** * Returns the index of the parent of the element at * index i of the heap. * @param i * @return the index of the parent of the element at index i of the heap */ private int parent(int i) { return (i-1)>>1; } /** * Swaps the positions of the elements at indices i * and j of the heap. * @param i * @param j */ private void swap(int i, int j) { T iElt = heap.elementAt(i); T jElt = heap.elementAt(j); heap.setElementAt(jElt, i); object_indices.put(jElt, new Integer(i)); heap.setElementAt(iElt, j); object_indices.put(iElt, new Integer(j)); } /** * Comparator used if none is specified in the constructor. * @author Joshua O'Madadhain */ private class ComparableComparator implements Comparator { /** * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ @SuppressWarnings("unchecked") public int compare(T arg0, T arg1) { if (!(arg0 instanceof Comparable) || !(arg1 instanceof Comparable)) throw new IllegalArgumentException("Arguments must be Comparable"); return ((Comparable)arg0).compareTo(arg1); } } /** * Returns an Iterator that does not support modification * of the heap. */ @Override public Iterator iterator() { return Iterators.unmodifiableIterator(heap.iterator()); } /** * This data structure does not support the removal of arbitrary elements. */ @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } /** * This data structure does not support the removal of arbitrary elements. */ @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } /** * This data structure does not support the removal of arbitrary elements. */ @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } public T element() throws NoSuchElementException { T top = this.peek(); if (top == null) throw new NoSuchElementException(); return top; } public boolean offer(T o) { return add(o); } public T poll() { T top = this.peek(); if (top != null) { T bottom_elt = heap.lastElement(); heap.setElementAt(bottom_elt, TOP); object_indices.put(bottom_elt, new Integer(TOP)); heap.setSize(heap.size() - 1); // remove the last element if (heap.size() > 1) percolateDown(TOP); object_indices.remove(top); } return top; } public T remove() { T top = this.poll(); if (top == null) throw new NoSuchElementException(); return top; } } MapSettableTransformer.java000066400000000000000000000017351276402340000346050ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/* * Created on Aug 5, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.util; import java.util.Map; /** * A SettableTransformer that operates on an underlying Map instance. * Similar to MapTransformer. * * @author Joshua O'Madadhain */ public class MapSettableTransformer implements SettableTransformer { protected Map map; /** * Creates an instance based on m. * @param m the map on which this instance is based */ public MapSettableTransformer(Map m) { this.map = m; } public O apply(I input) { return map.get(input); } public void set(I input, O output) { map.put(input, output); } } SelfLoopEdgePredicate.java000066400000000000000000000014001276402340000342770ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/utilpackage edu.uci.ics.jung.algorithms.util; import com.google.common.base.Predicate; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.graph.util.Pair; /** * A Predicate that returns true if the input edge's * endpoints in the input graph are identical. (Thus, an edge which connects * its sole incident vertex to itself). * * @param the vertex type * @param the edge type */ public class SelfLoopEdgePredicate implements Predicate,E>> { public boolean apply(Context,E> context) { Pair endpoints = context.graph.getEndpoints(context.element); return endpoints.getFirst().equals(endpoints.getSecond()); } } SettableTransformer.java000066400000000000000000000015651276402340000341500ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/* * Created on Aug 5, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.util; import com.google.common.base.Function; /** * An interface for classes that can set the value to be returned (from transform()) * when invoked on a given input. * * @author Joshua O'Madadhain */ public interface SettableTransformer extends Function { /** * Sets the value (output) to be returned by a call to * transform(input)). * @param input the value whose output value is being specified * @param output the output value for {@code input} */ public void set(I input, O output); } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/WeightedChoice.java000066400000000000000000000144311276402340000331100ustar00rootroot00000000000000/** * Copyright (c) 2009, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Jan 8, 2009 * */ package edu.uci.ics.jung.algorithms.util; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Random; /** * Selects items according to their probability in an arbitrary probability * distribution. The distribution is specified by a {@code Map} from * items (of type {@code T}) to weights of type {@code Number}, supplied * to the constructor; these weights are normalized internally to act as * probabilities. * *

    This implementation selects items in O(1) time, and requires O(n) space. * * @author Joshua O'Madadhain */ public class WeightedChoice { private List item_pairs; private Random random; /** * The default minimum value that is treated as a valid probability * (as opposed to rounding error from floating-point operations). */ public static final double DEFAULT_THRESHOLD = 0.00000000001; /** * Equivalent to {@code this(item_weights, new Random(), DEFAULT_THRESHOLD)}. * @param item_weights a map from items to their weights */ public WeightedChoice(Map item_weights) { this(item_weights, new Random(), DEFAULT_THRESHOLD); } /** * Equivalent to {@code this(item_weights, new Random(), threshold)}. * @param item_weights a map from items to their weights * @param threshold the minimum value that is treated as a probability * (anything smaller will be considered equivalent to a floating-point rounding error) */ public WeightedChoice(Map item_weights, double threshold) { this(item_weights, new Random(), threshold); } /** * Equivalent to {@code this(item_weights, random, DEFAULT_THRESHOLD)}. * @param item_weights a map from items to their weights * @param random the Random instance to use for selection */ public WeightedChoice(Map item_weights, Random random) { this(item_weights, random, DEFAULT_THRESHOLD); } /** * Creates an instance with the specified mapping from items to weights, * random number generator, and threshold value. * *

    The mapping defines the weight for each item to be selected; this * will be proportional to the probability of its selection. *

    The random number generator specifies the mechanism which will be * used to provide uniform integer and double values. *

    The threshold indicates default minimum value that is treated as a valid * probability (as opposed to rounding error from floating-point operations). * @param item_weights a map from items to their weights * @param random the Random instance to use for selection * @param threshold the minimum value that is treated as a probability * (anything smaller will be considered equivalent to a floating-point rounding error) */ public WeightedChoice(Map item_weights, Random random, double threshold) { if (item_weights.isEmpty()) throw new IllegalArgumentException("Item weights must be non-empty"); int item_count = item_weights.size(); item_pairs = new ArrayList(item_count); double sum = 0; for (Map.Entry entry : item_weights.entrySet()) { double value = entry.getValue().doubleValue(); if (value <= 0) throw new IllegalArgumentException("Weights must be > 0"); sum += value; } double bucket_weight = 1.0 / item_weights.size(); Queue light_weights = new LinkedList(); Queue heavy_weights = new LinkedList(); for (Map.Entry entry : item_weights.entrySet()) { double value = entry.getValue().doubleValue() / sum; enqueueItem(entry.getKey(), value, bucket_weight, light_weights, heavy_weights); } // repeat until both queues empty while (!heavy_weights.isEmpty() || !light_weights.isEmpty()) { ItemPair heavy_item = heavy_weights.poll(); ItemPair light_item = light_weights.poll(); double light_weight = 0; T light = null; T heavy = null; if (light_item != null) { light_weight = light_item.weight; light = light_item.light; } if (heavy_item != null) { heavy = heavy_item.heavy; // put the 'left over' weight from the heavy item--what wasn't // needed to make up the difference between the light weight and // 1/n--back in the appropriate queue double new_weight = heavy_item.weight - (bucket_weight - light_weight); if (new_weight > threshold) enqueueItem(heavy, new_weight, bucket_weight, light_weights, heavy_weights); } light_weight *= item_count; item_pairs.add(new ItemPair(light, heavy, light_weight)); } this.random = random; } /** * Adds key/value to the appropriate queue. Keys with values less than * the threshold get added to {@code light_weights}, all others get added * to {@code heavy_weights}. */ private void enqueueItem(T key, double value, double threshold, Queue light_weights, Queue heavy_weights) { if (value < threshold) light_weights.offer(new ItemPair(key, null, value)); else heavy_weights.offer(new ItemPair(null, key, value)); } /** * @param seed the seed to be used by the internal random number generator */ public void setRandomSeed(long seed) { this.random.setSeed(seed); } /** * Retrieves an item with probability proportional to its weight in the * {@code Map} provided in the input. * @return an item chosen randomly based on its specified weight */ public T nextItem() { ItemPair item_pair = item_pairs.get(random.nextInt(item_pairs.size())); if (random.nextDouble() < item_pair.weight) return item_pair.light; return item_pair.heavy; } /** * Manages light object/heavy object/light conditional probability tuples. */ private class ItemPair { T light; T heavy; double weight; private ItemPair(T light, T heavy, double weight) { this.light = light; this.heavy = heavy; this.weight = weight; } @Override public String toString() { return String.format("[L:%s, H:%s, %.3f]", light, heavy, weight); } } } jung-jung-2.1.1/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/package.html000066400000000000000000000017371276402340000316600ustar00rootroot00000000000000 Provides general algorithmic utilities. These include:

    • DiscreteDistribution: calculates statistical measures on discrete probability distributions represented as double arrays
    • KMeansClusterer: uses the k-means algorithm to cluster points in d-dimensional space into k clusters
    • MapBinaryHeap: a binary heap implementation that permits efficient element access and update operations
    • RandomLocationTransformer: a class that randomly assigns 2D coordinates to items (default initializer for iterative Layouts)
    • SettableTransformer: an extension of Transformer that allows mutation of the transformation
    jung-jung-2.1.1/jung-algorithms/src/site/000077500000000000000000000000001276402340000202625ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/site/site.xml000066400000000000000000000004441276402340000217520ustar00rootroot00000000000000 ${project.name} jung-jung-2.1.1/jung-algorithms/src/test/000077500000000000000000000000001276402340000202755ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/000077500000000000000000000000001276402340000212165ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/000077500000000000000000000000001276402340000217735ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/000077500000000000000000000000001276402340000225535ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/000077500000000000000000000000001276402340000233315ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/000077500000000000000000000000001276402340000242745ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/000077500000000000000000000000001276402340000264455ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/cluster/000077500000000000000000000000001276402340000301265ustar00rootroot00000000000000TestBicomponentClusterer.java000066400000000000000000000170001276402340000357160ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/cluster/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.cluster; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.UndirectedSparseMultigraph; /** * @author Scott White */ public class TestBicomponentClusterer extends TestCase { public static Test suite() { return new TestSuite(TestBicomponentClusterer.class); } @Override protected void setUp() { } public void testExtract0() throws Exception { UndirectedGraph graph = new UndirectedSparseMultigraph(); String[] v = {"0"}; graph.addVertex(v[0]); List> c = new ArrayList>(); c.add(0, new HashSet()); c.get(0).add(v[0]); // Set[] c = {new HashSet()}; // c[0].add(v[0]); testComponents(graph, v, c); } public void testExtractEdge() throws Exception { UndirectedGraph graph = new UndirectedSparseMultigraph(); String[] v = {"0","1"}; graph.addVertex(v[0]); graph.addVertex(v[1]); graph.addEdge(0, v[0], v[1]); List> c = new ArrayList>(); c.add(0, new HashSet()); c.get(0).add(v[0]); c.get(0).add(v[1]); // Set[] c = {new HashSet()}; // // c[0].add(v[0]); // c[0].add(v[1]); testComponents(graph, v, c); } public void testExtractV() throws Exception { UndirectedGraph graph = new UndirectedSparseMultigraph(); String[] v = new String[3]; for (int i = 0; i < 3; i++) { v[i] = ""+i; graph.addVertex(v[i]); } graph.addEdge(0, v[0], v[1]); graph.addEdge(1, v[0], v[2]); List> c = new ArrayList>(); c.add(0, new HashSet()); c.add(1, new HashSet()); c.get(0).add(v[0]); c.get(0).add(v[1]); c.get(1).add(v[0]); c.get(1).add(v[2]); // Set[] c = {new HashSet(), new HashSet()}; // // c[0].add(v[0]); // c[0].add(v[1]); // // c[1].add(v[0]); // c[1].add(v[2]); testComponents(graph, v, c); } public void createEdges(String[] v, int[][] edge_array, Graph g) { for (int k = 0; k < edge_array.length; k++) { int i = edge_array[k][0]; int j = edge_array[k][1]; String v1 = getVertex(v, i, g); String v2 = getVertex(v, j, g); g.addEdge(k, v1, v2); } } public String getVertex(String[] v_array, int i, Graph g) { String v = v_array[i]; if (v == null) { v_array[i] = Character.toString((char)('0'+i)); g.addVertex(v_array[i]); v = v_array[i]; } return v; } public void testExtract1() { String[] v = new String[6]; int[][] edges1 = {{0,1}, {0,5}, {0,3}, {0,4}, {1,5}, {3,4}, {2,3}}; UndirectedGraph graph = new UndirectedSparseMultigraph(); createEdges(v, edges1, graph); List> c = new ArrayList>(); for (int i = 0; i < 3; i++) c.add(i, new HashSet()); c.get(0).add(v[0]); c.get(0).add(v[1]); c.get(0).add(v[5]); c.get(1).add(v[0]); c.get(1).add(v[3]); c.get(1).add(v[4]); c.get(2).add(v[2]); c.get(2).add(v[3]); // Set[] c = new Set[3]; // for (int i = 0; i < c.length; i++) // c[i] = new HashSet(); // // c[0].add(v[0]); // c[0].add(v[1]); // c[0].add(v[5]); // // c[1].add(v[0]); // c[1].add(v[3]); // c[1].add(v[4]); // // c[2].add(v[2]); // c[2].add(v[3]); testComponents(graph, v, c); } public void testExtract2() { String[] v = new String[9]; int[][] edges1 = {{0,2}, {0,4}, {1,0}, {2,1}, {3,0}, {4,3}, {5,3}, {6,7}, {6,8}, {8,7}}; UndirectedGraph graph = new UndirectedSparseMultigraph(); createEdges(v, edges1, graph); List> c = new ArrayList>(); for (int i = 0; i < 4; i++) c.add(i, new HashSet()); c.get(0).add(v[0]); c.get(0).add(v[1]); c.get(0).add(v[2]); c.get(1).add(v[0]); c.get(1).add(v[3]); c.get(1).add(v[4]); c.get(2).add(v[5]); c.get(2).add(v[3]); c.get(3).add(v[6]); c.get(3).add(v[7]); c.get(3).add(v[8]); // Set[] c = new Set[4]; // for (int i = 0; i < c.length; i++) // c[i] = new HashSet(); // // c[0].add(v[0]); // c[0].add(v[1]); // c[0].add(v[2]); // // c[1].add(v[0]); // c[1].add(v[3]); // c[1].add(v[4]); // // c[2].add(v[5]); // c[2].add(v[3]); // // c[3].add(v[6]); // c[3].add(v[7]); // c[3].add(v[8]); testComponents(graph, v, c); } public void testComponents(UndirectedGraph graph, String[] vertices, List> c) { BicomponentClusterer finder = new BicomponentClusterer(); Set> bicomponents = finder.apply(graph); // check number of components assertEquals(bicomponents.size(), c.size()); // diagnostic; should be commented out for typical unit tests // for (int i = 0; i < bicomponents.size(); i++) // { // System.out.print("Component " + i + ": "); // Set bicomponent = bicomponents.getCluster(i); // for (Iterator iter = bicomponent.iterator(); iter.hasNext(); ) // { // Vertex w = (Vertex)iter.next(); // System.out.print(sl.getLabel(w) + " "); // } // System.out.println(); // } // System.out.println(); // make sure that each set in c[] is found in bicomponents List> clusterList = new ArrayList>(bicomponents); boolean found = false; for (int i = 0; i < c.size(); i++) { for (int j = 0; j < bicomponents.size(); j++) if (clusterList.get(j).equals(c.get(i))) { found = true; break; } assertTrue(found); } // make sure that each vertex is represented in >=1 element of bicomponents Set collapsedSet = new HashSet(); for(Set set : bicomponents) { collapsedSet.addAll(set); } for (String v : graph.getVertices()) { assertTrue(collapsedSet.contains(v)); // assertFalse(((LinkedHashSet)vset).get(v).isEmpty()); } } } TestEdgeBetweennessClusterer.java000066400000000000000000000043101276402340000365100ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/cluster/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.cluster; import java.util.Collection; import java.util.Set; import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseMultigraph; /** * @author Scott White */ public class TestEdgeBetweennessClusterer extends TestCase { public static Test suite() { return new TestSuite(TestEdgeBetweennessClusterer.class); } Supplier> graphFactory; Supplier vertexFactory; Supplier edgeFactory; @Override protected void setUp() { graphFactory = new Supplier>() { public Graph get() { return new SparseMultigraph(); } }; vertexFactory = new Supplier() { int n = 0; public Integer get() { return n++; } }; edgeFactory = new Supplier() { int n = 0; public Number get() { return n++; } }; } public void testRanker() { Graph graph = new SparseMultigraph(); for(int i=0; i<10; i++) { graph.addVertex(i+1); } int j=0; graph.addEdge(j++,1,2); graph.addEdge(j++,1,3); graph.addEdge(j++,2,3); graph.addEdge(j++,5,6); graph.addEdge(j++,5,7); graph.addEdge(j++,6,7); graph.addEdge(j++,8,10); graph.addEdge(j++,7,8); graph.addEdge(j++,7,10); graph.addEdge(j++,3,4); graph.addEdge(j++,4,6); graph.addEdge(j++,4,8); Assert.assertEquals(graph.getVertexCount(),10); Assert.assertEquals(graph.getEdgeCount(),12); EdgeBetweennessClusterer clusterer = new EdgeBetweennessClusterer(3); Collection> clusters = clusterer.apply(graph); Assert.assertEquals(clusters.size(),3); } } WeakComponentClustererTest.java000066400000000000000000000010261276402340000362140ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/clusterpackage edu.uci.ics.jung.algorithms.cluster; import junit.framework.TestCase; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.TestGraphs; public class WeakComponentClustererTest extends TestCase { Graph graph = TestGraphs.getDemoGraph(); public void testWeakComponent() { WeakComponentClusterer clusterer = new WeakComponentClusterer(); // Set> clusterSet = clusterer.apply(graph); // System.err.println("set is "+clusterSet); } } jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/filters/000077500000000000000000000000001276402340000301155ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/filters/impl/000077500000000000000000000000001276402340000310565ustar00rootroot00000000000000TestKNeighborhoodFilter.java000066400000000000000000000035441276402340000364000ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/filters/implpackage edu.uci.ics.jung.algorithms.filters.impl; /** * @author Tom Nelson */ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import edu.uci.ics.jung.algorithms.filters.Filter; import edu.uci.ics.jung.algorithms.filters.KNeighborhoodFilter; import edu.uci.ics.jung.algorithms.filters.KNeighborhoodFilter.EdgeType; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Graph; public class TestKNeighborhoodFilter extends TestCase { DirectedGraph graph; public static Test suite() { return new TestSuite(TestKNeighborhoodFilter.class); } @Override protected void setUp() { graph = new DirectedSparseMultigraph(); for(int i=0; i<7; i++) { graph.addVertex(i); } int j=0; graph.addEdge(j++, 0, 1); graph.addEdge(j++, 0, 2); graph.addEdge(j++, 2, 3); graph.addEdge(j++, 2, 4); graph.addEdge(j++, 3, 5); graph.addEdge(j++, 5, 6); graph.addEdge(j++, 5, 0); graph.addEdge(j++, 3, 0); graph.addEdge(j++, 6, 7); } public void testIn() { Filter filter = new KNeighborhoodFilter(0, 2, EdgeType.IN); Graph result = filter.apply(graph); assertEquals(result.getVertexCount(), 4); assertEquals(result.getEdgeCount(), 5); } public void testOut() { Filter filter = new KNeighborhoodFilter(0, 2, EdgeType.OUT); Graph result = filter.apply(graph); assertEquals(result.getVertexCount(), 5); assertEquals(result.getEdgeCount(), 5); } public void testInOut() { Filter filter = new KNeighborhoodFilter(0, 2, EdgeType.IN_OUT); Graph result = filter.apply(graph); assertEquals(result.getVertexCount(), 7); assertEquals(result.getEdgeCount(), 8); } } jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/flows/000077500000000000000000000000001276402340000275775ustar00rootroot00000000000000TestEdmondsKarpMaxFlow.java000066400000000000000000000134221276402340000347320ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/flows/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.flows; import java.util.HashMap; import java.util.Map; import java.util.Set; import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import com.google.common.base.Functions; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.util.EdgeType; /** * @author Scott White, Joshua O'Madadhain, Tom Nelson */ public class TestEdmondsKarpMaxFlow extends TestCase { public static Test suite() { return new TestSuite(TestEdmondsKarpMaxFlow.class); } @Override protected void setUp() { } public void testSanityChecks() { DirectedGraph g = new DirectedSparseMultigraph(); Number source = new Integer(1); Number sink = new Integer(2); g.addVertex(source); g.addVertex(sink); Number v = new Integer(3); DirectedGraph h = new DirectedSparseMultigraph(); Number w = new Integer(4); g.addVertex(w); try { new EdmondsKarpMaxFlow(g, source, source, null, null, null); fail("source and sink vertices not distinct"); } catch (IllegalArgumentException iae) {} try { new EdmondsKarpMaxFlow(h, source, w, null, null, null); fail("source and sink vertices not both part of specified graph"); } catch (IllegalArgumentException iae) {} try { new EdmondsKarpMaxFlow(g, source, v, null, null, null); fail("source and sink vertices not both part of specified graph"); } catch (IllegalArgumentException iae) {} } public void testSimpleFlow() { DirectedGraph graph = new DirectedSparseMultigraph(); Supplier edgeFactory = new Supplier() { int count = 0; public Number get() { return count++; } }; Map edgeCapacityMap = new HashMap(); for(int i=0; i<6; i++) { graph.addVertex(i); } Map edgeFlowMap = new HashMap(); graph.addEdge(edgeFactory.get(),0,1,EdgeType.DIRECTED); edgeCapacityMap.put(0, 16); graph.addEdge(edgeFactory.get(),0,2,EdgeType.DIRECTED); edgeCapacityMap.put(1,13); graph.addEdge(edgeFactory.get(),1,2,EdgeType.DIRECTED); edgeCapacityMap.put(2, 6); graph.addEdge(edgeFactory.get(),1,3,EdgeType.DIRECTED); edgeCapacityMap.put(3, 12); graph.addEdge(edgeFactory.get(),2,4,EdgeType.DIRECTED); edgeCapacityMap.put(4, 14); graph.addEdge(edgeFactory.get(),3,2,EdgeType.DIRECTED); edgeCapacityMap.put(5, 9); graph.addEdge(edgeFactory.get(),3,5,EdgeType.DIRECTED); edgeCapacityMap.put(6, 20); graph.addEdge(edgeFactory.get(),4,3,EdgeType.DIRECTED); edgeCapacityMap.put(7, 7); graph.addEdge(edgeFactory.get(),4,5,EdgeType.DIRECTED); edgeCapacityMap.put(8, 4); EdmondsKarpMaxFlow ek = new EdmondsKarpMaxFlow( graph, 0, 5, Functions.forMap(edgeCapacityMap, null), edgeFlowMap, edgeFactory); ek.evaluate(); assertTrue(ek.getMaxFlow() == 23); Set nodesInS = ek.getNodesInSourcePartition(); assertEquals(4,nodesInS.size()); for (Number v : nodesInS) { Assert.assertTrue(v.intValue() != 3 && v.intValue() != 5); } Set nodesInT = ek.getNodesInSinkPartition(); assertEquals(2,nodesInT.size()); for (Number v : nodesInT) { Assert.assertTrue(v.intValue() == 3 || v.intValue() == 5); } Set minCutEdges = ek.getMinCutEdges(); int maxFlow = 0; for (Number e : minCutEdges) { Number flow = edgeFlowMap.get(e); maxFlow += flow.intValue(); } Assert.assertEquals(23,maxFlow); Assert.assertEquals(3,minCutEdges.size()); } public void testAnotherSimpleFlow() { DirectedGraph graph = new DirectedSparseMultigraph(); Supplier edgeFactory = new Supplier() { int count=0; public Number get() { return count++; } }; Map edgeCapacityMap = new HashMap(); for(int i=0; i<6; i++) { graph.addVertex(i); } Map edgeFlowMap = new HashMap(); graph.addEdge(edgeFactory.get(),0,1,EdgeType.DIRECTED); edgeCapacityMap.put(0,5); graph.addEdge(edgeFactory.get(),0,2,EdgeType.DIRECTED); edgeCapacityMap.put(1,3); graph.addEdge(edgeFactory.get(),1,5,EdgeType.DIRECTED); edgeCapacityMap.put(2,2); graph.addEdge(edgeFactory.get(),1,2,EdgeType.DIRECTED); edgeCapacityMap.put(3,8); graph.addEdge(edgeFactory.get(),2,3,EdgeType.DIRECTED); edgeCapacityMap.put(4,4); graph.addEdge(edgeFactory.get(),2,4,EdgeType.DIRECTED); edgeCapacityMap.put(5,2); graph.addEdge(edgeFactory.get(),3,4,EdgeType.DIRECTED); edgeCapacityMap.put(6,3); graph.addEdge(edgeFactory.get(),3,5,EdgeType.DIRECTED); edgeCapacityMap.put(7,6); graph.addEdge(edgeFactory.get(),4,5,EdgeType.DIRECTED); edgeCapacityMap.put(8,1); EdmondsKarpMaxFlow ek = new EdmondsKarpMaxFlow( graph, 0, 5, Functions.forMap(edgeCapacityMap, null), edgeFlowMap, edgeFactory); ek.evaluate(); assertTrue(ek.getMaxFlow() == 7); } } jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/000077500000000000000000000000001276402340000306165ustar00rootroot00000000000000TestLattice2D.java000066400000000000000000000047321276402340000340230ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generatorspackage edu.uci.ics.jung.algorithms.generators; import junit.framework.Assert; import junit.framework.TestCase; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.UndirectedSparseMultigraph; public class TestLattice2D extends TestCase { protected Supplier> undirectedGraphFactory; protected Supplier> directedGraphFactory; protected Supplier vertexFactory; protected Supplier edgeFactory; @Override protected void setUp() { undirectedGraphFactory = new Supplier>() { public UndirectedGraph get() { return new UndirectedSparseMultigraph(); } }; directedGraphFactory = new Supplier>() { public DirectedGraph get() { return new DirectedSparseMultigraph(); } }; vertexFactory = new Supplier() { int count; public String get() { return Character.toString((char)('A'+count++)); } }; edgeFactory = new Supplier() { int count; public Number get() { return count++; } }; } public void testCreateSingular() { try { generate(1, 0, 0); fail("Did not reject lattice of size < 2"); } catch (IllegalArgumentException iae) {} } public void testget() { for (int i = 3; i <= 10; i++) { for (int j = 0; j < 2; j++) { for (int k = 0; k < 2; k++) { Lattice2DGenerator generator = generate(i, j, k); Graph graph = generator.get(); Assert.assertEquals(i*i, graph.getVertexCount()); checkEdgeCount(generator, graph); } } } } protected Lattice2DGenerator generate(int i, int j, int k) { return new Lattice2DGenerator( k == 0 ? undirectedGraphFactory : directedGraphFactory, vertexFactory, edgeFactory, i, j == 0 ? true : false); // toroidal? } protected void checkEdgeCount(Lattice2DGenerator generator, Graph graph) { Assert.assertEquals(generator.getGridEdgeCount(), graph.getEdgeCount()); } } jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/000077500000000000000000000000001276402340000320765ustar00rootroot00000000000000TestBarabasiAlbert.java000066400000000000000000000201631276402340000363620ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/* * Copyright (c) 2016, the JUNG Project and the Regents of the University * of California. All rights reserved. * * This software is open-source under the BSD license; see * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.generators.random; import java.util.HashSet; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.DirectedSparseGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseGraph; import edu.uci.ics.jung.graph.SparseMultigraph; import edu.uci.ics.jung.graph.UndirectedSparseGraph; import edu.uci.ics.jung.graph.UndirectedSparseMultigraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; /** * @author W. Giordano * @author Scott White * @author James Marchant */ public class TestBarabasiAlbert extends TestCase { protected Supplier> graphFactory; protected Supplier vertexFactory; protected Supplier edgeFactory; protected int init_vertices = 1; protected int edges_to_add_per_timestep = 1; protected int random_seed = 0; protected int num_timesteps = 10; protected int num_tests = 10; public static Test suite() { return new TestSuite(TestBarabasiAlbert.class); } @Override protected void setUp() { graphFactory = new Supplier>() { public Graph get() { return new SparseMultigraph(); } }; vertexFactory = new Supplier() { int count; public Integer get() { return count++; } }; edgeFactory = new Supplier() { int count; public Number get() { return count++; } }; } private Graph generateAndTestSizeOfBarabasiAlbertGraph( Supplier> graphFactory, Supplier vertexFactory, Supplier edgeFactory, int init_vertices, int edges_to_add_per_timestep, int random_seed, int num_tests) { BarabasiAlbertGenerator generator = new BarabasiAlbertGenerator(graphFactory, vertexFactory, edgeFactory, init_vertices, edges_to_add_per_timestep, random_seed, new HashSet()); Graph graph = null; // test the graph size over {@code num_tests} intervals of {@code // num_timesteps} timesteps for (int i = 1; i <= num_tests; i++) { generator.evolveGraph(num_timesteps); graph = generator.get(); assertEquals(graph.getVertexCount(), (i * num_timesteps) + init_vertices); assertEquals(graph.getEdgeCount(), edges_to_add_per_timestep * (i * num_timesteps)); } return graph; } public void testMultigraphCreation() { generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices, edges_to_add_per_timestep, random_seed, num_tests); } public void testDirectedMultigraphCreation() { graphFactory = new Supplier>() { public Graph get() { return new DirectedSparseMultigraph(); } }; generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices, edges_to_add_per_timestep, random_seed, num_tests); } public void testUndirectedMultigraphCreation() { graphFactory = new Supplier>() { public Graph get() { return new UndirectedSparseMultigraph(); } }; generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices, edges_to_add_per_timestep, random_seed, num_tests); } public void testGraphCreation() { graphFactory = new Supplier>() { public Graph get() { return new SparseGraph(); } }; generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices, edges_to_add_per_timestep, random_seed, num_tests); } public void testDirectedGraphCreation() { graphFactory = new Supplier>() { public Graph get() { return new DirectedSparseGraph(); } }; generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices, edges_to_add_per_timestep, random_seed, num_tests); } public void testUndirectedGraphCreation() { graphFactory = new Supplier>() { public Graph get() { return new UndirectedSparseGraph(); } }; generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices, edges_to_add_per_timestep, random_seed, num_tests); } /** * Due to the way the Barabasi-Albert algorithm works there should be no * opportunities for the generation of self-loops within the graph. */ public void testNoSelfLoops() { graphFactory = new Supplier>() { public Graph get() { return new UndirectedSparseGraph() { private static final long serialVersionUID = 1L; /** * This anonymous class works as an UndirectedSparseGraph * but will not accept edges that connect a vertex to * itself. */ @Override public boolean addEdge(Number edge, Pair endpoints, EdgeType edgeType) { if (endpoints == null) throw new IllegalArgumentException("endpoints may not be null"); Integer v1 = endpoints.getFirst(); Integer v2 = endpoints.getSecond(); if (v1.equals(v2)) throw new IllegalArgumentException("No self-loops"); else return super.addEdge(edge, endpoints, edgeType); } }; } }; generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices, edges_to_add_per_timestep, random_seed, num_tests); } public void testPreconditions() { // test init_vertices = 0 try { generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, 0, edges_to_add_per_timestep, random_seed, num_tests); fail(); } catch (IllegalArgumentException e) { } // test negative init_vertices try { generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, -1, edges_to_add_per_timestep, random_seed, num_tests); fail(); } catch (IllegalArgumentException e) { } // test edges_to_add_per_timestep = 0 try { generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices, 0, random_seed, num_tests); fail(); } catch (IllegalArgumentException e) { } // test negative edges_to_add_per_timestep try { generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices, -1, random_seed, num_tests); fail(); } catch (IllegalArgumentException e) { } // test edges_to_add_per_timestep > init_vertices try { generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, 2, 3, random_seed, num_tests); fail(); } catch (IllegalArgumentException e) { } } /** * Every node should have an out-degree AT LEAST equal to the number of * edges added per timestep (dependent on if it is directed or undirected). */ public void testEveryNodeHasCorrectMinimumNumberOfEdges() { Graph graph = generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices, edges_to_add_per_timestep, random_seed, num_tests); for (Integer v : graph.getVertices()) { assertTrue(graph.outDegree(v) >= edges_to_add_per_timestep); } } /** * Check that not every edge goes to one node; the in-degree of any node * should be strictly less than the number of edges. */ public void testNotEveryEdgeToOneNode() { Graph graph = generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices, edges_to_add_per_timestep, random_seed, num_tests); for (Integer v : graph.getVertices()) { assertTrue(graph.inDegree(v) < graph.getEdgeCount()); } } } TestEppsteinPowerLawGenerator.java000066400000000000000000000071101276402340000406400ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.generators.random; import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseMultigraph; /** * @author Scott White */ public class TestEppsteinPowerLawGenerator extends TestCase { Supplier> graphFactory; Supplier vertexFactory; Supplier edgeFactory; public static Test suite() { return new TestSuite(TestEppsteinPowerLawGenerator.class); } @Override protected void setUp() { graphFactory = new Supplier>() { public Graph get() { return new SparseMultigraph(); } }; vertexFactory = new Supplier() { int count; public Integer get() { return count++; } }; edgeFactory = new Supplier() { int count; public Number get() { return count++; } }; } public void testSimpleDirectedCase() { for (int r=0; r<10; r++) { EppsteinPowerLawGenerator generator = new EppsteinPowerLawGenerator(graphFactory, vertexFactory, edgeFactory, 10,40,r); generator.setSeed(2); Graph graph = generator.get(); Assert.assertEquals(graph.getVertexCount(),10); Assert.assertEquals(graph.getEdgeCount(),40); } } // TODO: convert what is needed for this test // public void testPowerLawProperties() { // // //long start = System.currentTimeMillis(); // EppsteinPowerLawGenerator generator = new EppsteinPowerLawGenerator(vertexFactory, edgeFactory, // 500,1500,100000); // generator.setSeed(5); // Graph graph = (Graph) generator.generateGraph(); // //long stop = System.currentTimeMillis(); // //System.out.println((stop-start)/1000l); // // DoubleArrayList degreeList = DegreeDistributions.getOutdegreeValues(graph.getVertices()); // int maxDegree = (int) Descriptive.max(degreeList); // Histogram degreeHistogram = GraphStatistics.createHistogram(degreeList,0,maxDegree,1); // //for (int index=0;index degreeHistogram.binHeight(2) + degreeHistogram.binHeight(3)); // // generator = new EppsteinPowerLawGenerator(500,1500,0); // graph = (Graph) generator.generateGraph(); // degreeList = DegreeDistributions.getOutdegreeValues(graph.getVertices()); // maxDegree = (int) Descriptive.max(degreeList); // degreeHistogram = GraphStatistics.createHistogram(degreeList,0,maxDegree,1); // //for (int index=0;index> graphFactory; Supplier vertexFactory; Supplier edgeFactory; public static Test suite() { return new TestSuite(TestErdosRenyi.class); } @Override protected void setUp() { graphFactory = new Supplier>() { public UndirectedGraph get() { return new UndirectedSparseMultigraph(); } }; vertexFactory = new Supplier() { int count; public String get() { return Character.toString((char)('A'+count++)); } }; edgeFactory = new Supplier() { int count; public Number get() { return count++; } }; } public void test() { int numVertices = 100; int total = 0; for (int i = 1; i <= 10; i++) { ErdosRenyiGenerator generator = new ErdosRenyiGenerator(graphFactory, vertexFactory, edgeFactory, numVertices,0.1); generator.setSeed(0); Graph graph = generator.get(); Assert.assertTrue(graph.getVertexCount() == numVertices); total += graph.getEdgeCount(); } total /= 10.0; Assert.assertTrue(total > 495-50 && total < 495+50); } } TestKleinberg.java000066400000000000000000000022271276402340000354270ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/randompackage edu.uci.ics.jung.algorithms.generators.random; import junit.framework.Assert; import edu.uci.ics.jung.algorithms.generators.Lattice2DGenerator; import edu.uci.ics.jung.algorithms.generators.TestLattice2D; import edu.uci.ics.jung.graph.Graph; /** * * @author Joshua O'Madadhain */ public class TestKleinberg extends TestLattice2D { @Override protected Lattice2DGenerator generate(int i, int j, int k) { return new KleinbergSmallWorldGenerator( k == 0 ? undirectedGraphFactory : directedGraphFactory, vertexFactory, edgeFactory, i, // rows i, // columns 0.1, // clustering exponent j == 0 ? true : false); // toroidal? } @Override protected void checkEdgeCount(Lattice2DGenerator generator, Graph graph) { Assert.assertEquals( generator.getGridEdgeCount() + ((KleinbergSmallWorldGenerator)generator).getConnectionCount() * graph.getVertexCount(), graph.getEdgeCount()); } } jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/importance/000077500000000000000000000000001276402340000306065ustar00rootroot00000000000000TestBetweennessCentrality.java000066400000000000000000000110371276402340000365550ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/importance/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.importance; import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseGraph; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.UndirectedSparseGraph; /** * @author Scott White */ public class TestBetweennessCentrality extends TestCase { public static Test suite() { return new TestSuite(TestBetweennessCentrality.class); } @Override protected void setUp() {} // private static E getEdge(Graph g, int v1Index, int v2Index, BidiMap id) { // V v1 = id.getKey(v1Index); // V v2 = id.getKey(v2Index); // return g.findEdge(v1, v2); // } public void testRanker() { UndirectedGraph graph = new UndirectedSparseGraph(); for(int i=0; i<9; i++) { graph.addVertex(i); } int edge = 0; graph.addEdge(edge++, 0,1); graph.addEdge(edge++, 0,6); graph.addEdge(edge++, 1,2); graph.addEdge(edge++, 1,3); graph.addEdge(edge++, 2,4); graph.addEdge(edge++, 3,4); graph.addEdge(edge++, 4,5); graph.addEdge(edge++, 5,8); graph.addEdge(edge++, 7,8); graph.addEdge(edge++, 6,7); BetweennessCentrality bc = new BetweennessCentrality(graph); bc.setRemoveRankScoresOnFinalize(false); bc.evaluate(); // System.out.println("ranking"); // for (int i = 0; i < 9; i++) // System.out.println(String.format("%d: %f", i, bc.getVertexRankScore(i))); Assert.assertEquals(bc.getVertexRankScore(0)/28.0,0.2142,.001); Assert.assertEquals(bc.getVertexRankScore(1)/28.0,0.2797,.001); Assert.assertEquals(bc.getVertexRankScore(2)/28.0,0.0892,.001); Assert.assertEquals(bc.getVertexRankScore(3)/28.0,0.0892,.001); Assert.assertEquals(bc.getVertexRankScore(4)/28.0,0.2797,.001); Assert.assertEquals(bc.getVertexRankScore(5)/28.0,0.2142,.001); Assert.assertEquals(bc.getVertexRankScore(6)/28.0,0.1666,.001); Assert.assertEquals(bc.getVertexRankScore(7)/28.0,0.1428,.001); Assert.assertEquals(bc.getVertexRankScore(8)/28.0,0.1666,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(0,1)), 10.66666,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(0,1)),10.66666,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(0,6)),9.33333,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(1,2)),6.5,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(1,3)),6.5,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(2,4)),6.5,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(3,4)),6.5,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(4,5)),10.66666,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(5,8)),9.33333,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(6,7)),8.0,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(7,8)),8.0,.001); } public void testRankerDirected() { DirectedGraph graph = new DirectedSparseGraph(); for(int i=0; i<5; i++) { graph.addVertex(i); } int edge=0; graph.addEdge(edge++, 0,1); graph.addEdge(edge++, 1,2); graph.addEdge(edge++, 3,1); graph.addEdge(edge++, 4,2); BetweennessCentrality bc = new BetweennessCentrality(graph); bc.setRemoveRankScoresOnFinalize(false); bc.evaluate(); Assert.assertEquals(bc.getVertexRankScore(0),0,.001); Assert.assertEquals(bc.getVertexRankScore(1),2,.001); Assert.assertEquals(bc.getVertexRankScore(2),0,.001); Assert.assertEquals(bc.getVertexRankScore(3),0,.001); Assert.assertEquals(bc.getVertexRankScore(4),0,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(0,1)),2,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(1,2)),3,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(3,1)),2,.001); Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(4,2)),1,.001); } } TestKStepMarkov.java000066400000000000000000000047261276402340000344510ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/importance/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.importance; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; /** * @author Scott White * @author Tom Nelson - adapted to jung2 */ public class TestKStepMarkov extends TestCase { public final static String EDGE_WEIGHT = "edu.uci.ics.jung.edge_weight"; DirectedGraph mGraph; double[][] mTransitionMatrix; Map edgeWeights = new HashMap(); public static Test suite() { return new TestSuite(TestKStepMarkov.class); } @Override protected void setUp() { mGraph = new DirectedSparseMultigraph(); mTransitionMatrix = new double[][] {{0.0, 0.5, 0.5}, {1.0/3.0, 0.0, 2.0/3.0}, {1.0/3.0, 2.0/3.0, 0.0}}; for (int i = 0; i < mTransitionMatrix.length; i++) mGraph.addVertex(i); for (int i = 0; i < mTransitionMatrix.length; i++) { for (int j = 0; j < mTransitionMatrix[i].length; j++) { if (mTransitionMatrix[i][j] > 0) { int edge = i*mTransitionMatrix.length+j; mGraph.addEdge(edge, i, j); edgeWeights.put(edge, mTransitionMatrix[i][j]); } } } } public void testRanker() { Set priors = new HashSet(); priors.add(1); priors.add(2); KStepMarkov ranker = new KStepMarkov(mGraph,priors,2,edgeWeights); // ranker.evaluate(); // System.out.println(ranker.getIterations()); for (int i = 0; i < 10; i++) { // System.out.println(ranker.getIterations()); // for (Number n : mGraph.getVertices()) // System.out.println(n + ": " + ranker.getVertexRankScore(n)); ranker.step(); } List> rankings = ranker.getRankings(); // System.out.println(rankings); } } TestWeightedNIPaths.java000066400000000000000000000052701276402340000352250ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/importance/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.importance; import java.util.HashSet; import java.util.Set; import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; /** * @author Scott White, adapted to jung2 by Tom Nelson */ public class TestWeightedNIPaths extends TestCase { Supplier vertexFactory; Supplier edgeFactory; public static Test suite() { return new TestSuite(TestWeightedNIPaths.class); } @Override protected void setUp() { vertexFactory = new Supplier() { char a = 'A'; public String get() { return Character.toString(a++); }}; edgeFactory = new Supplier() { int count; public Number get() { return count++; }}; } public void testRanker() { DirectedGraph graph = new DirectedSparseMultigraph(); for(int i=0; i<5; i++) { graph.addVertex(vertexFactory.get()); } graph.addEdge(edgeFactory.get(), "A", "B"); graph.addEdge(edgeFactory.get(), "A", "C"); graph.addEdge(edgeFactory.get(), "A", "D"); graph.addEdge(edgeFactory.get(), "B", "A"); graph.addEdge(edgeFactory.get(), "B", "E"); graph.addEdge(edgeFactory.get(), "B", "D"); graph.addEdge(edgeFactory.get(), "C", "A"); graph.addEdge(edgeFactory.get(), "C", "E"); graph.addEdge(edgeFactory.get(), "C", "D"); graph.addEdge(edgeFactory.get(), "D", "A"); graph.addEdge(edgeFactory.get(), "D", "B"); graph.addEdge(edgeFactory.get(), "D", "C"); graph.addEdge(edgeFactory.get(), "D", "E"); Set priors = new HashSet(); priors.add("A"); WeightedNIPaths ranker = new WeightedNIPaths(graph, vertexFactory, edgeFactory, 2.0,3,priors); ranker.evaluate(); Assert.assertEquals(ranker.getRankings().get(0).rankScore,0.277787,.0001); Assert.assertEquals(ranker.getRankings().get(1).rankScore,0.222222,.0001); Assert.assertEquals(ranker.getRankings().get(2).rankScore,0.166676,.0001); Assert.assertEquals(ranker.getRankings().get(3).rankScore,0.166676,.0001); Assert.assertEquals(ranker.getRankings().get(4).rankScore,0.166676,.0001); } }jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/layout/000077500000000000000000000000001276402340000277625ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/layout/FRLayout2Test.java000066400000000000000000000016271276402340000332620ustar00rootroot00000000000000package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.util.HashSet; import java.util.Set; import junit.framework.TestCase; import edu.uci.ics.jung.algorithms.layout.util.Relaxer; import edu.uci.ics.jung.algorithms.layout.util.VisRunner; import edu.uci.ics.jung.algorithms.util.IterativeContext; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.TestGraphs; public class FRLayout2Test extends TestCase { protected Set seedVertices = new HashSet(); public void testFRLayout() { Graph graph = TestGraphs.getOneComponentGraph(); Layout layout = new FRLayout2(graph); layout.setSize(new Dimension(600,600)); if(layout instanceof IterativeContext) { layout.initialize(); Relaxer relaxer = new VisRunner((IterativeContext)layout); relaxer.prerelax(); relaxer.relax(); } } } jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/layout/FRLayoutTest.java000066400000000000000000000016251276402340000331760ustar00rootroot00000000000000package edu.uci.ics.jung.algorithms.layout; import java.awt.Dimension; import java.util.HashSet; import java.util.Set; import junit.framework.TestCase; import edu.uci.ics.jung.algorithms.layout.util.Relaxer; import edu.uci.ics.jung.algorithms.layout.util.VisRunner; import edu.uci.ics.jung.algorithms.util.IterativeContext; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.TestGraphs; public class FRLayoutTest extends TestCase { protected Set seedVertices = new HashSet(); public void testFRLayout() { Graph graph = TestGraphs.getOneComponentGraph(); Layout layout = new FRLayout(graph); layout.setSize(new Dimension(600,600)); if(layout instanceof IterativeContext) { layout.initialize(); Relaxer relaxer = new VisRunner((IterativeContext)layout); relaxer.prerelax(); relaxer.relax(); } } } jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/metrics/000077500000000000000000000000001276402340000301135ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/metrics/TestTriad.java000066400000000000000000000107761276402340000326740ustar00rootroot00000000000000package edu.uci.ics.jung.algorithms.metrics; import junit.framework.TestCase; import edu.uci.ics.jung.algorithms.metrics.TriadicCensus; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; public class TestTriad extends TestCase { public void testConfigurationFromPaper() { DirectedGraph g = new DirectedSparseMultigraph(); char u = 'u'; g.addVertex(u); char v = 'v'; g.addVertex(v); char w = 'w'; g.addVertex(w); g.addEdge(0, w, u); g.addEdge(1, u, v); g.addEdge(2, v, u); assertEquals(35, TriadicCensus.triCode(g, u, v, w)); assertEquals(7, TriadicCensus.triType(35)); assertEquals("111D", TriadicCensus.TRIAD_NAMES[7]); assertEquals(7, TriadicCensus.triType(TriadicCensus.triCode(g, u, w, v))); assertEquals(7, TriadicCensus.triType(TriadicCensus.triCode(g, v, u, w))); long[] counts = TriadicCensus.getCounts(g); for (int i = 1; i <= 16; i++) { if (i == 7) { assertEquals(1, counts[i]); } else { assertEquals(0, counts[i]); } } } public void testFourVertexGraph() { // we'll set up a graph of // t->u // u->v // and that's it. // total count: // 2: 1(t, u, w)(u, v, w) // 6: 1(t, u, v) // 1: 1(u, v, w) DirectedGraph g = new DirectedSparseMultigraph(); char u = 'u'; g.addVertex(u); char v = 'v'; g.addVertex(v); char w = 'w'; g.addVertex(w); char t = 't'; g.addVertex(t); g.addEdge(0, t, u ); g.addEdge(1, u, v ); long[] counts = TriadicCensus.getCounts(g); for (int i = 1; i <= 16; i++) { if( i == 2 ) { assertEquals("On " + i, 2, counts[i]); } else if (i == 6 || i == 1 ) { assertEquals("On " + i, 1, counts[i]); } else { assertEquals(0, counts[i]); } } // now let's tweak to // t->u, u->v, v->t // w->u, v->w g.addEdge(2, v, t ); g.addEdge(3, w, u ); g.addEdge(4, v, w ); // that's two 030Cs. it's a 021D (v-t, v-w) and an 021U (t-u, w-u) counts = TriadicCensus.getCounts(g); for (int i = 1; i <= 16; i++) { if( i == 10 /* 030C */ ) { assertEquals("On " + i, 2, counts[i]); } else if (i == 4 || i == 5 ) { assertEquals("On " + i, 1, counts[i]); } else { assertEquals("On " + i , 0, counts[i]); } } } public void testThreeDotsThreeDashes() { DirectedGraph g = new DirectedSparseMultigraph(); char u = 'u'; g.addVertex(u); char v = 'v'; g.addVertex(v); char w = 'w'; g.addVertex(w); long[] counts = TriadicCensus.getCounts(g); for (int i = 1; i <= 16; i++) { if (i == 1) { assertEquals(1, counts[i]); } else { assertEquals(0, counts[i]); } } g.addEdge(0, v, u); g.addEdge(1, u, v); g.addEdge(2, v, w); g.addEdge(3, w, v); g.addEdge(4, u, w); g.addEdge(5, w, u); counts = TriadicCensus.getCounts(g); for (int i = 1; i <= 16; i++) { if (i == 16) { assertEquals(1, counts[i]); } else { assertEquals("Count on " + i + " failed", 0, counts[i]); } } } /** **************Boring accounting for zero graphs*********** */ public void testNull() { DirectedGraph g = new DirectedSparseMultigraph(); long[] counts = TriadicCensus.getCounts(g); // t looks like a hashtable for the twelve keys for (int i = 1; i < TriadicCensus.MAX_TRIADS; i++) { assertEquals("Empty Graph doesn't have count 0", 0, counts[i]); } } public void testOneVertex() { DirectedGraph g = new DirectedSparseMultigraph(); g.addVertex('u'); long[] counts = TriadicCensus.getCounts(g); // t looks like a hashtable for the twelve keys for (int i = 1; i < TriadicCensus.MAX_TRIADS; i++) { assertEquals("One vertex Graph doesn't have count 0", 0, counts[i]); } } public void testTwoVertices() { DirectedGraph g = new DirectedSparseMultigraph(); char v1, v2; g.addVertex(v1 = 'u'); g.addVertex(v2 = 'v'); g.addEdge(0, v1, v2); long[] counts = TriadicCensus.getCounts(g); // t looks like a hashtable for the twelve keys for (int i = 1; i < TriadicCensus.MAX_TRIADS; i++) { assertEquals("Two vertex Graph doesn't have count 0", 0, counts[i]); } } } jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/000077500000000000000000000000001276402340000301115ustar00rootroot00000000000000TestBetweennessCentrality.java000066400000000000000000000120361276402340000360600ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/** * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Sep 17, 2008 * */ package edu.uci.ics.jung.algorithms.scoring; import junit.framework.TestCase; import com.google.common.base.Function; import edu.uci.ics.jung.graph.DirectedSparseGraph; import edu.uci.ics.jung.graph.Graph; /** * */ public class TestBetweennessCentrality extends TestCase { // public void testUndirected() { // UndirectedGraph graph = // new UndirectedSparseGraph(); // for(int i=0; i<9; i++) { // graph.addVertex(i); // } // // int edge = 0; // graph.addEdge(edge++, 0,1); // graph.addEdge(edge++, 0,6); // graph.addEdge(edge++, 1,2); // graph.addEdge(edge++, 1,3); // graph.addEdge(edge++, 2,4); // graph.addEdge(edge++, 3,4); // graph.addEdge(edge++, 4,5); // graph.addEdge(edge++, 5,8); // graph.addEdge(edge++, 7,8); // graph.addEdge(edge++, 6,7); // // BetweennessCentrality bc = // new BetweennessCentrality(graph); // //// System.out.println("scoring"); //// for (int i = 0; i < graph.getVertexCount(); i++) //// System.out.println(String.format("%d: %f", i, bc.getVertexScore(i))); // // Assert.assertEquals(bc.getVertexScore(0),6.000,.001); // Assert.assertEquals(bc.getVertexScore(1),7.833,.001); // Assert.assertEquals(bc.getVertexScore(2),2.500,.001); // Assert.assertEquals(bc.getVertexScore(3),2.500,.001); // Assert.assertEquals(bc.getVertexScore(4),7.833,.001); // Assert.assertEquals(bc.getVertexScore(5),6.000,.001); // Assert.assertEquals(bc.getVertexScore(6),4.666,.001); // Assert.assertEquals(bc.getVertexScore(7),4.000,.001); // Assert.assertEquals(bc.getVertexScore(8),4.666,.001); // // Assert.assertEquals(bc.getEdgeScore(graph.findEdge(0,1)),10.666,.001); // Assert.assertEquals(bc.getEdgeScore(graph.findEdge(0,6)),9.333,.001); // Assert.assertEquals(bc.getEdgeScore(graph.findEdge(1,2)),6.500,.001); // Assert.assertEquals(bc.getEdgeScore(graph.findEdge(1,3)),6.500,.001); // Assert.assertEquals(bc.getEdgeScore(graph.findEdge(2,4)),6.500,.001); // Assert.assertEquals(bc.getEdgeScore(graph.findEdge(3,4)),6.500,.001); // Assert.assertEquals(bc.getEdgeScore(graph.findEdge(4,5)),10.666,.001); // Assert.assertEquals(bc.getEdgeScore(graph.findEdge(5,8)),9.333,.001); // Assert.assertEquals(bc.getEdgeScore(graph.findEdge(6,7)),8.000,.001); // Assert.assertEquals(bc.getEdgeScore(graph.findEdge(7,8)),8.000,.001); // } // // public void testDirected() // { // DirectedGraph graph = new DirectedSparseGraph(); // for(int i=0; i<5; i++) // graph.addVertex(i); // // int edge=0; // graph.addEdge(edge++, 0,1); // graph.addEdge(edge++, 1,2); // graph.addEdge(edge++, 3,1); // graph.addEdge(edge++, 4,2); // // BetweennessCentrality bc = // new BetweennessCentrality(graph); // // Assert.assertEquals(bc.getVertexScore(0),0,.001); // Assert.assertEquals(bc.getVertexScore(1),2,.001); // Assert.assertEquals(bc.getVertexScore(2),0,.001); // Assert.assertEquals(bc.getVertexScore(3),0,.001); // Assert.assertEquals(bc.getVertexScore(4),0,.001); // // Assert.assertEquals(bc.getEdgeScore(graph.findEdge(0,1)),2,.001); // Assert.assertEquals(bc.getEdgeScore(graph.findEdge(1,2)),3,.001); // Assert.assertEquals(bc.getEdgeScore(graph.findEdge(3,1)),2,.001); // Assert.assertEquals(bc.getEdgeScore(graph.findEdge(4,2)),1,.001); // } public void testWeighted() { Graph graph = new DirectedSparseGraph(); for(int i=0; i<5; i++) graph.addVertex(i); char edge='a'; graph.addEdge(edge++, 0,1); graph.addEdge(edge++, 0,2); graph.addEdge(edge++, 2,3); graph.addEdge(edge++, 3,1); graph.addEdge(edge++, 1,4); final int weights[] = {1, 1, 1, 1, 1}; Function edge_weights = new Function() { public Integer apply(Character arg0) { return weights[arg0 - 'a']; } }; BetweennessCentrality bc = new BetweennessCentrality(graph, edge_weights); // System.out.println("scoring"); // System.out.println("(weighted)"); // System.out.println("vertices:"); // for (int i = 0; i < graph.getVertexCount(); i++) // System.out.println(String.format("%d: %f", i, bc.getVertexScore(i))); // System.out.println("edges:"); // for (int i = 0; i < graph.getEdgeCount(); i++) // { // char e = (char)(i + 'a'); // System.out.println(String.format("%c: (weight: %d), %f", e, // edge_weights.apply(e), bc.getEdgeScore(e))); // } } } jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestHITS.java000066400000000000000000000114541276402340000323700ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; /** * @author Scott White * @author Tom Nelson - adapted to jung2 */ public class TestHITS extends TestCase { DirectedGraph graph; public static Test suite() { return new TestSuite(TestHITS.class); } @Override protected void setUp() { graph = new DirectedSparseMultigraph(); for(int i=0; i<5; i++) { graph.addVertex(i); } int j=0; graph.addEdge(j++, 0, 1); graph.addEdge(j++, 1, 2); graph.addEdge(j++, 2, 3); graph.addEdge(j++, 3, 0); graph.addEdge(j++, 2, 1); } public void testRanker() { HITS ranker = new HITS(graph); for (int i = 0; i < 10; i++) { ranker.step(); // // check hub scores in terms of previous authority scores // Assert.assertEquals(t.transform(0).hub, // 0.5*ranker.getAuthScore(1) + 0.2*ranker.getAuthScore(4)); // Assert.assertEquals(t.transform(1).hub, // ranker.getAuthScore(2) + 0.2*ranker.getAuthScore(4)); // Assert.assertEquals(t.transform(2).hub, // 0.5*ranker.getAuthScore(1) + ranker.getAuthScore(3) + 0.2*ranker.getAuthScore(4)); // Assert.assertEquals(t.transform(3).hub, // ranker.getAuthScore(0) + 0.2*ranker.getAuthScore(4)); // Assert.assertEquals(t.transform(4).hub, // 0.2*ranker.getAuthScore(4)); // // // check authority scores in terms of previous hub scores // Assert.assertEquals(t.transform(0).authority, // ranker.getVertexScore(3) + 0.2*ranker.getVertexScore(4)); // Assert.assertEquals(t.transform(1).authority, // ranker.getVertexScore(0) + 0.5 * ranker.getVertexScore(2) + 0.2*ranker.getVertexScore(4)); // Assert.assertEquals(t.transform(2).authority, // ranker.getVertexScore(1) + 0.2*ranker.getVertexScore(4)); // Assert.assertEquals(t.transform(3).authority, // 0.5*ranker.getVertexScore(2) + 0.2*ranker.getVertexScore(4)); // Assert.assertEquals(t.transform(4).authority, // 0.2*ranker.getVertexScore(4)); // // verify that sums of each scores are 1.0 double auth_sum = 0; double hub_sum = 0; for (int j = 0; j < 5; j++) { // auth_sum += ranker.getAuthScore(j); // hub_sum += ranker.getVertexScore(j); // auth_sum += (ranker.getAuthScore(j) * ranker.getAuthScore(j)); // hub_sum += (ranker.getVertexScore(j) * ranker.getVertexScore(j)); HITS.Scores score = ranker.getVertexScore(j); auth_sum += score.authority * score.authority; hub_sum += score.hub * score.hub; } Assert.assertEquals(auth_sum, 1.0, .0001); Assert.assertEquals(hub_sum, 1.0, 0.0001); } ranker.evaluate(); Assert.assertEquals(ranker.getVertexScore(0).authority, 0, .0001); Assert.assertEquals(ranker.getVertexScore(1).authority, 0.8507, .001); Assert.assertEquals(ranker.getVertexScore(2).authority, 0.0, .0001); Assert.assertEquals(ranker.getVertexScore(3).authority, 0.5257, .001); Assert.assertEquals(ranker.getVertexScore(0).hub, 0.5257, .001); Assert.assertEquals(ranker.getVertexScore(1).hub, 0.0, .0001); Assert.assertEquals(ranker.getVertexScore(2).hub, 0.8507, .0001); Assert.assertEquals(ranker.getVertexScore(3).hub, 0.0, .0001); // the values below assume scores sum to 1 // (rather than that sum of squares of scores sum to 1) // Assert.assertEquals(ranker.getVertexScore(0).authority, 0, .0001); // Assert.assertEquals(ranker.getVertexScore(1).authority, 0.618, .001); // Assert.assertEquals(ranker.getVertexScore(2).authority, 0.0, .0001); // Assert.assertEquals(ranker.getVertexScore(3).authority, 0.3819, .001); // // Assert.assertEquals(ranker.getVertexScore(0).hub, 0.38196, .001); // Assert.assertEquals(ranker.getVertexScore(1).hub, 0.0, .0001); // Assert.assertEquals(ranker.getVertexScore(2).hub, 0.618, .0001); // Assert.assertEquals(ranker.getVertexScore(3).hub, 0.0, .0001); } } TestHITSWithPriors.java000066400000000000000000000042051276402340000343400ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import java.util.HashSet; import java.util.Set; import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import edu.uci.ics.jung.algorithms.scoring.util.ScoringUtils; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; /** * Tests HITSWithPriors. */ public class TestHITSWithPriors extends TestCase { DirectedGraph graph; Set roots; public static Test suite() { return new TestSuite(TestHITSWithPriors.class); } @Override protected void setUp() { graph = new DirectedSparseMultigraph(); for(int i=0; i<4; i++) { graph.addVertex(i); } int j=0; graph.addEdge(j++, 0, 1); graph.addEdge(j++, 1, 2); graph.addEdge(j++, 2, 3); graph.addEdge(j++, 3, 0); graph.addEdge(j++, 2, 1); roots = new HashSet(); roots.add(2); } public void testRankings() { HITSWithPriors ranker = new HITSWithPriors(graph, ScoringUtils.getHITSUniformRootPrior(roots), 0.3); ranker.evaluate(); double[] expected_auth = {0.0, 0.765, 0.365, 0.530}; double[] expected_hub = {0.398, 0.190, 0.897, 0.0}; double hub_sum = 0; double auth_sum = 0; for (Number n : graph.getVertices()) { int i = n.intValue(); double auth = ranker.getVertexScore(i).authority; double hub = ranker.getVertexScore(i).hub; Assert.assertEquals(auth, expected_auth[i], 0.001); Assert.assertEquals(hub, expected_hub[i], 0.001); hub_sum += hub * hub; auth_sum += auth * auth; } Assert.assertEquals(1.0, hub_sum, 0.001); Assert.assertEquals(1.0, auth_sum, 0.001); } } TestKStepMarkov.java000066400000000000000000000041521276402340000337450ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoringpackage edu.uci.ics.jung.algorithms.scoring; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import junit.framework.TestCase; import com.google.common.base.Functions; import edu.uci.ics.jung.algorithms.scoring.util.ScoringUtils; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; public class TestKStepMarkov extends TestCase { DirectedGraph mGraph; double[][] mTransitionMatrix; Map edgeWeights = new HashMap(); @Override protected void setUp() { mGraph = new DirectedSparseMultigraph(); mTransitionMatrix = new double[][] {{0.0, 0.5, 0.5}, {1.0/3.0, 0.0, 2.0/3.0}, {1.0/3.0, 2.0/3.0, 0.0}}; for (int i = 0; i < mTransitionMatrix.length; i++) mGraph.addVertex(i); for (int i = 0; i < mTransitionMatrix.length; i++) { for (int j = 0; j < mTransitionMatrix[i].length; j++) { if (mTransitionMatrix[i][j] > 0) { int edge = i*mTransitionMatrix.length+j; mGraph.addEdge(edge, i, j); edgeWeights.put(edge, mTransitionMatrix[i][j]); } } } } public void testRanker() { Set priors = new HashSet(); priors.add(1); priors.add(2); KStepMarkov ranker = new KStepMarkov(mGraph, Functions.forMap(edgeWeights), ScoringUtils.getUniformRootPrior(priors),2); // ranker.evaluate(); // System.out.println(ranker.getIterations()); for (int i = 0; i < 10; i++) { // System.out.println(ranker.getIterations()); // for (Number n : mGraph.getVertices()) // System.out.println(n + ": " + ranker.getVertexScore(n)); ranker.step(); } // List> rankings = ranker.getRankings(); // System.out.println("New version:"); // System.out.println(rankings); } } jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestPageRank.java000066400000000000000000000051061276402340000333060ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import java.util.HashMap; import java.util.Map; import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import com.google.common.base.Functions; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Graph; /** * @author Joshua O'Madadhain */ public class TestPageRank extends TestCase { private Map edgeWeights; private DirectedGraph graph; private Supplier edgeFactory; public static Test suite() { return new TestSuite(TestPageRank.class); } @Override protected void setUp() { edgeWeights = new HashMap(); edgeFactory = new Supplier() { int i=0; public Integer get() { return i++; }}; } private void addEdge(Graph G, Integer v1, Integer v2, double weight) { Integer edge = edgeFactory.get(); graph.addEdge(edge, v1, v2); edgeWeights.put(edge, weight); } public void testRanker() { graph = new DirectedSparseMultigraph(); for(int i=0; i<4; i++) { graph.addVertex(i); } addEdge(graph,0,1,1.0); addEdge(graph,1,2,1.0); addEdge(graph,2,3,0.5); addEdge(graph,3,1,1.0); addEdge(graph,2,1,0.5); PageRankWithPriors pr = new PageRank(graph, Functions.forMap(edgeWeights), 0); pr.evaluate(); Assert.assertEquals(pr.getVertexScore(0), 0.0, pr.getTolerance()); Assert.assertEquals(pr.getVertexScore(1), 0.4, pr.getTolerance()); Assert.assertEquals(pr.getVertexScore(2), 0.4, pr.getTolerance()); Assert.assertEquals(pr.getVertexScore(3), 0.2, pr.getTolerance()); // Assert.assertTrue(NumericalPrecision.equal(((Ranking)ranker.getRankings().get(0)).rankScore,0.4,.001)); // Assert.assertTrue(NumericalPrecision.equal(((Ranking)ranker.getRankings().get(1)).rankScore,0.4,.001)); // Assert.assertTrue(NumericalPrecision.equal(((Ranking)ranker.getRankings().get(2)).rankScore,0.2,.001)); // Assert.assertTrue(NumericalPrecision.equal(((Ranking)ranker.getRankings().get(3)).rankScore,0,.001)); } } TestPageRankWithPriors.java000066400000000000000000000046531276402340000352700ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.scoring; import java.util.HashSet; import java.util.Set; import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.scoring.util.ScoringUtils; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Graph; /** * @author Scott White */ public class TestPageRankWithPriors extends TestCase { // private Map edgeWeights; private DirectedGraph graph; private Supplier edgeFactory; public static Test suite() { return new TestSuite(TestPageRankWithPriors.class); } @Override protected void setUp() { // edgeWeights = new HashMap(); edgeFactory = new Supplier() { int i=0; public Integer get() { return i++; }}; } private void addEdge(Graph G, Integer v1, Integer v2) { Integer edge = edgeFactory.get(); graph.addEdge(edge, v1, v2); // edgeWeights.put(edge, weight); } public void testGraphScoring() { graph = new DirectedSparseMultigraph(); double[] expected_score = new double[]{0.1157, 0.2463, 0.4724, 0.1653}; for(int i=0; i<4; i++) { graph.addVertex(i); } addEdge(graph,0,1); addEdge(graph,1,2); addEdge(graph,2,3); addEdge(graph,3,0); addEdge(graph,2,1); Set priors = new HashSet(); priors.add(2); PageRankWithPriors pr = new PageRankWithPriors(graph, ScoringUtils.getUniformRootPrior(priors), 0.3); pr.evaluate(); double score_sum = 0; for (int i = 0; i < graph.getVertexCount(); i++) { double score = pr.getVertexScore(i); Assert.assertEquals(expected_score[i], score, pr.getTolerance()); score_sum += score; } Assert.assertEquals(1.0, score_sum, pr.getTolerance() * graph.getVertexCount()); } public void testHypergraphScoring() { } } TestVoltageScore.java000066400000000000000000000042171276402340000341360ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/** * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Jul 14, 2008 * */ package edu.uci.ics.jung.algorithms.scoring; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import junit.framework.TestCase; import com.google.common.base.Functions; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedSparseMultigraph; /** * @author jrtom * */ public class TestVoltageScore extends TestCase { protected Graph g; @Override public void setUp() { g = new UndirectedSparseMultigraph(); for (int i = 0; i < 7; i++) { g.addVertex(i); } int j = 0; g.addEdge(j++,0,1); g.addEdge(j++,0,2); g.addEdge(j++,1,3); g.addEdge(j++,2,3); g.addEdge(j++,3,4); g.addEdge(j++,3,5); g.addEdge(j++,4,6); g.addEdge(j++,5,6); } public final void testCalculateVoltagesSourceTarget() { VoltageScorer vr = new VoltageScorer(g, Functions.constant(1), 0, 6); double[] voltages = {1.0, 0.75, 0.75, 0.5, 0.25, 0.25, 0}; vr.evaluate(); for (int i = 0; i < 7; i++) { assertEquals(vr.getVertexScore(i), voltages[i], 0.01); } } public final void testCalculateVoltagesSourcesTargets() { Map sources = new HashMap(); sources.put(0, new Double(1.0)); sources.put(1, new Double(0.5)); Set sinks = new HashSet(); sinks.add(6); sinks.add(5); VoltageScorer vr = new VoltageScorer(g, Functions.constant(1), sources, sinks); double[] voltages = {1.0, 0.5, 0.66, 0.33, 0.16, 0, 0}; vr.evaluate(); for (int i = 0; i < 7; i++) { assertEquals(vr.getVertexScore(i), voltages[i], 0.01); } } } jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/000077500000000000000000000000001276402340000311755ustar00rootroot00000000000000TestBFSDistanceLabeler.java000066400000000000000000000042171276402340000362010ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.algorithms.shortestpath; import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import edu.uci.ics.jung.algorithms.shortestpath.BFSDistanceLabeler; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedSparseMultigraph; /** * @author Scott White, adapted to jung2 by Tom Nelson */ public class TestBFSDistanceLabeler extends TestCase { public static Test suite() { return new TestSuite(TestBFSDistanceLabeler.class); } @Override protected void setUp() { } public void test() { Graph graph = new UndirectedSparseMultigraph(); for(int i=0; i<6; i++) { graph.addVertex(i); } int j = 0; graph.addEdge(j++,0,1); graph.addEdge(j++,0,5); graph.addEdge(j++,0,3); graph.addEdge(j++,0,4); graph.addEdge(j++,1,5); graph.addEdge(j++,3,4); graph.addEdge(j++,3,2); graph.addEdge(j++,5,2); Number root = 0; BFSDistanceLabeler labeler = new BFSDistanceLabeler(); labeler.labelDistances(graph,root); Assert.assertEquals(labeler.getPredecessors(root).size(),0); Assert.assertEquals(labeler.getPredecessors(1).size(),1); Assert.assertEquals(labeler.getPredecessors(2).size(),2); Assert.assertEquals(labeler.getPredecessors(3).size(),1); Assert.assertEquals(labeler.getPredecessors(4).size(),1); Assert.assertEquals(labeler.getPredecessors(5).size(),1); Assert.assertEquals(labeler.getDistance(graph,0),0); Assert.assertEquals(labeler.getDistance(graph,1),1); Assert.assertEquals(labeler.getDistance(graph,2),2); Assert.assertEquals(labeler.getDistance(graph,3),1); Assert.assertEquals(labeler.getDistance(graph,4),1); Assert.assertEquals(labeler.getDistance(graph,5),1); } }TestPrimMinimumSpanningTree.java000066400000000000000000000036771276402340000374170ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpathpackage edu.uci.ics.jung.algorithms.shortestpath; import junit.framework.TestCase; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.DelegateTree; import edu.uci.ics.jung.graph.Tree; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.UndirectedSparseMultigraph; public class TestPrimMinimumSpanningTree extends TestCase { public void testSimpleTree() { Tree tree = new DelegateTree(); tree.addVertex("A"); tree.addEdge(0,"A","B0"); tree.addEdge(1,"A","B1"); // System.err.println("tree = "+tree); PrimMinimumSpanningTree pmst = new PrimMinimumSpanningTree(DelegateTree.getFactory()); // Graph mst = pmst.apply(tree); // System.err.println("mst = "+mst); // assertEquals(tree.getVertices(), mst.getVertices()); // assertEquals(tree.getEdges(), mst.getEdges()); } public void testDAG() { DirectedGraph graph = new DirectedSparseMultigraph(); graph.addVertex("B0"); graph.addEdge(0, "A", "B0"); graph.addEdge(1, "A", "B1"); // System.err.println("graph = "+graph); PrimMinimumSpanningTree pmst = new PrimMinimumSpanningTree(DelegateTree.getFactory()); // Graph mst = pmst.apply(graph); // System.err.println("mst = "+mst); } public void testUAG() { UndirectedGraph graph = new UndirectedSparseMultigraph(); graph.addVertex("B0"); graph.addEdge(0, "A", "B0"); graph.addEdge(1, "A", "B1"); // System.err.println("graph = "+graph); PrimMinimumSpanningTree pmst = new PrimMinimumSpanningTree(DelegateTree.getFactory()); // Graph mst = pmst.apply(graph); // System.err.println("mst = "+mst); } } TestShortestPath.java000066400000000000000000000520111276402340000352500ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/* * Created on Aug 22, 2003 * */ package edu.uci.ics.jung.algorithms.shortestpath; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import junit.framework.TestCase; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Supplier; import com.google.common.collect.BiMap; import edu.uci.ics.jung.algorithms.util.Indexer; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.UndirectedSparseMultigraph; /** * @author Joshua O'Madadhain */ public class TestShortestPath extends TestCase { private DirectedGraph dg; private UndirectedGraph ug; // graph based on Weiss, _Data Structures and Algorithm Analysis_, // 1992, p. 292 private static int[][] edges = {{1,2,2}, {1,4,1}, // 0, 1 {2,4,3}, {2,5,10}, // 2, 3 {3,1,4}, {3,6,5}, // 4, 5 {4,3,2}, {4,5,2}, {4,6,8}, {4,7,4}, // 6,7,8,9 {5,7,6}, // 10 {7,6,1}, // 11 {8,9,4}, // (12) these three edges define a second connected component {9,10,1}, // 13 {10,8,2}}; // 14 private static Integer[][] ug_incomingEdges = { {null, new Integer(0), new Integer(6), new Integer(1), new Integer(7), new Integer(11), new Integer(9), null, null, null}, {new Integer(0), null, new Integer(6), new Integer(2), new Integer(7), new Integer(11), new Integer(9), null, null, null}, {new Integer(1), new Integer(2), null, new Integer(6), new Integer(7), new Integer(5), new Integer(9), null, null, null}, {new Integer(1), new Integer(2), new Integer(6), null, new Integer(7), new Integer(11), new Integer(9), null, null, null}, {new Integer(1), new Integer(2), new Integer(6), new Integer(7), null, new Integer(11), new Integer(10), null, null, null}, {new Integer(1), new Integer(2), new Integer(5), new Integer(9), new Integer(10), null, new Integer(11), null, null, null}, {new Integer(1), new Integer(2), new Integer(5), new Integer(9), new Integer(10), new Integer(11), null, null, null, null}, {null, null, null, null, null, null, null, null, new Integer(13), new Integer(14)}, {null, null, null, null, null, null, null, new Integer(14), null, new Integer(13)}, {null, null, null, null, null, null, null, new Integer(14), new Integer(13), null}, }; private static Integer[][] dg_incomingEdges = { {null, new Integer(0), new Integer(6), new Integer(1), new Integer(7), new Integer(11), new Integer(9), null, null, null}, {new Integer(4), null, new Integer(6), new Integer(2), new Integer(7), new Integer(11), new Integer(9), null, null, null}, {new Integer(4), new Integer(0), null, new Integer(1), new Integer(7), new Integer(5), new Integer(9), null, null, null}, {new Integer(4), new Integer(0), new Integer(6), null, new Integer(7), new Integer(11), new Integer(9), null, null, null}, {null, null, null, null, null, new Integer(11), new Integer(10), null, null, null}, {null, null, null, null, null, null, null, null, null, null}, {null, null, null, null, null, new Integer(11), null, null, null, null}, {null, null, null, null, null, null, null, null, new Integer(12), new Integer(13)}, {null, null, null, null, null, null, null, new Integer(14), null, new Integer(13)}, {null, null, null, null, null, null, null, new Integer(14), new Integer(12), null} }; private static double[][] dg_distances = { {0, 2, 3, 1, 3, 6, 5, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, {9, 0, 5, 3, 5, 8, 7, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, {4, 6, 0, 5, 7, 5, 9, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, {6, 8, 2, 0, 2, 5, 4, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 0, 7, 6, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 0, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 1, 0, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 0, 4, 5}, {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 3, 0, 1}, {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 2, 6, 0} }; private static double[][] ug_distances = { {0, 2, 3, 1, 3, 6, 5, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, {2, 0, 5, 3, 5, 8, 7, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, {3, 5, 0, 2, 4, 5, 6, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, {1, 3, 2, 0, 2, 5, 4, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, {3, 5, 4, 2, 0, 7, 6, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, {6, 8, 5, 5, 7, 0, 1, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, {5, 7, 6, 4, 6, 1, 0, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 0, 3, 2}, {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 3, 0, 1}, {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 2, 1, 0} }; private static Integer[][] shortestPaths1 = { null, {new Integer(0)}, {new Integer(1), new Integer(6)}, {new Integer(1)}, {new Integer(1), new Integer(7)}, {new Integer(1), new Integer(9), new Integer(11)}, {new Integer(1), new Integer(9)}, null, null, null }; private Map,Integer[]> edgeArrays; private Map edgeWeights; private Function nev; private Supplier vertexFactoryDG = new Supplier() { int count = 0; public String get() { return "V"+count++; }}; private Supplier vertexFactoryUG = new Supplier() { int count = 0; public String get() { return "U"+count++; }}; BiMap did; BiMap uid; @Override protected void setUp() { edgeWeights = new HashMap(); nev = Functions.forMap(edgeWeights); dg = new DirectedSparseMultigraph(); for(int i=0; icreate(dg.getVertices(), 1); Integer[] dg_array = new Integer[edges.length]; addEdges(dg, did, dg_array); ug = new UndirectedSparseMultigraph(); for(int i=0; icreate(ug.getVertices(),1); // GraphUtils.addVertices(ug, ug_distances.length); // Indexer.newIndexer(ug, 1); Integer[] ug_array = new Integer[edges.length]; addEdges(ug, uid, ug_array); edgeArrays = new HashMap,Integer[]>(); edgeArrays.put(dg, dg_array); edgeArrays.put(ug, ug_array); } @Override protected void tearDown() throws Exception { } public void exceptionTest(Graph g, BiMap indexer, int index) { DijkstraShortestPath dsp = new DijkstraShortestPath(g, nev); // Indexer id = Indexer.getIndexer(g); String start = indexer.inverse().get(index); Integer e = null; String v = "NOT IN GRAPH"; try { dsp.getDistance(start, v); fail("getDistance(): illegal destination vertex"); } catch (IllegalArgumentException iae) {} try { dsp.getDistance(v, start); fail("getDistance(): illegal source vertex"); } catch (IllegalArgumentException iae) {} try { dsp.getDistanceMap(v, 1); fail("getDistanceMap(): illegal source vertex"); } catch (IllegalArgumentException iae) {} try { dsp.getDistanceMap(start, 0); fail("getDistanceMap(): too few vertices requested"); } catch (IllegalArgumentException iae) {} try { dsp.getDistanceMap(start, g.getVertexCount()+1); fail("getDistanceMap(): too many vertices requested"); } catch (IllegalArgumentException iae) {} try { dsp.getIncomingEdge(start, v); fail("getIncomingEdge(): illegal destination vertex"); } catch (IllegalArgumentException iae) {} try { dsp.getIncomingEdge(v, start); fail("getIncomingEdge(): illegal source vertex"); } catch (IllegalArgumentException iae) {} try { dsp.getIncomingEdgeMap(v, 1); fail("getIncomingEdgeMap(): illegal source vertex"); } catch (IllegalArgumentException iae) {} try { dsp.getIncomingEdgeMap(start, 0); fail("getIncomingEdgeMap(): too few vertices requested"); } catch (IllegalArgumentException iae) {} try { dsp.getDistanceMap(start, g.getVertexCount()+1); fail("getIncomingEdgeMap(): too many vertices requested"); } catch (IllegalArgumentException iae) {} try { // test negative edge weight exception String v1 = indexer.inverse().get(1); String v2 = indexer.inverse().get(7); e = g.getEdgeCount()+1; g.addEdge(e, v1, v2); edgeWeights.put(e, -2); // e.addUserDatum("weight", new Double(-2), UserData.REMOVE); dsp.reset(); dsp.getDistanceMap(start); // for (Iterator it = g.getEdges().iterator(); it.hasNext(); ) // { // Edge edge = (Edge)it.next(); // double weight = ((Number)edge.getUserDatum("weight")).doubleValue(); // Pair p = edge.getEndpoints(); // int i = id.getIndex((Vertex)p.getFirst()); // int j = id.getIndex((Vertex)p.getSecond()); // System.out.print("(" + i + "," + j + "): " + weight); // if (weight < 0) // System.out.print(" *******"); // System.out.println(); // } fail("DijkstraShortestPath should not accept negative edge weights"); } catch (IllegalArgumentException iae) { g.removeEdge(e); } } public void testDijkstra() { setUp(); exceptionTest(dg, did, 1); setUp(); exceptionTest(ug, uid, 1); setUp(); getPathTest(dg, did, 1); setUp(); getPathTest(ug, uid, 1); for (int i = 1; i <= dg_distances.length; i++) { setUp(); weightedTest(dg, did, i, true); setUp(); weightedTest(dg, did, i, false); } for (int i = 1; i <= ug_distances.length; i++) { setUp(); weightedTest(ug, uid, i, true); setUp(); weightedTest(ug, uid, i, false); } } private void getPathTest(Graph g, BiMap indexer, int index) { DijkstraShortestPath dsp = new DijkstraShortestPath(g, nev); // Indexer id = Indexer.getIndexer(g); String start = indexer.inverse().get(index); Integer[] edge_array = edgeArrays.get(g); Integer[] incomingEdges1 = null; if (g instanceof DirectedGraph) incomingEdges1 = dg_incomingEdges[index-1]; if (g instanceof UndirectedGraph) incomingEdges1 = ug_incomingEdges[index-1]; assertEquals(incomingEdges1.length, g.getVertexCount()); // test getShortestPath(start, v) dsp.reset(); for (int i = 1; i <= incomingEdges1.length; i++) { List shortestPath = dsp.getPath(start, indexer.inverse().get(i)); Integer[] indices = shortestPaths1[i-1]; for (ListIterator iter = shortestPath.listIterator(); iter.hasNext(); ) { int j = iter.nextIndex(); Integer e = iter.next(); if (e != null) assertEquals(edge_array[indices[j].intValue()], e); else assertNull(indices[j]); } } } private void weightedTest(Graph g, BiMap indexer, int index, boolean cached) { // Indexer id = Indexer.getIndexer(g); String start = indexer.inverse().get(index); double[] distances1 = null; Integer[] incomingEdges1 = null; if (g instanceof DirectedGraph) { distances1 = dg_distances[index-1]; incomingEdges1 = dg_incomingEdges[index-1]; } if (g instanceof UndirectedGraph) { distances1 = ug_distances[index-1]; incomingEdges1 = ug_incomingEdges[index-1]; } assertEquals(distances1.length, g.getVertexCount()); assertEquals(incomingEdges1.length, g.getVertexCount()); DijkstraShortestPath dsp = new DijkstraShortestPath(g, nev, cached); Integer[] edge_array = edgeArrays.get(g); // test getDistance(start, v) for (int i = 1; i <= distances1.length; i++) { String v = indexer.inverse().get(i); Number n = dsp.getDistance(start, v); double d = distances1[i-1]; double dist; if (n == null) dist = Double.POSITIVE_INFINITY; else dist = n.doubleValue(); assertEquals(d, dist, .001); } // test getIncomingEdge(start, v) dsp.reset(); for (int i = 1; i <= incomingEdges1.length; i++) { String v = indexer.inverse().get(i); Integer e = dsp.getIncomingEdge(start, v); if (e != null) assertEquals(edge_array[incomingEdges1[i-1].intValue()], e); else assertNull(incomingEdges1[i-1]); } // test getDistanceMap(v) dsp.reset(); Map distances = dsp.getDistanceMap(start); assertTrue(distances.size() <= g.getVertexCount()); double d_prev = 0; // smallest possible distance Set reachable = new HashSet(); for (Iterator d_iter = distances.keySet().iterator(); d_iter.hasNext(); ) { String cur = d_iter.next(); double d_cur = ((Double)distances.get(cur)).doubleValue(); assertTrue(d_cur >= d_prev); d_prev = d_cur; int i = indexer.get(cur); assertEquals(distances1[i-1], d_cur, .001); reachable.add(cur); } // make sure that non-reachable vertices have no entries for (Iterator v_iter = g.getVertices().iterator(); v_iter.hasNext(); ) { String v = v_iter.next(); assertEquals(reachable.contains(v), distances.keySet().contains(v)); } // test getIncomingEdgeMap(v) dsp.reset(); Map incomingEdgeMap = dsp.getIncomingEdgeMap(start); assertTrue(incomingEdgeMap.size() <= g.getVertexCount()); for (Iterator e_iter = incomingEdgeMap.keySet().iterator(); e_iter.hasNext(); ) { String v = e_iter.next(); Integer e = incomingEdgeMap.get(v); int i = indexer.get(v); // if (e != null) // { // Pair endpoints = e.getEndpoints(); // int j = id.getIndex((Vertex)endpoints.getFirst()); // int k = id.getIndex((Vertex)endpoints.getSecond()); // System.out.print(i + ": (" + j + "," + k + "); "); // } // else // System.out.print(i + ": null; "); if (e != null) assertEquals(edge_array[incomingEdges1[i-1].intValue()], e); else assertNull(incomingEdges1[i-1]); } // test getDistanceMap(v, k) dsp.reset(); for (int i = 1; i <= distances1.length; i++) { distances = dsp.getDistanceMap(start, i); assertTrue(distances.size() <= i); d_prev = 0; // smallest possible distance reachable.clear(); for (Iterator d_iter = distances.keySet().iterator(); d_iter.hasNext(); ) { String cur = d_iter.next(); double d_cur = ((Double)distances.get(cur)).doubleValue(); assertTrue(d_cur >= d_prev); d_prev = d_cur; int j = indexer.get(cur); assertEquals(distances1[j-1], d_cur, .001); reachable.add(cur); } for (Iterator v_iter = g.getVertices().iterator(); v_iter.hasNext(); ) { String v = v_iter.next(); assertEquals(reachable.contains(v), distances.keySet().contains(v)); } } // test getIncomingEdgeMap(v, k) dsp.reset(); for (int i = 1; i <= incomingEdges1.length; i++) { incomingEdgeMap = dsp.getIncomingEdgeMap(start, i); assertTrue(incomingEdgeMap.size() <= i); for (Iterator e_iter = incomingEdgeMap.keySet().iterator(); e_iter.hasNext(); ) { String v = e_iter.next(); Integer e = incomingEdgeMap.get(v); int j = indexer.get(v); if (e != null) assertEquals(edge_array[incomingEdges1[j-1].intValue()], e); else assertNull(incomingEdges1[j-1]); } } } public void addEdges(Graph g, BiMap indexer, Integer[] edge_array) { // Indexer id = Indexer.getIndexer(g); for (int i = 0; i < edges.length; i++) { int[] edge = edges[i]; Integer e = i; g.addEdge(i, indexer.inverse().get(edge[0]), indexer.inverse().get(edge[1])); edge_array[i] = e; if (edge.length > 2) { edgeWeights.put(e, edge[2]); // e.addUserDatum("weight", edge[2]); } } } // private class UserDataEdgeWeight implements NumberEdgeValue // { // private Object ud_key; // // public UserDataEdgeWeight(Object key) // { // ud_key = key; // } // // /** // * @see edu.uci.ics.jung.utils.NumberEdgeValue#getNumber(edu.uci.ics.jung.graph.ArchetypeEdge) // */ // public Number getNumber(ArchetypeEdge e) // { // return (Number)e.getUserDatum(ud_key); // } // // /** // * @see edu.uci.ics.jung.utils.NumberEdgeValue#setNumber(edu.uci.ics.jung.graph.ArchetypeEdge, java.lang.Number) // */ // public void setNumber(ArchetypeEdge e, Number n) // { // throw new UnsupportedOperationException(); // } // } } TestUnweightedShortestPath.java000066400000000000000000000067201276402340000373020ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/* * Created on Aug 22, 2003 * */ package edu.uci.ics.jung.algorithms.shortestpath; import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import com.google.common.base.Supplier; import com.google.common.collect.BiMap; import edu.uci.ics.jung.algorithms.util.Indexer; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.UndirectedSparseMultigraph; /** * @author Scott White */ public class TestUnweightedShortestPath extends TestCase { private Supplier vertexFactory = new Supplier() { int count = 0; public String get() { return "V"+count++; }}; private Supplier edgeFactory = new Supplier() { int count = 0; public Integer get() { return count++; }}; BiMap id; @Override protected void setUp() { } public static Test suite() { return new TestSuite(TestUnweightedShortestPath.class); } public void testUndirected() { UndirectedGraph ug = new UndirectedSparseMultigraph(); for(int i=0; i<5; i++) { ug.addVertex(vertexFactory.get()); } id = Indexer.create(ug.getVertices()); // GraphUtils.addVertices(ug,5); // Indexer id = Indexer.getIndexer(ug); ug.addEdge(edgeFactory.get(), id.inverse().get(0), id.inverse().get(1)); ug.addEdge(edgeFactory.get(), id.inverse().get(1), id.inverse().get(2)); ug.addEdge(edgeFactory.get(), id.inverse().get(2), id.inverse().get(3)); ug.addEdge(edgeFactory.get(), id.inverse().get(0), id.inverse().get(4)); ug.addEdge(edgeFactory.get(), id.inverse().get(4), id.inverse().get(3)); UnweightedShortestPath usp = new UnweightedShortestPath(ug); Assert.assertEquals(usp.getDistance(id.inverse().get(0),id.inverse().get(3)).intValue(),2); Assert.assertEquals((usp.getDistanceMap(id.inverse().get(0)).get(id.inverse().get(3))).intValue(),2); Assert.assertNull(usp.getIncomingEdgeMap(id.inverse().get(0)).get(id.inverse().get(0))); Assert.assertNotNull(usp.getIncomingEdgeMap(id.inverse().get(0)).get(id.inverse().get(3))); } public void testDirected() { DirectedGraph dg = new DirectedSparseMultigraph(); for(int i=0; i<5; i++) { dg.addVertex(vertexFactory.get()); } id = Indexer.create(dg.getVertices()); dg.addEdge(edgeFactory.get(), id.inverse().get(0), id.inverse().get(1)); dg.addEdge(edgeFactory.get(), id.inverse().get(1), id.inverse().get(2)); dg.addEdge(edgeFactory.get(), id.inverse().get(2), id.inverse().get(3)); dg.addEdge(edgeFactory.get(), id.inverse().get(0), id.inverse().get(4)); dg.addEdge(edgeFactory.get(), id.inverse().get(4), id.inverse().get(3)); dg.addEdge(edgeFactory.get(), id.inverse().get(3), id.inverse().get(0)); UnweightedShortestPath usp = new UnweightedShortestPath(dg); Assert.assertEquals(usp.getDistance(id.inverse().get(0),id.inverse().get(3)).intValue(),2); Assert.assertEquals((usp.getDistanceMap(id.inverse().get(0)).get(id.inverse().get(3))).intValue(),2); Assert.assertNull(usp.getIncomingEdgeMap(id.inverse().get(0)).get(id.inverse().get(0))); Assert.assertNotNull(usp.getIncomingEdgeMap(id.inverse().get(0)).get(id.inverse().get(3))); } } jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/util/000077500000000000000000000000001276402340000274225ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/util/NotRandom.java000066400000000000000000000026451276402340000321750ustar00rootroot00000000000000package edu.uci.ics.jung.algorithms.util; import java.util.Random; /** * A decidedly non-random extension of {@code Random} that may be useful * for testing random algorithms that accept an instance of {@code Random} * as a parameter. This algorithm maintains internal counters which are * incremented after each call, and returns values which are functions of * those counter values. Thus the output is not only deterministic (as is * necessarily true of all software with no externalities) but precisely * predictable in distribution. * * @author Joshua O'Madadhain */ @SuppressWarnings("serial") public class NotRandom extends Random { private int i = 0; private int d = 0; private int size = 100; /** * Creates an instance with the specified sample size. * @param size the sample size */ public NotRandom(int size) { this.size = size; } /** * Returns the post-incremented value of the internal counter modulo n. */ @Override public int nextInt(int n) { return i++ % n; } /** * Returns the post-incremented value of the internal counter modulo * {@code size}, divided by {@code size}. */ @Override public double nextDouble() { return (d++ % size) / (double)size; } /** * Returns the post-incremented value of the internal counter modulo * {@code size}, divided by {@code size}. */ @Override public float nextFloat() { return (d++ % size) / (float)size; } } TestWeightedChoice.java000066400000000000000000000033501276402340000337220ustar00rootroot00000000000000jung-jung-2.1.1/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/util/** * Copyright (c) 2009, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Jan 13, 2009 * */ package edu.uci.ics.jung.algorithms.util; import java.util.HashMap; import java.util.Map; import junit.framework.TestCase; /** * @author jrtom * */ public class TestWeightedChoice extends TestCase { private WeightedChoice weighted_choice; private Map item_weights = new HashMap(); private Map item_counts = new HashMap(); @Override public void tearDown() { item_weights.clear(); item_counts.clear(); } private void initializeWeights(double[] weights) { item_weights.put("a", weights[0]); item_weights.put("b", weights[1]); item_weights.put("c", weights[2]); item_weights.put("d", weights[3]); for (String key : item_weights.keySet()) item_counts.put(key, 0); } private void runWeightedChoice() { weighted_choice = new WeightedChoice(item_weights, new NotRandom(100)); int max_iterations = 10000; for (int i = 0; i < max_iterations; i++) { String item = weighted_choice.nextItem(); int count = item_counts.get(item); item_counts.put(item, count+1); } for (String key : item_weights.keySet()) assertEquals((int)(item_weights.get(key) * max_iterations), item_counts.get(key).intValue()); } public void testUniform() { initializeWeights(new double[]{0.25, 0.25, 0.25, 0.25}); runWeightedChoice(); } public void testNonUniform() { initializeWeights(new double[]{0.45, 0.10, 0.13, 0.32}); runWeightedChoice(); } } jung-jung-2.1.1/jung-api/000077500000000000000000000000001276402340000151275ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/assembly.xml000066400000000000000000000006611276402340000174730ustar00rootroot00000000000000 dependencies tar.gz false / / false runtime jung-jung-2.1.1/jung-api/pom.xml000066400000000000000000000021771276402340000164530ustar00rootroot00000000000000 4.0.0 net.sf.jung jung-parent 2.1.1 jung-api JUNG - API Graph interfaces used by the JUNG project com.google.guava guava junit junit test maven-jar-plugin test-jar jung-jung-2.1.1/jung-api/src/000077500000000000000000000000001276402340000157165ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/main/000077500000000000000000000000001276402340000166425ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/main/java/000077500000000000000000000000001276402340000175635ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/main/java/edu/000077500000000000000000000000001276402340000203405ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/main/java/edu/uci/000077500000000000000000000000001276402340000211205ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/000077500000000000000000000000001276402340000216765ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/000077500000000000000000000000001276402340000226415ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/000077500000000000000000000000001276402340000237425ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/DirectedGraph.java000066400000000000000000000011471276402340000273150ustar00rootroot00000000000000/* * Created on Oct 17, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; /** * A tagging interface for implementations of Graph * that accept only directed edges. * * @author Tom Nelson - tomnelson@dev.java.net * * @param type specification for vertices * @param type specification for edges */ public interface DirectedGraph extends Graph { } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/Forest.java000066400000000000000000000076071276402340000260610ustar00rootroot00000000000000package edu.uci.ics.jung.graph; import java.util.Collection; /** * An interface for a graph which consists of a collection of rooted * directed acyclic graphs. * * @author Joshua O'Madadhain */ public interface Forest extends DirectedGraph { /** * Returns a view of this graph as a collection of Tree instances. * @return a view of this graph as a collection of Trees */ Collection> getTrees(); /** * Returns the parent of vertex in this tree. * (If vertex is the root, returns null.) * The parent of a vertex is defined as being its predecessor in the * (unique) shortest path from the root to this vertex. * This is a convenience method which is equivalent to * Graph.getPredecessors(vertex).iterator().next(). * * @param vertex the vertex whose parent is to be returned * @return the parent of vertex in this tree * @see Graph#getPredecessors(Object) * @see #getParentEdge(Object) */ public V getParent(V vertex); /** * Returns the edge connecting vertex to its parent in * this tree. * (If vertex is the root, returns null.) * The parent of a vertex is defined as being its predecessor in the * (unique) shortest path from the root to this vertex. * This is a convenience method which is equivalent to * Graph.getInEdges(vertex).iterator().next(), * and also to Graph.findEdge(vertex, getParent(vertex)). * * @param vertex the vertex whose incoming edge is to be returned * @return the edge connecting vertex to its parent, or * null if vertex is the root * * @see Graph#getInEdges(Object) * @see #getParent(Object) */ public E getParentEdge(V vertex); /** * Returns the children of vertex in this tree. * The children of a vertex are defined as being the successors of * that vertex on the respective (unique) shortest paths from the root to * those vertices. * This is syntactic (maple) sugar for getSuccessors(vertex). * @param vertex the vertex whose children are to be returned * @return the Collection of children of vertex * in this tree * @see Graph#getSuccessors(Object) * @see #getChildEdges(Object) */ public Collection getChildren(V vertex); /** * Returns the edges connecting vertex to its children * in this tree. * The children of a vertex are defined as being the successors of * that vertex on the respective (unique) shortest paths from the root to * those vertices. * This is syntactic (maple) sugar for getOutEdges(vertex). * @param vertex the vertex whose child edges are to be returned * @return the Collection of edges connecting * vertex to its children in this tree * @see Graph#getOutEdges(Object) * @see #getChildren(Object) */ public Collection getChildEdges(V vertex); /** * Returns the number of children that vertex has in this tree. * The children of a vertex are defined as being the successors of * that vertex on the respective (unique) shortest paths from the root to * those vertices. * This is syntactic (maple) sugar for getSuccessorCount(vertex). * @param vertex the vertex whose child edges are to be returned * @return the Collection of edges connecting * vertex to its children in this tree * @see #getChildEdges(Object) * @see #getChildren(Object) * @see Graph#getSuccessorCount(Object) */ public int getChildCount(V vertex); } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/Graph.java000066400000000000000000000257361276402340000256630ustar00rootroot00000000000000/* * Created on Oct 17, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.Collection; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * A graph consisting of a set of vertices of type V * set and a set of edges of type E. Edges of this * graph type have exactly two endpoints; whether these endpoints * must be distinct depends on the implementation. *

    * This interface permits, but does not enforce, any of the following * common variations of graphs: *

      *
    • directed and undirected edges *
    • vertices and edges with attributes (for example, weighted edges) *
    • vertices and edges of different types (for example, bipartite * or multimodal graphs) *
    • parallel edges (multiple edges which connect a single set of vertices) *
    • representations as matrices or as adjacency lists or adjacency maps *
    * Extensions or implementations of this interface * may enforce or disallow any or all of these variations. * *

    Definitions (with respect to a given vertex v): *

      *
    • incoming edge of v: an edge that can be traversed * from a neighbor of v to reach v *
    • outgoing edge of v: an edge that can be traversed * from v to reach some neighbor of v *
    • predecessor of v: a vertex at the other end of an * incoming edge of v *
    • successor of v: a vertex at the other end of an * outgoing edge of v *
    • *
    * * @author Joshua O'Madadhain */ public interface Graph extends Hypergraph { /** * Returns a Collection view of the incoming edges incident to vertex * in this graph. * @param vertex the vertex whose incoming edges are to be returned * @return a Collection view of the incoming edges incident * to vertex in this graph */ Collection getInEdges(V vertex); /** * Returns a Collection view of the outgoing edges incident to vertex * in this graph. * @param vertex the vertex whose outgoing edges are to be returned * @return a Collection view of the outgoing edges incident * to vertex in this graph */ Collection getOutEdges(V vertex); /** * Returns a Collection view of the predecessors of vertex * in this graph. A predecessor of vertex is defined as a vertex v * which is connected to * vertex by an edge e, where e is an outgoing edge of * v and an incoming edge of vertex. * @param vertex the vertex whose predecessors are to be returned * @return a Collection view of the predecessors of * vertex in this graph */ Collection getPredecessors(V vertex); /** * Returns a Collection view of the successors of vertex * in this graph. A successor of vertex is defined as a vertex v * which is connected to * vertex by an edge e, where e is an incoming edge of * v and an outgoing edge of vertex. * @param vertex the vertex whose predecessors are to be returned * @return a Collection view of the successors of * vertex in this graph */ Collection getSuccessors(V vertex); /** * Returns the number of incoming edges incident to vertex. * Equivalent to getInEdges(vertex).size(). * @param vertex the vertex whose indegree is to be calculated * @return the number of incoming edges incident to vertex */ int inDegree(V vertex); /** * Returns the number of outgoing edges incident to vertex. * Equivalent to getOutEdges(vertex).size(). * @param vertex the vertex whose outdegree is to be calculated * @return the number of outgoing edges incident to vertex */ int outDegree(V vertex); /** * Returns true if v1 is a predecessor of v2 in this graph. * Equivalent to v1.getPredecessors().contains(v2). * @param v1 the first vertex to be queried * @param v2 the second vertex to be queried * @return true if v1 is a predecessor of v2, and false otherwise. */ boolean isPredecessor(V v1, V v2); /** * Returns true if v1 is a successor of v2 in this graph. * Equivalent to v1.getSuccessors().contains(v2). * @param v1 the first vertex to be queried * @param v2 the second vertex to be queried * @return true if v1 is a successor of v2, and false otherwise. */ boolean isSuccessor(V v1, V v2); /** * Returns the number of predecessors that vertex has in this graph. * Equivalent to vertex.getPredecessors().size(). * @param vertex the vertex whose predecessor count is to be returned * @return the number of predecessors that vertex has in this graph */ int getPredecessorCount(V vertex); /** * Returns the number of successors that vertex has in this graph. * Equivalent to vertex.getSuccessors().size(). * @param vertex the vertex whose successor count is to be returned * @return the number of successors that vertex has in this graph */ int getSuccessorCount(V vertex); /** * If directed_edge is a directed edge in this graph, returns the source; * otherwise returns null. * The source of a directed edge d is defined to be the vertex for which * d is an outgoing edge. * directed_edge is guaranteed to be a directed edge if * its EdgeType is DIRECTED. * @param directed_edge the edge whose source is to be returned * @return the source of directed_edge if it is a directed edge in this graph, or null otherwise */ V getSource(E directed_edge); /** * If directed_edge is a directed edge in this graph, returns the destination; * otherwise returns null. * The destination of a directed edge d is defined to be the vertex * incident to d for which * d is an incoming edge. * directed_edge is guaranteed to be a directed edge if * its EdgeType is DIRECTED. * @param directed_edge the edge whose destination is to be returned * @return the destination of directed_edge if it is a directed edge in this graph, or null otherwise */ V getDest(E directed_edge); /** * Returns true if vertex is the source of edge. * Equivalent to getSource(edge).equals(vertex). * @param vertex the vertex to be queried * @param edge the edge to be queried * @return true iff vertex is the source of edge */ boolean isSource(V vertex, E edge); /** * Returns true if vertex is the destination of edge. * Equivalent to getDest(edge).equals(vertex). * @param vertex the vertex to be queried * @param edge the edge to be queried * @return true iff vertex is the destination of edge */ boolean isDest(V vertex, E edge); /** * Adds edge e to this graph such that it connects * vertex v1 to v2. * Equivalent to addEdge(e, new Pair(v1, v2)). * If this graph does not contain v1, v2, * or both, implementations may choose to either silently add * the vertices to the graph or throw an IllegalArgumentException. * If this graph assigns edge types to its edges, the edge type of * e will be the default for this graph. * See Hypergraph.addEdge() for a listing of possible reasons * for failure. * @param e the edge to be added * @param v1 the first vertex to be connected * @param v2 the second vertex to be connected * @return true if the add is successful, false otherwise * @see Hypergraph#addEdge(Object, Collection) * @see #addEdge(Object, Object, Object, EdgeType) */ boolean addEdge(E e, V v1, V v2); /** * Adds edge e to this graph such that it connects * vertex v1 to v2. * Equivalent to addEdge(e, new Pair(v1, v2)). * If this graph does not contain v1, v2, * or both, implementations may choose to either silently add * the vertices to the graph or throw an IllegalArgumentException. * If edgeType is not legal for this graph, this method will * throw IllegalArgumentException. * See Hypergraph.addEdge() for a listing of possible reasons * for failure. * @param e the edge to be added * @param v1 the first vertex to be connected * @param v2 the second vertex to be connected * @param edgeType the type to be assigned to the edge * @return true if the add is successful, false otherwise * @see Hypergraph#addEdge(Object, Collection) * @see #addEdge(Object, Object, Object) */ boolean addEdge(E e, V v1, V v2, EdgeType edgeType); /** * Returns the endpoints of edge as a Pair. * @param edge the edge whose endpoints are to be returned * @return the endpoints (incident vertices) of edge */ Pair getEndpoints(E edge); /** * Returns the vertex at the other end of edge from vertex. * (That is, returns the vertex incident to edge which is not vertex.) * @param vertex the vertex to be queried * @param edge the edge to be queried * @return the vertex at the other end of edge from vertex */ V getOpposite(V vertex, E edge); } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/GraphDecorator.java000066400000000000000000000201461276402340000275140ustar00rootroot00000000000000package edu.uci.ics.jung.graph; import java.io.Serializable; import java.util.Collection; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * An implementation of Graph that delegates its method calls to a * constructor-specified Graph instance. This is useful for adding * additional behavior (such as synchronization or unmodifiability) to an existing * instance. */ @SuppressWarnings("serial") public class GraphDecorator implements Graph, Serializable { protected Graph delegate; /** * Creates a new instance based on the provided {@code delegate}. * @param delegate the graph to which method calls will be delegated */ public GraphDecorator(Graph delegate) { this.delegate = delegate; } /** * @see edu.uci.ics.jung.graph.Hypergraph#addEdge(java.lang.Object, java.util.Collection) */ public boolean addEdge(E edge, Collection vertices) { return delegate.addEdge(edge, vertices); } /** * @see Hypergraph#addEdge(Object, Collection, EdgeType) */ public boolean addEdge(E edge, Collection vertices, EdgeType edge_type) { return delegate.addEdge(edge, vertices, edge_type); } /** * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object, edu.uci.ics.jung.graph.util.EdgeType) */ public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) { return delegate.addEdge(e, v1, v2, edgeType); } /** * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object) */ public boolean addEdge(E e, V v1, V v2) { return delegate.addEdge(e, v1, v2); } /** * @see edu.uci.ics.jung.graph.Hypergraph#addVertex(java.lang.Object) */ public boolean addVertex(V vertex) { return delegate.addVertex(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#isIncident(java.lang.Object, java.lang.Object) */ public boolean isIncident(V vertex, E edge) { return delegate.isIncident(vertex, edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#isNeighbor(java.lang.Object, java.lang.Object) */ public boolean isNeighbor(V v1, V v2) { return delegate.isNeighbor(v1, v2); } /** * @see edu.uci.ics.jung.graph.Hypergraph#degree(java.lang.Object) */ public int degree(V vertex) { return delegate.degree(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#findEdge(java.lang.Object, java.lang.Object) */ public E findEdge(V v1, V v2) { return delegate.findEdge(v1, v2); } /** * @see edu.uci.ics.jung.graph.Hypergraph#findEdgeSet(java.lang.Object, java.lang.Object) */ public Collection findEdgeSet(V v1, V v2) { return delegate.findEdgeSet(v1, v2); } /** * @see edu.uci.ics.jung.graph.Graph#getDest(java.lang.Object) */ public V getDest(E directed_edge) { return delegate.getDest(directed_edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount() */ public int getEdgeCount() { return delegate.getEdgeCount(); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount(EdgeType) */ public int getEdgeCount(EdgeType edge_type) { return delegate.getEdgeCount(edge_type); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getEdges() */ public Collection getEdges() { return delegate.getEdges(); } /** * @see edu.uci.ics.jung.graph.Graph#getEdges(edu.uci.ics.jung.graph.util.EdgeType) */ public Collection getEdges(EdgeType edgeType) { return delegate.getEdges(edgeType); } /** * @see edu.uci.ics.jung.graph.Graph#getEdgeType(java.lang.Object) */ public EdgeType getEdgeType(E edge) { return delegate.getEdgeType(edge); } /** * @see edu.uci.ics.jung.graph.Graph#getDefaultEdgeType() */ public EdgeType getDefaultEdgeType() { return delegate.getDefaultEdgeType(); } /** * @see edu.uci.ics.jung.graph.Graph#getEndpoints(java.lang.Object) */ public Pair getEndpoints(E edge) { return delegate.getEndpoints(edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentCount(java.lang.Object) */ public int getIncidentCount(E edge) { return delegate.getIncidentCount(edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentEdges(java.lang.Object) */ public Collection getIncidentEdges(V vertex) { return delegate.getIncidentEdges(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentVertices(java.lang.Object) */ public Collection getIncidentVertices(E edge) { return delegate.getIncidentVertices(edge); } /** * @see edu.uci.ics.jung.graph.Graph#getInEdges(java.lang.Object) */ public Collection getInEdges(V vertex) { return delegate.getInEdges(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getNeighborCount(java.lang.Object) */ public int getNeighborCount(V vertex) { return delegate.getNeighborCount(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getNeighbors(java.lang.Object) */ public Collection getNeighbors(V vertex) { return delegate.getNeighbors(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getOpposite(java.lang.Object, java.lang.Object) */ public V getOpposite(V vertex, E edge) { return delegate.getOpposite(vertex, edge); } /** * @see edu.uci.ics.jung.graph.Graph#getOutEdges(java.lang.Object) */ public Collection getOutEdges(V vertex) { return delegate.getOutEdges(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getPredecessorCount(java.lang.Object) */ public int getPredecessorCount(V vertex) { return delegate.getPredecessorCount(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getPredecessors(java.lang.Object) */ public Collection getPredecessors(V vertex) { return delegate.getPredecessors(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getSource(java.lang.Object) */ public V getSource(E directed_edge) { return delegate.getSource(directed_edge); } /** * @see edu.uci.ics.jung.graph.Graph#getSuccessorCount(java.lang.Object) */ public int getSuccessorCount(V vertex) { return delegate.getSuccessorCount(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getSuccessors(java.lang.Object) */ public Collection getSuccessors(V vertex) { return delegate.getSuccessors(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getVertexCount() */ public int getVertexCount() { return delegate.getVertexCount(); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getVertices() */ public Collection getVertices() { return delegate.getVertices(); } /** * @see edu.uci.ics.jung.graph.Graph#inDegree(java.lang.Object) */ public int inDegree(V vertex) { return delegate.inDegree(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#isDest(java.lang.Object, java.lang.Object) */ public boolean isDest(V vertex, E edge) { return delegate.isDest(vertex, edge); } /** * @see edu.uci.ics.jung.graph.Graph#isPredecessor(java.lang.Object, java.lang.Object) */ public boolean isPredecessor(V v1, V v2) { return delegate.isPredecessor(v1, v2); } /** * @see edu.uci.ics.jung.graph.Graph#isSource(java.lang.Object, java.lang.Object) */ public boolean isSource(V vertex, E edge) { return delegate.isSource(vertex, edge); } /** * @see edu.uci.ics.jung.graph.Graph#isSuccessor(java.lang.Object, java.lang.Object) */ public boolean isSuccessor(V v1, V v2) { return delegate.isSuccessor(v1, v2); } /** * @see edu.uci.ics.jung.graph.Graph#outDegree(java.lang.Object) */ public int outDegree(V vertex) { return delegate.outDegree(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#removeEdge(java.lang.Object) */ public boolean removeEdge(E edge) { return delegate.removeEdge(edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#removeVertex(java.lang.Object) */ public boolean removeVertex(V vertex) { return delegate.removeVertex(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#containsEdge(java.lang.Object) */ public boolean containsEdge(E edge) { return delegate.containsEdge(edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#containsVertex(java.lang.Object) */ public boolean containsVertex(V vertex) { return delegate.containsVertex(vertex); } } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/Hypergraph.java000066400000000000000000000471061276402340000267260ustar00rootroot00000000000000/* * Created on Oct 17, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.Collection; import edu.uci.ics.jung.graph.util.EdgeType; /** * A hypergraph, consisting of a set of vertices of type V * and a set of hyperedges of type E which connect the vertices. * This is the base interface for all JUNG graph types. *

    * This interface permits, but does not enforce, any of the following * common variations of graphs: *

      *
    • hyperedges (edges which connect a set of vertices of any size) *
    • edges (these have have exactly two endpoints, which may or may not be distinct) *
    • self-loops (edges which connect exactly one vertex) *
    • directed and undirected edges *
    • vertices and edges with attributes (for example, weighted edges) *
    • vertices and edges with different constraints or properties (for example, bipartite * or multimodal graphs) *
    • parallel edges (multiple edges which connect a single set of vertices) *
    • internal representations as matrices or as adjacency lists or adjacency maps *
    * Extensions or implementations of this interface * may enforce or disallow any or all of these variations. *

    Notes: *

      *
    • The collections returned by Hypergraph instances * should be treated in general as if read-only. While they are not contractually * guaranteed (or required) to be immutable, * this interface does not define the outcome if they are mutated. * Mutations should be done via {add,remove}{Edge,Vertex}, or * in the constructor. *
    • *
    * * @author Joshua O'Madadhain */ public interface Hypergraph { /** * Returns a view of all edges in this graph. In general, this * obeys the Collection contract, and therefore makes no guarantees * about the ordering of the vertices within the set. * @return a Collection view of all edges in this graph */ Collection getEdges(); /** * Returns a view of all vertices in this graph. In general, this * obeys the Collection contract, and therefore makes no guarantees * about the ordering of the vertices within the set. * @return a Collection view of all vertices in this graph */ Collection getVertices(); /** * Returns true if this graph's vertex collection contains vertex. * Equivalent to getVertices().contains(vertex). * @param vertex the vertex whose presence is being queried * @return true iff this graph contains a vertex vertex */ boolean containsVertex(V vertex); /** * Returns true if this graph's edge collection contains edge. * Equivalent to getEdges().contains(edge). * @param edge the edge whose presence is being queried * @return true iff this graph contains an edge edge */ boolean containsEdge(E edge); /** * Returns the number of edges in this graph. * @return the number of edges in this graph */ int getEdgeCount(); /** * Returns the number of vertices in this graph. * @return the number of vertices in this graph */ int getVertexCount(); /** * Returns the collection of vertices which are connected to vertex * via any edges in this graph. * If vertex is connected to itself with a self-loop, then * it will be included in the collection returned. * * @param vertex the vertex whose neighbors are to be returned * @return the collection of vertices which are connected to vertex, * or null if vertex is not present */ Collection getNeighbors(V vertex); /** * Returns the collection of edges in this graph which are connected to vertex. * * @param vertex the vertex whose incident edges are to be returned * @return the collection of edges which are connected to vertex, * or null if vertex is not present */ Collection getIncidentEdges(V vertex); /** * Returns the collection of vertices in this graph which are connected to edge. * Note that for some graph types there are guarantees about the size of this collection * (i.e., some graphs contain edges that have exactly two endpoints, which may or may * not be distinct). Implementations for those graph types may provide alternate methods * that provide more convenient access to the vertices. * * @param edge the edge whose incident vertices are to be returned * @return the collection of vertices which are connected to edge, * or null if edge is not present */ Collection getIncidentVertices(E edge); /** * Returns an edge that connects this vertex to v. * If this edge is not uniquely * defined (that is, if the graph contains more than one edge connecting * v1 to v2), any of these edges * may be returned. findEdgeSet(v1, v2) may be * used to return all such edges. * Returns null if either of the following is true: *
      *
    • v2 is not connected to v1 *
    • either v1 or v2 are not present in this graph *
    *

    Note: for purposes of this method, v1 is only considered to be connected to * v2 via a given directed edge e if * v1 == e.getSource() && v2 == e.getDest() evaluates to true. * (v1 and v2 are connected by an undirected edge u if * u is incident to both v1 and v2.) * * @param v1 the first endpoint of the returned edge * @param v2 the second endpoint of the returned edge * @return an edge that connects v1 to v2, * or null if no such edge exists (or either vertex is not present) * @see Hypergraph#findEdgeSet(Object, Object) */ E findEdge(V v1, V v2); /** * Returns all edges that connects this vertex to v. * If this edge is not uniquely * defined (that is, if the graph contains more than one edge connecting * v1 to v2), any of these edges * may be returned. findEdgeSet(v1, v2) may be * used to return all such edges. * Returns null if v2 is not connected to v1. *
    Returns an empty collection if either v1 or v2 * are not present in this graph. * *

    Note: for purposes of this method, v1 is only considered to be connected to * v2 via a given directed edge d if * v1 == d.getSource() && v2 == d.getDest() evaluates to true. * (v1 and v2 are connected by an undirected edge u if * u is incident to both v1 and v2.) * * @param v1 the first endpoint of the returned edge set * @param v2 the second endpoint of the returned edge set * @return a collection containing all edges that connect v1 to v2, * or null if either vertex is not present * @see Hypergraph#findEdge(Object, Object) */ Collection findEdgeSet(V v1, V v2); /** * Adds vertex to this graph. * Fails if vertex is null or already in the graph. * * @param vertex the vertex to add * @return true if the add is successful, and false otherwise * @throws IllegalArgumentException if vertex is null */ boolean addVertex(V vertex); /** * Adds edge to this graph. * Fails under the following circumstances: *

      *
    • edge is already an element of the graph *
    • either edge or vertices is null *
    • vertices has the wrong number of vertices for the graph type *
    • vertices are already connected by another edge in this graph, * and this graph does not accept parallel edges *
    * * @param edge the edge to add * @param vertices the vertices to which the edge will be connected * @return true if the add is successful, and false otherwise * @throws IllegalArgumentException if edge or vertices is null, * or if a different vertex set in this graph is already connected by edge, * or if vertices are not a legal vertex set for edge */ boolean addEdge(E edge, Collection vertices); /** * Adds edge to this graph with type edge_type. * Fails under the following circumstances: *
      *
    • edge is already an element of the graph *
    • either edge or vertices is null *
    • vertices has the wrong number of vertices for the graph type *
    • vertices are already connected by another edge in this graph, * and this graph does not accept parallel edges *
    • edge_type is not legal for this graph *
    * * @param edge edge to add to this graph * @param vertices vertices which are to be connected by this edge * @param edge_type type of edge to add * @return true if the add is successful, and false otherwise * @throws IllegalArgumentException if edge or vertices is null, * or if a different vertex set in this graph is already connected by edge, * or if vertices are not a legal vertex set for edge */ boolean addEdge(E edge, Collection vertices, EdgeType edge_type); /** * Removes vertex from this graph. * As a side effect, removes any edges e incident to vertex if the * removal of vertex would cause e to be incident to an illegal * number of vertices. (Thus, for example, incident hyperedges are not removed, but * incident edges--which must be connected to a vertex at both endpoints--are removed.) * *

    Fails under the following circumstances: *

      *
    • vertex is not an element of this graph *
    • vertex is null *
    * * @param vertex the vertex to remove * @return true if the removal is successful, false otherwise */ boolean removeVertex(V vertex); /** * Removes edge from this graph. * Fails if edge is null, or is otherwise not an element of this graph. * * @param edge the edge to remove * @return true if the removal is successful, false otherwise */ boolean removeEdge(E edge); /** * Returns true if v1 and v2 share an incident edge. * Equivalent to getNeighbors(v1).contains(v2). * * @param v1 the first vertex to test * @param v2 the second vertex to test * @return true if v1 and v2 share an incident edge */ boolean isNeighbor(V v1, V v2); /** * Returns true if vertex and edge * are incident to each other. * Equivalent to getIncidentEdges(vertex).contains(edge) and to * getIncidentVertices(edge).contains(vertex). * @param vertex vertex to test * @param edge edge to test * @return true if vertex and edge * are incident to each other */ boolean isIncident(V vertex, E edge); /** * Returns the number of edges incident to vertex. * Special cases of interest: *
      *
    • Incident self-loops are counted once. *
    • If there is only one edge that connects this vertex to * each of its neighbors (and vice versa), then the value returned * will also be equal to the number of neighbors that this vertex has * (that is, the output of getNeighborCount). *
    • If the graph is directed, then the value returned will be * the sum of this vertex's indegree (the number of edges whose * destination is this vertex) and its outdegree (the number * of edges whose source is this vertex), minus the number of * incident self-loops (to avoid double-counting). *
    *

    Equivalent to getIncidentEdges(vertex).size(). * * @param vertex the vertex whose degree is to be returned * @return the degree of this node * @see Hypergraph#getNeighborCount(Object) */ int degree(V vertex); /** * Returns the number of vertices that are adjacent to vertex * (that is, the number of vertices that are incident to edges in vertex's * incident edge set). * *

    Equivalent to getNeighbors(vertex).size(). * @param vertex the vertex whose neighbor count is to be returned * @return the number of neighboring vertices */ int getNeighborCount(V vertex); /** * Returns the number of vertices that are incident to edge. * For hyperedges, this can be any nonnegative integer; for edges this * must be 2 (or 1 if self-loops are permitted). * *

    Equivalent to getIncidentVertices(edge).size(). * @param edge the edge whose incident vertex count is to be returned * @return the number of vertices that are incident to edge. */ int getIncidentCount(E edge); /** * Returns the edge type of edge in this graph. * @param edge the edge whose type is to be returned * @return the EdgeType of edge, or null if edge has no defined type */ EdgeType getEdgeType(E edge); /** * Returns the default edge type for this graph. * * @return the default edge type for this graph */ EdgeType getDefaultEdgeType(); /** * Returns the collection of edges in this graph which are of type edge_type. * @param edge_type the type of edges to be returned * @return the collection of edges which are of type edge_type, or * null if the graph does not accept edges of this type * @see EdgeType */ Collection getEdges(EdgeType edge_type); /** * Returns the number of edges of type edge_type in this graph. * @param edge_type the type of edge for which the count is to be returned * @return the number of edges of type edge_type in this graph */ int getEdgeCount(EdgeType edge_type); /** * Returns a Collection view of the incoming edges incident to vertex * in this graph. * @param vertex the vertex whose incoming edges are to be returned * @return a Collection view of the incoming edges incident * to vertex in this graph */ Collection getInEdges(V vertex); /** * Returns a Collection view of the outgoing edges incident to vertex * in this graph. * @param vertex the vertex whose outgoing edges are to be returned * @return a Collection view of the outgoing edges incident * to vertex in this graph */ Collection getOutEdges(V vertex); /** * Returns the number of incoming edges incident to vertex. * Equivalent to getInEdges(vertex).size(). * @param vertex the vertex whose indegree is to be calculated * @return the number of incoming edges incident to vertex */ int inDegree(V vertex); /** * Returns the number of outgoing edges incident to vertex. * Equivalent to getOutEdges(vertex).size(). * @param vertex the vertex whose outdegree is to be calculated * @return the number of outgoing edges incident to vertex */ int outDegree(V vertex); /** * If directed_edge is a directed edge in this graph, returns the source; * otherwise returns null. * The source of a directed edge d is defined to be the vertex for which * d is an outgoing edge. * directed_edge is guaranteed to be a directed edge if * its EdgeType is DIRECTED. * @param directed_edge the edge whose source is to be returned * @return the source of directed_edge if it is a directed edge in this graph, or null otherwise */ V getSource(E directed_edge); /** * If directed_edge is a directed edge in this graph, returns the destination; * otherwise returns null. * The destination of a directed edge d is defined to be the vertex * incident to d for which * d is an incoming edge. * directed_edge is guaranteed to be a directed edge if * its EdgeType is DIRECTED. * @param directed_edge the edge whose destination is to be returned * @return the destination of directed_edge if it is a directed edge in this graph, or null otherwise */ V getDest(E directed_edge); /** * Returns a Collection view of the predecessors of vertex * in this graph. A predecessor of vertex is defined as a vertex v * which is connected to * vertex by an edge e, where e is an outgoing edge of * v and an incoming edge of vertex. * @param vertex the vertex whose predecessors are to be returned * @return a Collection view of the predecessors of * vertex in this graph */ Collection getPredecessors(V vertex); /** * Returns a Collection view of the successors of vertex * in this graph. A successor of vertex is defined as a vertex v * which is connected to * vertex by an edge e, where e is an incoming edge of * v and an outgoing edge of vertex. * @param vertex the vertex whose predecessors are to be returned * @return a Collection view of the successors of * vertex in this graph */ Collection getSuccessors(V vertex); } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/KPartiteGraph.java000066400000000000000000000022771276402340000273220ustar00rootroot00000000000000/* * Created on May 24, 2008 * * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.Collection; import com.google.common.base.Predicate; /** * An interface for graphs whose vertices are each members of one of 2 or more * disjoint sets (partitions), and whose edges connect only vertices in distinct * partitions. * * @author Joshua O'Madadhain */ public interface KPartiteGraph extends Graph { /** * Returns all vertices which satisfy the specified partition predicate. * @param partition Predicate which defines a partition * @return all vertices satisfying partition */ public Collection getVertices(Predicate partition); /** * Returns the set of Predicate instances which define this graph's partitions. * @return the set of Predicate instances which define this graph's partitions */ public Collection> getPartitions(); } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/MultiGraph.java000066400000000000000000000007301276402340000266610ustar00rootroot00000000000000/* * Created on Aug 31, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; /** * A tagging interface which indicates that the implementing graph accepts * parallel edges. * * @author Joshua O'Madadhain */ public interface MultiGraph {} jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/ObservableGraph.java000066400000000000000000000075661276402340000276710ustar00rootroot00000000000000package edu.uci.ics.jung.graph; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import edu.uci.ics.jung.graph.event.GraphEvent; import edu.uci.ics.jung.graph.event.GraphEventListener; import edu.uci.ics.jung.graph.util.EdgeType; /** * A decorator class for graphs which generates events * * @author Joshua O'Madadhain */ @SuppressWarnings("serial") public class ObservableGraph extends GraphDecorator { List> listenerList = Collections.synchronizedList(new LinkedList>()); /** * Creates a new instance based on the provided {@code delegate}. * * @param delegate the graph on which this class operates */ public ObservableGraph(Graph delegate) { super(delegate); } /** * Adds {@code l} as a listener to this graph. * * @param l the listener to add */ public void addGraphEventListener(GraphEventListener l) { listenerList.add(l); } /** * Removes {@code l} as a listener to this graph. * * @param l the listener to remove */ public void removeGraphEventListener(GraphEventListener l) { listenerList.remove(l); } protected void fireGraphEvent(GraphEvent evt) { for(GraphEventListener listener : listenerList) { listener.handleGraphEvent(evt); } } /** * @see edu.uci.ics.jung.graph.Hypergraph#addEdge(java.lang.Object, java.util.Collection) */ @Override public boolean addEdge(E edge, Collection vertices) { boolean state = super.addEdge(edge, vertices); if(state) { GraphEvent evt = new GraphEvent.Edge(delegate, GraphEvent.Type.EDGE_ADDED, edge); fireGraphEvent(evt); } return state; } /** * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object, edu.uci.ics.jung.graph.util.EdgeType) */ @Override public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) { boolean state = super.addEdge(e, v1, v2, edgeType); if(state) { GraphEvent evt = new GraphEvent.Edge(delegate, GraphEvent.Type.EDGE_ADDED, e); fireGraphEvent(evt); } return state; } /** * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object) */ @Override public boolean addEdge(E e, V v1, V v2) { boolean state = super.addEdge(e, v1, v2); if(state) { GraphEvent evt = new GraphEvent.Edge(delegate, GraphEvent.Type.EDGE_ADDED, e); fireGraphEvent(evt); } return state; } /** * @see edu.uci.ics.jung.graph.Hypergraph#addVertex(java.lang.Object) */ @Override public boolean addVertex(V vertex) { boolean state = super.addVertex(vertex); if(state) { GraphEvent evt = new GraphEvent.Vertex(delegate, GraphEvent.Type.VERTEX_ADDED, vertex); fireGraphEvent(evt); } return state; } /** * @see edu.uci.ics.jung.graph.Hypergraph#removeEdge(java.lang.Object) */ @Override public boolean removeEdge(E edge) { boolean state = delegate.removeEdge(edge); if(state) { GraphEvent evt = new GraphEvent.Edge(delegate, GraphEvent.Type.EDGE_REMOVED, edge); fireGraphEvent(evt); } return state; } /** * @see edu.uci.ics.jung.graph.Hypergraph#removeVertex(java.lang.Object) */ @Override public boolean removeVertex(V vertex) { // remove all incident edges first, so that the appropriate events will // be fired (otherwise they'll be removed inside {@code delegate.removeVertex} // and the events will not be fired) Collection incident_edges = new ArrayList(delegate.getIncidentEdges(vertex)); for (E e : incident_edges) this.removeEdge(e); boolean state = delegate.removeVertex(vertex); if(state) { GraphEvent evt = new GraphEvent.Vertex(delegate, GraphEvent.Type.VERTEX_REMOVED, vertex); fireGraphEvent(evt); } return state; } } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/Tree.java000066400000000000000000000032671276402340000255140ustar00rootroot00000000000000/* * Created on Feb 3, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; /** * A subtype of Graph which is a (directed, rooted) tree. * What we refer to as a "tree" here is actually (in the terminology of graph theory) a * rooted tree. (That is, there is a designated single vertex--the root--from which we measure * the shortest path to each vertex, which we call its depth; the maximum over all such * depths is the tree's height. Note that for a tree, there is exactly * one unique path from the root to any vertex.) * * @author Joshua O'Madadhain */ public interface Tree extends Forest { /** * Returns the (unweighted) distance of vertex * from the root of this tree. * @param vertex the vertex whose depth is to be returned. * @return the length of the shortest unweighted path * from vertex to the root of this tree * @see #getHeight() */ public int getDepth(V vertex); /** * Returns the maximum depth in this tree. * @return the maximum depth in this tree * @see #getDepth(Object) */ public int getHeight(); /** * Returns the root of this tree. * The root is defined to be the vertex (designated either at the tree's * creation time, or as the first vertex to be added) with respect to which * vertex depth is measured. * @return the root of this tree */ public V getRoot(); } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/UndirectedGraph.java000066400000000000000000000007221276402340000276560ustar00rootroot00000000000000/* * Created on Oct 17, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; /** * A tagging interface for extensions of Graph that * accept only undirected edges. */ public interface UndirectedGraph extends Graph {} jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/event/000077500000000000000000000000001276402340000250635ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/event/GraphEvent.java000066400000000000000000000044521276402340000277760ustar00rootroot00000000000000package edu.uci.ics.jung.graph.event; import edu.uci.ics.jung.graph.Graph; /** * * * @author tom nelson * * @param the vertex type * @param the edge type */ public abstract class GraphEvent { protected Graph source; protected Type type; /** * Creates an instance with the specified {@code source} graph and {@code Type} * (vertex/edge addition/removal). * * @param source the graph whose event this is * @param type the type of event this is */ public GraphEvent(Graph source, Type type) { this.source = source; this.type = type; } /** * Types of graph events. */ public static enum Type { VERTEX_ADDED, VERTEX_REMOVED, EDGE_ADDED, EDGE_REMOVED } /** * An event type pertaining to graph vertices. */ public static class Vertex extends GraphEvent { protected V vertex; /** * Creates a graph event for the specified graph, vertex, and type. * * @param source the graph whose event this is * @param type the type of event this is * @param vertex the vertex involved in this event */ public Vertex(Graph source, Type type, V vertex) { super(source,type); this.vertex = vertex; } /** * @return the vertex associated with this event */ public V getVertex() { return vertex; } @Override public String toString() { return "GraphEvent type:"+type+" for "+vertex; } } /** * An event type pertaining to graph edges. */ public static class Edge extends GraphEvent { protected E edge; /** * Creates a graph event for the specified graph, edge, and type. * * @param source the graph whose event this is * @param type the type of event this is * @param edge the edge involved in this event */ public Edge(Graph source, Type type, E edge) { super(source,type); this.edge = edge; } /** * @return the edge associated with this event. */ public E getEdge() { return edge; } @Override public String toString() { return "GraphEvent type:"+type+" for "+edge; } } /** * @return the source */ public Graph getSource() { return source; } /** * @return the type */ public Type getType() { return type; } } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/event/GraphEventListener.java000066400000000000000000000007731276402340000315060ustar00rootroot00000000000000package edu.uci.ics.jung.graph.event; import java.util.EventListener; /** * An interface for classes that listen for graph events. */ public interface GraphEventListener extends EventListener { /** * Method called by the process generating a graph event to which * this instance is listening. The implementor of this interface * is responsible for deciding what behavior is appropriate. * * @param evt the graph event to be handled */ void handleGraphEvent(GraphEvent evt); } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/event/package.html000066400000000000000000000005401276402340000273430ustar00rootroot00000000000000

    Support for generating events in response to graph actions, especially mutations. jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/package.html000066400000000000000000000043261276402340000262300ustar00rootroot00000000000000

    Interfaces for the JUNG graph types, and some representative implementations.

    A graph consists of a set of vertices set and a set of edges which connect the vertices. The base interface is Hypergraph, which defines the most general type of graph; other interfaces (Graph, DirectedGraph, etc.) define more restrictive graph types.

    Vertex and edge types are specified at compile type using Java 1.5 generics.

    Types of graphs which are supported include (but are not limited to)

    • edges (these have have exactly two endpoints, which may or may not be distinct)
    • self-loops (edges which connect exactly one vertex)
    • directed and undirected edges
    • vertices and edges with attributes (for example, weighted edges)
    • vertices and edges with different constraints or properties (examples: trees, bipartite graphs, or multimodal)
    • parallel edges (multiple edges which connect a single set of vertices)
    • internal representations as matrices or as adjacency lists or adjacency maps
    • internal representations that order their elements according to insertion time, natural ordering, or a specified Comparator
    Extensions or implementations of this interface may enforce or disallow any or all of these variations.

    Notes:

    • The collections returned by graph instances should be treated in general as if read-only. While they are not contractually guaranteed (or required) to be immutable, these interfaces do not define the outcome if they are mutated. Mutations should be done via {add,remove}{Edge,Vertex}, or in the constructor.
    • "Wrapper" graphs are available through GraphDecorator; these are useful if you want to create a graph implementation that uses another implementation to do the work, and adds some extra behavior. (One example: ObservableGraph, which notifies registered listeners when graph mutations occur.)
    jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/util/000077500000000000000000000000001276402340000247175ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/util/Context.java000066400000000000000000000024171276402340000272120ustar00rootroot00000000000000package edu.uci.ics.jung.graph.util; /** * A class that is used to link together a graph element and a specific graph. * Provides appropriate implementations of hashCode and equals. */ public class Context { @SuppressWarnings("rawtypes") private static Context instance = new Context(); /** * The graph element which defines this context. */ public G graph; /** * The edge element which defines this context. */ public E element; /** * @param the graph type * @param the element type * @param graph the graph for which the instance is created * @param element the element for which the instance is created * @return an instance of this type for the specified graph and element */ @SuppressWarnings("unchecked") public static Context getInstance(G graph, E element) { instance.graph = graph; instance.element = element; return instance; } @Override public int hashCode() { return graph.hashCode() ^ element.hashCode(); } @SuppressWarnings("rawtypes") @Override public boolean equals(Object o) { if (!(o instanceof Context)) return false; Context context = (Context) o; return context.graph.equals(graph) && context.element.equals(element); } } DefaultParallelEdgeIndexFunction.java000066400000000000000000000107761276402340000340420ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/util/* * Created on Sep 24, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph.util; import static com.google.common.base.Preconditions.checkNotNull; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import edu.uci.ics.jung.graph.Graph; /** * A class which creates and maintains indices for parallel edges. * Parallel edges are defined here to be the collection of edges * that are returned by v.findEdgeSet(w) for some * v and w. * *

    At this time, users are responsible for resetting the indices * (by calling reset()) if changes to the * graph make it appropriate. * * @author Joshua O'Madadhain * @author Tom Nelson * */ public class DefaultParallelEdgeIndexFunction implements EdgeIndexFunction { protected Map,E>, Integer> edge_index = new HashMap,E>, Integer>(); private DefaultParallelEdgeIndexFunction() { } /** * @param the vertex type * @param the edge type * @return an instance of this class */ public static DefaultParallelEdgeIndexFunction getInstance() { return new DefaultParallelEdgeIndexFunction(); } /** * Returns the index for e in graph. * Calculates the indices for e and for all edges parallel * to e, if they are not already assigned. */ public int getIndex(Graph graph, E e) { checkNotNull(graph, "graph must not be null"); checkNotNull(e, "'e' must not be null"); Integer index = edge_index.get(Context.,E>getInstance(graph,e)); //edge_index.get(e); if(index == null) { Pair endpoints = graph.getEndpoints(e); V u = endpoints.getFirst(); V v = endpoints.getSecond(); if(u.equals(v)) { index = getIndex(graph, e, v); } else { index = getIndex(graph, e, u, v); } } return index.intValue(); } protected int getIndex(Graph graph, E e, V v, V u) { Collection commonEdgeSet = new HashSet(graph.getIncidentEdges(u)); commonEdgeSet.retainAll(graph.getIncidentEdges(v)); for(Iterator iterator=commonEdgeSet.iterator(); iterator.hasNext(); ) { E edge = iterator.next(); Pair ep = graph.getEndpoints(edge); V first = ep.getFirst(); V second = ep.getSecond(); // remove loops if(first.equals(second) == true) { iterator.remove(); } // remove edges in opposite direction if(first.equals(v) == false) { iterator.remove(); } } int count=0; for(E other : commonEdgeSet) { if(e.equals(other) == false) { edge_index.put(Context.,E>getInstance(graph,other), count); count++; } } edge_index.put(Context.,E>getInstance(graph,e), count); return count; } protected int getIndex(Graph graph, E e, V v) { Collection commonEdgeSet = new HashSet(); for(E another : graph.getIncidentEdges(v)) { V u = graph.getOpposite(v, another); if(u.equals(v)) { commonEdgeSet.add(another); } } int count=0; for(E other : commonEdgeSet) { if(e.equals(other) == false) { edge_index.put(Context.,E>getInstance(graph,other), count); count++; } } edge_index.put(Context.,E>getInstance(graph,e), count); return count; } /** * Resets the indices for this edge and its parallel edges. * Should be invoked when an edge parallel to e * has been added or removed. * @param graph the graph for which the indices are to be reset * @param e the edge whose indices are to be reset * */ public void reset(Graph graph, E e) { Pair endpoints = graph.getEndpoints(e); getIndex(graph, e, endpoints.getFirst()); getIndex(graph, e, endpoints.getFirst(), endpoints.getSecond()); } /** * Clears all edge indices for all edges in all graphs. * Does not recalculate the indices. */ public void reset() { edge_index.clear(); } } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/util/EdgeIndexFunction.java000066400000000000000000000033521276402340000311270ustar00rootroot00000000000000/* * Created on Sep 24, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph.util; import edu.uci.ics.jung.graph.Graph; /** * An interface for a service to access the index of a given edge (in a given graph) * into the set formed by the given edge and all the other edges it is parallel to. * *

    Note that in current use, this index is assumed to be an integer value in * the interval [0,n-1], where n-1 is the number of edges parallel to e. * * @author Tom Nelson * */ public interface EdgeIndexFunction { /** * Returns e's index in graph. * The index of e is defined as its position in some * consistent ordering of e and all edges parallel to e. * @param graph the graph with respect to which the index is calculated * @param e the edge whose index is to be queried * @return e's index in graph */ int getIndex(Graph graph, E e); /** * Resets the indices for edge and its parallel edges in graph. * Should be invoked when an edge parallel to edge * has been added or removed. * * @param g the graph in which edge's index is to be reset * @param edge the edge whose index is to be reset */ void reset(Graph g, E edge); /** * Clears all edge indices for all edges in all graphs. * Does not recalculate the indices. */ void reset(); } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/util/EdgeType.java000066400000000000000000000007041276402340000272710ustar00rootroot00000000000000/* * Created on February 27, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph.util; /** * Defines the possible edge types for graphs which assign types to edges. */ public enum EdgeType { DIRECTED, UNDIRECTED } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/util/Graphs.java000066400000000000000000000657631276402340000270270ustar00rootroot00000000000000package edu.uci.ics.jung.graph.util; import java.io.Serializable; import java.util.Collection; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.Forest; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.Tree; import edu.uci.ics.jung.graph.UndirectedGraph; /** * Provides specialized implementations of GraphDecorator. Currently these * wrapper types include "synchronized" and "unmodifiable". * *

    The methods of this class may each throw a NullPointerException * if the graphs or class objects provided to them are null. * * @author Tom Nelson */ public class Graphs { /** * Returns a synchronized graph backed by the passed argument graph. * @param the vertex type * @param the edge type * @param graph the graph for which a synchronized wrapper is to be created * @return a synchronized graph backed by the passed argument graph */ public static Graph synchronizedGraph(Graph graph) { return new SynchronizedGraph(graph); } /** * Returns a synchronized DirectedGraph backed by the passed DirectedGraph. * @param the vertex type * @param the edge type * @param graph the graph for which a synchronized wrapper is to be created * @return a synchronized DirectedGraph backed by the passed DirectedGraph */ public static DirectedGraph synchronizedDirectedGraph(DirectedGraph graph) { return new SynchronizedDirectedGraph(graph); } /** * Returns a synchronized UndirectedGraph backed by the passed UndirectedGraph. * @param the vertex type * @param the edge type * @param graph the graph for which a synchronized wrapper is to be created * @return a synchronized UndirectedGraph backed by the passed UndirectedGraph */ public static UndirectedGraph synchronizedUndirectedGraph(UndirectedGraph graph) { return new SynchronizedUndirectedGraph(graph); } /** * Returns a synchronized Forest backed by the passed Forest. * @param the vertex type * @param the edge type * @param forest the forest for which a synchronized wrapper is to be created * @return a synchronized Forest backed by the passed Forest */ public static SynchronizedForest synchronizedForest(Forest forest) { return new SynchronizedForest(forest); } /** * Returns a synchronized Tree backed by the passed Tree. * @param the vertex type * @param the edge type * @param tree the tree for which a synchronized wrapper is to be created * @return a synchronized Tree backed by the passed Tree */ public static SynchronizedTree synchronizedTree(Tree tree) { return new SynchronizedTree(tree); } /** * Returns an unmodifiable Graph backed by the passed Graph. * @param the vertex type * @param the edge type * @param graph the graph for which the unmodifiable wrapper is to be returned * @return an unmodifiable Graph backed by the passed Graph */ public static Graph unmodifiableGraph(Graph graph) { return new UnmodifiableGraph(graph); } /** * Returns an unmodifiable DirectedGraph backed by the passed graph. * @param the vertex type * @param the edge type * @param graph the graph for which the unmodifiable wrapper is to be returned * @return an unmodifiable DirectedGraph backed by the passed graph */ public static DirectedGraph unmodifiableDirectedGraph(DirectedGraph graph) { return new UnmodifiableDirectedGraph(graph); } /** * Returns an unmodifiable UndirectedGraph backed by the passed graph. * @param the vertex type * @param the edge type * @param graph the graph for which the unmodifiable wrapper is to be returned * @return an unmodifiable UndirectedGraph backed by the passed graph */ public static UndirectedGraph unmodifiableUndirectedGraph(UndirectedGraph graph) { return new UnmodifiableUndirectedGraph(graph); } /** * Returns an unmodifiable Tree backed by the passed tree. * @param the vertex type * @param the edge type * @param tree the tree for which the unmodifiable wrapper is to be returned * @return an unmodifiable Tree backed by the passed tree */ public static UnmodifiableTree unmodifiableTree(Tree tree) { return new UnmodifiableTree(tree); } /** * Returns an unmodifiable Forest backed by the passed forest. * @param the vertex type * @param the edge type * @param forest the forest for which the unmodifiable wrapper is to be returned * @return an unmodifiable Forest backed by the passed forest */ public static UnmodifiableForest unmodifiableForest(Forest forest) { return new UnmodifiableForest(forest); } @SuppressWarnings("serial") static abstract class SynchronizedAbstractGraph implements Graph, Serializable { protected Graph delegate; private SynchronizedAbstractGraph(Graph delegate) { if(delegate == null) { throw new NullPointerException(); } this.delegate = delegate; } /** * @see edu.uci.ics.jung.graph.Graph#getDefaultEdgeType() */ public EdgeType getDefaultEdgeType() { return delegate.getDefaultEdgeType(); } /** * @see edu.uci.ics.jung.graph.Graph#addEdge(Object, Object, Object, EdgeType) */ public synchronized boolean addEdge(E e, V v1, V v2, EdgeType edgeType) { return delegate.addEdge(e, v1, v2, edgeType); } /** * @see edu.uci.ics.jung.graph.Hypergraph#addEdge(Object, Collection, EdgeType) */ public synchronized boolean addEdge(E e, Collection vertices, EdgeType edgeType) { return delegate.addEdge(e, vertices, edgeType); } /** * @see edu.uci.ics.jung.graph.Graph#addEdge(Object, Object, Object) */ public synchronized boolean addEdge(E e, V v1, V v2) { return delegate.addEdge(e, v1, v2); } /** * @see edu.uci.ics.jung.graph.Hypergraph#addVertex(java.lang.Object) */ public synchronized boolean addVertex(V vertex) { return delegate.addVertex(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#isIncident(java.lang.Object, java.lang.Object) */ public synchronized boolean isIncident(V vertex, E edge) { return delegate.isIncident(vertex, edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#isNeighbor(java.lang.Object, java.lang.Object) */ public synchronized boolean isNeighbor(V v1, V v2) { return delegate.isNeighbor(v1, v2); } /** * @see edu.uci.ics.jung.graph.Hypergraph#degree(java.lang.Object) */ public synchronized int degree(V vertex) { return delegate.degree(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#findEdge(java.lang.Object, java.lang.Object) */ public synchronized E findEdge(V v1, V v2) { return delegate.findEdge(v1, v2); } /** * @see edu.uci.ics.jung.graph.Hypergraph#findEdgeSet(java.lang.Object, java.lang.Object) */ public synchronized Collection findEdgeSet(V v1, V v2) { return delegate.findEdgeSet(v1, v2); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getEdges() */ public synchronized Collection getEdges() { return delegate.getEdges(); } /** * @see edu.uci.ics.jung.graph.Graph#getEdges(EdgeType) */ public synchronized Collection getEdges(EdgeType edgeType) { return delegate.getEdges(edgeType); } /** * @see edu.uci.ics.jung.graph.Graph#getEndpoints(java.lang.Object) */ public synchronized Pair getEndpoints(E edge) { return delegate.getEndpoints(edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentEdges(java.lang.Object) */ public synchronized Collection getIncidentEdges(V vertex) { return delegate.getIncidentEdges(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentVertices(java.lang.Object) */ public synchronized Collection getIncidentVertices(E edge) { return delegate.getIncidentVertices(edge); } /** * @see edu.uci.ics.jung.graph.Graph#getInEdges(java.lang.Object) */ public synchronized Collection getInEdges(V vertex) { return delegate.getInEdges(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getNeighbors(java.lang.Object) */ public synchronized Collection getNeighbors(V vertex) { return delegate.getNeighbors(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getOpposite(java.lang.Object, java.lang.Object) */ public synchronized V getOpposite(V vertex, E edge) { return delegate.getOpposite(vertex, edge); } /** * @see edu.uci.ics.jung.graph.Graph#getOutEdges(java.lang.Object) */ public synchronized Collection getOutEdges(V vertex) { return delegate.getOutEdges(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getPredecessors(java.lang.Object) */ public synchronized Collection getPredecessors(V vertex) { return delegate.getPredecessors(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getSuccessors(java.lang.Object) */ public synchronized Collection getSuccessors(V vertex) { return delegate.getSuccessors(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getVertices() */ public synchronized Collection getVertices() { return delegate.getVertices(); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount() */ public synchronized int getEdgeCount() { return delegate.getEdgeCount(); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount(EdgeType) */ public synchronized int getEdgeCount(EdgeType edge_type) { return delegate.getEdgeCount(edge_type); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getVertexCount() */ public synchronized int getVertexCount() { return delegate.getVertexCount(); } /** * @see edu.uci.ics.jung.graph.Graph#inDegree(java.lang.Object) */ public synchronized int inDegree(V vertex) { return delegate.inDegree(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getEdgeType(java.lang.Object) */ public synchronized EdgeType getEdgeType(E edge) { return delegate.getEdgeType(edge); } /** * @see edu.uci.ics.jung.graph.Graph#isPredecessor(java.lang.Object, java.lang.Object) */ public synchronized boolean isPredecessor(V v1, V v2) { return delegate.isPredecessor(v1, v2); } /** * @see edu.uci.ics.jung.graph.Graph#isSuccessor(java.lang.Object, java.lang.Object) */ public synchronized boolean isSuccessor(V v1, V v2) { return delegate.isSuccessor(v1, v2); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getNeighborCount(java.lang.Object) */ public synchronized int getNeighborCount(V vertex) { return delegate.getNeighborCount(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getPredecessorCount(java.lang.Object) */ public synchronized int getPredecessorCount(V vertex) { return delegate.getPredecessorCount(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getSuccessorCount(java.lang.Object) */ public synchronized int getSuccessorCount(V vertex) { return delegate.getSuccessorCount(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#outDegree(java.lang.Object) */ public synchronized int outDegree(V vertex) { return delegate.outDegree(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#removeEdge(java.lang.Object) */ public synchronized boolean removeEdge(E edge) { return delegate.removeEdge(edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#removeVertex(java.lang.Object) */ public synchronized boolean removeVertex(V vertex) { return delegate.removeVertex(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getDest(java.lang.Object) */ public synchronized V getDest(E directed_edge) { return delegate.getDest(directed_edge); } /** * @see edu.uci.ics.jung.graph.Graph#getSource(java.lang.Object) */ public synchronized V getSource(E directed_edge) { return delegate.getSource(directed_edge); } /** * @see edu.uci.ics.jung.graph.Graph#isDest(java.lang.Object, java.lang.Object) */ public synchronized boolean isDest(V vertex, E edge) { return delegate.isDest(vertex, edge); } /** * @see edu.uci.ics.jung.graph.Graph#isSource(java.lang.Object, java.lang.Object) */ public synchronized boolean isSource(V vertex, E edge) { return delegate.isSource(vertex, edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentCount(Object) */ public synchronized int getIncidentCount(E edge) { return delegate.getIncidentCount(edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#addEdge(java.lang.Object, java.util.Collection) */ public synchronized boolean addEdge(E hyperedge, Collection vertices) { return delegate.addEdge(hyperedge, vertices); } /** * @see edu.uci.ics.jung.graph.Hypergraph#containsEdge(java.lang.Object) */ public synchronized boolean containsEdge(E edge) { return delegate.containsEdge(edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#containsVertex(java.lang.Object) */ public synchronized boolean containsVertex(V vertex) { return delegate.containsVertex(vertex); } } @SuppressWarnings("serial") static class SynchronizedGraph extends SynchronizedAbstractGraph implements Serializable { private SynchronizedGraph(Graph delegate) { super(delegate); } } @SuppressWarnings("serial") static class SynchronizedUndirectedGraph extends SynchronizedAbstractGraph implements UndirectedGraph, Serializable { private SynchronizedUndirectedGraph(UndirectedGraph delegate) { super(delegate); } } @SuppressWarnings("serial") static class SynchronizedDirectedGraph extends SynchronizedAbstractGraph implements DirectedGraph, Serializable { private SynchronizedDirectedGraph(DirectedGraph delegate) { super(delegate); } @Override public synchronized V getDest(E directed_edge) { return ((DirectedGraph)delegate).getDest(directed_edge); } @Override public synchronized V getSource(E directed_edge) { return ((DirectedGraph)delegate).getSource(directed_edge); } @Override public synchronized boolean isDest(V vertex, E edge) { return ((DirectedGraph)delegate).isDest(vertex, edge); } @Override public synchronized boolean isSource(V vertex, E edge) { return ((DirectedGraph)delegate).isSource(vertex, edge); } } @SuppressWarnings("serial") static class SynchronizedTree extends SynchronizedForest implements Tree { /** * Creates a new instance based on the provided {@code delegate}. * @param delegate */ public SynchronizedTree(Tree delegate) { super(delegate); } public synchronized int getDepth(V vertex) { return ((Tree)delegate).getDepth(vertex); } public synchronized int getHeight() { return ((Tree)delegate).getHeight(); } public synchronized V getRoot() { return ((Tree)delegate).getRoot(); } } @SuppressWarnings("serial") static class SynchronizedForest extends SynchronizedDirectedGraph implements Forest { /** * Creates a new instance based on the provided {@code delegate}. * @param delegate */ public SynchronizedForest(Forest delegate) { super(delegate); } public synchronized Collection> getTrees() { return ((Forest)delegate).getTrees(); } public int getChildCount(V vertex) { return ((Forest)delegate).getChildCount(vertex); } public Collection getChildEdges(V vertex) { return ((Forest)delegate).getChildEdges(vertex); } public Collection getChildren(V vertex) { return ((Forest)delegate).getChildren(vertex); } public V getParent(V vertex) { return ((Forest)delegate).getParent(vertex); } public E getParentEdge(V vertex) { return ((Forest)delegate).getParentEdge(vertex); } } @SuppressWarnings("serial") static abstract class UnmodifiableAbstractGraph implements Graph, Serializable { protected Graph delegate; private UnmodifiableAbstractGraph(Graph delegate) { if(delegate == null) { throw new NullPointerException(); } this.delegate = delegate; } /** * @see edu.uci.ics.jung.graph.Graph#getDefaultEdgeType() */ public EdgeType getDefaultEdgeType() { return delegate.getDefaultEdgeType(); } /** * @see edu.uci.ics.jung.graph.Graph#addEdge(Object, Object, Object, EdgeType) */ public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) { throw new UnsupportedOperationException(); } /** * @see edu.uci.ics.jung.graph.Graph#addEdge(Object, Collection, EdgeType) */ public boolean addEdge(E e, Collection vertices, EdgeType edgeType) { throw new UnsupportedOperationException(); } /** * @see edu.uci.ics.jung.graph.Graph#addEdge(Object, Object, Object) */ public boolean addEdge(E e, V v1, V v2) { throw new UnsupportedOperationException(); } /** * @see edu.uci.ics.jung.graph.Hypergraph#addVertex(java.lang.Object) */ public boolean addVertex(V vertex) { throw new UnsupportedOperationException(); } /** * @see edu.uci.ics.jung.graph.Hypergraph#isIncident(java.lang.Object, java.lang.Object) */ public boolean isIncident(V vertex, E edge) { return delegate.isIncident(vertex, edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#isNeighbor(java.lang.Object, java.lang.Object) */ public boolean isNeighbor(V v1, V v2) { return delegate.isNeighbor(v1, v2); } /** * @see edu.uci.ics.jung.graph.Hypergraph#degree(java.lang.Object) */ public int degree(V vertex) { return delegate.degree(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#findEdge(java.lang.Object, java.lang.Object) */ public E findEdge(V v1, V v2) { return delegate.findEdge(v1, v2); } /** * @see edu.uci.ics.jung.graph.Hypergraph#findEdgeSet(java.lang.Object, java.lang.Object) */ public Collection findEdgeSet(V v1, V v2) { return delegate.findEdgeSet(v1, v2); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getEdges() */ public Collection getEdges() { return delegate.getEdges(); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount() */ public int getEdgeCount() { return delegate.getEdgeCount(); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount(EdgeType) */ public int getEdgeCount(EdgeType edge_type) { return delegate.getEdgeCount(edge_type); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getVertexCount() */ public int getVertexCount() { return delegate.getVertexCount(); } /** * @see edu.uci.ics.jung.graph.Graph#getEdges(edu.uci.ics.jung.graph.util.EdgeType) */ public Collection getEdges(EdgeType edgeType) { return delegate.getEdges(edgeType); } /** * @see edu.uci.ics.jung.graph.Graph#getEndpoints(java.lang.Object) */ public Pair getEndpoints(E edge) { return delegate.getEndpoints(edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentEdges(java.lang.Object) */ public Collection getIncidentEdges(V vertex) { return delegate.getIncidentEdges(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentVertices(java.lang.Object) */ public Collection getIncidentVertices(E edge) { return delegate.getIncidentVertices(edge); } /** * @see edu.uci.ics.jung.graph.Graph#getInEdges(java.lang.Object) */ public Collection getInEdges(V vertex) { return delegate.getInEdges(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getNeighbors(java.lang.Object) */ public Collection getNeighbors(V vertex) { return delegate.getNeighbors(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getOpposite(java.lang.Object, java.lang.Object) */ public V getOpposite(V vertex, E edge) { return delegate.getOpposite(vertex, edge); } /** * @see edu.uci.ics.jung.graph.Graph#getOutEdges(java.lang.Object) */ public Collection getOutEdges(V vertex) { return delegate.getOutEdges(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getPredecessors(java.lang.Object) */ public Collection getPredecessors(V vertex) { return delegate.getPredecessors(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getSuccessors(java.lang.Object) */ public Collection getSuccessors(V vertex) { return delegate.getSuccessors(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getVertices() */ public Collection getVertices() { return delegate.getVertices(); } /** * @see edu.uci.ics.jung.graph.Graph#inDegree(java.lang.Object) */ public int inDegree(V vertex) { return delegate.inDegree(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getEdgeType(java.lang.Object) */ public EdgeType getEdgeType(E edge) { return delegate.getEdgeType(edge); } /** * @see edu.uci.ics.jung.graph.Graph#isPredecessor(java.lang.Object, java.lang.Object) */ public boolean isPredecessor(V v1, V v2) { return delegate.isPredecessor(v1, v2); } /** * @see edu.uci.ics.jung.graph.Graph#isSuccessor(java.lang.Object, java.lang.Object) */ public boolean isSuccessor(V v1, V v2) { return delegate.isSuccessor(v1, v2); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getNeighborCount(java.lang.Object) */ public int getNeighborCount(V vertex) { return delegate.getNeighborCount(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getPredecessorCount(java.lang.Object) */ public int getPredecessorCount(V vertex) { return delegate.getPredecessorCount(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getSuccessorCount(java.lang.Object) */ public int getSuccessorCount(V vertex) { return delegate.getSuccessorCount(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#outDegree(java.lang.Object) */ public int outDegree(V vertex) { return delegate.outDegree(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#removeEdge(java.lang.Object) */ public boolean removeEdge(E edge) { throw new UnsupportedOperationException(); } /** * @see edu.uci.ics.jung.graph.Hypergraph#removeVertex(java.lang.Object) */ public boolean removeVertex(V vertex) { throw new UnsupportedOperationException(); } /** * @see edu.uci.ics.jung.graph.Graph#getDest(java.lang.Object) */ public V getDest(E directed_edge) { return delegate.getDest(directed_edge); } /** * @see edu.uci.ics.jung.graph.Graph#getSource(java.lang.Object) */ public V getSource(E directed_edge) { return delegate.getSource(directed_edge); } /** * @see edu.uci.ics.jung.graph.Graph#isDest(java.lang.Object, java.lang.Object) */ public boolean isDest(V vertex, E edge) { return delegate.isDest(vertex, edge); } /** * @see edu.uci.ics.jung.graph.Graph#isSource(java.lang.Object, java.lang.Object) */ public boolean isSource(V vertex, E edge) { return delegate.isSource(vertex, edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentCount(Object) */ public int getIncidentCount(E edge) { return delegate.getIncidentCount(edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#addEdge(java.lang.Object, java.util.Collection) */ public boolean addEdge(E hyperedge, Collection vertices) { return delegate.addEdge(hyperedge, vertices); } /** * @see edu.uci.ics.jung.graph.Hypergraph#containsEdge(java.lang.Object) */ public boolean containsEdge(E edge) { return delegate.containsEdge(edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#containsVertex(java.lang.Object) */ public boolean containsVertex(V vertex) { return delegate.containsVertex(vertex); } } @SuppressWarnings("serial") static class UnmodifiableGraph extends UnmodifiableAbstractGraph implements Serializable { private UnmodifiableGraph(Graph delegate) { super(delegate); } } @SuppressWarnings("serial") static class UnmodifiableDirectedGraph extends UnmodifiableAbstractGraph implements DirectedGraph, Serializable { private UnmodifiableDirectedGraph(DirectedGraph delegate) { super(delegate); } @Override public V getDest(E directed_edge) { return ((DirectedGraph)delegate).getDest(directed_edge); } @Override public V getSource(E directed_edge) { return ((DirectedGraph)delegate).getSource(directed_edge); } @Override public boolean isDest(V vertex, E edge) { return ((DirectedGraph)delegate).isDest(vertex, edge); } @Override public boolean isSource(V vertex, E edge) { return ((DirectedGraph)delegate).isSource(vertex, edge); } } @SuppressWarnings("serial") static class UnmodifiableUndirectedGraph extends UnmodifiableAbstractGraph implements UndirectedGraph, Serializable { private UnmodifiableUndirectedGraph(UndirectedGraph delegate) { super(delegate); } } @SuppressWarnings("serial") static class UnmodifiableForest extends UnmodifiableGraph implements Forest, Serializable { private UnmodifiableForest(Forest delegate) { super(delegate); } public Collection> getTrees() { return ((Forest)delegate).getTrees(); } public int getChildCount(V vertex) { return ((Forest)delegate).getChildCount(vertex); } public Collection getChildEdges(V vertex) { return ((Forest)delegate).getChildEdges(vertex); } public Collection getChildren(V vertex) { return ((Forest)delegate).getChildren(vertex); } public V getParent(V vertex) { return ((Forest)delegate).getParent(vertex); } public E getParentEdge(V vertex) { return ((Forest)delegate).getParentEdge(vertex); } } @SuppressWarnings("serial") static class UnmodifiableTree extends UnmodifiableForest implements Tree, Serializable { private UnmodifiableTree(Tree delegate) { super(delegate); } public int getDepth(V vertex) { return ((Tree)delegate).getDepth(vertex); } public int getHeight() { return ((Tree)delegate).getHeight(); } public V getRoot() { return ((Tree)delegate).getRoot(); } @Override public Collection> getTrees() { return ((Tree)delegate).getTrees(); } } } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/util/IncidentEdgeIndexFunction.java000066400000000000000000000062021276402340000326020ustar00rootroot00000000000000/* * Created on Sep 24, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph.util; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import edu.uci.ics.jung.graph.Graph; /** * A class which creates and maintains indices for incident edges. * * @author Tom Nelson * */ public class IncidentEdgeIndexFunction implements EdgeIndexFunction { protected Map edge_index = new HashMap(); private IncidentEdgeIndexFunction() { } /** * @param the vertex type * @param the edge type * @return an instance of this type. */ public static IncidentEdgeIndexFunction getInstance() { return new IncidentEdgeIndexFunction(); } /** * Returns the index for the specified edge. * Calculates the indices for e and for all edges parallel * to e. */ public int getIndex(Graph graph, E e) { Integer index = edge_index.get(e); if(index == null) { Pair endpoints = graph.getEndpoints(e); V u = endpoints.getFirst(); V v = endpoints.getSecond(); if(u.equals(v)) { index = getIndex(graph, e, v); } else { index = getIndex(graph, e, u, v); } } return index.intValue(); } protected int getIndex(Graph graph, E e, V u, V v) { Collection commonEdgeSet = new HashSet(graph.getIncidentEdges(u)); int count=0; for(E other : commonEdgeSet) { if(e.equals(other) == false) { edge_index.put(other, count); count++; } } edge_index.put(e, count); return count; } protected int getIndex(Graph graph, E e, V v) { Collection commonEdgeSet = new HashSet(); for(E another : graph.getIncidentEdges(v)) { V u = graph.getOpposite(v, another); if(u.equals(v)) { commonEdgeSet.add(another); } } int count=0; for(E other : commonEdgeSet) { if(e.equals(other) == false) { edge_index.put(other, count); count++; } } edge_index.put(e, count); return count; } /** * Resets the indices for this edge and its parallel edges. * Should be invoked when an edge parallel to e * has been added or removed. * @param graph the graph whose indices are to be reset * @param e the edge whose associated indices are to be reset */ public void reset(Graph graph, E e) { Pair endpoints = graph.getEndpoints(e); getIndex(graph, e, endpoints.getFirst()); getIndex(graph, e, endpoints.getFirst(), endpoints.getSecond()); } /** * Clears all edge indices for all edges in all graphs. * Does not recalculate the indices. */ public void reset() { edge_index.clear(); } } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/util/Pair.java000066400000000000000000000155541276402340000264670ustar00rootroot00000000000000/* * Created on Apr 2, 2006 * * Copyright (c) 2006, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph.util; import java.io.Serializable; import java.util.Collection; import java.util.Iterator; /** * An implementation of Collection that stores exactly * 2 non-null objects and is not mutable. They respect equals * and may be used as indices or map keys.

    * Note that they do not protect from malevolent behavior: if one or another * object in the tuple is mutable, then it can be changed with the usual bad * effects. */ @SuppressWarnings("serial") public final class Pair implements Collection, Serializable { private T first; private T second; /** * Creates a Pair from the specified elements. * @param value1 the first value in the new Pair * @param value2 the second value in the new Pair * @throws IllegalArgumentException if either argument is null */ public Pair(T value1, T value2) { if(value1 == null || value2 == null) throw new IllegalArgumentException("Pair cannot contain null values"); first = value1; second = value2; } /** * Creates a Pair from the passed Collection. * The size of the Collection must be 2. * @param values the elements of the new Pair * @throws IllegalArgumentException if the input collection is null, * contains null values, or has != 2 elements. */ public Pair(Collection values) { if (values == null) throw new IllegalArgumentException("Input collection cannot be null"); if (values.size() == 2) { if(values.contains(null)) throw new IllegalArgumentException("Pair cannot contain null values"); Iterator iter = values.iterator(); first = iter.next(); second = iter.next(); } else throw new IllegalArgumentException("Pair may only be created from a Collection of exactly 2 elements"); } /** * Creates a Pair from the passed array. * The size of the array must be 2. * * @param values the values to be used to construct this Pair * @throws IllegalArgumentException if the input array is null, * contains null values, or has != 2 elements. */ public Pair(T[] values) { if (values == null) throw new IllegalArgumentException("Input array cannot be null"); if (values.length == 2) { if(values[0] == null || values[1] == null) throw new IllegalArgumentException("Pair cannot contain null values"); first = values[0]; second = values[1]; } else throw new IllegalArgumentException("Pair may only be created from an " + "array of 2 elements"); } /** * @return the first element. */ public T getFirst() { return first; } /** * @return the second element. */ public T getSecond() { return second; } @Override public boolean equals( Object o ) { if (o == this) return true; if (o instanceof Pair) { @SuppressWarnings("rawtypes") Pair otherPair = (Pair) o; Object otherFirst = otherPair.getFirst(); Object otherSecond = otherPair.getSecond(); return (this.first == otherFirst || (this.first != null && this.first.equals(otherFirst))) && (this.second == otherSecond || (this.second != null && this.second.equals(otherSecond))); } else { return false; } } @Override public int hashCode() { int hashCode = 1; hashCode = 31*hashCode + (first==null ? 0 : first.hashCode()); hashCode = 31*hashCode + (second==null ? 0 : second.hashCode()); return hashCode; } @Override public String toString() { return "<" + first.toString() + ", " + second.toString() + ">"; } public boolean add(T o) { throw new UnsupportedOperationException("Pairs cannot be mutated"); } public boolean addAll(Collection c) { throw new UnsupportedOperationException("Pairs cannot be mutated"); } public void clear() { throw new UnsupportedOperationException("Pairs cannot be mutated"); } public boolean contains(Object o) { return (first == o || first.equals(o) || second == o || second.equals(o)); } public boolean containsAll(Collection c) { if (c.size() > 2) return false; Iterator iter = c.iterator(); Object c_first = iter.next(); Object c_second = iter.next(); return this.contains(c_first) && this.contains(c_second); } public boolean isEmpty() { return false; } public Iterator iterator() { return new PairIterator(); } public boolean remove(Object o) { throw new UnsupportedOperationException("Pairs cannot be mutated"); } public boolean removeAll(Collection c) { throw new UnsupportedOperationException("Pairs cannot be mutated"); } public boolean retainAll(Collection c) { throw new UnsupportedOperationException("Pairs cannot be mutated"); } public int size() { return 2; } public Object[] toArray() { Object[] to_return = new Object[2]; to_return[0] = first; to_return[1] = second; return to_return; } @SuppressWarnings("unchecked") public S[] toArray(S[] a) { S[] to_return = a; Class type = a.getClass().getComponentType(); if (a.length < 2) to_return = (S[])java.lang.reflect.Array.newInstance(type, 2); to_return[0] = (S)first; to_return[1] = (S)second; if (to_return.length > 2) to_return[2] = null; return to_return; } private class PairIterator implements Iterator { int position; private PairIterator() { position = 0; } public boolean hasNext() { return position < 2; } public T next() { position++; if (position == 1) return first; else if (position == 2) return second; else return null; } public void remove() { throw new UnsupportedOperationException("Pairs cannot be mutated"); } } } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/util/TreeUtils.java000066400000000000000000000104431276402340000275040ustar00rootroot00000000000000/* * Created on Mar 3, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph.util; import edu.uci.ics.jung.graph.Forest; import edu.uci.ics.jung.graph.Tree; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * Contains static methods for operating on instances of Tree. */ public class TreeUtils { /** * @param the vertex type * @param the edge type * @param forest the forest whose roots are to be returned * @return the roots of this forest. */ public static List getRoots(Forest forest) { List roots = new ArrayList(); for(Tree tree : forest.getTrees()) { roots.add(tree.getRoot()); } return roots; } /** * Returns the subtree of tree which is rooted at root as a Forest. * The tree returned is an independent entity, although it uses the same vertex and edge objects. * @param the vertex type * @param the edge type * @param forest the tree whose subtree is to be extracted * @param root the root of the subtree to be extracted * @return the subtree of tree which is rooted at root * @throws InstantiationException if a new tree of the same type cannot be created * @throws IllegalAccessException if a new tree of the same type cannot be created */ @SuppressWarnings("unchecked") public static Tree getSubTree(Forest forest, V root) throws InstantiationException, IllegalAccessException { if (!forest.containsVertex(root)) throw new IllegalArgumentException("Specified tree does not contain the specified root as a vertex"); Forest subforest = forest.getClass().newInstance(); subforest.addVertex(root); growSubTree(forest, subforest, root); return subforest.getTrees().iterator().next(); } /** * Populates subtree with the subtree of tree * which is rooted at root. * @param the vertex type * @param the edge type * @param tree the tree whose subtree is to be extracted * @param subTree the tree instance which is to be populated with the subtree of tree * @param root the root of the subtree to be extracted */ public static void growSubTree(Forest tree, Forest subTree, V root) { if(tree.getSuccessorCount(root) > 0) { Collection edges = tree.getOutEdges(root); for(E e : edges) { subTree.addEdge(e, tree.getEndpoints(e)); } Collection kids = tree.getSuccessors(root); for(V kid : kids) { growSubTree(tree, subTree, kid); } } } /** * Connects subTree to tree by attaching it as a child * of node with edge connectingEdge. * @param the vertex type * @param the edge type * @param tree the tree to which subTree is to be added * @param subTree the tree which is to be grafted on to tree * @param node the parent of subTree in its new position in tree * @param connectingEdge the edge used to connect subtree's root as a child of node */ public static void addSubTree(Forest tree, Forest subTree, V node, E connectingEdge) { if (node != null && !tree.containsVertex(node)) throw new IllegalArgumentException("Specified tree does not contain the specified node as a vertex"); V root = subTree.getTrees().iterator().next().getRoot(); addFromSubTree(tree, subTree, connectingEdge, node, root); } public static void addFromSubTree(Forest tree, Forest subTree, E edge, V parent, V root) { // add edge connecting parent and root to tree if(edge != null && parent != null) { tree.addEdge(edge, parent, root); } else { tree.addVertex(root); } Collection outEdges = subTree.getOutEdges(root); for(E e : outEdges) { V opposite = subTree.getOpposite(root, e); addFromSubTree(tree, subTree, e, root, opposite); } } } jung-jung-2.1.1/jung-api/src/main/java/edu/uci/ics/jung/graph/util/package.html000066400000000000000000000015241276402340000272020ustar00rootroot00000000000000

    Utility interfaces and classes for the JUNG API. These include:

    • Context: a wrapper for an element in the context of a specific graph
    • classes for maintaining edge indices (primarily for rendering)
    • Pair<T>: an implementation of Collection designed for two-element immutable collections
    • Graphs: facilitates the creation of special delegate types such as synchronized and unmodifiable graphs
    • TreeUtils: utilities for trees and forests (subtree extraction, grafting, merging, etc.)
    jung-jung-2.1.1/jung-api/src/site/000077500000000000000000000000001276402340000166625ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/site/site.xml000066400000000000000000000004441276402340000203520ustar00rootroot00000000000000 ${project.name} jung-jung-2.1.1/jung-api/src/test/000077500000000000000000000000001276402340000166755ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/test/java/000077500000000000000000000000001276402340000176165ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/test/java/edu/000077500000000000000000000000001276402340000203735ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/test/java/edu/uci/000077500000000000000000000000001276402340000211535ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/test/java/edu/uci/ics/000077500000000000000000000000001276402340000217315ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/test/java/edu/uci/ics/jung/000077500000000000000000000000001276402340000226745ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/test/java/edu/uci/ics/jung/graph/000077500000000000000000000000001276402340000237755ustar00rootroot00000000000000AbstractDirectedSparseMultigraphTest.java000066400000000000000000000075551276402340000340570ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/test/java/edu/uci/ics/jung/graphpackage edu.uci.ics.jung.graph; import java.util.Collection; import java.util.Collections; import junit.framework.TestCase; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; public abstract class AbstractDirectedSparseMultigraphTest extends TestCase { protected Integer v0 = new Integer(0); protected Integer v1 = new Integer(1); protected Integer v2 = new Integer(2); protected Float e01 = new Float(.1f); protected Float e10 = new Float(.2f); protected Float e12 = new Float(.3f); protected Float e21 = new Float(.4f); protected Graph graph; public void testGetEdges() { assertEquals(graph.getEdgeCount(), 4); } public void testGetVertices() { assertEquals(graph.getVertexCount(), 3); } public void testAddVertex() { int count = graph.getVertexCount(); graph.addVertex(new Integer(3)); assertEquals(graph.getVertexCount(), count+1); } public void testRemoveEndVertex() { int vertexCount = graph.getVertexCount(); graph.removeVertex(v0); assertEquals(vertexCount-1, graph.getVertexCount()); assertEquals(2, graph.getEdgeCount()); } public void testRemoveMiddleVertex() { int vertexCount = graph.getVertexCount(); graph.removeVertex(v1); assertEquals(vertexCount-1, graph.getVertexCount()); assertEquals(0, graph.getEdgeCount()); } public void testAddEdge() { int edgeCount = graph.getEdgeCount(); graph.addEdge(new Double(.5), v0, v1); assertEquals(graph.getEdgeCount(), edgeCount+1); } public void testRemoveEdge() { int edgeCount = graph.getEdgeCount(); graph.removeEdge(e12); assertEquals(graph.getEdgeCount(), edgeCount-1); } public void testNullEndpoint() { try { graph.addEdge(.99, new Pair(1,null)); fail("should not be able to add an edge with a null endpoint"); } catch(IllegalArgumentException e) { // all is well } } public void testGetInEdges() { assertEquals(graph.getInEdges(v1).size(), 2); } public void testGetOutEdges() { assertEquals(graph.getOutEdges(v1).size(), 2); } public void testGetPredecessors() { assertTrue(graph.getPredecessors(v0).containsAll(Collections.singleton(v1))); } public void testGetSuccessors() { assertTrue(graph.getPredecessors(v1).contains(v0)); assertTrue(graph.getPredecessors(v1).contains(v2)); } public void testGetNeighbors() { Collection neighbors = graph.getNeighbors(v1); assertTrue(neighbors.contains(v0)); assertTrue(neighbors.contains(v2)); } public void testGetIncidentEdges() { assertEquals(graph.getIncidentEdges(v0).size(), 2); } public void testFindEdge() { Number edge = graph.findEdge(v1, v2); assertTrue(edge == e12 || edge == e21); } public void testGetEndpoints() { Pair endpoints = graph.getEndpoints(e01); assertTrue((endpoints.getFirst() == v0 && endpoints.getSecond() == v1) || endpoints.getFirst() == v1 && endpoints.getSecond() == v0); } public void testIsDirected() { for(Number edge : graph.getEdges()) { assertEquals(graph.getEdgeType(edge), EdgeType.DIRECTED); } } public void testAddDirectedEdge() { Float edge = new Float(.9); graph.addEdge(edge, v1, v2, EdgeType.DIRECTED); assertEquals(graph.getEdgeType(edge), EdgeType.DIRECTED); } public void testAddUndirectedEdge() { try { graph.addEdge(new Float(.9), v1, v2, EdgeType.UNDIRECTED); fail("Cannot add an undirected edge to this graph"); } catch(IllegalArgumentException uoe) { // all is well } } } jung-jung-2.1.1/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractHypergraphTest.java000066400000000000000000000122771276402340000313060ustar00rootroot00000000000000/* * Created on Apr 21, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.ArrayList; import java.util.Collection; import junit.framework.TestCase; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.Pair; public abstract class AbstractHypergraphTest extends TestCase { protected Supplier> factory; protected Hypergraph h; public AbstractHypergraphTest(Supplier> factory) { this.factory = factory; } @Override public void runTest() throws Exception { setUp(); testAddVertex(); testAddEdge(); testEdgeEndpoints(); tearDown(); } /** * test for the following: *
      *
    • add successful iff arg is not present *
    • count increases by 1 iff add is successful *
    • null vertex argument actively rejected *
    • vertex reported as present iff add is successful *
    */ public void testAddVertex() { int count = h.getVertexCount(); assertTrue(h.addVertex(new Integer(1))); assertEquals(count+1, h.getVertexCount()); assertTrue(h.containsVertex(1)); boolean success = false; try { success = h.addVertex(null); fail("Implementation should disallow null vertices"); } catch (IllegalArgumentException iae) {} catch (NullPointerException npe) { fail("Implementation should actively prevent null vertices"); } assertFalse(success); assertFalse(h.addVertex(1)); assertEquals(count+1, h.getVertexCount()); assertFalse(h.containsVertex(2)); } /** * test for the following: *
      *
    • add successful iff edge is not present *
    • edge count increases by 1 iff add successful *
    • null edge arg actively rejected *
    • edge reported as present iff add is successful *
    • throw if edge is present with different endpoints *
    */ public void testAddEdge() { int edge_count = h.getEdgeCount(); int vertex_count = h.getVertexCount(); Pair p = new Pair(2, 3); assertTrue(h.addEdge('a', p)); assertEquals(edge_count+1, h.getEdgeCount()); assertEquals(vertex_count+2, h.getVertexCount()); assertTrue(h.containsEdge('a')); boolean success = false; try { success = h.addEdge('b', null); fail("Implementation should disallow null pairs/collections"); success = h.addEdge(null, p); fail("Implementation should disallow null edges"); } catch (IllegalArgumentException iae) {} catch (NullPointerException npe) { fail("Implementation should actively prevent null edges, pairs, and collections"); } assertFalse(success); // adding the same edge with an equal Pair should return false assertFalse(h.addEdge('a', new Pair(2,3))); // adding the same edge with the same Pair should return false assertFalse(h.addEdge('a', p)); try { success = h.addEdge('a', new Pair(3,4)); fail("Implementation should disallow existing edge objects from connecting new pairs/collections"); } catch (IllegalArgumentException iae) {} assertEquals(edge_count+1, h.getEdgeCount()); assertFalse(h.containsEdge('b')); } /** * test for the following: *
      *
    • if Graph, reject # of endpoints != 2 *
    • otherwise, accept any (non-negative) number of endpoints * *
    * */ public void testEdgeEndpoints() { Collection c = new ArrayList(); for (int i = 0; i < 10; i++) { try { h.addEdge((char)i, c); c.add(i); } catch (IllegalArgumentException iae) { if (h instanceof Graph) { if (c.size() == 2) fail("improperly rejected incident vertex collection " + c); } else fail("hypergraph implementations should accept any positive number of incident vertices"); } } } /** * should return null if any of the following is true *
      *
    • v1 is null *
    • v2 is null *
    • there is no edge connecting v1 to v2 in this graph *
    * otherwise should return _an_ edge connecting v1 to v2. * May be directed or undirected (depending on the graph); * may be any of the edges in the graph that so connect v1 and v2. * * Must _not_ return any directed edge for which v1 and v2 are distinct * and v2 is the source. */ public void testFindEdge() { } } AbstractOrderedSparseMultigraphTest.java000066400000000000000000000120641276402340000337070ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/test/java/edu/uci/ics/jung/graphpackage edu.uci.ics.jung.graph; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import junit.framework.TestCase; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; public abstract class AbstractOrderedSparseMultigraphTest extends TestCase { protected Integer v0 = 0; protected Integer v1 = 1; protected Integer v2 = 2; protected Number e01 = .1; protected Number e10 = .2; protected Number e12 = .3; protected Number e21 = .4; protected Supplier vertexFactory = new Supplier() { int v=0; public Number get() { return v++; } }; protected Supplier edgeFactory = new Supplier() { int e=0; public Number get() { return e++; } }; protected Graph graph; protected int vertexCount = 50; protected Graph smallGraph; public void testGetEdges() { assertEquals(smallGraph.getEdgeCount(), 4); // System.err.println("getEdges()="+graph.getEdges()); } public void testGetVertices() { assertEquals(smallGraph.getVertexCount(), 3); // System.err.println("getVertices()="+graph.getVertices()); } public void testAddVertex() { int count = graph.getVertexCount(); graph.addVertex(count); assertEquals(graph.getVertexCount(), count+1); } public void testRemoveEndVertex() { int vertexCount = graph.getVertexCount(); int edgeCount = graph.getEdgeCount(); Collection incident = graph.getIncidentEdges(vertexCount-1); graph.removeVertex(vertexCount-1); assertEquals(vertexCount-1, graph.getVertexCount()); assertEquals(edgeCount - incident.size(), graph.getEdgeCount()); } public void testRemoveMiddleVertex() { int vertexCount = graph.getVertexCount(); int edgeCount = graph.getEdgeCount(); Collection incident = graph.getIncidentEdges(vertexCount/2); graph.removeVertex(vertexCount/2); assertEquals(vertexCount-1, graph.getVertexCount()); assertEquals(edgeCount - incident.size(), graph.getEdgeCount()); } public void testAddEdge() { int edgeCount = graph.getEdgeCount(); graph.addEdge(edgeFactory.get(), 0, 1); assertEquals(graph.getEdgeCount(), edgeCount+1); } public void testNullEndpoint() { try { graph.addEdge(edgeFactory.get(), new Pair(1,null)); fail("should not be able to add an edge with a null endpoint"); } catch(IllegalArgumentException e) { // all is well } } public void testRemoveEdge() { List edgeList = new ArrayList(graph.getEdges()); int edgeCount = graph.getEdgeCount(); graph.removeEdge(edgeList.get(edgeList.size()/2)); assertEquals(graph.getEdgeCount(), edgeCount-1); } public void testGetInOutEdges() { for(Number v : graph.getVertices()) { Collection incident = graph.getIncidentEdges(v); Collection in = graph.getInEdges(v); Collection out = graph.getOutEdges(v); assertTrue(incident.containsAll(in)); assertTrue(incident.containsAll(out)); for(Number e : in) { if(out.contains(e)) { assertTrue(graph.getEdgeType(e) == EdgeType.UNDIRECTED); } } for(Number e : out) { if(in.contains(e)) { assertTrue(graph.getEdgeType(e) == EdgeType.UNDIRECTED); } } } assertEquals(smallGraph.getInEdges(v1).size(), 4); assertEquals(smallGraph.getOutEdges(v1).size(), 3); assertEquals(smallGraph.getOutEdges(v0).size(), 2); } public void testGetPredecessors() { assertTrue(smallGraph.getPredecessors(v0).containsAll(Collections.singleton(v1))); } public void testGetSuccessors() { assertTrue(smallGraph.getPredecessors(v1).contains(v0)); assertTrue(smallGraph.getPredecessors(v1).contains(v2)); } public void testGetNeighbors() { Collection neighbors = smallGraph.getNeighbors(v1); assertTrue(neighbors.contains(v0)); assertTrue(neighbors.contains(v2)); } public void testGetIncidentEdges() { assertEquals(smallGraph.getIncidentEdges(v0).size(), 2); } public void testFindEdge() { Number edge = smallGraph.findEdge(v1, v2); assertTrue(edge == e12 || edge == e21); } public void testGetEndpoints() { Pair endpoints = smallGraph.getEndpoints(e01); assertTrue((endpoints.getFirst() == v0 && endpoints.getSecond() == v1) || endpoints.getFirst() == v1 && endpoints.getSecond() == v0); } public void testIsDirected() { for(Number edge : smallGraph.getEdges()) { if(edge == e21) { assertEquals(smallGraph.getEdgeType(edge), EdgeType.DIRECTED); } else { assertEquals(smallGraph.getEdgeType(edge), EdgeType.UNDIRECTED); } } } } AbstractSortedSparseMultigraphTest.java000066400000000000000000000121311276402340000335560ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/test/java/edu/uci/ics/jung/graphpackage edu.uci.ics.jung.graph; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import junit.framework.TestCase; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; public abstract class AbstractSortedSparseMultigraphTest extends TestCase { public static class Foo {} public static class Bar {} protected Integer v0 = 0; protected Integer v1 = 1; protected Integer v2 = 2; protected Double e01 = .1; protected Double e10 = .2; protected Double e12 = .3; protected Double e21 = .4; protected Supplier vertexFactory = new Supplier() { int v=0; public Number get() { return v++; } }; protected Supplier edgeFactory = new Supplier() { double e=0; public Double get() { return e++; } }; protected Graph graph; protected int vertexCount = 50; protected Graph smallGraph; public void testGetEdges() { assertEquals(smallGraph.getEdgeCount(), 4); // System.err.println("getEdges()="+graph.getEdges()); } public void testGetVertices() { assertEquals(smallGraph.getVertexCount(), 3); // System.err.println("getVertices()="+graph.getVertices()); } public void testAddVertex() { int count = graph.getVertexCount(); graph.addVertex(count); assertEquals(graph.getVertexCount(), count+1); } public void testRemoveEndVertex() { int vertexCount = graph.getVertexCount(); int edgeCount = graph.getEdgeCount(); Collection incident = graph.getIncidentEdges(vertexCount-1); graph.removeVertex(vertexCount-1); assertEquals(vertexCount-1, graph.getVertexCount()); assertEquals(edgeCount - incident.size(), graph.getEdgeCount()); } public void testRemoveMiddleVertex() { int vertexCount = graph.getVertexCount(); int edgeCount = graph.getEdgeCount(); Collection incident = graph.getIncidentEdges(vertexCount/2); graph.removeVertex(vertexCount/2); assertEquals(vertexCount-1, graph.getVertexCount()); assertEquals(edgeCount - incident.size(), graph.getEdgeCount()); } public void testAddEdge() { int edgeCount = graph.getEdgeCount(); graph.addEdge(edgeFactory.get(), 0, 1); assertEquals(graph.getEdgeCount(), edgeCount+1); } public void testNullEndpoint() { try { graph.addEdge(edgeFactory.get(), new Pair(1,null)); fail("should not be able to add an edge with a null endpoint"); } catch(IllegalArgumentException e) { // all is well } } public void testRemoveEdge() { List edgeList = new ArrayList(graph.getEdges()); int edgeCount = graph.getEdgeCount(); graph.removeEdge(edgeList.get(edgeList.size()/2)); assertEquals(graph.getEdgeCount(), edgeCount-1); } public void testGetInOutEdges() { for(Integer v : graph.getVertices()) { Collection incident = graph.getIncidentEdges(v); Collection in = graph.getInEdges(v); Collection out = graph.getOutEdges(v); assertTrue(incident.containsAll(in)); assertTrue(incident.containsAll(out)); for(Double e : in) { if(out.contains(e)) { assertTrue(graph.getEdgeType(e) == EdgeType.UNDIRECTED); } } for(Double e : out) { if(in.contains(e)) { assertTrue(graph.getEdgeType(e) == EdgeType.UNDIRECTED); } } } assertEquals(smallGraph.getInEdges(v1).size(), 4); assertEquals(smallGraph.getOutEdges(v1).size(), 3); assertEquals(smallGraph.getOutEdges(v0).size(), 2); } public void testGetPredecessors() { assertTrue(smallGraph.getPredecessors(v0).containsAll(Collections.singleton(v1))); } public void testGetSuccessors() { assertTrue(smallGraph.getPredecessors(v1).contains(v0)); assertTrue(smallGraph.getPredecessors(v1).contains(v2)); } public void testGetNeighbors() { Collection neighbors = smallGraph.getNeighbors(v1); assertTrue(neighbors.contains(v0)); assertTrue(neighbors.contains(v2)); } public void testGetIncidentEdges() { assertEquals(smallGraph.getIncidentEdges(v0).size(), 2); } public void testFindEdge() { Number edge = smallGraph.findEdge(v1, v2); assertTrue(edge == e12 || edge == e21); } public void testGetEndpoints() { Pair endpoints = smallGraph.getEndpoints(e01); assertTrue((endpoints.getFirst() == v0 && endpoints.getSecond() == v1) || endpoints.getFirst() == v1 && endpoints.getSecond() == v0); } public void testIsDirected() { for(Double edge : smallGraph.getEdges()) { if(edge == e21) { assertEquals(smallGraph.getEdgeType(edge), EdgeType.DIRECTED); } else { assertEquals(smallGraph.getEdgeType(edge), EdgeType.UNDIRECTED); } } } } jung-jung-2.1.1/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractSparseMultigraphTest.java000066400000000000000000000116531276402340000324640ustar00rootroot00000000000000package edu.uci.ics.jung.graph; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import junit.framework.TestCase; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; public abstract class AbstractSparseMultigraphTest extends TestCase { protected Integer v0 = 0; protected Integer v1 = 1; protected Integer v2 = 2; protected Number e01 = .1; protected Number e10 = .2; protected Number e12 = .3; protected Number e21 = .4; protected Supplier vertexFactory = new Supplier() { int v=0; public Number get() { return v++; } }; protected Supplier edgeFactory = new Supplier() { int e=0; public Number get() { return e++; } }; protected Graph graph; protected int vertexCount = 50; protected Graph smallGraph; public void testGetEdges() { assertEquals(smallGraph.getEdgeCount(), 4); } public void testGetVertices() { assertEquals(smallGraph.getVertexCount(), 3); } public void testAddVertex() { int count = graph.getVertexCount(); graph.addVertex(count); assertEquals(graph.getVertexCount(), count+1); } public void testRemoveEndVertex() { int vertexCount = graph.getVertexCount(); int edgeCount = graph.getEdgeCount(); Collection incident = graph.getIncidentEdges(vertexCount-1); graph.removeVertex(vertexCount-1); assertEquals(vertexCount-1, graph.getVertexCount()); assertEquals(edgeCount - incident.size(), graph.getEdgeCount()); } public void testRemoveMiddleVertex() { int vertexCount = graph.getVertexCount(); int edgeCount = graph.getEdgeCount(); Collection incident = graph.getIncidentEdges(vertexCount/2); graph.removeVertex(vertexCount/2); assertEquals(vertexCount-1, graph.getVertexCount()); assertEquals(edgeCount - incident.size(), graph.getEdgeCount()); } public void testAddEdge() { int edgeCount = graph.getEdgeCount(); graph.addEdge(edgeFactory.get(), 0, 1); assertEquals(graph.getEdgeCount(), edgeCount+1); } public void testNullEndpoint() { try { graph.addEdge(edgeFactory.get(), new Pair(1,null)); fail("should not be able to add an edge with a null endpoint"); } catch(IllegalArgumentException e) { // all is well } } public void testRemoveEdge() { List edgeList = new ArrayList(graph.getEdges()); int edgeCount = graph.getEdgeCount(); graph.removeEdge(edgeList.get(edgeList.size()/2)); assertEquals(graph.getEdgeCount(), edgeCount-1); } public void testGetInOutEdges() { for(Number v : graph.getVertices()) { Collection incident = graph.getIncidentEdges(v); Collection in = graph.getInEdges(v); Collection out = graph.getOutEdges(v); assertTrue(incident.containsAll(in)); assertTrue(incident.containsAll(out)); for(Number e : in) { if(out.contains(e)) { assertTrue(graph.getEdgeType(e) == EdgeType.UNDIRECTED); } } for(Number e : out) { if(in.contains(e)) { assertTrue(graph.getEdgeType(e) == EdgeType.UNDIRECTED); } } } assertEquals(smallGraph.getInEdges(v1).size(), 4); assertEquals(smallGraph.getOutEdges(v1).size(), 3); assertEquals(smallGraph.getOutEdges(v0).size(), 2); } public void testGetPredecessors() { assertTrue(smallGraph.getPredecessors(v0).containsAll(Collections.singleton(v1))); } public void testGetSuccessors() { assertTrue(smallGraph.getPredecessors(v1).contains(v0)); assertTrue(smallGraph.getPredecessors(v1).contains(v2)); } public void testGetNeighbors() { Collection neighbors = smallGraph.getNeighbors(v1); assertTrue(neighbors.contains(v0)); assertTrue(neighbors.contains(v2)); } public void testGetIncidentEdges() { assertEquals(smallGraph.getIncidentEdges(v0).size(), 2); } public void testFindEdge() { Number edge = smallGraph.findEdge(v1, v2); assertTrue(edge == e12 || edge == e21); } public void testGetEndpoints() { Pair endpoints = smallGraph.getEndpoints(e01); assertTrue((endpoints.getFirst() == v0 && endpoints.getSecond() == v1) || endpoints.getFirst() == v1 && endpoints.getSecond() == v0); } public void testIsDirected() { for(Number edge : smallGraph.getEdges()) { if(edge == e21) { assertEquals(smallGraph.getEdgeType(edge), EdgeType.DIRECTED); } else { assertEquals(smallGraph.getEdgeType(edge), EdgeType.UNDIRECTED); } } } } jung-jung-2.1.1/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractSparseTreeTest.java000066400000000000000000000071141276402340000312440ustar00rootroot00000000000000package edu.uci.ics.jung.graph; import junit.framework.TestCase; import com.google.common.base.Supplier; public abstract class AbstractSparseTreeTest extends TestCase { protected Tree tree; protected Supplier> graphFactory; protected Supplier edgeFactory; public void testRemoveVertex() { tree.addVertex("A"); tree.addEdge(edgeFactory.get(), "A", "B"); tree.addEdge(edgeFactory.get(), "A", "C"); tree.addEdge(edgeFactory.get(), "B", "E"); tree.addEdge(edgeFactory.get(), "B", "F"); // System.err.println("tree is "+tree); tree.removeVertex("B"); // System.err.println("tree now "+tree); } public void testSimpleTree() { tree.addVertex("A"); tree.addEdge(edgeFactory.get(), "A", "B"); tree.addEdge(edgeFactory.get(), "A", "C"); } public void testCreateLoop() { try { tree.addVertex("A"); tree.addEdge(edgeFactory.get(), "A", "A"); fail("should not be able to addChild(v,v)"); } catch(IllegalArgumentException e) { // all is well } try { tree.addEdge(edgeFactory.get(), "A", "B"); tree.addEdge(edgeFactory.get(), "B", "A"); fail("should not allow loop"); } catch(IllegalArgumentException e) { // all is well } } public void testHeightAndDepth() { tree.addVertex("V0"); assertEquals(tree.getHeight(), 0); assertEquals(tree.getDepth("V0"), 0); tree.addEdge(edgeFactory.get(), "V0", "V1"); assertEquals(tree.getHeight(), 1); assertEquals(tree.getDepth("V1"), 1); tree.addEdge(edgeFactory.get(), "V0", "V2"); assertEquals(tree.getHeight(), 1); assertEquals(tree.getDepth("V2"), 1); tree.addEdge(edgeFactory.get(), "V1", "V4"); assertEquals(tree.getHeight(), 2); assertEquals(tree.getDepth("V4"), 2); tree.addEdge(edgeFactory.get(), "V2", "V3"); assertEquals(tree.getHeight(), 2); assertEquals(tree.getDepth("V3"), 2); tree.addEdge(edgeFactory.get(), "V2", "V5"); assertEquals(tree.getHeight(), 2); assertEquals(tree.getDepth("V5"), 2); tree.addEdge(edgeFactory.get(), "V4", "V6"); assertEquals(tree.getHeight(), 3); assertEquals(tree.getDepth("V6"), 3); tree.addEdge(edgeFactory.get(), "V4", "V7"); assertEquals(tree.getHeight(), 3); assertEquals(tree.getDepth("V7"), 3); tree.addEdge(edgeFactory.get(), "V3", "V8"); assertEquals(tree.getHeight(), 3); assertEquals(tree.getDepth("V8"), 3); tree.addEdge(edgeFactory.get(), "V6", "V9"); assertEquals(tree.getHeight(), 4); assertEquals(tree.getDepth("V9"), 4); tree.addEdge(edgeFactory.get(), "V4", "V10"); assertEquals(tree.getHeight(), 4); assertEquals(tree.getDepth("V10"), 3); tree.addEdge(edgeFactory.get(), "V4", "V11"); assertEquals(tree.getHeight(), 4); assertEquals(tree.getDepth("V11"), 3); tree.addEdge(edgeFactory.get(), "V4", "V12"); assertEquals(tree.getHeight(), 4); assertEquals(tree.getDepth("V12"), 3); tree.addEdge(edgeFactory.get(), "V6", "V13"); assertEquals(tree.getHeight(), 4); assertEquals(tree.getDepth("V13"), 4); tree.addEdge(edgeFactory.get(), "V10", "V14"); assertEquals(tree.getHeight(), 4); assertEquals(tree.getDepth("V14"), 4); tree.addEdge(edgeFactory.get(), "V13", "V15"); assertEquals(tree.getHeight(), 5); assertEquals(tree.getDepth("V15"), 5); tree.addEdge(edgeFactory.get(), "V13", "V16"); assertEquals(tree.getHeight(), 5); assertEquals(tree.getDepth("V16"), 5); } } jung-jung-2.1.1/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractTreeUtilsTest.java000066400000000000000000000020451276402340000311050ustar00rootroot00000000000000package edu.uci.ics.jung.graph; import junit.framework.TestCase; import edu.uci.ics.jung.graph.util.TreeUtils; public abstract class AbstractTreeUtilsTest extends TestCase { protected Tree tree; public void testRemove() { try { TreeUtils.getSubTree(tree, "C0"); tree.removeVertex("C0"); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void testAdd() { try { Forest subTree = TreeUtils.getSubTree(tree, "C0"); Integer edge = tree.getInEdges("C0").iterator().next(); String parent = tree.getPredecessors("C0").iterator().next(); tree.removeVertex("C0"); TreeUtils.addSubTree(tree, subTree, parent, edge); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } AbstractUndirectedSparseMultigraphTest.java000066400000000000000000000072271276402340000344160ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/test/java/edu/uci/ics/jung/graphpackage edu.uci.ics.jung.graph; import java.util.Collection; import java.util.Collections; import junit.framework.TestCase; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; public abstract class AbstractUndirectedSparseMultigraphTest extends TestCase { protected Integer v0 = new Integer(0); protected Integer v1 = new Integer(1); protected Integer v2 = new Integer(2); protected Float e01 = new Float(.1f); protected Float e10 = new Float(.2f); protected Float e12 = new Float(.3f); protected Float e21 = new Float(.4f); protected Graph graph; public void testGetEdges() { assertEquals(graph.getEdgeCount(), 4); } public void testGetVertices() { assertEquals(graph.getVertexCount(), 3); } public void testAddVertex() { int count = graph.getVertexCount(); graph.addVertex(new Integer(3)); assertEquals(graph.getVertexCount(), count+1); } public void testRemoveEndVertex() { int vertexCount = graph.getVertexCount(); graph.removeVertex(v0); assertEquals(vertexCount-1, graph.getVertexCount()); assertEquals(2, graph.getEdgeCount()); } public void testRemoveMiddleVertex() { int vertexCount = graph.getVertexCount(); graph.removeVertex(v1); assertEquals(vertexCount-1, graph.getVertexCount()); assertEquals(0, graph.getEdgeCount()); } public void testAddEdge() { int edgeCount = graph.getEdgeCount(); graph.addEdge(new Double(.5), v0, v1); assertEquals(graph.getEdgeCount(), edgeCount+1); } public void testRemoveEdge() { int edgeCount = graph.getEdgeCount(); graph.removeEdge(e12); assertEquals(graph.getEdgeCount(), edgeCount-1); } public void testGetInEdges() { assertEquals(graph.getInEdges(v1).size(), 4); } public void testGetOutEdges() { assertEquals(graph.getOutEdges(v1).size(), 4); } public void testGetPredecessors() { assertTrue(graph.getPredecessors(v0).containsAll(Collections.singleton(v1))); } public void testGetSuccessors() { assertTrue(graph.getPredecessors(v1).contains(v0)); assertTrue(graph.getPredecessors(v1).contains(v2)); } public void testGetNeighbors() { Collection neighbors = graph.getNeighbors(v1); assertTrue(neighbors.contains(v0)); assertTrue(neighbors.contains(v2)); } public void testGetIncidentEdges() { assertEquals(graph.getIncidentEdges(v0).size(), 2); } public void testFindEdge() { Number edge = graph.findEdge(v1, v2); assertTrue(edge == e12 || edge == e21); } public void testNullEndpoint() { try { graph.addEdge(.99, new Pair(1,null)); fail("should not be able to add an edge with a null endpoint"); } catch(IllegalArgumentException e) { // all is well } } public void testGetEndpoints() { Pair endpoints = graph.getEndpoints(e01); assertTrue((endpoints.getFirst() == v0 && endpoints.getSecond() == v1) || endpoints.getFirst() == v1 && endpoints.getSecond() == v0); } public void testIsDirected() { for(Number edge : graph.getEdges()) { assertEquals(graph.getEdgeType(edge), EdgeType.UNDIRECTED); } } public void testAddDirectedEdge() { try { graph.addEdge(new Float(.9), v1, v2, EdgeType.DIRECTED); fail("Cannot add a directed edge to this graph"); } catch(IllegalArgumentException uoe) { // all is well } } } jung-jung-2.1.1/jung-api/src/test/java/edu/uci/ics/jung/graph/util/000077500000000000000000000000001276402340000247525ustar00rootroot00000000000000jung-jung-2.1.1/jung-api/src/test/java/edu/uci/ics/jung/graph/util/PairTest.java000066400000000000000000000052751276402340000273610ustar00rootroot00000000000000package edu.uci.ics.jung.graph.util; import java.util.ArrayList; import java.util.List; import edu.uci.ics.jung.graph.util.Pair; import junit.framework.TestCase; public class PairTest extends TestCase { Pair pair; @Override protected void setUp() throws Exception { pair = new Pair(1,2); super.setUp(); } public void testGetFirst() { assertEquals(pair.getFirst(), 1); } public void testGetSecond() { assertEquals(pair.getSecond(), 2); } public void testEqualsObject() { Pair ipair = new Pair(1,2); assertTrue(pair.equals(ipair)); } public void testAdd() { try { pair.add(3); fail("should not be able to add to Pair"); } catch(Exception e) { // all is well } } public void testAddAll() { try { List list = new ArrayList(pair); pair.addAll(list); fail("should not be able to addAll to Pair"); } catch(Exception e) { // all is well } } public void testClear() { try { pair.clear(); fail("should not be able to clear a Pair"); } catch(Exception e) { // all is well } } public void testContains() { assertTrue(pair.contains(1)); } public void testContainsAll() { List list = new ArrayList(pair); assertTrue(pair.containsAll(list)); } public void testIsEmpty() { assertFalse(pair.isEmpty()); } public void testRemove() { try { pair.remove(1); fail("should not be able to remove from a Pair"); } catch(Exception e) { // all is well } } public void testRemoveAll() { try { List list = new ArrayList(pair); pair.removeAll(list); fail("should not be able to removeAll from Pair"); } catch(Exception e) { // all is well } } public void testRetainAll() { try { List list = new ArrayList(pair); pair.retainAll(list); fail("should not be able to retainAll from Pair"); } catch(Exception e) { // all is well } } public void testSize() { assertEquals(pair.size(), 2); } public void testToArray() { @SuppressWarnings("unused") Object[] arr = pair.toArray(); } public void testToArraySArray() { @SuppressWarnings("unused") Integer[] arr = pair.toArray(new Integer[2]); } } jung-jung-2.1.1/jung-graph-impl/000077500000000000000000000000001276402340000164165ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/assembly.xml000066400000000000000000000006611276402340000207620ustar00rootroot00000000000000 dependencies tar.gz false / / false runtime jung-jung-2.1.1/jung-graph-impl/pom.xml000066400000000000000000000020351276402340000177330ustar00rootroot00000000000000 4.0.0 net.sf.jung jung-parent 2.1.1 jung-graph-impl JUNG - Graph Implementations Graph implementations for the JUNG project net.sf.jung jung-api ${project.version} net.sf.jung jung-api ${project.version} test-jar test junit junit test jung-jung-2.1.1/jung-graph-impl/src/000077500000000000000000000000001276402340000172055ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/main/000077500000000000000000000000001276402340000201315ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/main/java/000077500000000000000000000000001276402340000210525ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/000077500000000000000000000000001276402340000216275ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/000077500000000000000000000000001276402340000224075ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/000077500000000000000000000000001276402340000231655ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/000077500000000000000000000000001276402340000241305ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/000077500000000000000000000000001276402340000252315ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/AbstractGraph.java000066400000000000000000000166111276402340000306260ustar00rootroot00000000000000/* * Created on Apr 2, 2006 * * Copyright (c) 2006, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * Abstract implementation of the Graph interface. * Designed to simplify implementation of new graph classes. * * @author Joshua O'Madadhain */ @SuppressWarnings("serial") public abstract class AbstractGraph implements Graph, Serializable { public boolean addEdge(E edge, Collection vertices) { return addEdge(edge, vertices, this.getDefaultEdgeType()); } @SuppressWarnings("unchecked") public boolean addEdge(E edge, Collection vertices, EdgeType edgeType) { if (vertices == null) throw new IllegalArgumentException("'vertices' parameter must not be null"); if (vertices.size() == 2) return addEdge(edge, vertices instanceof Pair ? (Pair)vertices : new Pair(vertices), edgeType); else if (vertices.size() == 1) { V vertex = vertices.iterator().next(); return addEdge(edge, new Pair(vertex, vertex), edgeType); } else throw new IllegalArgumentException("Graph objects connect 1 or 2 vertices; vertices arg has " + vertices.size()); } public boolean addEdge(E e, V v1, V v2) { return addEdge(e, v1, v2, this.getDefaultEdgeType()); } public boolean addEdge(E e, V v1, V v2, EdgeType edge_type) { return addEdge(e, new Pair(v1, v2), edge_type); } /** * Adds {@code edge} to this graph with the specified {@code endpoints}, * with the default edge type. * * @param edge the edge to be added * @param endpoints the endpoints to be connected to this edge * @return {@code true} iff the graph was modified as a result of this call */ public boolean addEdge(E edge, Pair endpoints) { return addEdge(edge, endpoints, this.getDefaultEdgeType()); } /** * Adds {@code edge} to this graph with the specified {@code endpoints} * and {@code EdgeType}. * * @param edge the edge to be added * @param endpoints the endpoints to be connected to this edge * @param edgeType the type of edge to add * @return {@code} true iff the graph was modified as a result of this call */ public abstract boolean addEdge(E edge, Pair endpoints, EdgeType edgeType); protected Pair getValidatedEndpoints(E edge, Pair endpoints) { if (edge == null) throw new IllegalArgumentException("input edge may not be null"); if (endpoints == null) throw new IllegalArgumentException("endpoints may not be null"); Pair new_endpoints = new Pair(endpoints.getFirst(), endpoints.getSecond()); if (containsEdge(edge)) { Pair existing_endpoints = getEndpoints(edge); if (!existing_endpoints.equals(new_endpoints)) { throw new IllegalArgumentException("edge " + edge + " already exists in this graph with endpoints " + existing_endpoints + " and cannot be added with endpoints " + endpoints); } else { return null; } } return new_endpoints; } public int inDegree(V vertex) { return this.getInEdges(vertex).size(); } public int outDegree(V vertex) { return this.getOutEdges(vertex).size(); } public boolean isPredecessor(V v1, V v2) { return this.getPredecessors(v1).contains(v2); } public boolean isSuccessor(V v1, V v2) { return this.getSuccessors(v1).contains(v2); } public int getPredecessorCount(V vertex) { return this.getPredecessors(vertex).size(); } public int getSuccessorCount(V vertex) { return this.getSuccessors(vertex).size(); } public boolean isNeighbor(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) throw new IllegalArgumentException("At least one of these not in this graph: " + v1 + ", " + v2); return this.getNeighbors(v1).contains(v2); } public boolean isIncident(V vertex, E edge) { if (!containsVertex(vertex) || !containsEdge(edge)) throw new IllegalArgumentException("At least one of these not in this graph: " + vertex + ", " + edge); return this.getIncidentEdges(vertex).contains(edge); } public int getNeighborCount(V vertex) { if (!containsVertex(vertex)) throw new IllegalArgumentException(vertex + " is not a vertex in this graph"); return this.getNeighbors(vertex).size(); } public int degree(V vertex) { if (!containsVertex(vertex)) throw new IllegalArgumentException(vertex + " is not a vertex in this graph"); return this.getIncidentEdges(vertex).size(); } public int getIncidentCount(E edge) { Pair incident = this.getEndpoints(edge); if (incident == null) return 0; if (incident.getFirst() == incident.getSecond()) return 1; else return 2; } public V getOpposite(V vertex, E edge) { Pair incident = this.getEndpoints(edge); V first = incident.getFirst(); V second = incident.getSecond(); if (vertex.equals(first)) return second; else if (vertex.equals(second)) return first; else throw new IllegalArgumentException(vertex + " is not incident to " + edge + " in this graph"); } public E findEdge(V v1, V v2) { for (E e : getOutEdges(v1)) { if (getOpposite(v1, e).equals(v2)) return e; } return null; } public Collection findEdgeSet(V v1, V v2) { if (!getVertices().contains(v1)) throw new IllegalArgumentException(v1 + " is not an element of this graph"); if (!getVertices().contains(v2)) throw new IllegalArgumentException(v2 + " is not an element of this graph"); Collection edges = new ArrayList(); for (E e : getOutEdges(v1)) { if (getOpposite(v1, e).equals(v2)) edges.add(e); } return Collections.unmodifiableCollection(edges); } public Collection getIncidentVertices(E edge) { Pair endpoints = this.getEndpoints(edge); Collection incident = new ArrayList(); incident.add(endpoints.getFirst()); incident.add(endpoints.getSecond()); return Collections.unmodifiableCollection(incident); } @Override public String toString() { StringBuffer sb = new StringBuffer("Vertices:"); for(V v : getVertices()) { sb.append(v+","); } sb.setLength(sb.length()-1); sb.append("\nEdges:"); for(E e : getEdges()) { Pair ep = getEndpoints(e); sb.append(e+"["+ep.getFirst()+","+ep.getSecond()+"] "); } return sb.toString(); } } jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/AbstractTypedGraph.java000066400000000000000000000052231276402340000316310ustar00rootroot00000000000000/** * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Sep 1, 2008 * */ package edu.uci.ics.jung.graph; import java.util.Collection; import java.util.Collections; import edu.uci.ics.jung.graph.util.EdgeType; /** * An abstract class for graphs whose edges all have the same {@code EdgeType}. * Intended to simplify the implementation of such graph classes. */ @SuppressWarnings("serial") public abstract class AbstractTypedGraph extends AbstractGraph { /** * The edge type for all edges in this graph. */ protected final EdgeType edge_type; /** * Creates an instance with the specified edge type. * @param edge_type the type of edges that this graph accepts */ public AbstractTypedGraph(EdgeType edge_type) { this.edge_type = edge_type; } /** * Returns this graph's edge type. */ public EdgeType getDefaultEdgeType() { return this.edge_type; } /** * Returns this graph's edge type, or {@code null} if {@code e} is not * in this graph. */ public EdgeType getEdgeType(E e) { return hasEqualEdgeType(edge_type) ? this.edge_type : null; } /** * Returns the edge set for this graph if {@code edgeType} matches the * edge type for this graph, and an empty set otherwise. */ public Collection getEdges(EdgeType edge_type) { return hasEqualEdgeType(edge_type) ? this.getEdges() : Collections.emptySet(); } /** * Returns the edge count for this graph if {@code edge_type} matches * the edge type for this graph, and 0 otherwise. */ public int getEdgeCount(EdgeType edge_type) { return hasEqualEdgeType(edge_type) ? this.getEdgeCount() : 0; } /** * @param edge_type the edge type to compare to this instance's default edge type * @return {@code true} if {@code edge_type} matches the default edge type for * this graph, and {@code false} otherwise */ protected boolean hasEqualEdgeType(EdgeType edge_type) { return this.edge_type.equals(edge_type); } /** * Throws an {@code IllegalArgumentException} if {@code edge_type} does not * match the default edge type for this graph. * @param edge_type the edge type to compare to this instance's default edge type */ protected void validateEdgeType(EdgeType edge_type) { if (!hasEqualEdgeType(edge_type)) throw new IllegalArgumentException("Edge type '" + edge_type + "' does not match the default edge type for this graph: '" + this.edge_type + "'"); } } jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DelegateForest.java000066400000000000000000000220061276402340000307710ustar00rootroot00000000000000package edu.uci.ics.jung.graph; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.graph.util.TreeUtils; /** * An implementation of Forest that delegates to a specified DirectedGraph * instance. * @author Tom Nelson * * @param the vertex type * @param the edge type */ @SuppressWarnings("serial") public class DelegateForest extends GraphDecorator implements Forest { /** * Creates an instance backed by a new {@code DirectedSparseGraph} instance. */ public DelegateForest() { this(new DirectedSparseGraph()); } /** * Creates an instance backed by the input {@code DirectedGraph}. * @param delegate the graph to which operations will be delegated */ public DelegateForest(DirectedGraph delegate) { super(delegate); } /** * Add an edge to the tree, connecting v1, the parent and v2, the child. * v1 must already exist in the tree, and v2 must not already exist * the passed edge must be unique in the tree. Passing an edgeType * other than EdgeType.DIRECTED may cause an illegal argument exception * in the delegate graph. * * @param e a unique edge to add * @param v1 the parent node * @param v2 the child node * @param edgeType should be EdgeType.DIRECTED * @return true if this call mutates the underlying graph * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object, edu.uci.ics.jung.graph.util.EdgeType) */ @Override public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) { if(delegate.getVertices().contains(v1) == false) { throw new IllegalArgumentException("Tree must already contain "+v1); } if(delegate.getVertices().contains(v2)) { throw new IllegalArgumentException("Tree must not already contain "+v2); } return delegate.addEdge(e, v1, v2, edgeType); } /** * Add vertex as a root of the tree * * @param vertex the tree root to add * @return true if this call mutates the underlying graph * @see edu.uci.ics.jung.graph.Graph#addVertex(java.lang.Object) */ @Override public boolean addVertex(V vertex) { setRoot(vertex); return true; } /** * Removes edge from this tree, and the subtree rooted * at the child vertex incident to edge. * (The subtree is removed to ensure that the tree in which the edge * was found is still a tree rather than a forest. To change this * behavior so that the * @param edge the edge to remove * @return true iff the tree was modified * @see edu.uci.ics.jung.graph.Hypergraph#removeEdge(java.lang.Object) */ @Override public boolean removeEdge(E edge) { return removeEdge(edge, true); } /** * Removes edge from this tree. * If remove_subtree is true, removes * the subtree rooted at the child vertex incident to edge. * Otherwise, leaves the subtree intact as a new component tree of this * forest. * @param edge the edge to remove * @param remove_subtree if true, remove the subtree * @return true iff the tree was modified */ public boolean removeEdge(E edge, boolean remove_subtree) { if (!delegate.containsEdge(edge)) return false; V child = getDest(edge); if (remove_subtree) return removeVertex(child); else { delegate.removeEdge(edge); return false; } } /** * Removes vertex from this tree, and the subtree * rooted at vertex. * @param vertex the vertex to remove * @return true iff the tree was modified * @see edu.uci.ics.jung.graph.Hypergraph#removeVertex(java.lang.Object) */ @Override public boolean removeVertex(V vertex) { return removeVertex(vertex, true); } /** * Removes vertex from this tree. * If remove_subtrees is true, removes * the subtrees rooted at the children of vertex. * Otherwise, leaves these subtrees intact as new component trees of this * forest. * @param vertex the vertex to remove * @param remove_subtrees if true, remove the subtrees * rooted at vertex's children * @return true iff the tree was modified */ public boolean removeVertex(V vertex, boolean remove_subtrees) { if (!delegate.containsVertex(vertex)) return false; if (remove_subtrees) for(V v : new ArrayList(delegate.getSuccessors(vertex))) removeVertex(v, true); return delegate.removeVertex(vertex); } /** * returns an ordered list of the nodes beginning at the root * and ending at the passed child node, including all intermediate * nodes. * @param child the last node in the path from the root * @return an ordered list of the nodes from root to child */ public List getPath(V child) { if (!delegate.containsVertex(child)) return null; List list = new ArrayList(); list.add(child); V parent = getParent(child); while(parent != null) { list.add(list.size(), parent); parent = getParent(parent); } return list; } public V getParent(V child) { if (!delegate.containsVertex(child)) return null; Collection parents = delegate.getPredecessors(child); if(parents.size() > 0) { return parents.iterator().next(); } return null; } /** * @return the root of the tree, or null if the tree has > 1 roots */ public V getRoot() { V root = null; for (V v : delegate.getVertices()) { if (delegate.getPredecessorCount(v) == 0) { if (root == null) { root = v; } else { // we've found > 1 root, return null return null; } } } return root; } /** * adds root as a root of the tree * @param root the initial tree root */ public void setRoot(V root) { delegate.addVertex(root); } /** * removes a node from the tree, causing all descendants of * the removed node also to be removed * @param orphan the node to remove * @return whether this call mutates the underlying graph */ public boolean removeChild(V orphan) { return removeVertex(orphan); } /** * computes and returns the depth of the tree from the * root to the passed vertex * * @param v the node who's depth is computed * @return the depth to the passed node. */ public int getDepth(V v) { return getPath(v).size(); } /** * computes and returns the height of the tree * * @return the height */ public int getHeight() { int height = 0; for(V v : getVertices()) { height = Math.max(height, getDepth(v)); } return height; } /** * @param v the vertex to test * @return true if v is neither a leaf * nor a root */ public boolean isInternal(V v) { return isLeaf(v) == false && isRoot(v) == false; } /** * @param v the vertex to test * @return {@code true} if {@code v} has no child nodes. */ public boolean isLeaf(V v) { return getChildren(v).size() == 0; } /** * @param v the vertex whose children are to be returned * @return the children of {@code v}. */ public Collection getChildren(V v) { return delegate.getSuccessors(v); } /** * @param v the vertex to test * @return {@code true} if {@code v} has no parent node. */ public boolean isRoot(V v) { return getParent(v) == null; } @Override public int getIncidentCount(E edge) { return 2; } @SuppressWarnings("unchecked") @Override public boolean addEdge(E edge, Collection vertices) { Pair pair = null; if(vertices instanceof Pair) { pair = (Pair)vertices; } else { pair = new Pair(vertices); } return addEdge(edge, pair.getFirst(), pair.getSecond()); } /** * @return the root of each tree of this forest as a {@code Collection}. */ public Collection getRoots() { Collection roots = new HashSet(); for(V v : delegate.getVertices()) { if(delegate.getPredecessorCount(v) == 0) { roots.add(v); } } return roots; } public Collection> getTrees() { Collection> trees = new HashSet>(); for(V v : getRoots()) { Tree tree = new DelegateTree(); tree.addVertex(v); TreeUtils.growSubTree(this, tree, v); trees.add(tree); } return trees; } /** * Adds {@code tree} to this graph as an element of this forest. * * @param tree the tree to add to this forest as a component */ public void addTree(Tree tree) { TreeUtils.addSubTree(this, tree, null, null); } public int getChildCount(V vertex) { return delegate.getSuccessorCount(vertex); } public Collection getChildEdges(V vertex) { return delegate.getOutEdges(vertex); } public E getParentEdge(V vertex) { if (isRoot(vertex)) return null; return delegate.getInEdges(vertex).iterator().next(); } } jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DelegateTree.java000066400000000000000000000246511276402340000304360ustar00rootroot00000000000000package edu.uci.ics.jung.graph; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * An implementation of Tree that delegates to * a specified instance of DirectedGraph. * @author Tom Nelson * * @param the vertex type * @param the edge type */ @SuppressWarnings("serial") public class DelegateTree extends GraphDecorator implements Tree { /** * @param the vertex type for the graph Supplier * @param the edge type for the graph Supplier * @return a {@code Supplier} that creates an instance of this graph type. */ public static final Supplier> getFactory() { return new Supplier> () { public Tree get() { return new DelegateTree(new DirectedSparseMultigraph()); } }; } protected V root; protected Map vertex_depths; /** * Creates an instance. */ public DelegateTree() { this(DirectedSparseMultigraph.getFactory()); } /** * create an instance with passed values. * @param graphFactory must create a DirectedGraph to use as a delegate */ public DelegateTree(Supplier> graphFactory) { super(graphFactory.get()); this.vertex_depths = new HashMap(); } /** * Creates a new DelegateTree which delegates to graph. * Assumes that graph is already a tree; if it's not, future behavior * of this instance is undefined. * @param graph the graph to which this instance will delegate operations. */ public DelegateTree(DirectedGraph graph) { super(graph); this.vertex_depths = new HashMap(); } /** * Add an edge to the tree, connecting v1, the parent and v2, the child. * v1 must already exist in the tree, and v2 must not already exist * the passed edge must be unique in the tree. Passing an edgeType * other than EdgeType.DIRECTED may cause an illegal argument exception * in the delegate graph. * * @param e a unique edge to add * @param v1 the parent node * @param v2 the child node * @param edgeType should be EdgeType.DIRECTED * @return true if this call mutates the underlying graph * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object, edu.uci.ics.jung.graph.util.EdgeType) */ @Override public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) { return addChild(e, v1, v2, edgeType); } /** * Add an edge to the tree, connecting v1, the parent and v2, the child. * v1 must already exist in the tree, and v2 must not already exist * the passed edge must be unique in the tree. * * @param e a unique edge to add * @param v1 the parent node * @param v2 the child node * @return true if this call mutates the underlying graph * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object) */ @Override public boolean addEdge(E e, V v1, V v2) { return addChild(e, v1, v2); } /** * Will set the root of the Tree, only if the Tree is empty and the * root is currently unset. * * @param vertex the tree root to set * @return true if this call mutates the underlying graph * @see edu.uci.ics.jung.graph.Graph#addVertex(java.lang.Object) * @throws UnsupportedOperationException if the root was previously set */ @Override public boolean addVertex(V vertex) { if(root == null) { this.root = vertex; vertex_depths.put(vertex, 0); return delegate.addVertex(vertex); } else { throw new UnsupportedOperationException("Unless you are setting the root, use addChild()"); } } /** * remove the passed node, and all nodes that are descendants of the * passed node. * @param vertex the vertex to remove * @return true iff the tree was modified * @see edu.uci.ics.jung.graph.Graph#removeVertex(java.lang.Object) */ @Override public boolean removeVertex(V vertex) { if (!delegate.containsVertex(vertex)) return false; for(V v : getChildren(vertex)) { removeVertex(v); vertex_depths.remove(v); } // recalculate height vertex_depths.remove(vertex); return delegate.removeVertex(vertex); } /** * add the passed child node as a child of parent. * parent must exist in the tree, and child must not already exist. * * @param edge the unique edge to connect the parent and child nodes * @param parent the existing parent to attach the child to * @param child the new child to add to the tree as a child of parent * @param edgeType must be EdgeType.DIRECTED or the underlying graph may throw an exception * @return whether this call mutates the underlying graph */ public boolean addChild(E edge, V parent, V child, EdgeType edgeType) { Collection vertices = delegate.getVertices(); if(vertices.contains(parent) == false) { throw new IllegalArgumentException("Tree must already contain parent "+parent); } if(vertices.contains(child)) { throw new IllegalArgumentException("Tree must not already contain child "+child); } vertex_depths.put(child, vertex_depths.get(parent) + 1); return delegate.addEdge(edge, parent, child, edgeType); } /** * add the passed child node as a child of parent. * parent must exist in the tree, and child must not already exist * @param edge the unique edge to connect the parent and child nodes * @param parent the existing parent to attach the child to * @param child the new child to add to the tree as a child of parent * @return whether this call mutates the underlying graph */ public boolean addChild(E edge, V parent, V child) { Collection vertices = delegate.getVertices(); if(vertices.contains(parent) == false) { throw new IllegalArgumentException("Tree must already contain parent "+parent); } if(vertices.contains(child)) { throw new IllegalArgumentException("Tree must not already contain child "+child); } vertex_depths.put(child, vertex_depths.get(parent) + 1); return delegate.addEdge(edge, parent, child); } /** * get the number of children of the passed parent node */ public int getChildCount(V parent) { if (!delegate.containsVertex(parent)) return 0; return getChildren(parent).size(); } /** * get the immediate children nodes of the passed parent */ public Collection getChildren(V parent) { if (!delegate.containsVertex(parent)) return null; return delegate.getSuccessors(parent); } /** * get the single parent node of the passed child */ public V getParent(V child) { if (!delegate.containsVertex(child)) return null; Collection predecessors = delegate.getPredecessors(child); if(predecessors.size() == 0) { return null; } return predecessors.iterator().next(); } /** * Returns an ordered list of the nodes beginning at the root * and ending at {@code vertex}, including all intermediate * nodes. * @param vertex the last node in the path from the root * @return an ordered list of the nodes from root to child */ public List getPath(V vertex) { if (!delegate.containsVertex(vertex)) return null; List vertex_to_root = new ArrayList(); vertex_to_root.add(vertex); V parent = getParent(vertex); while(parent != null) { vertex_to_root.add(parent); parent = getParent(parent); } // reverse list so that it goes from root to child List root_to_vertex = new ArrayList(vertex_to_root.size()); for (int i = vertex_to_root.size() - 1; i >= 0; i--) root_to_vertex.add(vertex_to_root.get(i)); return root_to_vertex; } /** * getter for the root of the tree * @return the root */ public V getRoot() { return root; } /** * sets the root to the passed value, only if the root is * previously unset * @param root the initial tree root */ public void setRoot(V root) { addVertex(root); } /** * removes a node from the tree, causing all descendants of * the removed node also to be removed * @param orphan the node to remove * @return whether this call mutates the underlying graph */ public boolean removeChild(V orphan) { return removeVertex(orphan); } /** * computes and returns the depth of the tree from the * root to the passed vertex * * @param v the node who's depth is computed * @return the depth to the passed node. */ public int getDepth(V v) { return this.vertex_depths.get(v); } /** * Computes and returns the height of the tree. * * @return the height */ public int getHeight() { int height = 0; for(V v : getVertices()) { height = Math.max(height, getDepth(v)); } return height; } /** * @param v the vertex to test * @return true if v is neither * a leaf nor the root of this tree */ public boolean isInternal(V v) { if (!delegate.containsVertex(v)) return false; return isLeaf(v) == false && isRoot(v) == false; } /** * @param v the vertex to test * @return true if {@code v} has no children */ public boolean isLeaf(V v) { if (!delegate.containsVertex(v)) return false; return getChildren(v).size() == 0; } /** * @param v the vertex to test * @return true if {@code v} has no parent */ public boolean isRoot(V v) { if (!delegate.containsVertex(v)) return false; return getParent(v) == null; } @Override public int getIncidentCount(E edge) { if (!delegate.containsEdge(edge)) return 0; // all edges in a tree connect exactly 2 vertices return 2; } @SuppressWarnings("unchecked") @Override public boolean addEdge(E edge, Collection vertices) { Pair pair = null; if(vertices instanceof Pair) { pair = (Pair)vertices; } else { pair = new Pair(vertices); } return addEdge(edge, pair.getFirst(), pair.getSecond()); } @Override public String toString() { return "Tree of "+delegate.toString(); } public Collection> getTrees() { return Collections.>singleton(this); } public Collection getChildEdges(V vertex) { return getOutEdges(vertex); } public E getParentEdge(V vertex) { return getInEdges(vertex).iterator().next(); } } DirectedOrderedSparseMultigraph.java000066400000000000000000000064461276402340000342720ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/* * Created on Oct 17, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Set; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.Pair; /** * An implementation of DirectedGraph, suitable for sparse graphs, * that orders its vertex and edge collections * according to insertion time. */ @SuppressWarnings("serial") public class DirectedOrderedSparseMultigraph extends DirectedSparseMultigraph implements DirectedGraph, MultiGraph { /** * @param the vertex type for the graph Supplier * @param the edge type for the graph Supplier * @return a {@code Supplier} that creates an instance of this graph type. */ public static Supplier> getFactory() { return new Supplier> () { public DirectedGraph get() { return new DirectedOrderedSparseMultigraph(); } }; } /** * Creates a new instance. */ public DirectedOrderedSparseMultigraph() { vertices = new LinkedHashMap>>(); edges = new LinkedHashMap>(); } @Override public boolean addVertex(V vertex) { if(vertex == null) { throw new IllegalArgumentException("vertex may not be null"); } if (!containsVertex(vertex)) { vertices.put(vertex, new Pair>(new LinkedHashSet(), new LinkedHashSet())); return true; } else { return false; } } @Override public Collection getPredecessors(V vertex) { if (!containsVertex(vertex)) return null; Set preds = new LinkedHashSet(); for (E edge : getIncoming_internal(vertex)) preds.add(this.getSource(edge)); return Collections.unmodifiableCollection(preds); } @Override public Collection getSuccessors(V vertex) { if (!containsVertex(vertex)) return null; Set succs = new LinkedHashSet(); for (E edge : getOutgoing_internal(vertex)) succs.add(this.getDest(edge)); return Collections.unmodifiableCollection(succs); } @Override public Collection getNeighbors(V vertex) { if (!containsVertex(vertex)) return null; Collection neighbors = new LinkedHashSet(); for (E edge : getIncoming_internal(vertex)) neighbors.add(this.getSource(edge)); for (E edge : getOutgoing_internal(vertex)) neighbors.add(this.getDest(edge)); return Collections.unmodifiableCollection(neighbors); } @Override public Collection getIncidentEdges(V vertex) { if (!containsVertex(vertex)) return null; Collection incident = new LinkedHashSet(); incident.addAll(getIncoming_internal(vertex)); incident.addAll(getOutgoing_internal(vertex)); return incident; } } jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DirectedSparseGraph.java000066400000000000000000000177241276402340000317720ustar00rootroot00000000000000/* * Created on Mar 26, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * An implementation of DirectedGraph suitable for sparse graphs. */ @SuppressWarnings("serial") public class DirectedSparseGraph extends AbstractTypedGraph implements DirectedGraph { /** * @param the vertex type for the graph Supplier * @param the edge type for the graph Supplier * @return a {@code Supplier} that creates an instance of this graph type. */ public static final Supplier> getFactory() { return new Supplier> () { public DirectedGraph get() { return new DirectedSparseGraph(); } }; } protected Map>> vertices; // Map of vertices to Pair of adjacency maps {incoming, outgoing} // of neighboring vertices to incident edges protected Map> edges; // Map of edges to incident vertex pairs /** * Creates an instance. */ public DirectedSparseGraph() { super(EdgeType.DIRECTED); vertices = new HashMap>>(); edges = new HashMap>(); } @Override public boolean addEdge(E edge, Pair endpoints, EdgeType edgeType) { this.validateEdgeType(edgeType); Pair new_endpoints = getValidatedEndpoints(edge, endpoints); if (new_endpoints == null) return false; V source = new_endpoints.getFirst(); V dest = new_endpoints.getSecond(); if (findEdge(source, dest) != null) return false; edges.put(edge, new_endpoints); if (!vertices.containsKey(source)) this.addVertex(source); if (!vertices.containsKey(dest)) this.addVertex(dest); // map source of this edge to and vice versa vertices.get(source).getSecond().put(dest, edge); vertices.get(dest).getFirst().put(source, edge); return true; } @Override public E findEdge(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) return null; return vertices.get(v1).getSecond().get(v2); } @Override public Collection findEdgeSet(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) return null; ArrayList edge_collection = new ArrayList(1); E e = findEdge(v1, v2); if (e == null) return edge_collection; edge_collection.add(e); return edge_collection; } protected Collection getIncoming_internal(V vertex) { return vertices.get(vertex).getFirst().values(); } protected Collection getOutgoing_internal(V vertex) { return vertices.get(vertex).getSecond().values(); } protected Collection getPreds_internal(V vertex) { return vertices.get(vertex).getFirst().keySet(); } protected Collection getSuccs_internal(V vertex) { return vertices.get(vertex).getSecond().keySet(); } public Collection getInEdges(V vertex) { if (!containsVertex(vertex)) return null; return Collections.unmodifiableCollection(getIncoming_internal(vertex)); } public Collection getOutEdges(V vertex) { if (!containsVertex(vertex)) return null; return Collections.unmodifiableCollection(getOutgoing_internal(vertex)); } public Collection getPredecessors(V vertex) { if (!containsVertex(vertex)) return null; return Collections.unmodifiableCollection(getPreds_internal(vertex)); } public Collection getSuccessors(V vertex) { if (!containsVertex(vertex)) return null; return Collections.unmodifiableCollection(getSuccs_internal(vertex)); } public Pair getEndpoints(E edge) { if (!containsEdge(edge)) return null; return edges.get(edge); } public V getSource(E directed_edge) { if (!containsEdge(directed_edge)) return null; return edges.get(directed_edge).getFirst(); } public V getDest(E directed_edge) { if (!containsEdge(directed_edge)) return null; return edges.get(directed_edge).getSecond(); } public boolean isSource(V vertex, E edge) { if (!containsEdge(edge) || !containsVertex(vertex)) return false; return vertex.equals(this.getEndpoints(edge).getFirst()); } public boolean isDest(V vertex, E edge) { if (!containsEdge(edge) || !containsVertex(vertex)) return false; return vertex.equals(this.getEndpoints(edge).getSecond()); } public Collection getEdges() { return Collections.unmodifiableCollection(edges.keySet()); } public Collection getVertices() { return Collections.unmodifiableCollection(vertices.keySet()); } public boolean containsVertex(V vertex) { return vertices.containsKey(vertex); } public boolean containsEdge(E edge) { return edges.containsKey(edge); } public int getEdgeCount() { return edges.size(); } public int getVertexCount() { return vertices.size(); } public Collection getNeighbors(V vertex) { if (!containsVertex(vertex)) return null; Collection neighbors = new HashSet(); neighbors.addAll(getPreds_internal(vertex)); neighbors.addAll(getSuccs_internal(vertex)); return Collections.unmodifiableCollection(neighbors); } public Collection getIncidentEdges(V vertex) { if (!containsVertex(vertex)) return null; Collection incident_edges = new HashSet(); incident_edges.addAll(getIncoming_internal(vertex)); incident_edges.addAll(getOutgoing_internal(vertex)); return Collections.unmodifiableCollection(incident_edges); } public boolean addVertex(V vertex) { if(vertex == null) { throw new IllegalArgumentException("vertex may not be null"); } if (!containsVertex(vertex)) { vertices.put(vertex, new Pair>(new HashMap(), new HashMap())); return true; } else { return false; } } public boolean removeVertex(V vertex) { if (!containsVertex(vertex)) return false; // copy to avoid concurrent modification in removeEdge ArrayList incident = new ArrayList(getIncoming_internal(vertex)); incident.addAll(getOutgoing_internal(vertex)); for (E edge : incident) removeEdge(edge); vertices.remove(vertex); return true; } public boolean removeEdge(E edge) { if (!containsEdge(edge)) return false; Pair endpoints = this.getEndpoints(edge); V source = endpoints.getFirst(); V dest = endpoints.getSecond(); // remove vertices from each others' adjacency maps vertices.get(source).getSecond().remove(dest); vertices.get(dest).getFirst().remove(source); edges.remove(edge); return true; } } jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DirectedSparseMultigraph.java000066400000000000000000000163571276402340000330460ustar00rootroot00000000000000/* * Created on Oct 17, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * An implementation of DirectedGraph, suitable for sparse graphs, * that permits parallel edges. */ @SuppressWarnings("serial") public class DirectedSparseMultigraph extends AbstractTypedGraph implements DirectedGraph, MultiGraph { /** * @param the vertex type for the graph Supplier * @param the edge type for the graph Supplier * @return a {@code Supplier} that creates an instance of this graph type. */ public static Supplier> getFactory() { return new Supplier> () { public DirectedGraph get() { return new DirectedSparseMultigraph(); } }; } protected Map>> vertices; // Map of vertices to Pair of adjacency sets {incoming, outgoing} protected Map> edges; // Map of edges to incident vertex pairs /** * Creates a new instance. */ public DirectedSparseMultigraph() { super(EdgeType.DIRECTED); vertices = new HashMap>>(); edges = new HashMap>(); } public Collection getEdges() { return Collections.unmodifiableCollection(edges.keySet()); } public Collection getVertices() { return Collections.unmodifiableCollection(vertices.keySet()); } public boolean containsVertex(V vertex) { return vertices.keySet().contains(vertex); } public boolean containsEdge(E edge) { return edges.keySet().contains(edge); } protected Collection getIncoming_internal(V vertex) { return vertices.get(vertex).getFirst(); } protected Collection getOutgoing_internal(V vertex) { return vertices.get(vertex).getSecond(); } public boolean addVertex(V vertex) { if(vertex == null) { throw new IllegalArgumentException("vertex may not be null"); } if (!containsVertex(vertex)) { vertices.put(vertex, new Pair>(new HashSet(), new HashSet())); return true; } else { return false; } } public boolean removeVertex(V vertex) { if (!containsVertex(vertex)) return false; // copy to avoid concurrent modification in removeEdge Set incident = new HashSet(getIncoming_internal(vertex)); incident.addAll(getOutgoing_internal(vertex)); for (E edge : incident) removeEdge(edge); vertices.remove(vertex); return true; } public boolean removeEdge(E edge) { if (!containsEdge(edge)) return false; Pair endpoints = this.getEndpoints(edge); V source = endpoints.getFirst(); V dest = endpoints.getSecond(); // remove edge from incident vertices' adjacency sets getOutgoing_internal(source).remove(edge); getIncoming_internal(dest).remove(edge); edges.remove(edge); return true; } public Collection getInEdges(V vertex) { if (!containsVertex(vertex)) return null; return Collections.unmodifiableCollection(getIncoming_internal(vertex)); } public Collection getOutEdges(V vertex) { if (!containsVertex(vertex)) return null; return Collections.unmodifiableCollection(getOutgoing_internal(vertex)); } public Collection getPredecessors(V vertex) { if (!containsVertex(vertex)) return null; Set preds = new HashSet(); for (E edge : getIncoming_internal(vertex)) preds.add(this.getSource(edge)); return Collections.unmodifiableCollection(preds); } public Collection getSuccessors(V vertex) { if (!containsVertex(vertex)) return null; Set succs = new HashSet(); for (E edge : getOutgoing_internal(vertex)) succs.add(this.getDest(edge)); return Collections.unmodifiableCollection(succs); } public Collection getNeighbors(V vertex) { if (!containsVertex(vertex)) return null; Collection neighbors = new HashSet(); for (E edge : getIncoming_internal(vertex)) neighbors.add(this.getSource(edge)); for (E edge : getOutgoing_internal(vertex)) neighbors.add(this.getDest(edge)); return Collections.unmodifiableCollection(neighbors); } public Collection getIncidentEdges(V vertex) { if (!containsVertex(vertex)) return null; Collection incident = new HashSet(); incident.addAll(getIncoming_internal(vertex)); incident.addAll(getOutgoing_internal(vertex)); return incident; } @Override public E findEdge(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) return null; for (E edge : getOutgoing_internal(v1)) if (this.getDest(edge).equals(v2)) return edge; return null; } @Override public boolean addEdge(E edge, Pair endpoints, EdgeType edgeType) { this.validateEdgeType(edgeType); Pair new_endpoints = getValidatedEndpoints(edge, endpoints); if (new_endpoints == null) return false; edges.put(edge, new_endpoints); V source = new_endpoints.getFirst(); V dest = new_endpoints.getSecond(); if (!containsVertex(source)) this.addVertex(source); if (!containsVertex(dest)) this.addVertex(dest); getIncoming_internal(dest).add(edge); getOutgoing_internal(source).add(edge); return true; } public V getSource(E edge) { if (!containsEdge(edge)) return null; return this.getEndpoints(edge).getFirst(); } public V getDest(E edge) { if (!containsEdge(edge)) return null; return this.getEndpoints(edge).getSecond(); } public boolean isSource(V vertex, E edge) { if (!containsEdge(edge) || !containsVertex(vertex)) return false; return vertex.equals(this.getEndpoints(edge).getFirst()); } public boolean isDest(V vertex, E edge) { if (!containsEdge(edge) || !containsVertex(vertex)) return false; return vertex.equals(this.getEndpoints(edge).getSecond()); } public Pair getEndpoints(E edge) { return edges.get(edge); } public int getEdgeCount() { return edges.size(); } public int getVertexCount() { return vertices.size(); } } jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/OrderedKAryTree.java000066400000000000000000000572431276402340000311020ustar00rootroot00000000000000/* * Created on May 8, 2008 * * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * An implementation of Tree in which each vertex has * ≤ k children. The value of 'k' is specified by the constructor * parameter. A specific child (edge) can be retrieved directly by specifying the * index at which the child is located. By default, new (child) vertices * are added at the lowest index available, if no index is specified. * */ @SuppressWarnings("serial") public class OrderedKAryTree extends AbstractTypedGraph implements Tree { protected Map> edge_vpairs; protected Map vertex_data; protected int height; protected V root; protected int order; /** * @param the vertex type for the graph Supplier * @param the edge type for the graph Supplier * @param order the maximum number of children ("k") that any vertex can have * @return a {@code Supplier} that creates an instance of this graph type. */ public static Supplier> getFactory(final int order) { return new Supplier> () { public DirectedGraph get() { return new OrderedKAryTree(order); } }; } /** * Creates a new instance with the specified order (maximum number of children). * @param order the maximum number of children ("k") that any vertex can have */ public OrderedKAryTree(int order) { super(EdgeType.DIRECTED); this.order = order; this.height = -1; this.edge_vpairs = new HashMap>(); this.vertex_data = new HashMap(); } /** * @param vertex the vertex whose number of children is to be returned * @return the number of children that {@code vertex} has * @see edu.uci.ics.jung.graph.Tree#getChildCount(java.lang.Object) */ public int getChildCount(V vertex) { if (!containsVertex(vertex)) return 0; List edges = vertex_data.get(vertex).child_edges; if (edges == null) return 0; int count = 0; for (E edge : edges) count += edge == null ? 0 : 1; return count; } /** * @param vertex the vertex whose child edge is to be returned * @param index the index of the edge to be returned * @return the child edge of {@code vertex} at index {@code index}, that is, * its ith child edge. */ public E getChildEdge(V vertex, int index) { if (!containsVertex(vertex)) return null; List edges = vertex_data.get(vertex).child_edges; if (edges == null) return null; return edges.get(index); } /** * @see edu.uci.ics.jung.graph.Tree#getChildEdges(java.lang.Object) */ public Collection getChildEdges(V vertex) { if (!containsVertex(vertex)) return null; List edges = vertex_data.get(vertex).child_edges; return edges == null ? Collections.emptySet() : new ImmutableList.Builder().addAll(edges).build(); } /** * Returns an ordered list of {@code vertex}'s child vertices. * If there is no child in position i, then the list will contain * {@code null} in position i. If {@code vertex} has no children * then the empty set will be returned. * @see edu.uci.ics.jung.graph.Tree#getChildren(java.lang.Object) */ public Collection getChildren(V vertex) { if (!containsVertex(vertex)) return null; List edges = vertex_data.get(vertex).child_edges; if (edges == null) return Collections.emptySet(); Collection children = new ArrayList(order); for (E edge : edges) children.add(this.getOpposite(vertex, edge)); return new ImmutableList.Builder().addAll(children).build(); } /** * @see edu.uci.ics.jung.graph.Tree#getDepth(java.lang.Object) * @return the depth of the vertex in this tree, or -1 if the vertex is * not present in this tree */ public int getDepth(V vertex) { if (!containsVertex(vertex)) return -1; return vertex_data.get(vertex).depth; } /** * Returns the height of the tree, or -1 if the tree is empty. * @see edu.uci.ics.jung.graph.Tree#getHeight() */ public int getHeight() { return height; } /** * @see edu.uci.ics.jung.graph.Tree#getParent(java.lang.Object) */ public V getParent(V vertex) { if (!containsVertex(vertex)) return null; else if (vertex.equals(root)) return null; return edge_vpairs.get(vertex_data.get(vertex).parent_edge).getFirst(); } /** * @see edu.uci.ics.jung.graph.Tree#getParentEdge(java.lang.Object) */ public E getParentEdge(V vertex) { if (!containsVertex(vertex)) return null; return vertex_data.get(vertex).parent_edge; } /** * @see edu.uci.ics.jung.graph.Tree#getRoot() */ public V getRoot() { return root; } /** * @see edu.uci.ics.jung.graph.Forest#getTrees() */ public Collection> getTrees() { Collection> forest = new ArrayList>(1); forest.add(this); return forest; } /** * Adds the specified {@code child} vertex and edge {@code e} to the graph * with the specified parent vertex {@code parent}. If {@code index} is * greater than or equal to 0, then the child is placed at position * {@code index}; if it is less than 0, the child is placed at the lowest * available position; if it is greater than or equal to the order of this * tree, an exception is thrown. * * @param e the edge to add * @param parent the source of the edge to be added * @param child the destination of the edge to be added * @param index the position at which e is to be added as a child of {@code parent} * @return {@code true} if the graph has been modified * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object) */ public boolean addEdge(E e, V parent, V child, int index) { if (e == null || child == null || parent == null) throw new IllegalArgumentException("Inputs may not be null"); if (!containsVertex(parent)) throw new IllegalArgumentException("Tree must already " + "include parent: " + parent); if (containsVertex(child)) throw new IllegalArgumentException("Tree must not already " + "include child: " + child); if (parent.equals(child)) throw new IllegalArgumentException("Input vertices must be distinct"); if (index < 0 || index >= order) throw new IllegalArgumentException("'index' must be in [0, [order-1]]"); Pair endpoints = new Pair(parent, child); if (containsEdge(e)) if (!endpoints.equals(edge_vpairs.get(e))) throw new IllegalArgumentException("Tree already includes edge" + e + " with different endpoints " + edge_vpairs.get(e)); else return false; VertexData parent_data = vertex_data.get(parent); List outedges = parent_data.child_edges; if (outedges == null) outedges = new ArrayList(this.order); boolean edge_placed = false; if (index >= 0) if (outedges.get(index) != null) throw new IllegalArgumentException("Parent " + parent + " already has a child at index " + index + " in this tree"); else outedges.set(index, e); for (int i = 0; i < order; i++) { if (outedges.get(i) == null) { outedges.set(i, e); edge_placed = true; break; } } if (!edge_placed) throw new IllegalArgumentException("Parent " + parent + " already" + " has " + order + " children in this tree"); // initialize VertexData for child; leave child's child_edges null for now VertexData child_data = new VertexData(e, parent_data.depth + 1); vertex_data.put(child, child_data); height = child_data.depth > height ? child_data.depth : height; edge_vpairs.put(e, endpoints); return true; } /** * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object) */ @Override public boolean addEdge(E e, V parent, V child) { return addEdge(e, parent, child, -1); } /** * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object, edu.uci.ics.jung.graph.util.EdgeType) */ @Override public boolean addEdge(E e, V v1, V v2, EdgeType edge_type) { this.validateEdgeType(edge_type); return addEdge(e, v1, v2); } /** * @see edu.uci.ics.jung.graph.Graph#getDest(java.lang.Object) */ public V getDest(E directed_edge) { if (!containsEdge(directed_edge)) return null; return edge_vpairs.get(directed_edge).getSecond(); } /** * @see edu.uci.ics.jung.graph.Graph#getEndpoints(java.lang.Object) */ public Pair getEndpoints(E edge) { if (!containsEdge(edge)) return null; return edge_vpairs.get(edge); } /** * @see edu.uci.ics.jung.graph.Graph#getInEdges(java.lang.Object) */ public Collection getInEdges(V vertex) { if (!containsVertex(vertex)) return null; else if (vertex.equals(root)) return Collections.emptySet(); else return Collections.singleton(getParentEdge(vertex)); } /** * @see edu.uci.ics.jung.graph.Graph#getOpposite(java.lang.Object, java.lang.Object) */ @Override public V getOpposite(V vertex, E edge) { if (!containsVertex(vertex) || !containsEdge(edge)) return null; Pair endpoints = edge_vpairs.get(edge); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); return v1.equals(vertex) ? v2 : v1; } /** * @see edu.uci.ics.jung.graph.Graph#getOutEdges(java.lang.Object) */ public Collection getOutEdges(V vertex) { return getChildEdges(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getPredecessorCount(java.lang.Object) * @return 0 if vertex is the root, -1 if the vertex is * not an element of this tree, and 1 otherwise */ @Override public int getPredecessorCount(V vertex) { if (!containsVertex(vertex)) return -1; return vertex.equals(root) ? 0 : 1; } /** * @see edu.uci.ics.jung.graph.Graph#getPredecessors(java.lang.Object) */ public Collection getPredecessors(V vertex) { if (!containsVertex(vertex)) return null; if (vertex.equals(root)) return Collections.emptySet(); return Collections.singleton(getParent(vertex)); } /** * @see edu.uci.ics.jung.graph.Graph#getSource(java.lang.Object) */ public V getSource(E directed_edge) { if (!containsEdge(directed_edge)) return null; return edge_vpairs.get(directed_edge).getFirst(); } /** * @see edu.uci.ics.jung.graph.Graph#getSuccessorCount(java.lang.Object) */ @Override public int getSuccessorCount(V vertex) { return getChildCount(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#getSuccessors(java.lang.Object) */ public Collection getSuccessors(V vertex) { return getChildren(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#inDegree(java.lang.Object) */ @Override public int inDegree(V vertex) { if (!containsVertex(vertex)) return 0; if (vertex.equals(root)) return 0; return 1; } /** * @see edu.uci.ics.jung.graph.Graph#isDest(java.lang.Object, java.lang.Object) */ public boolean isDest(V vertex, E edge) { if (!containsEdge(edge) || !containsVertex(vertex)) return false; return edge_vpairs.get(edge).getSecond().equals(vertex); } /** * Returns true if vertex is a leaf of this tree, * i.e., if it has no children. * @param vertex the vertex to be queried * @return true if outDegree(vertex)==0 */ public boolean isLeaf(V vertex) { if (!containsVertex(vertex)) return false; return outDegree(vertex) == 0; } /** * Returns true iff v1 is the parent of v2. * Note that if v2 is the root and v1 is null, * this method returns true. * * @see edu.uci.ics.jung.graph.Graph#isPredecessor(java.lang.Object, java.lang.Object) */ @Override public boolean isPredecessor(V v1, V v2) { if (!containsVertex(v2)) return false; return getParent(v2).equals(v1); } /** * Returns true if vertex is a leaf of this tree, * i.e., if it has no children. * @param vertex the vertex to be queried * @return true if outDegree(vertex)==0 */ public boolean isRoot(V vertex) { if (root == null) return false; return root.equals(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#isSource(java.lang.Object, java.lang.Object) */ public boolean isSource(V vertex, E edge) { if (!containsEdge(edge) || !containsVertex(vertex)) return false; return edge_vpairs.get(edge).getFirst().equals(vertex); } /** * @see edu.uci.ics.jung.graph.Graph#isSuccessor(java.lang.Object, java.lang.Object) */ @Override public boolean isSuccessor(V v1, V v2) { if (!containsVertex(v2)) return false; if (containsVertex(v1)) return getParent(v1).equals(v2); return isLeaf(v2) && v1 == null; } /** * @see edu.uci.ics.jung.graph.Graph#outDegree(java.lang.Object) */ @Override public int outDegree(V vertex) { if (!containsVertex(vertex)) return 0; List out_edges = vertex_data.get(vertex).child_edges; if (out_edges == null) return 0; int degree = 0; for (E e : out_edges) degree += (e == null) ? 0 : 1; return degree; } /** * @see edu.uci.ics.jung.graph.Hypergraph#addEdge(java.lang.Object, java.util.Collection) */ @Override @SuppressWarnings("unchecked") public boolean addEdge(E edge, Collection vertices, EdgeType edge_type) { if (edge == null || vertices == null) throw new IllegalArgumentException("inputs may not be null"); if (vertices.size() != 2) throw new IllegalArgumentException("'vertices' must contain " + "exactly 2 distinct vertices"); this.validateEdgeType(edge_type); Pair endpoints; if (vertices instanceof Pair) endpoints = (Pair)vertices; else endpoints = new Pair(vertices); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); if (v1.equals(v2)) throw new IllegalArgumentException("Input vertices must be distinct"); return addEdge(edge, v1, v2); } /** * @see edu.uci.ics.jung.graph.Hypergraph#addVertex(java.lang.Object) */ public boolean addVertex(V vertex) { if(root == null) { this.root = vertex; vertex_data.put(vertex, new VertexData(null, 0)); this.height = 0; return true; } else { throw new UnsupportedOperationException("Unless you are setting " + "the root, use addEdge() or addChild()"); } } /** * @see edu.uci.ics.jung.graph.Hypergraph#isIncident(java.lang.Object, java.lang.Object) */ @Override public boolean isIncident(V vertex, E edge) { if (!containsVertex(vertex) || !containsEdge(edge)) return false; return edge_vpairs.get(edge).contains(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#isNeighbor(java.lang.Object, java.lang.Object) */ @Override public boolean isNeighbor(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) return false; return getNeighbors(v1).contains(v2); } /** * @see edu.uci.ics.jung.graph.Hypergraph#containsEdge(java.lang.Object) */ public boolean containsEdge(E edge) { return edge_vpairs.containsKey(edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#containsVertex(java.lang.Object) */ public boolean containsVertex(V vertex) { return vertex_data.containsKey(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#findEdge(java.lang.Object, java.lang.Object) */ @Override public E findEdge(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) return null; VertexData v1_data = vertex_data.get(v1); if (edge_vpairs.get(v1_data.parent_edge).getFirst().equals(v2)) return v1_data.parent_edge; List edges = v1_data.child_edges; if (edges == null) return null; for (E edge : edges) if (edge != null && edge_vpairs.get(edge).getSecond().equals(v2)) return edge; return null; } /** * @see edu.uci.ics.jung.graph.Hypergraph#findEdgeSet(java.lang.Object, java.lang.Object) */ @Override public Collection findEdgeSet(V v1, V v2) { E edge = findEdge(v1, v2); if (edge == null) return Collections.emptySet(); else return Collections.singleton(edge); } /** * Returns the child of vertex at position index * in this tree, or null if it has no child at that position. * @param vertex the vertex to query * @param index the index of the child to return * @return the child of vertex at position index * in this tree, or null if it has no child at that position * @throws ArrayIndexOutOfBoundsException if index is not in * the range {@code [0, order-1]} */ public V getChild(V vertex, int index) { if (index < 0 || index >= order) throw new ArrayIndexOutOfBoundsException(index + " is not in [0, order-1]"); if (!containsVertex(vertex)) return null; List edges = vertex_data.get(vertex).child_edges; if (edges == null) return null; E edge = edges.get(index); return edge == null ? null : edge_vpairs.get(edge).getSecond(); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount() */ public int getEdgeCount() { return edge_vpairs.size(); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getEdges() */ public Collection getEdges() { return new ImmutableSet.Builder().addAll(edge_vpairs.keySet()).build(); //CollectionUtils.unmodifiableCollection(edge_vpairs.keySet()); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentCount(java.lang.Object) */ @Override public int getIncidentCount(E edge) { return 2; // all tree edges have 2 incident vertices } /** * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentEdges(java.lang.Object) */ public Collection getIncidentEdges(V vertex) { if (!containsVertex(vertex)) return null; ArrayList edges = new ArrayList(order+1); VertexData v_data = vertex_data.get(vertex); if (v_data.parent_edge != null) edges.add(v_data.parent_edge); if (v_data.child_edges != null) { for (E edge : v_data.child_edges) if (edge != null) edges.add(edge); } if (edges.isEmpty()) return Collections.emptySet(); return Collections.unmodifiableCollection(edges); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentVertices(java.lang.Object) */ @Override public Collection getIncidentVertices(E edge) { return edge_vpairs.get(edge); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getNeighborCount(java.lang.Object) */ @Override public int getNeighborCount(V vertex) { if (!containsVertex(vertex)) return 0; return (vertex.equals(root) ? 0 : 1) + this.getChildCount(vertex); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getNeighbors(java.lang.Object) */ public Collection getNeighbors(V vertex) { if (!containsVertex(vertex)) return null; ArrayList vertices = new ArrayList(order+1); VertexData v_data = vertex_data.get(vertex); if (v_data.parent_edge != null) vertices.add(edge_vpairs.get(v_data.parent_edge).getFirst()); if (v_data.child_edges != null) { for (E edge : v_data.child_edges) if (edge != null) vertices.add(edge_vpairs.get(edge).getSecond()); } if (vertices.isEmpty()) return Collections.emptySet(); return Collections.unmodifiableCollection(vertices); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getVertexCount() */ public int getVertexCount() { return vertex_data.size(); } /** * @see edu.uci.ics.jung.graph.Hypergraph#getVertices() */ public Collection getVertices() { return new ImmutableSet.Builder().addAll(vertex_data.keySet()).build(); //CollectionUtils.unmodifiableCollection(vertex_data.keySet()); } /** * @see edu.uci.ics.jung.graph.Hypergraph#removeEdge(java.lang.Object) */ public boolean removeEdge(E edge) { if (!containsEdge(edge)) return false; removeVertex(edge_vpairs.get(edge).getSecond()); edge_vpairs.remove(edge); return true; } /** * @see edu.uci.ics.jung.graph.Hypergraph#removeVertex(java.lang.Object) */ public boolean removeVertex(V vertex) { if (!containsVertex(vertex)) return false; // recursively remove all of vertex's children for(V v : getChildren(vertex)) removeVertex(v); E parent_edge = getParentEdge(vertex); edge_vpairs.remove(parent_edge); List edges = vertex_data.get(vertex).child_edges; if (edges != null) for (E edge : edges) edge_vpairs.remove(edge); vertex_data.remove(vertex); return true; } protected class VertexData { List child_edges; E parent_edge; int depth; protected VertexData(E parent_edge, int depth) { this.parent_edge = parent_edge; this.depth = depth; } } @Override public boolean addEdge(E edge, Pair endpoints, EdgeType edgeType) { if (edge == null || endpoints == null) throw new IllegalArgumentException("inputs must not be null"); return addEdge(edge, endpoints.getFirst(), endpoints.getSecond(), edgeType); } } jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/OrderedSparseMultigraph.java000066400000000000000000000067461276402340000327100ustar00rootroot00000000000000/* * Created on Oct 18, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Set; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * An implementation of Graph that orders its vertex and edge collections * according to insertion time, is suitable for sparse graphs, and * permits directed, undirected, and parallel edges. */ @SuppressWarnings("serial") public class OrderedSparseMultigraph extends SparseMultigraph implements MultiGraph { /** * @param the vertex type for the graph Supplier * @param the edge type for the graph Supplier * @return a {@code Supplier} that creates an instance of this graph type. */ public static Supplier> getFactory() { return new Supplier> () { public Graph get() { return new OrderedSparseMultigraph(); } }; } /** * Creates a new instance. */ public OrderedSparseMultigraph() { vertices = new LinkedHashMap>>(); edges = new LinkedHashMap>(); directedEdges = new LinkedHashSet(); } @Override public boolean addVertex(V vertex) { if(vertex == null) { throw new IllegalArgumentException("vertex may not be null"); } if (!containsVertex(vertex)) { vertices.put(vertex, new Pair>(new LinkedHashSet(), new LinkedHashSet())); return true; } else { return false; } } @Override public Collection getPredecessors(V vertex) { if (!containsVertex(vertex)) return null; Set preds = new LinkedHashSet(); for (E edge : getIncoming_internal(vertex)) { if(getEdgeType(edge) == EdgeType.DIRECTED) { preds.add(this.getSource(edge)); } else { preds.add(getOpposite(vertex, edge)); } } return Collections.unmodifiableCollection(preds); } @Override public Collection getSuccessors(V vertex) { if (!containsVertex(vertex)) return null; Set succs = new LinkedHashSet(); for (E edge : getOutgoing_internal(vertex)) { if(getEdgeType(edge) == EdgeType.DIRECTED) { succs.add(this.getDest(edge)); } else { succs.add(getOpposite(vertex, edge)); } } return Collections.unmodifiableCollection(succs); } @Override public Collection getNeighbors(V vertex) { if (!containsVertex(vertex)) return null; Collection out = new LinkedHashSet(); out.addAll(this.getPredecessors(vertex)); out.addAll(this.getSuccessors(vertex)); return out; } @Override public Collection getIncidentEdges(V vertex) { if (!containsVertex(vertex)) return null; Collection out = new LinkedHashSet(); out.addAll(this.getInEdges(vertex)); out.addAll(this.getOutEdges(vertex)); return out; } } jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SetHypergraph.java000066400000000000000000000210151276402340000306600ustar00rootroot00000000000000/* * Created on Feb 4, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.EdgeType; /** * An implementation of Hypergraph that is suitable for sparse graphs and * permits parallel edges. */ @SuppressWarnings("serial") public class SetHypergraph implements Hypergraph, MultiGraph, Serializable { protected Map> vertices; // Map of vertices to incident hyperedge sets protected Map> edges; // Map of hyperedges to incident vertex sets /** * Returns a Factory which creates instances of this class. * @param vertex type of the hypergraph to be created * @param edge type of the hypergraph to be created * @return a Factory which creates instances of this class */ public static Supplier> getFactory() { return new Supplier> () { public Hypergraph get() { return new SetHypergraph(); } }; } /** * Creates a SetHypergraph and initializes the internal data structures. */ public SetHypergraph() { vertices = new HashMap>(); edges = new HashMap>(); } /** * Adds hyperedge to this graph and connects them to the vertex collection to_attach. * Any vertices in to_attach that appear more than once will only appear once in the * incident vertex collection for hyperedge, that is, duplicates will be ignored. * * @see Hypergraph#addEdge(Object, Collection) */ public boolean addEdge(H hyperedge, Collection to_attach) { if (hyperedge == null) throw new IllegalArgumentException("input hyperedge may not be null"); if (to_attach == null) throw new IllegalArgumentException("endpoints may not be null"); if(to_attach.contains(null)) throw new IllegalArgumentException("cannot add an edge with a null endpoint"); Set new_endpoints = new HashSet(to_attach); if (edges.containsKey(hyperedge)) { Collection attached = edges.get(hyperedge); if (!attached.equals(new_endpoints)) { throw new IllegalArgumentException("Edge " + hyperedge + " exists in this graph with endpoints " + attached); } else return false; } edges.put(hyperedge, new_endpoints); for (V v : to_attach) { // add v if it's not already in the graph addVertex(v); // associate v with hyperedge vertices.get(v).add(hyperedge); } return true; } /** * @see Hypergraph#addEdge(Object, Collection, EdgeType) */ public boolean addEdge(H hyperedge, Collection to_attach, EdgeType edge_type) { if (edge_type != EdgeType.UNDIRECTED) throw new IllegalArgumentException("Edge type for this " + "implementation must be EdgeType.HYPER, not " + edge_type); return addEdge(hyperedge, to_attach); } /** * @see Hypergraph#getEdgeType(Object) */ public EdgeType getEdgeType(H edge) { if (containsEdge(edge)) return EdgeType.UNDIRECTED; else return null; } public boolean containsVertex(V vertex) { return vertices.keySet().contains(vertex); } public boolean containsEdge(H edge) { return edges.keySet().contains(edge); } public Collection getEdges() { return edges.keySet(); } public Collection getVertices() { return vertices.keySet(); } public int getEdgeCount() { return edges.size(); } public int getVertexCount() { return vertices.size(); } public Collection getNeighbors(V vertex) { if (!containsVertex(vertex)) return null; Set neighbors = new HashSet(); for (H hyperedge : vertices.get(vertex)) { neighbors.addAll(edges.get(hyperedge)); } return neighbors; } public Collection getIncidentEdges(V vertex) { return vertices.get(vertex); } public Collection getIncidentVertices(H edge) { return edges.get(edge); } public H findEdge(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) return null; for (H h : getIncidentEdges(v1)) { if (isIncident(v2, h)) return h; } return null; } public Collection findEdgeSet(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) return null; Collection edges = new ArrayList(); for (H h : getIncidentEdges(v1)) { if (isIncident(v2, h)) edges.add(h); } return Collections.unmodifiableCollection(edges); } public boolean addVertex(V vertex) { if(vertex == null) throw new IllegalArgumentException("cannot add a null vertex"); if (containsVertex(vertex)) return false; vertices.put(vertex, new HashSet()); return true; } public boolean removeVertex(V vertex) { if (!containsVertex(vertex)) return false; for (H hyperedge : vertices.get(vertex)) { edges.get(hyperedge).remove(vertex); } vertices.remove(vertex); return true; } public boolean removeEdge(H hyperedge) { if (!containsEdge(hyperedge)) return false; for (V vertex : edges.get(hyperedge)) { vertices.get(vertex).remove(hyperedge); } edges.remove(hyperedge); return true; } public boolean isNeighbor(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) return false; if (vertices.get(v2).isEmpty()) return false; for (H hyperedge : vertices.get(v1)) { if (edges.get(hyperedge).contains(v2)) return true; } return false; } public boolean isIncident(V vertex, H edge) { if (!containsVertex(vertex) || !containsEdge(edge)) return false; return vertices.get(vertex).contains(edge); } public int degree(V vertex) { if (!containsVertex(vertex)) return 0; return vertices.get(vertex).size(); } public int getNeighborCount(V vertex) { if (!containsVertex(vertex)) return 0; return getNeighbors(vertex).size(); } public int getIncidentCount(H edge) { if (!containsEdge(edge)) return 0; return edges.get(edge).size(); } public int getEdgeCount(EdgeType edge_type) { if (edge_type == EdgeType.UNDIRECTED) return edges.size(); return 0; } public Collection getEdges(EdgeType edge_type) { if (edge_type == EdgeType.UNDIRECTED) return edges.keySet(); return null; } public EdgeType getDefaultEdgeType() { return EdgeType.UNDIRECTED; } public Collection getInEdges(V vertex) { return getIncidentEdges(vertex); } public Collection getOutEdges(V vertex) { return getIncidentEdges(vertex); } public int inDegree(V vertex) { return degree(vertex); } public int outDegree(V vertex) { return degree(vertex); } public V getDest(H directed_edge) { return null; } public V getSource(H directed_edge) { return null; } public Collection getPredecessors(V vertex) { return getNeighbors(vertex); } public Collection getSuccessors(V vertex) { return getNeighbors(vertex); } } jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SortedSparseMultigraph.java000066400000000000000000000100061276402340000325440ustar00rootroot00000000000000/* * Created on Oct 18, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.Comparator; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import com.google.common.base.Supplier; import com.google.common.collect.Ordering; import edu.uci.ics.jung.graph.util.Pair; /** * An implementation of Graph that is suitable for sparse graphs, * orders its vertex and edge collections according to either specified Comparator * instances or the natural ordering of their elements, and permits directed, undirected, * and parallel edges. * * @author Joshua O'Madadhain */ @SuppressWarnings("serial") public class SortedSparseMultigraph extends OrderedSparseMultigraph implements MultiGraph { /** * @param the vertex type for the graph Supplier * @param the edge type for the graph Supplier * @return a {@code Supplier} that creates an instance of this graph type. */ public static Supplier> getFactory() { return new Supplier> () { public Graph get() { return new SortedSparseMultigraph(); } }; } /** * Comparator used in ordering vertices. Defaults to util.ComparableComparator * if no comparators are specified in the constructor. */ protected Comparator vertex_comparator; /** * Comparator used in ordering edges. Defaults to util.ComparableComparator * if no comparators are specified in the constructor. */ protected Comparator edge_comparator; /** * Creates a new instance which sorts its vertices and edges according to the * specified {@code Comparator}s. * @param vertex_comparator specifies how the vertices are to be compared * @param edge_comparator specifies how the edges are to be compared */ public SortedSparseMultigraph(Comparator vertex_comparator, Comparator edge_comparator) { this.vertex_comparator = vertex_comparator; this.edge_comparator = edge_comparator; vertices = new TreeMap>>(vertex_comparator); edges = new TreeMap>(edge_comparator); directedEdges = new TreeSet(edge_comparator); } /** * Creates a new instance which sorts its vertices and edges according to * their natural ordering. */ public SortedSparseMultigraph() { this(new Ordering(){ @SuppressWarnings("unchecked") public int compare(V v1, V v2) { return ((Comparable) v1).compareTo(v2); }}, new Ordering(){ @SuppressWarnings("unchecked") public int compare(E e1, E e2) { return ((Comparable) e1).compareTo(e2); }}); } /** * Provides a new {@code Comparator} to be used in sorting the vertices. * @param vertex_comparator the comparator that defines the new ordering */ public void setVertexComparator(Comparator vertex_comparator) { this.vertex_comparator = vertex_comparator; Map>> tmp_vertices = new TreeMap>>(vertex_comparator); for (Map.Entry>> entry : vertices.entrySet()) tmp_vertices.put(entry.getKey(), entry.getValue()); this.vertices = tmp_vertices; } @Override public boolean addVertex(V vertex) { if(vertex == null) { throw new IllegalArgumentException("vertex may not be null"); } if (!containsVertex(vertex)) { vertices.put(vertex, new Pair>(new TreeSet(edge_comparator), new TreeSet(edge_comparator))); return true; } else { return false; } } } jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SparseGraph.java000066400000000000000000000257101276402340000303200ustar00rootroot00000000000000/* * Created on Apr 15, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * An implementation of Graph that is suitable for sparse graphs and * permits both directed and undirected edges. */ @SuppressWarnings("serial") public class SparseGraph extends AbstractGraph implements Graph { /** * @param the vertex type for the graph Supplier * @param the edge type for the graph Supplier * @return a {@code Supplier} that creates an instance of this graph type. */ public static Supplier> getFactory() { return new Supplier> () { public Graph get() { return new SparseGraph(); } }; } protected static final int INCOMING = 0; protected static final int OUTGOING = 1; protected static final int INCIDENT = 2; protected Map[]> vertex_maps; // Map of vertices to adjacency maps of vertices to {incoming, outgoing, incident} edges protected Map> directed_edges; // Map of directed edges to incident vertex sets protected Map> undirected_edges; // Map of undirected edges to incident vertex sets /** * Creates an instance. */ public SparseGraph() { vertex_maps = new HashMap[]>(); directed_edges = new HashMap>(); undirected_edges = new HashMap>(); } @Override public E findEdge(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) return null; E edge = vertex_maps.get(v1)[OUTGOING].get(v2); if (edge == null) edge = vertex_maps.get(v1)[INCIDENT].get(v2); return edge; } @Override public Collection findEdgeSet(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) return null; Collection edges = new ArrayList(2); E e1 = vertex_maps.get(v1)[OUTGOING].get(v2); if (e1 != null) edges.add(e1); E e2 = vertex_maps.get(v1)[INCIDENT].get(v2); if (e1 != null) edges.add(e2); return edges; } @Override public boolean addEdge(E edge, Pair endpoints, EdgeType edgeType) { Pair new_endpoints = getValidatedEndpoints(edge, endpoints); if (new_endpoints == null) return false; V v1 = new_endpoints.getFirst(); V v2 = new_endpoints.getSecond(); // undirected edges and directed edges are not considered to be parallel to each other, // so as long as anything that's returned by findEdge is not of the same type as // edge, we're fine E connection = findEdge(v1, v2); if (connection != null && getEdgeType(connection) == edgeType) return false; if (!containsVertex(v1)) this.addVertex(v1); if (!containsVertex(v2)) this.addVertex(v2); // map v1 to and vice versa if (edgeType == EdgeType.DIRECTED) { vertex_maps.get(v1)[OUTGOING].put(v2, edge); vertex_maps.get(v2)[INCOMING].put(v1, edge); directed_edges.put(edge, new_endpoints); } else { vertex_maps.get(v1)[INCIDENT].put(v2, edge); vertex_maps.get(v2)[INCIDENT].put(v1, edge); undirected_edges.put(edge, new_endpoints); } return true; } public Collection getInEdges(V vertex) { if (!containsVertex(vertex)) return null; // combine directed inedges and undirected Collection in = new HashSet(vertex_maps.get(vertex)[INCOMING].values()); in.addAll(vertex_maps.get(vertex)[INCIDENT].values()); return Collections.unmodifiableCollection(in); } public Collection getOutEdges(V vertex) { if (!containsVertex(vertex)) return null; // combine directed outedges and undirected Collection out = new HashSet(vertex_maps.get(vertex)[OUTGOING].values()); out.addAll(vertex_maps.get(vertex)[INCIDENT].values()); return Collections.unmodifiableCollection(out); } public Collection getPredecessors(V vertex) { if (!containsVertex(vertex)) return null; // consider directed inedges and undirected Collection preds = new HashSet(vertex_maps.get(vertex)[INCOMING].keySet()); preds.addAll(vertex_maps.get(vertex)[INCIDENT].keySet()); return Collections.unmodifiableCollection(preds); } public Collection getSuccessors(V vertex) { if (!containsVertex(vertex)) return null; // consider directed outedges and undirected Collection succs = new HashSet(vertex_maps.get(vertex)[OUTGOING].keySet()); succs.addAll(vertex_maps.get(vertex)[INCIDENT].keySet()); return Collections.unmodifiableCollection(succs); } public Collection getEdges(EdgeType edgeType) { if (edgeType == EdgeType.DIRECTED) return Collections.unmodifiableCollection(directed_edges.keySet()); else if (edgeType == EdgeType.UNDIRECTED) return Collections.unmodifiableCollection(undirected_edges.keySet()); else return null; } public Pair getEndpoints(E edge) { Pair endpoints; endpoints = directed_edges.get(edge); if (endpoints == null) return undirected_edges.get(edge); else return endpoints; } public EdgeType getEdgeType(E edge) { if (directed_edges.containsKey(edge)) return EdgeType.DIRECTED; else if (undirected_edges.containsKey(edge)) return EdgeType.UNDIRECTED; else return null; } public V getSource(E directed_edge) { if (getEdgeType(directed_edge) == EdgeType.DIRECTED) return directed_edges.get(directed_edge).getFirst(); else return null; } public V getDest(E directed_edge) { if (getEdgeType(directed_edge) == EdgeType.DIRECTED) return directed_edges.get(directed_edge).getSecond(); else return null; } public boolean isSource(V vertex, E edge) { if (!containsVertex(vertex) || !containsEdge(edge)) return false; V source = getSource(edge); if (source != null) return source.equals(vertex); else return false; } public boolean isDest(V vertex, E edge) { if (!containsVertex(vertex) || !containsEdge(edge)) return false; V dest = getDest(edge); if (dest != null) return dest.equals(vertex); else return false; } public Collection getEdges() { Collection edges = new ArrayList(directed_edges.keySet()); edges.addAll(undirected_edges.keySet()); return Collections.unmodifiableCollection(edges); } public Collection getVertices() { return Collections.unmodifiableCollection(vertex_maps.keySet()); } public boolean containsVertex(V vertex) { return vertex_maps.containsKey(vertex); } public boolean containsEdge(E edge) { return directed_edges.containsKey(edge) || undirected_edges.containsKey(edge); } public int getEdgeCount() { return directed_edges.size() + undirected_edges.size(); } public int getVertexCount() { return vertex_maps.size(); } public Collection getNeighbors(V vertex) { if (!containsVertex(vertex)) return null; // consider directed edges and undirected edges Collection neighbors = new HashSet(vertex_maps.get(vertex)[INCOMING].keySet()); neighbors.addAll(vertex_maps.get(vertex)[OUTGOING].keySet()); neighbors.addAll(vertex_maps.get(vertex)[INCIDENT].keySet()); return Collections.unmodifiableCollection(neighbors); } public Collection getIncidentEdges(V vertex) { if (!containsVertex(vertex)) return null; Collection incident = new HashSet(vertex_maps.get(vertex)[INCOMING].values()); incident.addAll(vertex_maps.get(vertex)[OUTGOING].values()); incident.addAll(vertex_maps.get(vertex)[INCIDENT].values()); return Collections.unmodifiableCollection(incident); } @SuppressWarnings("unchecked") public boolean addVertex(V vertex) { if(vertex == null) { throw new IllegalArgumentException("vertex may not be null"); } if (!containsVertex(vertex)) { vertex_maps.put(vertex, new HashMap[]{new HashMap(), new HashMap(), new HashMap()}); return true; } else { return false; } } public boolean removeVertex(V vertex) { if (!containsVertex(vertex)) return false; // copy to avoid concurrent modification in removeEdge Collection incident = new ArrayList(getIncidentEdges(vertex)); for (E edge : incident) removeEdge(edge); vertex_maps.remove(vertex); return true; } public boolean removeEdge(E edge) { if (!containsEdge(edge)) return false; Pair endpoints = getEndpoints(edge); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); // remove edge from incident vertices' adjacency maps if (getEdgeType(edge) == EdgeType.DIRECTED) { vertex_maps.get(v1)[OUTGOING].remove(v2); vertex_maps.get(v2)[INCOMING].remove(v1); directed_edges.remove(edge); } else { vertex_maps.get(v1)[INCIDENT].remove(v2); vertex_maps.get(v2)[INCIDENT].remove(v1); undirected_edges.remove(edge); } return true; } public int getEdgeCount(EdgeType edge_type) { if (edge_type == EdgeType.DIRECTED) return directed_edges.size(); if (edge_type == EdgeType.UNDIRECTED) return undirected_edges.size(); return 0; } public EdgeType getDefaultEdgeType() { return EdgeType.UNDIRECTED; } } jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SparseMultigraph.java000066400000000000000000000213601276402340000313700ustar00rootroot00000000000000/* * Created on Oct 18, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * An implementation of Graph that is suitable for sparse graphs * and permits directed, undirected, and parallel edges. */ @SuppressWarnings("serial") public class SparseMultigraph extends AbstractGraph implements MultiGraph { /** * @param the vertex type for the graph Supplier * @param the edge type for the graph Supplier * @return a {@code Supplier} that creates an instance of this graph type */ public static Supplier> getFactory() { return new Supplier> () { public Graph get() { return new SparseMultigraph(); } }; } // TODO: refactor internal representation: right now directed edges each have two references (in vertices and directedEdges) // and undirected also have two (incoming and outgoing). protected Map>> vertices; // Map of vertices to Pair of adjacency sets {incoming, outgoing} protected Map> edges; // Map of edges to incident vertex pairs protected Set directedEdges; /** * Creates a new instance. */ public SparseMultigraph() { vertices = new HashMap>>(); edges = new HashMap>(); directedEdges = new HashSet(); } public Collection getEdges() { return Collections.unmodifiableCollection(edges.keySet()); } public Collection getVertices() { return Collections.unmodifiableCollection(vertices.keySet()); } public boolean containsVertex(V vertex) { return vertices.keySet().contains(vertex); } public boolean containsEdge(E edge) { return edges.keySet().contains(edge); } protected Collection getIncoming_internal(V vertex) { return vertices.get(vertex).getFirst(); } protected Collection getOutgoing_internal(V vertex) { return vertices.get(vertex).getSecond(); } public boolean addVertex(V vertex) { if(vertex == null) { throw new IllegalArgumentException("vertex may not be null"); } if (!vertices.containsKey(vertex)) { vertices.put(vertex, new Pair>(new HashSet(), new HashSet())); return true; } else { return false; } } public boolean removeVertex(V vertex) { if (!containsVertex(vertex)) return false; // copy to avoid concurrent modification in removeEdge Set incident = new HashSet(getIncoming_internal(vertex)); incident.addAll(getOutgoing_internal(vertex)); for (E edge : incident) removeEdge(edge); vertices.remove(vertex); return true; } @Override public boolean addEdge(E edge, Pair endpoints, EdgeType edgeType) { Pair new_endpoints = getValidatedEndpoints(edge, endpoints); if (new_endpoints == null) return false; V v1 = new_endpoints.getFirst(); V v2 = new_endpoints.getSecond(); if (!vertices.containsKey(v1)) this.addVertex(v1); if (!vertices.containsKey(v2)) this.addVertex(v2); vertices.get(v1).getSecond().add(edge); vertices.get(v2).getFirst().add(edge); edges.put(edge, new_endpoints); if(edgeType == EdgeType.DIRECTED) { directedEdges.add(edge); } else { vertices.get(v1).getFirst().add(edge); vertices.get(v2).getSecond().add(edge); } return true; } public boolean removeEdge(E edge) { if (!containsEdge(edge)) { return false; } Pair endpoints = getEndpoints(edge); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); // remove edge from incident vertices' adjacency sets vertices.get(v1).getSecond().remove(edge); vertices.get(v2).getFirst().remove(edge); if(directedEdges.remove(edge) == false) { // its an undirected edge, remove the other ends vertices.get(v2).getSecond().remove(edge); vertices.get(v1).getFirst().remove(edge); } edges.remove(edge); return true; } public Collection getInEdges(V vertex) { if (!containsVertex(vertex)) return null; return Collections.unmodifiableCollection(vertices.get(vertex).getFirst()); } public Collection getOutEdges(V vertex) { if (!containsVertex(vertex)) return null; return Collections.unmodifiableCollection(vertices.get(vertex).getSecond()); } // TODO: this will need to get changed if we modify the internal representation public Collection getPredecessors(V vertex) { if (!containsVertex(vertex)) return null; Set preds = new HashSet(); for (E edge : getIncoming_internal(vertex)) { if(getEdgeType(edge) == EdgeType.DIRECTED) { preds.add(this.getSource(edge)); } else { preds.add(getOpposite(vertex, edge)); } } return Collections.unmodifiableCollection(preds); } // TODO: this will need to get changed if we modify the internal representation public Collection getSuccessors(V vertex) { if (!containsVertex(vertex)) return null; Set succs = new HashSet(); for (E edge : getOutgoing_internal(vertex)) { if(getEdgeType(edge) == EdgeType.DIRECTED) { succs.add(this.getDest(edge)); } else { succs.add(getOpposite(vertex, edge)); } } return Collections.unmodifiableCollection(succs); } public Collection getNeighbors(V vertex) { if (!containsVertex(vertex)) return null; Collection out = new HashSet(); out.addAll(this.getPredecessors(vertex)); out.addAll(this.getSuccessors(vertex)); return out; } public Collection getIncidentEdges(V vertex) { if (!containsVertex(vertex)) return null; Collection out = new HashSet(); out.addAll(this.getInEdges(vertex)); out.addAll(this.getOutEdges(vertex)); return out; } @Override public E findEdge(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) return null; for (E edge : getOutgoing_internal(v1)) if (this.getOpposite(v1, edge).equals(v2)) return edge; return null; } public Pair getEndpoints(E edge) { return edges.get(edge); } public V getSource(E edge) { if(directedEdges.contains(edge)) { return this.getEndpoints(edge).getFirst(); } return null; } public V getDest(E edge) { if(directedEdges.contains(edge)) { return this.getEndpoints(edge).getSecond(); } return null; } public boolean isSource(V vertex, E edge) { if (!containsEdge(edge) || !containsVertex(vertex)) return false; return getSource(edge).equals(vertex); } public boolean isDest(V vertex, E edge) { if (!containsEdge(edge) || !containsVertex(vertex)) return false; return getDest(edge).equals(vertex); } public EdgeType getEdgeType(E edge) { return directedEdges.contains(edge) ? EdgeType.DIRECTED : EdgeType.UNDIRECTED; } @SuppressWarnings("unchecked") public Collection getEdges(EdgeType edgeType) { if(edgeType == EdgeType.DIRECTED) { return Collections.unmodifiableSet(this.directedEdges); } else if(edgeType == EdgeType.UNDIRECTED) { Collection edges = new HashSet(getEdges()); edges.removeAll(directedEdges); return edges; } else { return Collections.EMPTY_SET; } } public int getEdgeCount() { return edges.keySet().size(); } public int getVertexCount() { return vertices.keySet().size(); } public int getEdgeCount(EdgeType edge_type) { return getEdges(edge_type).size(); } public EdgeType getDefaultEdgeType() { return EdgeType.UNDIRECTED; } } UndirectedOrderedSparseMultigraph.java000066400000000000000000000046451276402340000346340ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/* * Created on Oct 18, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Set; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.Pair; /** * An implementation of UndirectedGraph that is suitable for sparse graphs, * orders its vertex and edge collections according to insertion time, and permits * parallel edges. */ @SuppressWarnings("serial") public class UndirectedOrderedSparseMultigraph extends UndirectedSparseMultigraph implements UndirectedGraph { /** * @param the vertex type for the graph Supplier * @param the edge type for the graph Supplier * @return a {@code Supplier} that creates an instance of this graph type. */ public static Supplier> getFactory() { return new Supplier> () { public UndirectedGraph get() { return new UndirectedOrderedSparseMultigraph(); } }; } /** * Creates a new instance. */ public UndirectedOrderedSparseMultigraph() { vertices = new LinkedHashMap>(); edges = new LinkedHashMap>(); } @Override public boolean addVertex(V vertex) { if(vertex == null) { throw new IllegalArgumentException("vertex may not be null"); } if (!containsVertex(vertex)) { vertices.put(vertex, new LinkedHashSet()); return true; } else { return false; } } @Override public Collection getNeighbors(V vertex) { if (!containsVertex(vertex)) return null; Set neighbors = new LinkedHashSet(); for (E edge : getIncident_internal(vertex)) { Pair endpoints = this.getEndpoints(edge); V e_a = endpoints.getFirst(); V e_b = endpoints.getSecond(); if (vertex.equals(e_a)) neighbors.add(e_b); else neighbors.add(e_a); } return Collections.unmodifiableCollection(neighbors); } } jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/UndirectedSparseGraph.java000066400000000000000000000136671276402340000323370ustar00rootroot00000000000000/* * Created on Apr 1, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * An implementation of UndirectedGraph that is suitable * for sparse graphs. */ @SuppressWarnings("serial") public class UndirectedSparseGraph extends AbstractTypedGraph implements UndirectedGraph { /** * @param the vertex type for the graph Supplier * @param the edge type for the graph Supplier * @return a {@code Supplier} that creates an instance of this graph type. */ public static Supplier> getFactory() { return new Supplier> () { public UndirectedGraph get() { return new UndirectedSparseGraph(); } }; } protected Map> vertices; // Map of vertices to adjacency maps of vertices to incident edges protected Map> edges; // Map of edges to incident vertex sets /** * Creates an instance. */ public UndirectedSparseGraph() { super(EdgeType.UNDIRECTED); vertices = new HashMap>(); edges = new HashMap>(); } @Override public boolean addEdge(E edge, Pair endpoints, EdgeType edgeType) { this.validateEdgeType(edgeType); Pair new_endpoints = getValidatedEndpoints(edge, endpoints); if (new_endpoints == null) return false; V v1 = new_endpoints.getFirst(); V v2 = new_endpoints.getSecond(); if (findEdge(v1, v2) != null) return false; edges.put(edge, new_endpoints); if (!vertices.containsKey(v1)) this.addVertex(v1); if (!vertices.containsKey(v2)) this.addVertex(v2); // map v1 to and vice versa vertices.get(v1).put(v2, edge); vertices.get(v2).put(v1, edge); return true; } public Collection getInEdges(V vertex) { return this.getIncidentEdges(vertex); } public Collection getOutEdges(V vertex) { return this.getIncidentEdges(vertex); } public Collection getPredecessors(V vertex) { return this.getNeighbors(vertex); } public Collection getSuccessors(V vertex) { return this.getNeighbors(vertex); } @Override public E findEdge(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) return null; return vertices.get(v1).get(v2); } @Override public Collection findEdgeSet(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) return null; ArrayList edge_collection = new ArrayList(1); // if (!containsVertex(v1) || !containsVertex(v2)) // return edge_collection; E e = findEdge(v1, v2); if (e == null) return edge_collection; edge_collection.add(e); return edge_collection; } public Pair getEndpoints(E edge) { return edges.get(edge); } public V getSource(E directed_edge) { return null; } public V getDest(E directed_edge) { return null; } public boolean isSource(V vertex, E edge) { return false; } public boolean isDest(V vertex, E edge) { return false; } public Collection getEdges() { return Collections.unmodifiableCollection(edges.keySet()); } public Collection getVertices() { return Collections.unmodifiableCollection(vertices.keySet()); } public boolean containsVertex(V vertex) { return vertices.containsKey(vertex); } public boolean containsEdge(E edge) { return edges.containsKey(edge); } public int getEdgeCount() { return edges.size(); } public int getVertexCount() { return vertices.size(); } public Collection getNeighbors(V vertex) { if (!containsVertex(vertex)) return null; return Collections.unmodifiableCollection(vertices.get(vertex).keySet()); } public Collection getIncidentEdges(V vertex) { if (!containsVertex(vertex)) return null; return Collections.unmodifiableCollection(vertices.get(vertex).values()); } public boolean addVertex(V vertex) { if(vertex == null) { throw new IllegalArgumentException("vertex may not be null"); } if (!containsVertex(vertex)) { vertices.put(vertex, new HashMap()); return true; } else { return false; } } public boolean removeVertex(V vertex) { if (!containsVertex(vertex)) return false; // iterate over copy of incident edge collection for (E edge : new ArrayList(vertices.get(vertex).values())) removeEdge(edge); vertices.remove(vertex); return true; } public boolean removeEdge(E edge) { if (!containsEdge(edge)) return false; Pair endpoints = getEndpoints(edge); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); // remove incident vertices from each others' adjacency maps vertices.get(v1).remove(v2); vertices.get(v2).remove(v1); edges.remove(edge); return true; } } jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/UndirectedSparseMultigraph.java000066400000000000000000000147651276402340000334120ustar00rootroot00000000000000/* * Created on Mar 6, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * Created on Oct 18, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * An implementation of UndirectedGraph that is suitable for * sparse graphs and permits parallel edges. */ @SuppressWarnings("serial") public class UndirectedSparseMultigraph extends AbstractTypedGraph implements UndirectedGraph, MultiGraph { /** * @param the vertex type for the graph Supplier * @param the edge type for the graph Supplier * @return a {@code Supplier} that creates an instance of this graph type. */ public static Supplier> getFactory() { return new Supplier> () { public UndirectedGraph get() { return new UndirectedSparseMultigraph(); } }; } protected Map> vertices; // Map of vertices to adjacency sets protected Map> edges; // Map of edges to incident vertex sets /** * Creates a new instance. */ public UndirectedSparseMultigraph() { super(EdgeType.UNDIRECTED); vertices = new HashMap>(); edges = new HashMap>(); } public Collection getEdges() { return Collections.unmodifiableCollection(edges.keySet()); } public Collection getVertices() { return Collections.unmodifiableCollection(vertices.keySet()); } public boolean containsVertex(V vertex) { return vertices.keySet().contains(vertex); } public boolean containsEdge(E edge) { return edges.keySet().contains(edge); } protected Collection getIncident_internal(V vertex) { return vertices.get(vertex); } public boolean addVertex(V vertex) { if(vertex == null) { throw new IllegalArgumentException("vertex may not be null"); } if (!containsVertex(vertex)) { vertices.put(vertex, new HashSet()); return true; } else { return false; } } public boolean removeVertex(V vertex) { if (!containsVertex(vertex)) return false; for (E edge : new ArrayList(getIncident_internal(vertex))) removeEdge(edge); vertices.remove(vertex); return true; } @Override public boolean addEdge(E edge, V v1, V v2, EdgeType edgeType) { return addEdge(edge, new Pair(v1, v2), edgeType); } @Override public boolean addEdge(E edge, Pair endpoints, EdgeType edge_type) { validateEdgeType(edge_type); Pair new_endpoints = getValidatedEndpoints(edge, endpoints); if (new_endpoints == null) return false; V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); edges.put(edge, new_endpoints); if (!containsVertex(v1)) this.addVertex(v1); if (!containsVertex(v2)) this.addVertex(v2); vertices.get(v1).add(edge); vertices.get(v2).add(edge); return true; } public boolean removeEdge(E edge) { if (!containsEdge(edge)) return false; Pair endpoints = getEndpoints(edge); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); // remove edge from incident vertices' adjacency sets vertices.get(v1).remove(edge); vertices.get(v2).remove(edge); edges.remove(edge); return true; } public Collection getInEdges(V vertex) { return this.getIncidentEdges(vertex); } public Collection getOutEdges(V vertex) { return this.getIncidentEdges(vertex); } public Collection getPredecessors(V vertex) { return this.getNeighbors(vertex); } public Collection getSuccessors(V vertex) { return this.getNeighbors(vertex); } public Collection getNeighbors(V vertex) { if (!containsVertex(vertex)) return null; Set neighbors = new HashSet(); for (E edge : getIncident_internal(vertex)) { Pair endpoints = this.getEndpoints(edge); V e_a = endpoints.getFirst(); V e_b = endpoints.getSecond(); if (vertex.equals(e_a)) neighbors.add(e_b); else neighbors.add(e_a); } return Collections.unmodifiableCollection(neighbors); } public Collection getIncidentEdges(V vertex) { if (!containsVertex(vertex)) return null; return Collections.unmodifiableCollection(getIncident_internal(vertex)); } @Override public E findEdge(V v1, V v2) { if (!containsVertex(v1) || !containsVertex(v2)) return null; for (E edge : getIncident_internal(v1)) { Pair endpoints = this.getEndpoints(edge); V e_a = endpoints.getFirst(); V e_b = endpoints.getSecond(); if ((v1.equals(e_a) && v2.equals(e_b)) || (v1.equals(e_b) && v2.equals(e_a))) return edge; } return null; } public Pair getEndpoints(E edge) { return edges.get(edge); } public V getDest(E directed_edge) { return null; } public V getSource(E directed_edge) { return null; } public boolean isDest(V vertex, E edge) { return false; } public boolean isSource(V vertex, E edge) { return false; } public int getEdgeCount() { return edges.size(); } public int getVertexCount() { return vertices.size(); } } jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/util/000077500000000000000000000000001276402340000262065ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/util/TestGraphs.java000066400000000000000000000172651276402340000311500ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * Created on Jul 2, 2003 * */ package edu.uci.ics.jung.graph.util; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseMultigraph; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.UndirectedSparseMultigraph; /** * Provides generators for several different test graphs. */ public class TestGraphs { /** * A series of pairs that may be useful for generating graphs. The * miniature graph consists of 8 edges, 10 nodes, and is formed of two * connected components, one of 8 nodes, the other of 2. * */ public static String[][] pairs = { { "a", "b", "3" }, { "a", "c", "4" }, { "a", "d", "5" }, { "d", "c", "6" }, { "d", "e", "7" }, { "e", "f", "8" }, { "f", "g", "9" }, { "h", "i", "1" } }; /** * Creates a small sample graph that can be used for testing purposes. The * graph is as described in the section on {@link #pairs pairs}. If isDirected, * the graph is a {@link DirectedSparseMultigraph DirectedSparseMultigraph}, * otherwise, it is an {@link UndirectedSparseMultigraph UndirectedSparseMultigraph}. * * @param directed true iff the graph created is to have directed edges * @return a graph consisting of eight edges and ten nodes. */ public static Graph createTestGraph(boolean directed) { Graph graph = null; if(directed) { graph = new DirectedSparseMultigraph(); } else { graph = new UndirectedSparseMultigraph(); } for (int i = 0; i < pairs.length; i++) { String[] pair = pairs[i]; graph.addEdge(Integer.parseInt(pair[2]), pair[0], pair[1]); } return graph; } /** * @param chain_length the length of the chain of vertices to add to the returned graph * @param isolate_count the number of isolated vertices to add to the returned graph * @return a graph consisting of a chain of {@code chain_length} vertices * and {@code isolate_count} isolated vertices. */ public static Graph createChainPlusIsolates(int chain_length, int isolate_count) { Graph g = new UndirectedSparseMultigraph(); if (chain_length > 0) { String[] v = new String[chain_length]; v[0] = "v"+0; g.addVertex(v[0]); for (int i = 1; i < chain_length; i++) { v[i] = "v"+i; g.addVertex(v[i]); g.addEdge(new Double(Math.random()), v[i], v[i-1]); } } for (int i = 0; i < isolate_count; i++) { String v = "v"+(chain_length+i); g.addVertex(v); } return g; } /** * Creates a sample directed acyclic graph by generating several "layers", * and connecting nodes (randomly) to nodes in earlier (but never later) * layers. The number of vertices in each layer is a random value in the range * [1, maxNodesPerLayer]. * * @param layers the number of layers of vertices to create in the graph * @param maxNodesPerLayer the maximum number of vertices to put in any layer * @param linkprob the probability that this method will add an edge from a vertex in layer * k to a vertex in layer k+1 * @return the created graph */ public static Graph createDirectedAcyclicGraph( int layers, int maxNodesPerLayer, double linkprob) { DirectedGraph dag = new DirectedSparseMultigraph(); Set previousLayers = new HashSet(); Set inThisLayer = new HashSet(); for (int i = 0; i < layers; i++) { int nodesThisLayer = (int) (Math.random() * maxNodesPerLayer) + 1; for (int j = 0; j < nodesThisLayer; j++) { String v = i+":"+j; dag.addVertex(v); inThisLayer.add(v); // for each previous node... for(String v2 : previousLayers) { if (Math.random() < linkprob) { Double de = new Double(Math.random()); dag.addEdge(de, v, v2); } } } previousLayers.addAll(inThisLayer); inThisLayer.clear(); } return dag; } private static void createEdge( Graph g, String v1Label, String v2Label, int weight) { g.addEdge(new Double(Math.random()), v1Label, v2Label); } /** * Returns a bigger, undirected test graph with a just one component. This * graph consists of a clique of ten edges, a partial clique (randomly * generated, with edges of 0.6 probability), and one series of edges * running from the first node to the last. * * @return the testgraph */ public static Graph getOneComponentGraph() { UndirectedGraph g = new UndirectedSparseMultigraph(); // let's throw in a clique, too for (int i = 1; i <= 10; i++) { for (int j = i + 1; j <= 10; j++) { String i1 = "" + i; String i2 = "" + j; g.addEdge(Math.pow(i+2,j), i1, i2); } } // and, last, a partial clique for (int i = 11; i <= 20; i++) { for (int j = i + 1; j <= 20; j++) { if (Math.random() > 0.6) continue; String i1 = "" + i; String i2 = "" + j; g.addEdge(Math.pow(i+2,j), i1, i2); } } List index = new ArrayList(); index.addAll(g.getVertices()); // and one edge to connect them all for (int i = 0; i < index.size() - 1; i++) g.addEdge(new Integer(i), index.get(i), index.get(i+1)); return g; } /** * Returns a bigger test graph with a clique, several components, and other * parts. * * @return a demonstration graph of type UndirectedSparseMultigraph * with 28 vertices. */ public static Graph getDemoGraph() { UndirectedGraph g = new UndirectedSparseMultigraph(); for (int i = 0; i < pairs.length; i++) { String[] pair = pairs[i]; createEdge(g, pair[0], pair[1], Integer.parseInt(pair[2])); } // let's throw in a clique, too for (int i = 1; i <= 10; i++) { for (int j = i + 1; j <= 10; j++) { String i1 = "c" + i; String i2 = "c" + j; g.addEdge(Math.pow(i+2,j), i1, i2); } } // and, last, a partial clique for (int i = 11; i <= 20; i++) { for (int j = i + 1; j <= 20; j++) { if (Math.random() > 0.6) continue; String i1 = "p" + i; String i2 = "p" + j; g.addEdge(Math.pow(i+2,j), i1, i2); } } return g; } /** * @return a small graph with directed and undirected edges, and parallel edges. */ public static Graph getSmallGraph() { Graph graph = new SparseMultigraph(); String[] v = new String[3]; for (int i = 0; i < 3; i++) { v[i] = String.valueOf(i); graph.addVertex(v[i]); } graph.addEdge(new Double(0), v[0], v[1], EdgeType.DIRECTED); graph.addEdge(new Double(.1), v[0], v[1], EdgeType.DIRECTED); graph.addEdge(new Double(.2), v[0], v[1], EdgeType.DIRECTED); graph.addEdge(new Double(.3), v[1], v[0], EdgeType.DIRECTED); graph.addEdge(new Double(.4), v[1], v[0], EdgeType.DIRECTED); graph.addEdge(new Double(.5), v[1], v[2]); graph.addEdge(new Double(.6), v[1], v[2]); return graph; } } jung-jung-2.1.1/jung-graph-impl/src/site/000077500000000000000000000000001276402340000201515ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/site/site.xml000066400000000000000000000004441276402340000216410ustar00rootroot00000000000000 ${project.name} jung-jung-2.1.1/jung-graph-impl/src/test/000077500000000000000000000000001276402340000201645ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/test/java/000077500000000000000000000000001276402340000211055ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/test/java/edu/000077500000000000000000000000001276402340000216625ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/test/java/edu/uci/000077500000000000000000000000001276402340000224425ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/test/java/edu/uci/ics/000077500000000000000000000000001276402340000232205ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/test/java/edu/uci/ics/jung/000077500000000000000000000000001276402340000241635ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/000077500000000000000000000000001276402340000252645ustar00rootroot00000000000000DirectedSparseMultigraphTest.java000066400000000000000000000007441276402340000336530ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/test/java/edu/uci/ics/jung/graphpackage edu.uci.ics.jung.graph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; public class DirectedSparseMultigraphTest extends AbstractDirectedSparseMultigraphTest { @Override protected void setUp() throws Exception { super.setUp(); graph = new DirectedSparseMultigraph(); graph.addEdge(e01, v0, v1); graph.addEdge(e10, v1, v0); graph.addEdge(e12, v1, v2); graph.addEdge(e21, v2, v1); } } jung-jung-2.1.1/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/HypergraphTest.java000066400000000000000000000037101276402340000311010ustar00rootroot00000000000000/* * Created on Apr 21, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.graph; import junit.framework.Test; import junit.framework.TestSuite; import com.google.common.base.Supplier; public class HypergraphTest extends AbstractHypergraphTest { public HypergraphTest(Supplier> factory) { super(factory); } @Override public void setUp() { h = factory.get(); // System.out.println(h.getClass().getSimpleName()); } public static Test suite() { TestSuite ts = new TestSuite("HypergraphTest"); ts.addTest(new HypergraphTest(SetHypergraph.getFactory())); ts.addTest(new HypergraphTest(DirectedOrderedSparseMultigraph.getFactory())); ts.addTest(new HypergraphTest(DirectedSparseGraph.getFactory())); ts.addTest(new HypergraphTest(DirectedSparseMultigraph.getFactory())); ts.addTest(new HypergraphTest(OrderedSparseMultigraph.getFactory())); ts.addTest(new HypergraphTest(SortedSparseMultigraph.getFactory())); ts.addTest(new HypergraphTest(SparseGraph.getFactory())); ts.addTest(new HypergraphTest(SparseMultigraph.getFactory())); ts.addTest(new HypergraphTest(UndirectedOrderedSparseMultigraph.getFactory())); ts.addTest(new HypergraphTest(UndirectedSparseGraph.getFactory())); ts.addTest(new HypergraphTest(UndirectedSparseMultigraph.getFactory())); // ts.addTest(new HypergraphTest(.getFactory())); return ts; } } OrderedSparseMultigraphTest.java000066400000000000000000000030341276402340000335070ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/test/java/edu/uci/ics/jung/graphpackage edu.uci.ics.jung.graph; import java.util.HashSet; import java.util.Set; import edu.uci.ics.jung.graph.OrderedSparseMultigraph; import edu.uci.ics.jung.graph.SparseMultigraph; import edu.uci.ics.jung.graph.util.EdgeType; public class OrderedSparseMultigraphTest extends AbstractOrderedSparseMultigraphTest { @Override protected void setUp() throws Exception { super.setUp(); Set seeds = new HashSet(); seeds.add(1); seeds.add(5); graph = new OrderedSparseMultigraph(); graph.addEdge(4, 2, 1); graph.addEdge(5, 3, 1); graph.addEdge(6, 0, 4, EdgeType.DIRECTED); graph.addEdge(7, 0, 5, EdgeType.DIRECTED); graph.addEdge(1, 0, 1); graph.addEdge(2, 1, 2); graph.addEdge(3, 0, 2); graph.addEdge(8, 5, 1, EdgeType.DIRECTED); graph.addEdge(9, 6, 1, EdgeType.DIRECTED); graph.addEdge(10, 4, 3, EdgeType.DIRECTED); graph.addEdge(16, 8, 3); graph.addEdge(17, 5, 7); graph.addEdge(11, 2, 7); graph.addEdge(12, 1, 5); graph.addEdge(13, 2, 6); graph.addEdge(14, 6, 4); graph.addEdge(15, 7, 8); smallGraph = new SparseMultigraph(); smallGraph.addVertex(v0); smallGraph.addVertex(v1); smallGraph.addVertex(v2); smallGraph.addEdge(e01, v0, v1); smallGraph.addEdge(e10, v1, v0); smallGraph.addEdge(e12, v1, v2); smallGraph.addEdge(e21, v2, v1, EdgeType.DIRECTED); } } jung-jung-2.1.1/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SortedSparseMultigraphTest.java000066400000000000000000000034731276402340000334510ustar00rootroot00000000000000package edu.uci.ics.jung.graph; import java.util.HashSet; import java.util.Set; import edu.uci.ics.jung.graph.util.EdgeType; public class SortedSparseMultigraphTest extends AbstractSortedSparseMultigraphTest { @Override protected void setUp() throws Exception { super.setUp(); Set seeds = new HashSet(); seeds.add(1); seeds.add(5); graph = new SortedSparseMultigraph(); graph.addEdge(4., 2, 1); graph.addEdge(5., 3, 1); graph.addEdge(6., 0, 4, EdgeType.DIRECTED); graph.addEdge(7., 0, 5, EdgeType.DIRECTED); graph.addEdge(1., 0, 1); graph.addEdge(2., 1, 2); graph.addEdge(3., 0, 2); graph.addEdge(8., 5, 1, EdgeType.DIRECTED); graph.addEdge(9., 6, 1, EdgeType.DIRECTED); graph.addEdge(10., 4, 3, EdgeType.DIRECTED); graph.addEdge(16., 8, 3); graph.addEdge(17., 5, 7); graph.addEdge(11., 2, 7); graph.addEdge(12., 1, 5); graph.addEdge(13., 2, 6); graph.addEdge(14., 6, 4); graph.addEdge(15., 7, 8); smallGraph = new SparseMultigraph(); smallGraph.addVertex(v0); smallGraph.addVertex(v1); smallGraph.addVertex(v2); smallGraph.addEdge(e01, v0, v1); smallGraph.addEdge(e10, v1, v0); smallGraph.addEdge(e12, v1, v2); smallGraph.addEdge(e21, v2, v1, EdgeType.DIRECTED); Graph fooBar = new SortedSparseMultigraph(); try { fooBar.addVertex(new Foo()); fooBar.addVertex(new Foo()); fooBar.addEdge(new Bar(), new Foo(), new Foo()); fail("should have thrown an exception as Foo Bar are not Comparable"); } catch(Exception ex) { // all is well } } } SparseGraphAddRemoveAddEdgeTest.java000066400000000000000000000011051276402340000341110ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/test/java/edu/uci/ics/jung/graphpackage edu.uci.ics.jung.graph; import junit.framework.TestCase; public class SparseGraphAddRemoveAddEdgeTest extends TestCase { SparseGraph g; @Override protected void setUp() throws Exception { super.setUp(); g = new SparseGraph(); } public void testAddRemoveAddEdge() { g.addVertex("A"); g.addVertex("B"); g.addEdge(1, "A", "B"); g.removeEdge(1); // Remove the edge between A and B g.addEdge(2, "A", "B"); // Then add a different edge -> Exception thrown } } jung-jung-2.1.1/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SparseMultigraphTest.java000066400000000000000000000027221276402340000322640ustar00rootroot00000000000000package edu.uci.ics.jung.graph; import java.util.HashSet; import java.util.Set; import edu.uci.ics.jung.graph.SparseMultigraph; import edu.uci.ics.jung.graph.util.EdgeType; public class SparseMultigraphTest extends AbstractSparseMultigraphTest { @Override protected void setUp() throws Exception { super.setUp(); Set seeds = new HashSet(); seeds.add(1); seeds.add(5); graph = new SparseMultigraph(); graph.addEdge(1, 0, 1); graph.addEdge(2, 1, 2); graph.addEdge(3, 0, 2); graph.addEdge(4, 2, 1); graph.addEdge(5, 3, 1); graph.addEdge(6, 0, 4, EdgeType.DIRECTED); graph.addEdge(7, 0, 5, EdgeType.DIRECTED); graph.addEdge(8, 5, 1, EdgeType.DIRECTED); graph.addEdge(9, 6, 1, EdgeType.DIRECTED); graph.addEdge(10, 4, 3, EdgeType.DIRECTED); graph.addEdge(11, 2, 7); graph.addEdge(12, 1, 5); graph.addEdge(13, 2, 6); graph.addEdge(14, 6, 4); graph.addEdge(15, 7, 8); graph.addEdge(16, 8, 3); graph.addEdge(17, 5, 7); smallGraph = new SparseMultigraph(); smallGraph.addVertex(v0); smallGraph.addVertex(v1); smallGraph.addVertex(v2); smallGraph.addEdge(e01, v0, v1); smallGraph.addEdge(e10, v1, v0); smallGraph.addEdge(e12, v1, v2); smallGraph.addEdge(e21, v2, v1, EdgeType.DIRECTED); } } jung-jung-2.1.1/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SparseTreeTest.java000066400000000000000000000007751276402340000310550ustar00rootroot00000000000000package edu.uci.ics.jung.graph; import com.google.common.base.Supplier; public class SparseTreeTest extends AbstractSparseTreeTest { /* (non-Javadoc) * @see junit.framework.TestCase#setUp() */ @Override protected void setUp() throws Exception { super.setUp(); graphFactory = DirectedSparseMultigraph.getFactory(); edgeFactory = new Supplier() { int i=0; public Integer get() { return i++; }}; tree = new DelegateTree(graphFactory); } } jung-jung-2.1.1/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/TreeUtilsTest.java000066400000000000000000000007531276402340000307140ustar00rootroot00000000000000package edu.uci.ics.jung.graph; import edu.uci.ics.jung.graph.DelegateTree; public class TreeUtilsTest extends AbstractTreeUtilsTest { /** * @see junit.framework.TestCase#setUp() */ @Override protected void setUp() throws Exception { super.setUp(); tree = new DelegateTree(); tree.addVertex("A"); tree.addEdge(1, "A", "B0"); tree.addEdge(2, "A", "B1"); tree.addEdge(3, "B0", "C0"); tree.addEdge(4, "C0", "D0"); tree.addEdge(5, "C0", "D1"); } } UndirectedSparseMultigraphTest.java000066400000000000000000000007521276402340000342150ustar00rootroot00000000000000jung-jung-2.1.1/jung-graph-impl/src/test/java/edu/uci/ics/jung/graphpackage edu.uci.ics.jung.graph; import edu.uci.ics.jung.graph.UndirectedSparseMultigraph; public class UndirectedSparseMultigraphTest extends AbstractUndirectedSparseMultigraphTest { @Override protected void setUp() throws Exception { super.setUp(); graph = new UndirectedSparseMultigraph(); graph.addEdge(e01, v0, v1); graph.addEdge(e10, v1, v0); graph.addEdge(e12, v1, v2); graph.addEdge(e21, v2, v1); } } jung-jung-2.1.1/jung-io/000077500000000000000000000000001276402340000147655ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/pom.xml000066400000000000000000000023001276402340000162750ustar00rootroot00000000000000 4.0.0 net.sf.jung jung-parent 2.1.1 jung-io JUNG - I/O Support IO support classes for JUNG net.sf.jung jung-api ${project.version} net.sf.jung jung-algorithms ${project.version} net.sf.jung jung-graph-impl ${project.version} test junit junit test jung-jung-2.1.1/jung-io/src/000077500000000000000000000000001276402340000155545ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/main/000077500000000000000000000000001276402340000165005ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/main/java/000077500000000000000000000000001276402340000174215ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/main/java/edu/000077500000000000000000000000001276402340000201765ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/main/java/edu/uci/000077500000000000000000000000001276402340000207565ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/000077500000000000000000000000001276402340000215345ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/000077500000000000000000000000001276402340000224775ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/000077500000000000000000000000001276402340000231065ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/GraphFile.java000066400000000000000000000015661276402340000256220ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * Created on Jan 6, 2002 * */ package edu.uci.ics.jung.io; import edu.uci.ics.jung.graph.Graph; /** * General interface for loading and saving a graph from/to disk. * @author Scott * @author Tom Nelson - converted to jung2 * */ public interface GraphFile { /** * Loads a graph from a file per the appropriate format * @param filename the location and name of the file * @return the graph */ Graph load(String filename); /** * Save a graph to disk per the appropriate format * @param graph the location and name of the file * @param filename the graph */ void save(Graph graph, String filename); } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/GraphIOException.java000066400000000000000000000026631276402340000271300ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io; /** * Exception thrown when IO errors occur when reading/writing graphs. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class GraphIOException extends Exception { private static final long serialVersionUID = 3773882099782535606L; /** * Creates a new instance with no specified message or cause. */ public GraphIOException() { super(); } /** * Creates a new instance with the specified message and cause. * @param message a description of the exception-triggering event * @param cause the exception which triggered this one */ public GraphIOException(String message, Throwable cause) { super(message, cause); } /** * Creates a new instance with the specified message and no * specified cause. * @param message a description of the exception-triggering event */ public GraphIOException(String message) { super(message); } /** * Creats a new instance with the specified cause and no specified message. * @param cause the exception which triggered this one */ public GraphIOException(Throwable cause) { super(cause); } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/GraphMLMetadata.java000066400000000000000000000025201276402340000267030ustar00rootroot00000000000000/** * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Jun 30, 2008 * */ package edu.uci.ics.jung.io; import com.google.common.base.Function; /** * Maintains information relating to data for the specified type. * This includes a Function from objects to their values, * a default value, and a description. */ public class GraphMLMetadata { /** * The description of this data type. */ public String description; /** * The default value for objects of this type. */ public String default_value; /** * A Function mapping objects to string representations of their values. */ public Function transformer; /** * Creates a new instance with the specified description, * default value, and function. * * @param description a textual description of the object * @param default_value the default value for the object, as a String * @param function maps objects of this type to string representations */ public GraphMLMetadata(String description, String default_value, Function function) { this.description = description; this.transformer = function; this.default_value = default_value; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/GraphMLReader.java000066400000000000000000000717251276402340000264020ustar00rootroot00000000000000/* * Created on Sep 21, 2007 * * Copyright (c) 2007, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXNotSupportedException; import org.xml.sax.helpers.DefaultHandler; import com.google.common.base.Supplier; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import edu.uci.ics.jung.algorithms.util.MapSettableTransformer; import edu.uci.ics.jung.algorithms.util.SettableTransformer; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * Reads in data from a GraphML-formatted file and generates graphs based on * that data. Currently supports the following parts of the GraphML * specification: *
      *
    • graphs and hypergraphs *
    • directed and undirected edges *
    • graph, vertex, edge data *
    • graph, vertex, edge descriptions and data descriptions *
    • vertex and edge IDs *
    * Each of these is exposed via appropriate get methods. * * Does not currently support nested graphs or ports. * *

    Note that the user is responsible for supplying a graph * Factory that can support the edge types in the supplied * GraphML file. If the graph generated by the Factory is * not compatible (for example: if the graph does not accept directed * edges, and the GraphML file contains a directed edge) then the results * are graph-implementation-dependent. * * @see "http://graphml.graphdrawing.org/specification.html" */ public class GraphMLReader, V, E> extends DefaultHandler { protected enum TagState {NO_TAG, VERTEX, EDGE, HYPEREDGE, ENDPOINT, GRAPH, DATA, KEY, DESC, DEFAULT_KEY, GRAPHML, OTHER} protected enum KeyType {NONE, VERTEX, EDGE, GRAPH, ALL} protected SAXParser saxp; protected EdgeType default_edgetype; protected G current_graph; protected V current_vertex; protected E current_edge; protected String current_key; protected LinkedList current_states; protected BiMap tag_state; protected Supplier graph_factory; protected Supplier vertex_factory; protected Supplier edge_factory; protected BiMap vertex_ids; protected BiMap edge_ids; protected Map> graph_metadata; protected Map> vertex_metadata; protected Map> edge_metadata; protected Map vertex_desc; protected Map edge_desc; protected Map graph_desc; protected KeyType key_type; protected Collection hyperedge_vertices; protected List graphs; protected StringBuilder current_text = new StringBuilder(); /** * Creates a GraphMLReader instance with the specified * vertex and edge factories. * * @param vertex_factory the vertex supplier to use to create vertex objects * @param edge_factory the edge supplier to use to create edge objects * @throws ParserConfigurationException if a SAX parser cannot be constructed * @throws SAXException if the SAX parser factory cannot be constructed */ public GraphMLReader(Supplier vertex_factory, Supplier edge_factory) throws ParserConfigurationException, SAXException { current_vertex = null; current_edge = null; SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); saxp = saxParserFactory.newSAXParser(); current_states = new LinkedList(); tag_state = HashBiMap.create(); tag_state.put("node", TagState.VERTEX); tag_state.put("edge", TagState.EDGE); tag_state.put("hyperedge", TagState.HYPEREDGE); tag_state.put("endpoint", TagState.ENDPOINT); tag_state.put("graph", TagState.GRAPH); tag_state.put("data", TagState.DATA); tag_state.put("key", TagState.KEY); tag_state.put("desc", TagState.DESC); tag_state.put("default", TagState.DEFAULT_KEY); tag_state.put("graphml", TagState.GRAPHML); this.key_type = KeyType.NONE; this.vertex_factory = vertex_factory; this.edge_factory = edge_factory; } /** * Creates a GraphMLReader instance that assigns the vertex * and edge id strings to be the vertex and edge objects, * as well as their IDs. * Note that this requires that (a) each edge have a valid ID, which is not * normally a requirement for edges in GraphML, and (b) that the vertex * and edge types be assignment-compatible with String. * @throws ParserConfigurationException if a SAX parser cannot be constructed * @throws SAXException if the SAX parser factory cannot be constructed */ public GraphMLReader() throws ParserConfigurationException, SAXException { this(null, null); } /** * Returns a list of the graphs parsed from the specified reader, as created by * the specified Supplier. * @param reader the source of the graph data in GraphML format * @param graph_factory used to build graph instances * @return the graphs parsed from the specified reader * @throws IOException if an error is encountered while parsing the graph */ public List loadMultiple(Reader reader, Supplier graph_factory) throws IOException { this.graph_factory = graph_factory; initializeData(); clearData(); parse(reader); return graphs; } /** * Returns a list of the graphs parsed from the specified file, as created by * the specified Supplier. * @param filename the source of the graph data in GraphML format * @param graph_factory used to build graph instances * @return the graphs parsed from the specified file * @throws IOException if an error is encountered while parsing the graph */ public List loadMultiple(String filename, Supplier graph_factory) throws IOException { return loadMultiple(new FileReader(filename), graph_factory); } /** * Populates the specified graph with the data parsed from the reader. * @param reader the source of the graph data in GraphML format * @param g the graph instance to populate * @throws IOException if an error is encountered while parsing the graph */ public void load(Reader reader, G g) throws IOException { this.current_graph = g; this.graph_factory = null; initializeData(); clearData(); parse(reader); } /** * Populates the specified graph with the data parsed from the specified file. * @param filename the source of the graph data in GraphML format * @param g the graph instance to populate * @throws IOException if an error is encountered while parsing the graph */ public void load(String filename, G g) throws IOException { load(new FileReader(filename), g); } protected void clearData() { this.vertex_ids.clear(); this.vertex_desc.clear(); this.edge_ids.clear(); this.edge_desc.clear(); this.graph_desc.clear(); this.hyperedge_vertices.clear(); } /** * This is separate from initialize() because these data structures are shared among all * graphs loaded (i.e., they're defined inside graphml rather than graph. */ protected void initializeData() { this.vertex_ids = HashBiMap.create(); this.vertex_desc = new HashMap(); this.vertex_metadata = new HashMap>(); this.edge_ids = HashBiMap.create(); this.edge_desc = new HashMap(); this.edge_metadata = new HashMap>(); this.graph_desc = new HashMap(); this.graph_metadata = new HashMap>(); this.hyperedge_vertices = new ArrayList(); } protected void parse(Reader reader) throws IOException { try { saxp.parse(new InputSource(reader), this); reader.close(); } catch (SAXException saxe) { throw new IOException(saxe.getMessage()); } } @Override public void startElement(String uri, String name, String qName, Attributes atts) throws SAXNotSupportedException { String tag = qName.toLowerCase(); TagState state = tag_state.get(tag); if (state == null) state = TagState.OTHER; switch (state) { case GRAPHML: break; case VERTEX: if (this.current_graph == null) throw new SAXNotSupportedException("Graph must be defined prior to elements"); if (this.current_edge != null || this.current_vertex != null) throw new SAXNotSupportedException("Nesting elements not supported"); createVertex(atts); break; case ENDPOINT: if (this.current_graph == null) throw new SAXNotSupportedException("Graph must be defined prior to elements"); if (this.current_edge == null) throw new SAXNotSupportedException("No edge defined for endpoint"); if (this.current_states.getFirst() != TagState.HYPEREDGE) throw new SAXNotSupportedException("Endpoints must be defined inside hyperedge"); Map endpoint_atts = getAttributeMap(atts); String node = endpoint_atts.remove("node"); if (node == null) throw new SAXNotSupportedException("Endpoint must include an 'id' attribute"); V v = vertex_ids.inverse().get(node); if (v == null) throw new SAXNotSupportedException("Endpoint refers to nonexistent node ID: " + node); this.current_vertex = v; hyperedge_vertices.add(v); break; case EDGE: case HYPEREDGE: if (this.current_graph == null) throw new SAXNotSupportedException("Graph must be defined prior to elements"); if (this.current_edge != null || this.current_vertex != null) throw new SAXNotSupportedException("Nesting elements not supported"); createEdge(atts, state); break; case GRAPH: if (this.current_graph != null && graph_factory != null) throw new SAXNotSupportedException("Nesting graphs not currently supported"); // graph Supplier is null if there's only one graph if (graph_factory != null) current_graph = graph_factory.get(); // reset all non-key data structures (to avoid collisions between different graphs) clearData(); // set up default direction of edges Map graph_atts = getAttributeMap(atts); String default_direction = graph_atts.remove("edgedefault"); if (default_direction == null) throw new SAXNotSupportedException("All graphs must specify a default edge direction"); if (default_direction.equals("directed")) this.default_edgetype = EdgeType.DIRECTED; else if (default_direction.equals("undirected")) this.default_edgetype = EdgeType.UNDIRECTED; else throw new SAXNotSupportedException("Invalid or unrecognized default edge direction: " + default_direction); // put remaining attribute/value pairs in graph_data addExtraData(graph_atts, graph_metadata, current_graph); break; case DATA: if (this.current_states.contains(TagState.DATA)) throw new SAXNotSupportedException("Nested data not supported"); handleData(atts); break; case KEY: createKey(atts); break; default: break; } current_states.addFirst(state); } /** * * @param * @param atts * @param metadata_map * @param current_elt */ private void addExtraData(Map atts, Map> metadata_map, T current_elt) { // load in the default values; these override anything that might // be in the attribute map (because that's not really a proper // way to associate data) for (Map.Entry> entry: metadata_map.entrySet()) { GraphMLMetadata gmlm = entry.getValue(); if (gmlm.default_value != null) { SettableTransformer st = (SettableTransformer)gmlm.transformer; st.set(current_elt, gmlm.default_value); } } // place remaining items in data for (Map.Entry entry : atts.entrySet()) { String key = entry.getKey(); GraphMLMetadata key_data = metadata_map.get(key); SettableTransformer st; if (key_data != null) { // if there's a default value, don't override it if (key_data.default_value != null) continue; st = (SettableTransformer)key_data.transformer; } else { st = new MapSettableTransformer( new HashMap()); key_data = new GraphMLMetadata(null, null, st); metadata_map.put(key, key_data); } st.set(current_elt, entry.getValue()); } } @Override public void characters(char[] ch, int start, int length) throws SAXNotSupportedException { this.current_text.append(new String(ch, start, length)); } protected void addDatum(Map> metadata, T current_elt, String text) throws SAXNotSupportedException { if (metadata.containsKey(this.current_key)) { SettableTransformer st = (SettableTransformer)(metadata.get(this.current_key).transformer); st.set(current_elt, text); } else throw new SAXNotSupportedException("key " + this.current_key + " not valid for element " + current_elt); } @Override public void endElement(String uri, String name, String qName) throws SAXNotSupportedException { String text = current_text.toString().trim(); current_text.setLength(0); String tag = qName.toLowerCase(); TagState state = tag_state.get(tag); if (state == null) state = TagState.OTHER; if (state == TagState.OTHER) return; if (state != current_states.getFirst()) throw new SAXNotSupportedException("Unbalanced tags: opened " + tag_state.inverse().get(current_states.getFirst()) + ", closed " + tag); switch(state) { case VERTEX: case ENDPOINT: current_vertex = null; break; case EDGE: current_edge = null; break; case HYPEREDGE: current_graph.addEdge(current_edge, hyperedge_vertices); hyperedge_vertices.clear(); current_edge = null; break; case GRAPH: current_graph = null; break; case KEY: current_key = null; break; case DESC: switch (this.current_states.get(1)) // go back one { case GRAPH: graph_desc.put(current_graph, text); break; case VERTEX: case ENDPOINT: vertex_desc.put(current_vertex, text); break; case EDGE: case HYPEREDGE: edge_desc.put(current_edge, text); break; case DATA: switch (key_type) { case GRAPH: graph_metadata.get(current_key).description = text; break; case VERTEX: vertex_metadata.get(current_key).description = text; break; case EDGE: edge_metadata.get(current_key).description = text; break; case ALL: graph_metadata.get(current_key).description = text; vertex_metadata.get(current_key).description = text; edge_metadata.get(current_key).description = text; break; default: throw new SAXNotSupportedException("Invalid key type" + " specified for default: " + key_type); } break; default: break; } break; case DATA: this.key_type = KeyType.NONE; switch (this.current_states.get(1)) { case GRAPH: addDatum(graph_metadata, current_graph, text); break; case VERTEX: case ENDPOINT: addDatum(vertex_metadata, current_vertex, text); break; case EDGE: case HYPEREDGE: addDatum(edge_metadata, current_edge, text); break; default: break; } break; case DEFAULT_KEY: if (this.current_states.get(1) != TagState.KEY) throw new SAXNotSupportedException("'default' only defined in context of 'key' tag: " + "stack: " + current_states.toString()); switch (key_type) { case GRAPH: graph_metadata.get(current_key).default_value = text; break; case VERTEX: vertex_metadata.get(current_key).default_value = text; break; case EDGE: edge_metadata.get(current_key).default_value = text; break; case ALL: graph_metadata.get(current_key).default_value = text; vertex_metadata.get(current_key).default_value = text; edge_metadata.get(current_key).default_value = text; break; default: throw new SAXNotSupportedException("Invalid key type" + " specified for default: " + key_type); } break; default: break; } current_states.removeFirst(); } protected Map getAttributeMap(Attributes atts) { Map att_map = new HashMap(); for (int i = 0; i < atts.getLength(); i++) att_map.put(atts.getQName(i), atts.getValue(i)); return att_map; } protected void handleData(Attributes atts) throws SAXNotSupportedException { switch (this.current_states.getFirst()) { case GRAPH: break; case VERTEX: case ENDPOINT: break; case EDGE: break; case HYPEREDGE: break; default: throw new SAXNotSupportedException("'data' tag only defined " + "if immediately containing tag is 'graph', 'node', " + "'edge', or 'hyperedge'"); } this.current_key = getAttributeMap(atts).get("key"); if (this.current_key == null) throw new SAXNotSupportedException("'data' tag requires a key specification"); if (this.current_key.equals("")) throw new SAXNotSupportedException("'data' tag requires a non-empty key"); if (!getGraphMetadata().containsKey(this.current_key) && !getVertexMetadata().containsKey(this.current_key) && !getEdgeMetadata().containsKey(this.current_key)) { throw new SAXNotSupportedException("'data' tag's key specification must reference a defined key"); } } protected void createKey(Attributes atts) throws SAXNotSupportedException { Map key_atts = getAttributeMap(atts); String id = key_atts.remove("id"); String for_type = key_atts.remove("for"); if (for_type == null || for_type.equals("") || for_type.equals("all")) { vertex_metadata.put(id, new GraphMLMetadata(null, null, new MapSettableTransformer(new HashMap()))); edge_metadata.put(id, new GraphMLMetadata(null, null, new MapSettableTransformer(new HashMap()))); graph_metadata.put(id, new GraphMLMetadata(null, null, new MapSettableTransformer(new HashMap()))); key_type = KeyType.ALL; } else { TagState type = tag_state.get(for_type); switch (type) { case VERTEX: vertex_metadata.put(id, new GraphMLMetadata(null, null, new MapSettableTransformer(new HashMap()))); key_type = KeyType.VERTEX; break; case EDGE: case HYPEREDGE: edge_metadata.put(id, new GraphMLMetadata(null, null, new MapSettableTransformer(new HashMap()))); key_type = KeyType.EDGE; break; case GRAPH: graph_metadata.put(id, new GraphMLMetadata(null, null, new MapSettableTransformer(new HashMap()))); key_type = KeyType.GRAPH; break; default: throw new SAXNotSupportedException( "Invalid metadata target type: " + for_type); } } this.current_key = id; } @SuppressWarnings("unchecked") protected void createVertex(Attributes atts) throws SAXNotSupportedException { Map vertex_atts = getAttributeMap(atts); String id = vertex_atts.remove("id"); if (id == null) throw new SAXNotSupportedException("node attribute list missing " + "'id': " + atts.toString()); V v = vertex_ids.inverse().get(id); if (v == null) { if (vertex_factory != null) v = vertex_factory.get(); else v = (V)id; vertex_ids.put(v, id); this.current_graph.addVertex(v); // put remaining attribute/value pairs in vertex_data addExtraData(vertex_atts, vertex_metadata, v); } else throw new SAXNotSupportedException("Node id \"" + id + " is a duplicate of an existing node ID"); this.current_vertex = v; } @SuppressWarnings("unchecked") protected void createEdge(Attributes atts, TagState state) throws SAXNotSupportedException { Map edge_atts = getAttributeMap(atts); String id = edge_atts.remove("id"); E e; if (edge_factory != null) e = edge_factory.get(); else if (id != null) e = (E)id; else throw new IllegalArgumentException("If no edge Supplier is supplied, " + "edge id may not be null: " + edge_atts); if (id != null) { if (edge_ids.containsKey(e)) throw new SAXNotSupportedException("Edge id \"" + id + "\" is a duplicate of an existing edge ID"); edge_ids.put(e, id); } if (state == TagState.EDGE) assignEdgeSourceTarget(e, atts, edge_atts); //, id); // put remaining attribute/value pairs in edge_data addExtraData(edge_atts, edge_metadata, e); this.current_edge = e; } protected void assignEdgeSourceTarget(E e, Attributes atts, Map edge_atts)//, String id) throws SAXNotSupportedException { String source_id = edge_atts.remove("source"); if (source_id == null) throw new SAXNotSupportedException("edge attribute list missing " + "'source': " + atts.toString()); V source = vertex_ids.inverse().get(source_id); if (source == null) throw new SAXNotSupportedException("specified 'source' attribute " + "\"" + source_id + "\" does not match any node ID"); String target_id = edge_atts.remove("target"); if (target_id == null) throw new SAXNotSupportedException("edge attribute list missing " + "'target': " + atts.toString()); V target = vertex_ids.inverse().get(target_id); if (target == null) throw new SAXNotSupportedException("specified 'target' attribute " + "\"" + target_id + "\" does not match any node ID"); String directed = edge_atts.remove("directed"); EdgeType edge_type; if (directed == null) edge_type = default_edgetype; else if (directed.equals("true")) edge_type = EdgeType.DIRECTED; else if (directed.equals("false")) edge_type = EdgeType.UNDIRECTED; else throw new SAXNotSupportedException("Unrecognized edge direction specifier 'direction=\"" + directed + "\"': " + "source: " + source_id + ", target: " + target_id); if (current_graph instanceof Graph) ((Graph)this.current_graph).addEdge(e, source, target, edge_type); else this.current_graph.addEdge(e, new Pair(source, target)); } /** * @return a bidirectional map relating vertices and IDs. */ public BiMap getVertexIDs() { return vertex_ids; } /** * Returns a bidirectional map relating edges and IDs. * This is not guaranteed to always be populated (edge IDs are not * required in GraphML files. * @return a bidirectional map relating edges and IDs. */ public BiMap getEdgeIDs() { return edge_ids; } /** * @return a map from graph type name to type metadata */ public Map> getGraphMetadata() { return graph_metadata; } /** * @return a map from vertex type name to type metadata */ public Map> getVertexMetadata() { return vertex_metadata; } /** * @return a map from edge type name to type metadata */ public Map> getEdgeMetadata() { return edge_metadata; } /** * @return a map from graphs to graph descriptions */ public Map getGraphDescriptions() { return graph_desc; } /** * @return a map from vertices to vertex descriptions */ public Map getVertexDescriptions() { return vertex_desc; } /** * @return a map from edges to edge descriptions */ public Map getEdgeDescriptions() { return edge_desc; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/GraphMLWriter.java000066400000000000000000000303711276402340000264440ustar00rootroot00000000000000/* * Created on June 16, 2008 * * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io; import java.io.BufferedWriter; import java.io.IOException; import java.io.Writer; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import com.google.common.base.Function; import com.google.common.base.Functions; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * Writes graphs out in GraphML format. * * Current known issues: *

      *
    • Only supports one graph per output file. *
    • Does not indent lines for text-format readability. *
    * */ public class GraphMLWriter { protected Function vertex_ids; protected Function edge_ids; protected Map>> graph_data; protected Map> vertex_data; protected Map> edge_data; protected Function vertex_desc; protected Function edge_desc; protected Function, String> graph_desc; protected boolean directed; protected int nest_level; public GraphMLWriter() { vertex_ids = new Function() { public String apply(V v) { return v.toString(); } }; edge_ids = Functions.constant(null); graph_data = Collections.emptyMap(); vertex_data = Collections.emptyMap(); edge_data = Collections.emptyMap(); vertex_desc = Functions.constant(null); edge_desc = Functions.constant(null); graph_desc = Functions.constant(null); nest_level = 0; } /** * Writes {@code graph} out using {@code w}. * @param graph the graph to write out * @param w the writer instance to which the graph data will be written out * @throws IOException if writing the graph fails */ public void save(Hypergraph graph, Writer w) throws IOException { BufferedWriter bw = new BufferedWriter(w); // write out boilerplate header bw.write("\n"); bw.write("\n"); // write out data specifiers, including defaults for (String key : graph_data.keySet()) writeKeySpecification(key, "graph", graph_data.get(key), bw); for (String key : vertex_data.keySet()) writeKeySpecification(key, "node", vertex_data.get(key), bw); for (String key : edge_data.keySet()) writeKeySpecification(key, "edge", edge_data.get(key), bw); // write out graph-level information // set edge default direction bw.write("\n"); else bw.write("undirected\">\n"); // write graph description, if any String desc = graph_desc.apply(graph); if (desc != null) bw.write("" + desc + "\n"); // write graph data out if any for (String key : graph_data.keySet()) { Function, ?> t = graph_data.get(key).transformer; Object value = t.apply(graph); if (value != null) bw.write(format("data", "key", key, value.toString()) + "\n"); } // write vertex information writeVertexData(graph, bw); // write edge information writeEdgeData(graph, bw); // close graph bw.write("\n"); bw.write("\n"); bw.flush(); bw.close(); } // public boolean save(Collection> graphs, Writer w) // { // return true; // } protected void writeIndentedText(BufferedWriter w, String to_write) throws IOException { for (int i = 0; i < nest_level; i++) w.write(" "); w.write(to_write); } protected void writeVertexData(Hypergraph graph, BufferedWriter w) throws IOException { for (V v: graph.getVertices()) { String v_string = String.format("\n"); closed = true; w.write("" + desc + "\n"); } // write data out if any for (String key : vertex_data.keySet()) { Function t = vertex_data.get(key).transformer; if (t != null) { Object value = t.apply(v); if (value != null) { if (!closed) { w.write(v_string + ">\n"); closed = true; } w.write(format("data", "key", key, value.toString()) + "\n"); } } } if (!closed) w.write(v_string + "/>\n"); // no contents; close the node with "/>" else w.write("\n"); } } protected void writeEdgeData(Hypergraph g, Writer w) throws IOException { for (E e: g.getEdges()) { Collection vertices = g.getIncidentVertices(e); String id = edge_ids.apply(e); String e_string; boolean is_hyperedge = !(g instanceof Graph); if (is_hyperedge) { e_string = " endpoints = new Pair(vertices); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); e_string = "\n"); closed = true; w.write("" + desc + "\n"); } // write data out if any for (String key : edge_data.keySet()) { Function t = edge_data.get(key).transformer; Object value = t.apply(e); if (value != null) { if (!closed) { w.write(e_string + ">\n"); closed = true; } w.write(format("data", "key", key, value.toString()) + "\n"); } } // if this is a hyperedge, write endpoints out if any if (is_hyperedge) { for (V v : vertices) { if (!closed) { w.write(e_string + ">\n"); closed = true; } w.write("\n"); } } if (!closed) w.write(e_string + "/>\n"); // no contents; close the edge with "/>" else if (is_hyperedge) w.write("\n"); else w.write("\n"); } } protected void writeKeySpecification(String key, String type, GraphMLMetadata ds, BufferedWriter bw) throws IOException { bw.write("\n"); closed = true; } bw.write("" + desc + "\n"); } // write out default if any Object def = ds.default_value; if (def != null) { if (!closed) { bw.write(">\n"); closed = true; } bw.write("" + def.toString() + "\n"); } if (!closed) bw.write("/>\n"); else bw.write("\n"); } protected String format(String type, String attr, String value, String contents) { return String.format("<%s %s=\"%s\">%s", type, attr, value, contents, type); } /** * Provides an ID that will be used to identify a vertex in the output file. * If the vertex IDs are not set, the ID for each vertex will default to * the output of toString * (and thus not guaranteed to be unique). * * @param vertex_ids a mapping from vertex to ID */ public void setVertexIDs(Function vertex_ids) { this.vertex_ids = vertex_ids; } /** * Provides an ID that will be used to identify an edge in the output file. * If any edge ID is missing, no ID will be written out for the * corresponding edge. * * @param edge_ids a mapping from edge to ID */ public void setEdgeIDs(Function edge_ids) { this.edge_ids = edge_ids; } /** * Provides a map from data type name to graph data. * * @param graph_map map from data type name to graph data */ public void setGraphData(Map>> graph_map) { graph_data = graph_map; } /** * Provides a map from data type name to vertex data. * * @param vertex_map map from data type name to vertex data */ public void setVertexData(Map> vertex_map) { vertex_data = vertex_map; } /** * Provides a map from data type name to edge data. * * @param edge_map map from data type name to edge data */ public void setEdgeData(Map> edge_map) { edge_data = edge_map; } /** * Adds a new graph data specification. * * @param id the ID of the data to add * @param description a description of the data to add * @param default_value a default value for the data type * @param graph_transformer a mapping from graphs to their string representations */ public void addGraphData(String id, String description, String default_value, Function, String> graph_transformer) { if (graph_data.equals(Collections.EMPTY_MAP)) graph_data = new HashMap>>(); graph_data.put(id, new GraphMLMetadata>(description, default_value, graph_transformer)); } /** * Adds a new vertex data specification. * * @param id the ID of the data to add * @param description a description of the data to add * @param default_value a default value for the data type * @param vertex_transformer a mapping from vertices to their string representations */ public void addVertexData(String id, String description, String default_value, Function vertex_transformer) { if (vertex_data.equals(Collections.EMPTY_MAP)) vertex_data = new HashMap>(); vertex_data.put(id, new GraphMLMetadata(description, default_value, vertex_transformer)); } /** * Adds a new edge data specification. * * @param id the ID of the data to add * @param description a description of the data to add * @param default_value a default value for the data type * @param edge_transformer a mapping from edges to their string representations */ public void addEdgeData(String id, String description, String default_value, Function edge_transformer) { if (edge_data.equals(Collections.EMPTY_MAP)) edge_data = new HashMap>(); edge_data.put(id, new GraphMLMetadata(description, default_value, edge_transformer)); } /** * Provides vertex descriptions. * @param vertex_desc a mapping from vertices to their descriptions */ public void setVertexDescriptions(Function vertex_desc) { this.vertex_desc = vertex_desc; } /** * Provides edge descriptions. * @param edge_desc a mapping from edges to their descriptions */ public void setEdgeDescriptions(Function edge_desc) { this.edge_desc = edge_desc; } /** * Provides graph descriptions. * @param graph_desc a mapping from graphs to their descriptions */ public void setGraphDescriptions(Function, String> graph_desc) { this.graph_desc = graph_desc; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/GraphReader.java000066400000000000000000000021311276402340000261320ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io; import edu.uci.ics.jung.graph.Hypergraph; /** * Interface for a reader of graph objects * * @author Nathan Mittler - nathan.mittler@gmail.com * * @param * the graph type * @param the vertex type * the vertex type * @param the edge type * the edge type */ public interface GraphReader, V, E> { /** * Reads a single graph object, if one is available. * * @return the next graph object, or null if none exists. * @throws GraphIOException * thrown if an error occurred. */ G readGraph() throws GraphIOException; /** * Closes this resource and frees any resources. * * @throws GraphIOException * thrown if an error occurred. */ void close() throws GraphIOException; } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/PajekNetReader.java000066400000000000000000000445311276402340000266040ustar00rootroot00000000000000/* * Created on May 3, 2004 * * Copyright (c) 2004, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io; import java.awt.geom.Point2D; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.StringTokenizer; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.util.MapSettableTransformer; import edu.uci.ics.jung.algorithms.util.SettableTransformer; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.util.EdgeType; /** * Reads a Graph from a Pajek NET formatted source. * *

    If the edge constraints specify that the graph is strictly undirected, * and an "*Arcs" section is encountered, or if the edge constraints specify that the * graph is strictly directed, and an "*Edges" section is encountered, * an IllegalArgumentException is thrown. * *

    If the edge constraints do not permit parallel edges, only the first encountered * of a set of parallel edges will be read; subsequent edges in that set will be ignored. * *

    More restrictive edge constraints will cause vertices to be generated * that are more time- and space-efficient. * * At the moment, only supports the * part of the specification that defines: *

      *
    • vertex ids (each must have a value from 1 to n, where n is the number of vertices) *
    • vertex labels (must be in quotes if interrupted by whitespace) *
    • directed edge connections (single or list) *
    • undirected edge connections (single or list) *
    • edge weights (not compatible with edges specified in list form) *
      note: this version of PajekNetReader does not support multiple edge * weights, as PajekNetFile does; this behavior is consistent with the NET format. *
    • vertex locations (x and y; z coordinate is ignored) *

    * * Here is an example format for a directed graph without edge weights * and edges specified in list form:
    *

     * *vertices [# of vertices] 
     * 1 "a" 
     * 2 "b" 
     * 3 "c" 
     * *arcslist 
     * 1 2 3 
     * 2 3  
     * 
    * * Here is an example format for an undirected graph with edge weights * and edges specified in non-list form:
    *
     * *vertices [# of vertices] 
     * 1 "a" 
     * 2 "b" 
     * 3 "c" 
     * *edges 
     * 1 2 0.1 
     * 1 3 0.9 
     * 2 3 1.0 
     * 
    * * @author Joshua O'Madadhain * @see "'Pajek - Program for Analysis and Visualization of Large Networks', Vladimir Batagelj and Andrej Mrvar, http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/pajekman.pdf" * @author Tom Nelson - converted to jung2 */ public class PajekNetReader,V,E> { protected Supplier vertex_factory; protected Supplier edge_factory; /** * The map for vertex labels (if any) created by this class. */ protected SettableTransformer vertex_labels = new MapSettableTransformer(new HashMap()); /** * The map for vertex locations (if any) defined by this class. */ protected SettableTransformer vertex_locations = new MapSettableTransformer(new HashMap()); protected SettableTransformer edge_weights = new MapSettableTransformer(new HashMap()); /** * Used to specify whether the most recently read line is a * Pajek-specific tag. */ private static final Predicate v_pred = new StartsWithPredicate("*vertices"); private static final Predicate a_pred = new StartsWithPredicate("*arcs"); private static final Predicate e_pred = new StartsWithPredicate("*edges"); private static final Predicate t_pred = new StartsWithPredicate("*"); private static final Predicate c_pred = Predicates.or(a_pred, e_pred); protected static final Predicate l_pred = ListTagPred.getInstance(); /** * Creates a PajekNetReader instance with the specified vertex and edge factories. * @param vertex_factory the Supplier to use to create vertex objects * @param edge_factory the Supplier to use to create edge objects */ public PajekNetReader(Supplier vertex_factory, Supplier edge_factory) { this.vertex_factory = vertex_factory; this.edge_factory = edge_factory; } /** * Creates a PajekNetReader instance with the specified edge Supplier, * and whose vertex objects correspond to the integer IDs assigned in the file. * Note that this requires V to be assignment-compatible with * an Integer value. * @param edge_factory the Supplier to use to create edge objects */ public PajekNetReader(Supplier edge_factory) { this(null, edge_factory); } /** * Returns the graph created by parsing the specified file, as created * by the specified Supplier. * @param filename the file from which the graph is to be read * @param graph_factory used to provide a graph instance * @return a graph parsed from the specified file * @throws IOException if the graph cannot be loaded */ public G load(String filename, Supplier graph_factory) throws IOException { return load(new FileReader(filename), graph_factory.get()); } /** * Returns the graph created by parsing the specified reader, as created * by the specified Supplier. * @param reader the reader instance from which the graph is to be read * @param graph_factory used to provide a graph instance * @return a graph parsed from the specified reader * @throws IOException if the graph cannot be loaded */ public G load(Reader reader, Supplier graph_factory) throws IOException { return load(reader, graph_factory.get()); } /** * Returns the graph created by parsing the specified file, by populating the * specified graph. * @param filename the file from which the graph is to be read * @param g the graph instance to populate * @return a graph parsed from the specified file * @throws IOException if the graph cannot be loaded */ public G load(String filename, G g) throws IOException { if (g == null) throw new IllegalArgumentException("Graph provided must be non-null"); return load(new FileReader(filename), g); } /** * Populates the graph g with the graph represented by the * Pajek-format data supplied by reader. Stores edge weights, * if any, according to nev (if non-null). * *

    Any existing vertices/edges of g, if any, are unaffected. * *

    The edge data are filtered according to g's constraints, if any; thus, if * g only accepts directed edges, any undirected edges in the * input are ignored. * * @param reader the reader from which the graph is to be read * @param g the graph instance to populate * @return a graph parsed from the specified reader * @throws IOException if the graph cannot be loaded */ public G load(Reader reader, G g) throws IOException { BufferedReader br = new BufferedReader(reader); // ignore everything until we see '*Vertices' String curLine = skip(br, v_pred); if (curLine == null) // no vertices in the graph; return empty graph return g; // create appropriate number of vertices StringTokenizer st = new StringTokenizer(curLine); st.nextToken(); // skip past "*vertices"; int num_vertices = Integer.parseInt(st.nextToken()); List id = null; if (vertex_factory != null) { for (int i = 1; i <= num_vertices; i++) g.addVertex(vertex_factory.get()); id = new ArrayList(g.getVertices()); } // read vertices until we see any Pajek format tag ('*...') curLine = null; while (br.ready()) { curLine = br.readLine(); if (curLine == null || t_pred.apply(curLine)) break; if (curLine == "") // skip blank lines continue; try { readVertex(curLine, id, num_vertices); } catch (IllegalArgumentException iae) { br.close(); reader.close(); throw iae; } } // skip over the intermediate stuff (if any) // and read the next arcs/edges section that we find curLine = readArcsOrEdges(curLine, br, g, id, edge_factory); // ditto readArcsOrEdges(curLine, br, g, id, edge_factory); br.close(); reader.close(); return g; } /** * Parses curLine as a reference to a vertex, and optionally assigns * label and location information. */ @SuppressWarnings("unchecked") private void readVertex(String curLine, List id, int num_vertices) { V v; String[] parts = null; int coord_idx = -1; // index of first coordinate in parts; -1 indicates no coordinates found String index; String label = null; // if there are quote marks on this line, split on them; label is surrounded by them if (curLine.indexOf('"') != -1) { String[] initial_split = curLine.trim().split("\""); // if there are any quote marks, there should be exactly 2 if (initial_split.length < 2 || initial_split.length > 3) throw new IllegalArgumentException("Unbalanced (or too many) " + "quote marks in " + curLine); index = initial_split[0].trim(); label = initial_split[1].trim(); if (initial_split.length == 3) parts = initial_split[2].trim().split("\\s+", -1); coord_idx = 0; } else // no quote marks, but are there coordinates? { parts = curLine.trim().split("\\s+", -1); index = parts[0]; switch (parts.length) { case 1: // just the ID; nothing to do, continue break; case 2: // just the ID and a label label = parts[1]; break; case 3: // ID, no label, coordinates coord_idx = 1; break; default: // ID, label, (x,y) coordinates, maybe some other stuff coord_idx = 2; break; } } int v_id = Integer.parseInt(index) - 1; // go from 1-based to 0-based index if (v_id >= num_vertices || v_id < 0) throw new IllegalArgumentException("Vertex number " + v_id + "is not in the range [1," + num_vertices + "]"); if (id != null) v = id.get(v_id); else v = (V)(new Integer(v_id)); // only attach the label if there's one to attach if (label != null && label.length() > 0 && vertex_labels != null) vertex_labels.set(v, label); // parse the rest of the line if (coord_idx != -1 && parts != null && parts.length >= coord_idx+2 && vertex_locations != null) { double x = Double.parseDouble(parts[coord_idx]); double y = Double.parseDouble(parts[coord_idx+1]); vertex_locations.set(v, new Point2D.Double(x,y)); } } @SuppressWarnings("unchecked") private String readArcsOrEdges(String curLine, BufferedReader br, Graph g, List id, Supplier edge_factory) throws IOException { String nextLine = curLine; // in case we're not there yet (i.e., format tag isn't arcs or edges) if (! c_pred.apply(curLine)) nextLine = skip(br, c_pred); boolean reading_arcs = false; boolean reading_edges = false; EdgeType directedness = null; if (a_pred.apply(nextLine)) { if (g instanceof UndirectedGraph) { throw new IllegalArgumentException("Supplied undirected-only graph cannot be populated with directed edges"); } else { reading_arcs = true; directedness = EdgeType.DIRECTED; } } if (e_pred.apply(nextLine)) { if (g instanceof DirectedGraph) throw new IllegalArgumentException("Supplied directed-only graph cannot be populated with undirected edges"); else reading_edges = true; directedness = EdgeType.UNDIRECTED; } if (!(reading_arcs || reading_edges)) return nextLine; boolean is_list = l_pred.apply(nextLine); while (br.ready()) { nextLine = br.readLine(); if (nextLine == null || t_pred.apply(nextLine)) break; if (curLine == "") // skip blank lines continue; StringTokenizer st = new StringTokenizer(nextLine.trim()); int vid1 = Integer.parseInt(st.nextToken()) - 1; V v1; if (id != null) v1 = id.get(vid1); else v1 = (V)new Integer(vid1); if (is_list) // one source, multiple destinations { do { createAddEdge(st, v1, directedness, g, id, edge_factory); } while (st.hasMoreTokens()); } else // one source, one destination, at most one weight { E e = createAddEdge(st, v1, directedness, g, id, edge_factory); // get the edge weight if we care if (edge_weights != null && st.hasMoreTokens()) edge_weights.set(e, new Float(st.nextToken())); } } return nextLine; } @SuppressWarnings("unchecked") protected E createAddEdge(StringTokenizer st, V v1, EdgeType directed, Graph g, List id, Supplier edge_factory) { int vid2 = Integer.parseInt(st.nextToken()) - 1; V v2; if (id != null) v2 = id.get(vid2); else v2 = (V)new Integer(vid2); E e = edge_factory.get(); // don't error-check this: let the graph implementation do whatever it's going to do // (add the edge, replace the existing edge, throw an exception--depends on the graph implementation) g.addEdge(e, v1, v2, directed); return e; } /** * Returns the first line read from br for which p * returns true, or null if there is no * such line. * @param br the reader from which the graph is being read * @param p predicate specifying what line to accept * @return the first line from {@code br} that matches {@code p}, or null * @throws IOException if an error is encountered while reading from {@code br} */ protected String skip(BufferedReader br, Predicate p) throws IOException { while (br.ready()) { String curLine = br.readLine(); if (curLine == null) break; curLine = curLine.trim(); if (p.apply(curLine)) return curLine; } return null; } /** * A Predicate which evaluates to true if the * argument starts with the constructor-specified String. * * @author Joshua O'Madadhain */ protected static class StartsWithPredicate implements Predicate { private String tag; protected StartsWithPredicate(String s) { this.tag = s; } public boolean apply(String str) { return (str != null && str.toLowerCase().startsWith(tag)); } } /** * A Predicate which evaluates to true if the * argument ends with the string "list". * * @author Joshua O'Madadhain */ protected static class ListTagPred implements Predicate { protected static ListTagPred instance; protected ListTagPred() {} protected static ListTagPred getInstance() { if (instance == null) instance = new ListTagPred(); return instance; } public boolean apply(String s) { return (s != null && s.toLowerCase().endsWith("list")); } } /** * @return the vertexLocationTransformer */ public SettableTransformer getVertexLocationTransformer() { return vertex_locations; } /** * Provides a Function which will be used to write out the vertex locations. * @param vertex_locations a container for the vertex locations */ public void setVertexLocationTransformer(SettableTransformer vertex_locations) { this.vertex_locations = vertex_locations; } /** * @return a mapping from vertices to their labels */ public SettableTransformer getVertexLabeller() { return vertex_labels; } /** * Provides a Function which will be used to write out the vertex labels. * @param vertex_labels a container for the vertex labels */ public void setVertexLabeller(SettableTransformer vertex_labels) { this.vertex_labels = vertex_labels; } /** * @return a mapping from edges to their weights */ public SettableTransformer getEdgeWeightTransformer() { return edge_weights; } /** * Provides a Function which will be used to write out edge weights. * @param edge_weights a container for the edge weights */ public void setEdgeWeightTransformer(SettableTransformer edge_weights) { this.edge_weights = edge_weights; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/PajekNetWriter.java000066400000000000000000000163541276402340000266600ustar00rootroot00000000000000/* * Created on May 4, 2004 * * Copyright (c) 2004, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io; import java.awt.geom.Point2D; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import com.google.common.base.Function; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * Writes graphs in the Pajek NET format. * *

    Labels for vertices, edge weights, and vertex locations may each optionally * be specified. Note that vertex location coordinates * must be normalized to the interval [0, 1] on each axis in order to conform to the * Pajek specification. * * @author Joshua O'Madadhain * @author Tom Nelson - converted to jung2 */ public class PajekNetWriter { /** * Creates a new instance. */ public PajekNetWriter() { } /** * Saves the graph to the specified file. * @param g the graph to be saved * @param filename the filename of the file to write the graph to * @param vs mapping from vertices to labels * @param nev mapping from edges to weights * @param vld mapping from vertices to locations * @throws IOException if the graph cannot be saved */ public void save(Graph g, String filename, Function vs, Function nev, Function vld) throws IOException { save(g, new FileWriter(filename), vs, nev, vld); } /** * Saves the graph to the specified file. * @param g the graph to be saved * @param filename the filename of the file to write the graph to * @param vs mapping from vertices to labels * @param nev mapping from edges to weights * @throws IOException if the graph cannot be saved */ public void save(Graph g, String filename, Function vs, Function nev) throws IOException { save(g, new FileWriter(filename), vs, nev, null); } /** * Saves the graph to the specified file. No vertex labels are written, and the * edge weights are written as 1.0. * @param g the graph to be saved * @param filename the filename of the file to write the graph to * @throws IOException if the graph cannot be saved */ public void save(Graph g, String filename) throws IOException { save(g, filename, null, null, null); } /** * Saves the graph to the specified writer. No vertex labels are written, and the * edge weights are written as 1.0. * @param g the graph to be saved * @param w the writer instance to write the graph to * @throws IOException if the graph cannot be saved */ public void save(Graph g, Writer w) throws IOException { save(g, w, null, null, null); } /** * Saves the graph to the specified writer. * @param g the graph to be saved * @param w the writer instance to write the graph to * @param vs mapping from vertices to labels * @param nev mapping from edges to weights * @throws IOException if the graph cannot be saved */ public void save(Graph g, Writer w, Function vs, Function nev) throws IOException { save(g, w, vs, nev, null); } /** * Saves the graph to the specified writer. * @param graph the graph to be saved * @param w the writer instance to write the graph to * @param vs mapping from vertices to labels (no labels are written if null) * @param nev mapping from edges to weights (defaults to weights of 1.0 if null) * @param vld mapping from vertices to locations (no locations are written if null) * @throws IOException if the graph cannot be saved */ public void save(Graph graph, Writer w, Function vs, Function nev, Function vld) throws IOException { /* * TODO: Changes we might want to make: * - optionally writing out in list form */ BufferedWriter writer = new BufferedWriter(w); if (nev == null) nev = new Function() { public Number apply(E e) { return 1; } }; writer.write("*Vertices " + graph.getVertexCount()); writer.newLine(); List id = new ArrayList(graph.getVertices()); for (V currentVertex : graph.getVertices()) { // convert from 0-based to 1-based index int v_id = id.indexOf(currentVertex) + 1; writer.write(""+v_id); if (vs != null) { String label = vs.apply(currentVertex); if (label != null) writer.write (" \"" + label + "\""); } if (vld != null) { Point2D location = vld.apply(currentVertex); if (location != null) writer.write (" " + location.getX() + " " + location.getY() + " 0.0"); } writer.newLine(); } Collection d_set = new HashSet(); Collection u_set = new HashSet(); boolean directed = graph instanceof DirectedGraph; boolean undirected = graph instanceof UndirectedGraph; // if it's strictly one or the other, no need to create extra sets if (directed) d_set.addAll(graph.getEdges()); if (undirected) u_set.addAll(graph.getEdges()); if (!directed && !undirected) // mixed-mode graph { u_set.addAll(graph.getEdges()); d_set.addAll(graph.getEdges()); for(E e : graph.getEdges()) { if(graph.getEdgeType(e) == EdgeType.UNDIRECTED) { d_set.remove(e); } else { u_set.remove(e); } } } // write out directed edges if (!d_set.isEmpty()) { writer.write("*Arcs"); writer.newLine(); } for (E e : d_set) { int source_id = id.indexOf(graph.getEndpoints(e).getFirst()) + 1; int target_id = id.indexOf(graph.getEndpoints(e).getSecond()) + 1; float weight = nev.apply(e).floatValue(); writer.write(source_id + " " + target_id + " " + weight); writer.newLine(); } // write out undirected edges if (!u_set.isEmpty()) { writer.write("*Edges"); writer.newLine(); } for (E e : u_set) { Pair endpoints = graph.getEndpoints(e); int v1_id = id.indexOf(endpoints.getFirst()) + 1; int v2_id = id.indexOf(endpoints.getSecond()) + 1; float weight = nev.apply(e).floatValue(); writer.write(v1_id + " " + v2_id + " " + weight); writer.newLine(); } writer.close(); } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/000077500000000000000000000000001276402340000245405ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/AbstractMetadata.java000066400000000000000000000017631276402340000306160ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; import java.util.HashMap; import java.util.Map; /** * Abstract base class for metadata - implements the property functionality * * @author Nathan Mittler - nathan.mittler@gmail.com */ public abstract class AbstractMetadata implements Metadata { final private Map properties = new HashMap(); public Map getProperties() { return properties; } public String getProperty(String key) { return properties.get(key); } public String setProperty(String key, String value) { return properties.put(key, value); } public void addData(DataMetadata data) { properties.put(data.getKey(), data.getValue()); } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/DataMetadata.java000066400000000000000000000014571276402340000277240ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; /** * Metadata structure for the 'data' GraphML element. * * @author Nathan Mittler - nathan.mittler@gmail.com * * @see "http://graphml.graphdrawing.org/specification.html" */ public class DataMetadata { private String key; private String value; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/EdgeMetadata.java000066400000000000000000000037621276402340000277200ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; /** * Metadata structure for the 'edge' GraphML element. * * @author Nathan Mittler - nathan.mittler@gmail.com * * @see "http://graphml.graphdrawing.org/specification.html" */ public class EdgeMetadata extends AbstractMetadata { private String id; private Boolean directed; private String source; private String target; private String sourcePort; private String targetPort; private String description; private Object edge; public String getId() { return id; } public void setId(String id) { this.id = id; } public Boolean isDirected() { return directed; } public void setDirected(Boolean directed) { this.directed = directed; } public String getSource() { return source; } public void setSource(String source) { this.source = source; } public String getTarget() { return target; } public void setTarget(String target) { this.target = target; } public String getSourcePort() { return sourcePort; } public void setSourcePort(String sourcePort) { this.sourcePort = sourcePort; } public String getTargetPort() { return targetPort; } public void setTargetPort(String targetPort) { this.targetPort = targetPort; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Object getEdge() { return edge; } public void setEdge(Object edge) { this.edge = edge; } public MetadataType getMetadataType() { return MetadataType.EDGE; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/EndpointMetadata.java000066400000000000000000000031021276402340000306200ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; /** * Metadata structure for the 'endpoint' GraphML element. * * @author Nathan Mittler - nathan.mittler@gmail.com * * @see "http://graphml.graphdrawing.org/specification.html" */ public class EndpointMetadata extends AbstractMetadata { public enum EndpointType { IN, OUT, UNDIR } private String id; private String port; private String node; private String description; private EndpointType endpointType = EndpointType.UNDIR; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getPort() { return port; } public void setPort(String port) { this.port = port; } public String getNode() { return node; } public void setNode(String node) { this.node = node; } public EndpointType getEndpointType() { return endpointType; } public void setEndpointType(EndpointType endpointType) { this.endpointType = endpointType; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public MetadataType getMetadataType() { return MetadataType.ENDPOINT; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/ExceptionConverter.java000066400000000000000000000031731276402340000312350ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; import edu.uci.ics.jung.io.GraphIOException; import javax.xml.stream.XMLStreamException; /** * Converts an exception to the a GraphIOException. Runtime exceptions * are checked for the cause. If the cause is an XMLStreamException, it is * converted to a GraphIOException. Otherwise, the RuntimeException is * rethrown. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class ExceptionConverter { /** * Converts an exception to the a GraphIOException. Runtime exceptions * are checked for the cause. If the cause is an XMLStreamException, it is * converted to a GraphReaderException. Otherwise, the RuntimeException is * rethrown. * * @param e the exception to be converted * @throws GraphIOException the converted exception */ static public void convert(Exception e) throws GraphIOException { if (e instanceof GraphIOException) { throw (GraphIOException) e; } if (e instanceof RuntimeException) { // If the cause was an XMLStreamException, throw a GraphReaderException if (e.getCause() instanceof XMLStreamException) { throw new GraphIOException(e.getCause()); } throw (RuntimeException) e; } throw new GraphIOException(e); } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMLConstants.java000066400000000000000000000036621276402340000306010ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; /** * Provides some constants for element/attribute names in GraphML * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class GraphMLConstants { public static final String GRAPHML_NAME = "graphml"; public static final String GRAPH_NAME = "graph"; public static final String NODE_NAME = "node"; public static final String EDGE_NAME = "edge"; public static final String ENDPOINT_NAME = "endpoint"; public static final String HYPEREDGE_NAME = "hyperedge"; public static final String PORT_NAME = "port"; public static final String KEY_NAME = "key"; public static final String DATA_NAME = "data"; public static final String ALL_NAME = "all"; public static final String ID_NAME = "id"; public static final String FOR_NAME = "for"; public static final String DESC_NAME = "desc"; public static final String DEFAULT_NAME = "default"; public static final String ATTRNAME_NAME = "attr.name"; public static final String ATTRTYPE_NAME = "attr.type"; public static final String NAME_NAME = "name"; public static final String EDGEDEFAULT_NAME = "edgedefault"; public static final String TYPE_NAME = "type"; public static final String IN_NAME = "in"; public static final String OUT_NAME = "out"; public static final String UNDIR_NAME = "undir"; public static final String DIRECTED_NAME = "directed"; public static final String UNDIRECTED_NAME = "undirected"; public static final String SOURCE_NAME = "source"; public static final String TARGET_NAME = "target"; public static final String SOURCEPORT_NAME = "sourceport"; public static final String TARGETPORT_NAME = "targetport"; } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMLDocument.java000066400000000000000000000015231276402340000303750ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; import java.util.ArrayList; import java.util.List; /** * Maintains all the metadata read in from a single GraphML XML document. */ public class GraphMLDocument { final private KeyMap keyMap = new KeyMap(); final private List graphMetadata = new ArrayList(); public KeyMap getKeyMap() { return keyMap; } public List getGraphMetadata() { return graphMetadata; } public void clear() { graphMetadata.clear(); keyMap.clear(); } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMLReader2.java000066400000000000000000000334501276402340000301070ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; import java.io.IOException; import java.io.Reader; import java.io.InputStream; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.GraphReader; import edu.uci.ics.jung.io.graphml.parser.ElementParserRegistry; import edu.uci.ics.jung.io.graphml.parser.GraphMLEventFilter; /** * Reads in data from a GraphML-formatted file and generates graphs based on * that data. Does not currently support nested graphs. * *

    Note that the user is responsible for supplying a graph * Transformer that will create graphs capable of supporting the * edge types in the supplied GraphML file. If the graph generated by the * Factory is not compatible (for example: if the graph does not * accept directed edges, and the GraphML file contains a directed edge) then * the results are graph-implementation-dependent. * * @author Nathan Mittler - nathan.mittler@gmail.com * * @param * The graph type to be read from the GraphML file * @param the vertex type * The vertex type used by the graph * @param the edge type * The edge type used by the graph * @see "http://graphml.graphdrawing.org/specification.html" */ public class GraphMLReader2, V, E> implements GraphReader { protected XMLEventReader xmlEventReader; protected Reader fileReader; protected Function graphTransformer; protected Function vertexTransformer; protected Function edgeTransformer; protected Function hyperEdgeTransformer; protected boolean initialized; final protected GraphMLDocument document = new GraphMLDocument(); final protected ElementParserRegistry parserRegistry; private InputStream inputStream ; /** * Constructs a GraphML reader around the given reader. This constructor * requires the user to supply transformation functions to convert from the * GraphML metadata to Graph, Vertex, Edge instances. These Function * functions can be used as purely factories (i.e. the metadata is * disregarded) or can use the metadata to set particular fields in the * objects. * * @param fileReader the reader for the input GraphML document. * @param graphTransformer Transformation function to convert from GraphML GraphMetadata * to graph objects. This must be non-null. * @param vertexTransformer Transformation function to convert from GraphML NodeMetadata * to vertex objects. This must be non-null. * @param edgeTransformer Transformation function to convert from GraphML EdgeMetadata * to edge objects. This must be non-null. * @param hyperEdgeTransformer Transformation function to convert from GraphML * HyperEdgeMetadata to edge objects. This must be non-null. * @throws IllegalArgumentException thrown if any of the arguments are null. */ public GraphMLReader2(Reader fileReader, Function graphTransformer, Function vertexTransformer, Function edgeTransformer, Function hyperEdgeTransformer) { if (fileReader == null) { throw new IllegalArgumentException( "Argument fileReader must be non-null"); } if (graphTransformer == null) { throw new IllegalArgumentException( "Argument graphTransformer must be non-null"); } if (vertexTransformer == null) { throw new IllegalArgumentException( "Argument vertexTransformer must be non-null"); } if (edgeTransformer == null) { throw new IllegalArgumentException( "Argument edgeTransformer must be non-null"); } if (hyperEdgeTransformer == null) { throw new IllegalArgumentException( "Argument hyperEdgeTransformer must be non-null"); } this.fileReader = fileReader; this.graphTransformer = graphTransformer; this.vertexTransformer = vertexTransformer; this.edgeTransformer = edgeTransformer; this.hyperEdgeTransformer = hyperEdgeTransformer; // Create the parser registry. this.parserRegistry = new ElementParserRegistry(document.getKeyMap(), graphTransformer, vertexTransformer, edgeTransformer, hyperEdgeTransformer); } /** * Constructs a GraphML reader around the given reader. This constructor * requires the user to supply transformation functions to convert from the * GraphML metadata to Graph, Vertex, Edge instances. These Function * functions can be used as purely factories (i.e. the metadata is * disregarded) or can use the metadata to set particular fields in the * objects. * * @param inputStream the inputstream for the input GraphML document. * @param graphTransformer Transformation function to convert from GraphML GraphMetadata * to graph objects. This must be non-null. * @param vertexTransformer Transformation function to convert from GraphML NodeMetadata * to vertex objects. This must be non-null. * @param edgeTransformer Transformation function to convert from GraphML EdgeMetadata * to edge objects. This must be non-null. * @param hyperEdgeTransformer Transformation function to convert from GraphML * HyperEdgeMetadata to edge objects. This must be non-null. * @throws IllegalArgumentException thrown if any of the arguments are null. */ public GraphMLReader2(InputStream inputStream, Function graphTransformer, Function vertexTransformer, Function edgeTransformer, Function hyperEdgeTransformer) { if (inputStream == null) { throw new IllegalArgumentException( "Argument inputStream must be non-null"); } if (graphTransformer == null) { throw new IllegalArgumentException( "Argument graphTransformer must be non-null"); } if (vertexTransformer == null) { throw new IllegalArgumentException( "Argument vertexTransformer must be non-null"); } if (edgeTransformer == null) { throw new IllegalArgumentException( "Argument edgeTransformer must be non-null"); } if (hyperEdgeTransformer == null) { throw new IllegalArgumentException( "Argument hyperEdgeTransformer must be non-null"); } this.inputStream = inputStream; this.graphTransformer = graphTransformer; this.vertexTransformer = vertexTransformer; this.edgeTransformer = edgeTransformer; this.hyperEdgeTransformer = hyperEdgeTransformer; // Create the parser registry. this.parserRegistry = new ElementParserRegistry(document.getKeyMap(), graphTransformer, vertexTransformer, edgeTransformer, hyperEdgeTransformer); } /** * Gets the current Function that is being used for graph objects. * * @return the current Function. */ public Function getGraphTransformer() { return graphTransformer; } /** * Gets the current Function that is being used for vertex objects. * * @return the current Function. */ public Function getVertexTransformer() { return vertexTransformer; } /** * Gets the current Function that is being used for edge objects. * * @return the current Function. */ public Function getEdgeTransformer() { return edgeTransformer; } /** * Gets the current Function that is being used for hyperedge objects. * * @return the current Function. */ public Function getHyperEdgeTransformer() { return hyperEdgeTransformer; } /** * Verifies the object state and initializes this reader. All Function * properties must be set and be non-null or a GraphReaderException * will be thrown. This method may be called more than once. * Successive calls will have no effect. * * @throws edu.uci.ics.jung.io.GraphIOException thrown if an error occurred. */ public void init() throws GraphIOException { try { if (!initialized) { // Create the event reader. XMLInputFactory Supplier = XMLInputFactory.newInstance(); if(fileReader==null && inputStream != null) { xmlEventReader = Supplier.createXMLEventReader(inputStream); } else { xmlEventReader = Supplier.createXMLEventReader(fileReader); } xmlEventReader = Supplier.createFilteredReader(xmlEventReader, new GraphMLEventFilter()); initialized = true; } } catch( Exception e ) { ExceptionConverter.convert(e); } } /** * Closes the GraphML reader and disposes of any resources. * * @throws edu.uci.ics.jung.io.GraphIOException thrown if an error occurs. */ public void close() throws GraphIOException { try { // Clear the contents of the document. document.clear(); if (xmlEventReader != null) { xmlEventReader.close(); } if (fileReader != null) { fileReader.close(); } if (inputStream != null) { inputStream.close(); } } catch (IOException e) { throw new GraphIOException(e); } catch (XMLStreamException e) { throw new GraphIOException(e); } finally { fileReader = null; inputStream = null; xmlEventReader = null; graphTransformer = null; vertexTransformer = null; edgeTransformer = null; hyperEdgeTransformer = null; } } /** * Returns the object that contains the metadata read in from the GraphML * document * * @return the GraphML document */ public GraphMLDocument getGraphMLDocument() { return document; } /** * Reads a single graph object from the GraphML document. Automatically * calls init to initialize the state of the reader. * * @return the graph that was read if one was found, otherwise null. */ @SuppressWarnings("unchecked") public G readGraph() throws GraphIOException { try { // Initialize if not already. init(); while (xmlEventReader.hasNext()) { XMLEvent event = xmlEventReader.nextEvent(); if (event.isStartElement()) { StartElement element = (StartElement) event; String name = element.getName().getLocalPart(); // The element should be one of: key, graph, graphml if (GraphMLConstants.KEY_NAME.equals(name)) { // Parse the key object. Key key = (Key) parserRegistry.getParser(name).parse( xmlEventReader, element); // Add the key to the key map. document.getKeyMap().addKey(key); } else if (GraphMLConstants.GRAPH_NAME.equals(name)) { // Parse the graph. GraphMetadata graph = (GraphMetadata) parserRegistry .getParser(name).parse(xmlEventReader, element); // Add it to the graph metadata list. document.getGraphMetadata().add(graph); // Return the graph object. return (G)graph.getGraph(); } else if (GraphMLConstants.GRAPHML_NAME.equals(name)) { // Ignore the graphML object. } else { // Encounted an unknown element - just skip by it. parserRegistry.getUnknownElementParser().parse( xmlEventReader, element); } } else if (event.isEndDocument()) { break; } } } catch (Exception e) { ExceptionConverter.convert(e); } // We didn't read anything from the document. throw new GraphIOException("Unable to read Graph from document - the document could be empty"); } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMetadata.java000066400000000000000000000103571276402340000301130ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; import java.util.HashMap; import java.util.Map; /** * Metadata structure for the 'graph' GraphML element. * * @author Nathan Mittler - nathan.mittler@gmail.com * * @see "http://graphml.graphdrawing.org/specification.html" */ public class GraphMetadata extends AbstractMetadata { public enum EdgeDefault { DIRECTED, UNDIRECTED } private String id; private EdgeDefault edgeDefault; private String description; private Object graph; final private Map nodes = new HashMap(); final private Map edges = new HashMap(); final private Map hyperEdges = new HashMap(); public String getId() { return id; } public void setId(String id) { this.id = id; } public EdgeDefault getEdgeDefault() { return edgeDefault; } public void setEdgeDefault(EdgeDefault edgeDefault) { this.edgeDefault = edgeDefault; } public String getDescription() { return description; } public void setDescription(String desc) { this.description = desc; } public void addNodeMetadata(Object vertex, NodeMetadata metadata) { nodes.put(vertex, metadata); } public NodeMetadata getNodeMetadata(Object vertex) { return nodes.get(vertex); } public Map getNodeMap() { return nodes; } public void addEdgeMetadata(Object edge, EdgeMetadata metadata) { edges.put(edge, metadata); } public EdgeMetadata getEdgeMetadata(Object edge) { return edges.get(edge); } public Map getEdgeMap() { return edges; } public void addHyperEdgeMetadata(Object edge, HyperEdgeMetadata metadata) { hyperEdges.put(edge, metadata); } public HyperEdgeMetadata getHyperEdgeMetadata(Object edge) { return hyperEdges.get(edge); } public Map getHyperEdgeMap() { return hyperEdges; } public Object getGraph() { return graph; } public void setGraph(Object graph) { this.graph = graph; } public MetadataType getMetadataType() { return MetadataType.GRAPH; } /** * Gets the property for the given vertex object. * * @param vertex * the subject vertex * @param key * the property key * @return the property value * @throws IllegalArgumentException * thrown if there is no metadata associated with the provided * vertex object. */ public String getVertexProperty(Object vertex, String key) throws IllegalArgumentException { NodeMetadata metadata = getNodeMetadata(vertex); if (metadata == null) { throw new IllegalArgumentException( "Metadata does not exist for provided vertex"); } return metadata.getProperty(key); } /** * Gets the property for the given edge object. * * @param edge * the subject edge. * @param key * the property key * @return the property value * @throws IllegalArgumentException * thrown if there is no metadata associated with the provided * edge object. */ public String getEdgeProperty(Object edge, String key) throws IllegalArgumentException { // First, try standard edges. EdgeMetadata em = getEdgeMetadata(edge); if (em != null) { return em.getProperty(key); } // Next, try hyperedges. HyperEdgeMetadata hem = getHyperEdgeMetadata(edge); if (hem != null) { return hem.getProperty(key); } // Couldn't find the edge. throw new IllegalArgumentException( "Metadata does not exist for provided edge"); } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/HyperEdgeMetadata.java000066400000000000000000000026501276402340000307230ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; import java.util.ArrayList; import java.util.List; /** * Metadata structure for the 'hyperedge' GraphML element. * * @author Nathan Mittler - nathan.mittler@gmail.com * * @see "http://graphml.graphdrawing.org/specification.html" */ public class HyperEdgeMetadata extends AbstractMetadata { private String id; private String description; private Object edge; final private List endpoints = new ArrayList(); public String getId() { return id; } public void setId(String id) { this.id = id; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public void addEndpoint( EndpointMetadata endpoint ) { endpoints.add(endpoint); } public List getEndpoints() { return endpoints; } public Object getEdge() { return edge; } public void setEdge(Object edge) { this.edge = edge; } public MetadataType getMetadataType() { return MetadataType.HYPEREDGE; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/Key.java000066400000000000000000000043001276402340000261300ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; import java.util.Map; /** * GraphML key object that was parsed from the input stream. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class Key { /** * Enumeration for the 'for' type of this key. The for property indicates * which elements (e.g. graph, node, edge) this key applies to. */ public enum ForType { ALL, GRAPH, NODE, EDGE, HYPEREDGE, PORT, ENDPOINT } private String id; private String description; private String attributeName; private String attributeType; private String defaultValue; private ForType forType = ForType.ALL; public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getAttributeName() { return attributeName; } public void setAttributeName(String attributeName) { this.attributeName = attributeName; } public String getAttributeType() { return attributeType; } public void setAttributeType(String attributeType) { this.attributeType = attributeType; } public String getDefaultValue() { return defaultValue; } public void setDefaultValue(String defaultValue) { this.defaultValue = defaultValue; } public void setId(String id) { this.id = id; } public void setForType(ForType forType) { this.forType = forType; } public String getId() { return this.id; } public String defaultValue() { return this.defaultValue; } public ForType getForType() { return this.forType; } public void applyKey( Metadata metadata ) { Map props = metadata.getProperties(); if( defaultValue != null && !props.containsKey(id) ) { props.put(id, defaultValue); } } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/KeyMap.java000066400000000000000000000066411276402340000266000ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * A KeyMap is a storage mechanism for the keys read from the GraphML file. It * stores the keys indexed by the type of GraphML metadata (node, edge, etc) * that the key applies to. The applyKeys method will obtain the * list of keys that apply to the given metadata type and apply the keys * one-by-one to the metadata. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class KeyMap { final private Map> map = new HashMap>(); /** * Adds the given key to the map. * * @param key the key to be added. */ public void addKey(Key key) { switch (key.getForType()) { case EDGE: { getKeyList(Metadata.MetadataType.EDGE).add(key); break; } case ENDPOINT: { getKeyList(Metadata.MetadataType.ENDPOINT).add(key); break; } case GRAPH: { getKeyList(Metadata.MetadataType.GRAPH).add(key); break; } case HYPEREDGE: { getKeyList(Metadata.MetadataType.HYPEREDGE).add(key); break; } case NODE: { getKeyList(Metadata.MetadataType.NODE).add(key); break; } case PORT: { getKeyList(Metadata.MetadataType.PORT).add(key); break; } default: { // Default = ALL getKeyList(Metadata.MetadataType.EDGE).add(key); getKeyList(Metadata.MetadataType.ENDPOINT).add(key); getKeyList(Metadata.MetadataType.GRAPH).add(key); getKeyList(Metadata.MetadataType.HYPEREDGE).add(key); getKeyList(Metadata.MetadataType.NODE).add(key); getKeyList(Metadata.MetadataType.PORT).add(key); } } } /** * Applies all keys that are applicable to the given metadata. * * @param metadata the target metadata. */ public void applyKeys(Metadata metadata) { List keys = getKeyList(metadata.getMetadataType()); for (Key key : keys) { key.applyKey(metadata); } } /** * Clears this map. */ public void clear() { map.clear(); } /** * Retrieves the set of entries contained in this map. * * @return all of the entries in this map. */ public Set>> entrySet() { return map.entrySet(); } /** * Gets the list for the given metadata type. If doesn't exist, the list is * created. * * @param type the metadata type. * @return the list for the metadata type. */ private List getKeyList(Metadata.MetadataType type) { List keys = map.get(type); if (keys == null) { keys = new ArrayList(); map.put(type, keys); } return keys; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/Metadata.java000066400000000000000000000015731276402340000271310ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; import java.util.Map; /** * Interface for any GraphML metadata. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public interface Metadata { /** * Metadata type enumeration */ enum MetadataType { GRAPH, NODE, EDGE, HYPEREDGE, PORT, ENDPOINT } /** * Gets the metadata type of this object. * * @return the metadata type */ MetadataType getMetadataType(); /** * Gets any properties that were associated with this metadata in the * GraphML * * @return GraphML properties */ Map getProperties(); }jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/NodeMetadata.java000066400000000000000000000025741276402340000277410ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; import java.util.ArrayList; import java.util.List; /** * Metadata structure for the 'node' GraphML element. * * @author Nathan Mittler - nathan.mittler@gmail.com * * @see "http://graphml.graphdrawing.org/specification.html" */ public class NodeMetadata extends AbstractMetadata { private String id; private String description; private Object vertex; final private List ports = new ArrayList(); public String getId() { return id; } public void setId(String id) { this.id = id; } public String getDescription() { return description; } public void setDescription(String desc) { this.description = desc; } public void addPort(PortMetadata port) { ports.add(port); } public List getPorts() { return ports; } public Object getVertex() { return vertex; } public void setVertex(Object vertex) { this.vertex = vertex; } public MetadataType getMetadataType() { return MetadataType.NODE; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/PortMetadata.java000066400000000000000000000017041276402340000277720ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; /** * Metadata structure for the 'port' GraphML element. * * @author Nathan Mittler - nathan.mittler@gmail.com * * @see "http://graphml.graphdrawing.org/specification.html" */ public class PortMetadata extends AbstractMetadata { private String name; private String description; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String desc) { this.description = desc; } public MetadataType getMetadataType() { return MetadataType.PORT; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/000077500000000000000000000000001276402340000260345ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/AbstractElementParser.java000066400000000000000000000036111276402340000331320ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.Metadata; /** * Base class for element parsers - provides some minimal functionality. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public abstract class AbstractElementParser,V,E> implements ElementParser { final private ParserContext parserContext; protected AbstractElementParser(ParserContext parserContext) { this.parserContext = parserContext; } public ParserContext getParserContext() { return this.parserContext; } public ElementParser getParser(String localName) { return parserContext.getElementParserRegistry().getParser(localName); } public void applyKeys(Metadata metadata) { getParserContext().getKeyMap().applyKeys(metadata); } public ElementParser getUnknownParser() { return parserContext.getElementParserRegistry().getUnknownElementParser(); } protected void verifyMatch(StartElement start, EndElement end) throws GraphIOException { String startName = start.getName().getLocalPart(); String endName = end.getName().getLocalPart(); if (!startName.equals(endName)) { throw new GraphIOException( "Failed parsing document: Start/end tag mismatch! " + "StartTag:" + startName + ", EndTag: " + endName); } } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/DataElementParser.java000066400000000000000000000057031276402340000322440ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import java.util.Iterator; import javax.xml.stream.XMLEventReader; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.Characters; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.DataMetadata; import edu.uci.ics.jung.io.graphml.GraphMLConstants; import edu.uci.ics.jung.io.graphml.ExceptionConverter; /** * Parses the data element. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class DataElementParser,V,E> extends AbstractElementParser { public DataElementParser(ParserContext parserContext) { super(parserContext); } public DataMetadata parse(XMLEventReader xmlEventReader, StartElement start) throws GraphIOException { try { // Create the new port. DataMetadata data = new DataMetadata(); // Parse the attributes. @SuppressWarnings("unchecked") Iterator iterator = start.getAttributes(); while (iterator.hasNext()) { Attribute attribute = (Attribute) iterator.next(); String name = attribute.getName().getLocalPart(); String value = attribute.getValue(); if (data.getKey() == null && GraphMLConstants.KEY_NAME.equals(name)) { data.setKey(value); } } // Make sure the key has been set. if (data.getKey() == null) { throw new GraphIOException( "Element 'data' is missing attribute 'key'"); } while (xmlEventReader.hasNext()) { XMLEvent event = xmlEventReader.nextEvent(); if (event.isStartElement()) { StartElement element = (StartElement) event; // Treat any child elements as unknown getUnknownParser().parse(xmlEventReader, element); } if (event.isCharacters()) { Characters characters = (Characters) event; data.setValue(characters.getData()); } if (event.isEndElement()) { EndElement end = (EndElement) event; verifyMatch(start, end); break; } } return data; } catch (Exception e) { ExceptionConverter.convert(e); } return null; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/EdgeElementParser.java000066400000000000000000000101521276402340000322310ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import java.util.Iterator; import javax.xml.stream.XMLEventReader; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.*; /** * Parses an edge element. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class EdgeElementParser,V,E> extends AbstractElementParser { public EdgeElementParser(ParserContext parserContext) { super(parserContext); } public EdgeMetadata parse(XMLEventReader xmlEventReader, StartElement start) throws GraphIOException { try { // Create the new edge. EdgeMetadata edge = new EdgeMetadata(); // Parse the attributes. @SuppressWarnings("unchecked") Iterator iterator = start.getAttributes(); while (iterator.hasNext()) { Attribute attribute = iterator.next(); String name = attribute.getName().getLocalPart(); String value = attribute.getValue(); if (edge.getId() == null && GraphMLConstants.ID_NAME.equals(name)) { edge.setId(value); } else if (edge.isDirected() == null && GraphMLConstants.DIRECTED_NAME.equals(name)) { edge.setDirected(("true".equals(value))); } else if (edge.getSource() == null && GraphMLConstants.SOURCE_NAME.equals(name)) { edge.setSource(value); } else if (edge.getTarget() == null && GraphMLConstants.TARGET_NAME.equals(name)) { edge.setTarget(value); } else if (edge.getSourcePort() == null && GraphMLConstants.SOURCEPORT_NAME.equals(name)) { edge.setSourcePort(value); } else if (edge.getTargetPort() == null && GraphMLConstants.TARGETPORT_NAME.equals(name)) { edge.setTargetPort(value); } else { edge.setProperty(name, value); } } // Make sure the source and target have been been set. if (edge.getSource() == null || edge.getTarget() == null) { throw new GraphIOException( "Element 'edge' is missing attribute 'source' or 'target'"); } while (xmlEventReader.hasNext()) { XMLEvent event = xmlEventReader.nextEvent(); if (event.isStartElement()) { StartElement element = (StartElement) event; String name = element.getName().getLocalPart(); if(GraphMLConstants.DESC_NAME.equals(name)) { String desc = (String)getParser(name).parse(xmlEventReader, element); edge.setDescription(desc); } else if(GraphMLConstants.DATA_NAME.equals(name)) { DataMetadata data = (DataMetadata)getParser(name).parse(xmlEventReader, element); edge.addData(data); } else { // Treat anything else as unknown getUnknownParser().parse(xmlEventReader, element); } } if (event.isEndElement()) { EndElement end = (EndElement) event; verifyMatch(start, end); break; } } // Apply the keys to this object. applyKeys(edge); return edge; } catch (Exception e) { ExceptionConverter.convert(e); } return null; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/ElementParser.java000066400000000000000000000013441276402340000314470ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import javax.xml.stream.XMLEventReader; import javax.xml.stream.events.StartElement; import edu.uci.ics.jung.io.GraphIOException; /** * Interface for all element parsers. All parsers will be registered with the registry. * * @author Nathan Mittler - nathan.mittler@gmail.com * * @see ElementParserRegistry */ public interface ElementParser { Object parse(XMLEventReader xmlEventReader, StartElement start) throws GraphIOException; } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/ElementParserRegistry.java000066400000000000000000000054171276402340000332050ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import java.util.HashMap; import java.util.Map; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.io.graphml.EdgeMetadata; import edu.uci.ics.jung.io.graphml.GraphMLConstants; import edu.uci.ics.jung.io.graphml.GraphMetadata; import edu.uci.ics.jung.io.graphml.HyperEdgeMetadata; import edu.uci.ics.jung.io.graphml.KeyMap; import edu.uci.ics.jung.io.graphml.NodeMetadata; /** * Registry for all element parsers. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class ElementParserRegistry, V, E> { final private Map parserMap = new HashMap(); final private ElementParser unknownElementParser = new UnknownElementParser(); public ElementParserRegistry(KeyMap keyMap, Function graphTransformer, Function vertexTransformer, Function edgeTransformer, Function hyperEdgeTransformer) { // Create the parser context. ParserContext context = new ParserContext(this, keyMap, graphTransformer, vertexTransformer, edgeTransformer, hyperEdgeTransformer); parserMap.put(GraphMLConstants.DEFAULT_NAME, new StringElementParser(context)); parserMap.put(GraphMLConstants.DESC_NAME, new StringElementParser(context)); parserMap.put(GraphMLConstants.KEY_NAME, new KeyElementParser(context)); parserMap.put(GraphMLConstants.DATA_NAME, new DataElementParser(context)); parserMap.put(GraphMLConstants.PORT_NAME, new PortElementParser(context)); parserMap.put(GraphMLConstants.NODE_NAME, new NodeElementParser(context)); parserMap.put(GraphMLConstants.GRAPH_NAME, new GraphElementParser(context)); parserMap.put(GraphMLConstants.ENDPOINT_NAME, new EndpointElementParser(context)); parserMap.put(GraphMLConstants.EDGE_NAME, new EdgeElementParser(context)); parserMap.put(GraphMLConstants.HYPEREDGE_NAME, new HyperEdgeElementParser(context)); } public ElementParser getUnknownElementParser() { return unknownElementParser; } public ElementParser getParser(String localName) { ElementParser parser = parserMap.get(localName); if (parser == null) { parser = unknownElementParser; } return parser; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/EndpointElementParser.java000066400000000000000000000104411276402340000331460ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.xml.stream.XMLEventReader; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.EndpointMetadata; import edu.uci.ics.jung.io.graphml.GraphMLConstants; import edu.uci.ics.jung.io.graphml.ExceptionConverter; import edu.uci.ics.jung.io.graphml.EndpointMetadata.EndpointType; /** * Parses endpoint elements. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class EndpointElementParser,V,E> extends AbstractElementParser { final static private Map endpointTypeMap = new HashMap(); static { endpointTypeMap.put(GraphMLConstants.IN_NAME, EndpointType.IN); endpointTypeMap.put(GraphMLConstants.OUT_NAME, EndpointType.OUT); endpointTypeMap.put(GraphMLConstants.UNDIR_NAME, EndpointType.UNDIR); } public EndpointElementParser(ParserContext parserContext) { super(parserContext); } public EndpointMetadata parse(XMLEventReader xmlEventReader, StartElement start) throws GraphIOException { try { // Create the new endpoint. EndpointMetadata endpoint = new EndpointMetadata(); // Parse the attributes. @SuppressWarnings("unchecked") Iterator iterator = start.getAttributes(); while (iterator.hasNext()) { Attribute attribute = iterator.next(); String name = attribute.getName().getLocalPart(); String value = attribute.getValue(); if (endpoint.getId() == null && GraphMLConstants.ID_NAME.equals(name)) { endpoint.setId(value); } if (endpoint.getPort() == null && GraphMLConstants.PORT_NAME.equals(name)) { endpoint.setPort(value); } if (endpoint.getNode() == null && GraphMLConstants.NODE_NAME.equals(name)) { endpoint.setNode(value); } if (GraphMLConstants.TYPE_NAME.equals(name)) { EndpointType t = endpointTypeMap.get(value); if( t == null ) { t = EndpointType.UNDIR; } endpoint.setEndpointType(t); } else { endpoint.setProperty(name, value); } } // Make sure the node has been set. if (endpoint.getNode() == null) { throw new GraphIOException( "Element 'endpoint' is missing attribute 'node'"); } while (xmlEventReader.hasNext()) { XMLEvent event = xmlEventReader.nextEvent(); if (event.isStartElement()) { StartElement element = (StartElement) event; String name = element.getName().getLocalPart(); if(GraphMLConstants.DESC_NAME.equals(name)) { String desc = (String)getParser(name).parse(xmlEventReader, element); endpoint.setDescription(desc); } else { // Treat anything else as unknown getUnknownParser().parse(xmlEventReader, element); } } if (event.isEndElement()) { EndElement end = (EndElement) event; verifyMatch(start, end); break; } } // Apply the keys to this object. applyKeys(endpoint); return endpoint; } catch (Exception e) { ExceptionConverter.convert(e); } return null; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/GraphElementParser.java000066400000000000000000000240151276402340000324310ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.xml.stream.XMLEventReader; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.*; import edu.uci.ics.jung.io.graphml.GraphMetadata.EdgeDefault; /** * Parses graph elements. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class GraphElementParser,V,E> extends AbstractElementParser { public GraphElementParser(ParserContext parserContext) { super(parserContext); } public GraphMetadata parse(XMLEventReader xmlEventReader, StartElement start) throws GraphIOException { try { // Create the new graph. GraphMetadata graphMetadata = new GraphMetadata(); // Parse the attributes. @SuppressWarnings("unchecked") Iterator iterator = start.getAttributes(); while (iterator.hasNext()) { Attribute attribute = iterator.next(); String name = attribute.getName().getLocalPart(); String value = attribute.getValue(); if (graphMetadata.getId() == null && GraphMLConstants.ID_NAME.equals(name)) { graphMetadata.setId(value); } else if (graphMetadata.getEdgeDefault() == null && GraphMLConstants.EDGEDEFAULT_NAME.equals(name)) { graphMetadata.setEdgeDefault(GraphMLConstants.DIRECTED_NAME .equals(value) ? EdgeDefault.DIRECTED : EdgeDefault.UNDIRECTED); } else { graphMetadata.setProperty(name, value); } } // Make sure the graphdefault has been set. if (graphMetadata.getEdgeDefault() == null) { throw new GraphIOException( "Element 'graph' is missing attribute 'edgedefault'"); } Map idToVertexMap = new HashMap(); Collection edgeMetadata = new LinkedList(); Collection hyperEdgeMetadata = new LinkedList(); while (xmlEventReader.hasNext()) { XMLEvent event = xmlEventReader.nextEvent(); if (event.isStartElement()) { StartElement element = (StartElement) event; String name = element.getName().getLocalPart(); if (GraphMLConstants.DESC_NAME.equals(name)) { // Parse the description and set it in the graph. String desc = (String) getParser(name).parse( xmlEventReader, element); graphMetadata.setDescription(desc); } else if (GraphMLConstants.DATA_NAME.equals(name)) { // Parse the data element and store the property in the graph. DataMetadata data = (DataMetadata) getParser(name).parse( xmlEventReader, element); graphMetadata.addData(data); } else if (GraphMLConstants.NODE_NAME.equals(name)) { // Parse the node metadata NodeMetadata metadata = (NodeMetadata) getParser(name).parse( xmlEventReader, element); // Create the vertex object and store it in the metadata V vertex = getParserContext().createVertex(metadata); metadata.setVertex(vertex); idToVertexMap.put(metadata.getId(), vertex); // Add it to the graph graphMetadata.addNodeMetadata(vertex, metadata); } else if (GraphMLConstants.EDGE_NAME.equals(name)) { // Parse the edge metadata EdgeMetadata metadata = (EdgeMetadata) getParser(name).parse( xmlEventReader, element); // Set the directed property if not overridden. if (metadata.isDirected() == null) { metadata.setDirected(graphMetadata.getEdgeDefault() == EdgeDefault.DIRECTED); } // Create the edge object and store it in the metadata E edge = getParserContext().createEdge(metadata); edgeMetadata.add(metadata); metadata.setEdge(edge); // Add it to the graph. graphMetadata.addEdgeMetadata(edge, metadata); } else if (GraphMLConstants.HYPEREDGE_NAME.equals(name)) { // Parse the edge metadata HyperEdgeMetadata metadata = (HyperEdgeMetadata) getParser(name).parse( xmlEventReader, element); // Create the edge object and store it in the metadata E edge = getParserContext().createHyperEdge(metadata); hyperEdgeMetadata.add(metadata); metadata.setEdge(edge); // Add it to the graph graphMetadata.addHyperEdgeMetadata(edge, metadata); } else { // Treat anything else as unknown getUnknownParser().parse(xmlEventReader, element); } } if (event.isEndElement()) { EndElement end = (EndElement) event; verifyMatch(start, end); break; } } // Apply the keys to this object. applyKeys(graphMetadata); // Create the graph object and store it in the metadata G graph = getParserContext().createGraph(graphMetadata); graphMetadata.setGraph(graph); // Add all of the vertices to the graph object. addVerticesToGraph(graph, idToVertexMap.values()); // Add the edges to the graph object. addEdgesToGraph(graph, edgeMetadata, idToVertexMap); addHyperEdgesToGraph(graph, hyperEdgeMetadata, idToVertexMap); return graphMetadata; } catch (Exception e) { ExceptionConverter.convert(e); } return null; } private void addVerticesToGraph(G graph, Collection vertices) { for (V vertex : vertices) { graph.addVertex(vertex); } } @SuppressWarnings("unchecked") private void addEdgesToGraph(G graph, Collection metadata, Map idToVertexMap) throws GraphIOException { for (EdgeMetadata emd : metadata) { // Get the edge out of the metadata E edge = (E)emd.getEdge(); // Get the verticies. V source = idToVertexMap.get(emd.getSource()); V target = idToVertexMap.get(emd.getTarget()); if (source == null || target == null) { throw new GraphIOException( "edge references undefined source or target vertex. " + "Source: " + emd.getSource() + ", Target: " + emd.getTarget()); } // Add it to the graph. if (graph instanceof Graph) { ((Graph) graph).addEdge(edge, source, target, emd .isDirected() ? EdgeType.DIRECTED : EdgeType.UNDIRECTED); } else { graph.addEdge(edge, new Pair(source, target)); } } } @SuppressWarnings("unchecked") private void addHyperEdgesToGraph(G graph, Collection metadata, Map idToVertexMap) throws GraphIOException { for (HyperEdgeMetadata emd : metadata) { // Get the edge out of the metadata E edge = (E)emd.getEdge(); // Add the verticies to a list. List verticies = new ArrayList(); List endpoints = emd.getEndpoints(); for (EndpointMetadata ep : endpoints) { V v = idToVertexMap.get(ep.getNode()); if (v == null) { throw new GraphIOException( "hyperedge references undefined vertex: " + ep.getNode()); } verticies.add(v); } // Add it to the graph. graph.addEdge(edge, verticies); } } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/GraphMLEventFilter.java000066400000000000000000000021321276402340000323370ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import javax.xml.stream.EventFilter; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.events.XMLEvent; /** * Filter to ignore unsupported XML events. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class GraphMLEventFilter implements EventFilter { public boolean accept(XMLEvent event) { switch( event.getEventType() ) { case XMLStreamConstants.START_ELEMENT: case XMLStreamConstants.END_ELEMENT: case XMLStreamConstants.CHARACTERS: case XMLStreamConstants.ATTRIBUTE: case XMLStreamConstants.NAMESPACE: case XMLStreamConstants.START_DOCUMENT: case XMLStreamConstants.END_DOCUMENT: { return true; } default: { return false; } } } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/HyperEdgeElementParser.java000066400000000000000000000065571276402340000332570ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import java.util.Iterator; import javax.xml.stream.XMLEventReader; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.*; /** * Parses hyper edge elements. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class HyperEdgeElementParser,V,E> extends AbstractElementParser { public HyperEdgeElementParser(ParserContext parserContext) { super(parserContext); } public HyperEdgeMetadata parse(XMLEventReader xmlEventReader, StartElement start) throws GraphIOException { try { // Create the new edge. HyperEdgeMetadata edge = new HyperEdgeMetadata(); // Parse the attributes. @SuppressWarnings("unchecked") Iterator iterator = start.getAttributes(); while (iterator.hasNext()) { Attribute attribute = (Attribute) iterator.next(); String name = attribute.getName().getLocalPart(); String value = attribute.getValue(); if (edge.getId() == null && GraphMLConstants.ID_NAME.equals(name)) { edge.setId(value); } else { edge.setProperty(name, value); } } while (xmlEventReader.hasNext()) { XMLEvent event = xmlEventReader.nextEvent(); if (event.isStartElement()) { StartElement element = (StartElement) event; String name = element.getName().getLocalPart(); if(GraphMLConstants.DESC_NAME.equals(name)) { String desc = (String)getParser(name).parse(xmlEventReader, element); edge.setDescription(desc); } else if(GraphMLConstants.DATA_NAME.equals(name)) { DataMetadata data = (DataMetadata)getParser(name).parse(xmlEventReader, element); edge.addData(data); } else if(GraphMLConstants.ENDPOINT_NAME.equals(name)) { EndpointMetadata ep = (EndpointMetadata)getParser(name).parse(xmlEventReader, element); edge.addEndpoint(ep); } else { // Treat anything else as unknown getUnknownParser().parse(xmlEventReader, element); } } if (event.isEndElement()) { EndElement end = (EndElement) event; verifyMatch(start, end); break; } } // Apply the keys to this object. applyKeys(edge); return edge; } catch (Exception e) { ExceptionConverter.convert(e); } return null; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/KeyElementParser.java000066400000000000000000000110601276402340000321140ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import java.util.Iterator; import javax.xml.stream.XMLEventReader; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.GraphMLConstants; import edu.uci.ics.jung.io.graphml.Key; import edu.uci.ics.jung.io.graphml.ExceptionConverter; /** * Parses key elements. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class KeyElementParser,V,E> extends AbstractElementParser { public KeyElementParser(ParserContext parserContext) { super(parserContext); } public Key parse(XMLEventReader xmlEventReader, StartElement start) throws GraphIOException { try { // Create the new key. ForType defaults to ALL. Key key = new Key(); // Parse the attributes. @SuppressWarnings("unchecked") Iterator iterator = start.getAttributes(); while (iterator.hasNext()) { Attribute attribute = iterator.next(); String name = attribute.getName().getLocalPart(); String value = attribute.getValue(); if (key.getId() == null && GraphMLConstants.ID_NAME.equals(name)) { key.setId(value); } else if (key.getAttributeName() == null && GraphMLConstants.ATTRNAME_NAME.equals(name)) { key.setAttributeName(value); } else if (key.getAttributeType() == null && GraphMLConstants.ATTRTYPE_NAME.equals(name)) { key.setAttributeType(value); } else if (GraphMLConstants.FOR_NAME.equals(name)) { key.setForType(convertFor(value)); } } // Make sure the id has been set. if (key.getId() == null) { throw new GraphIOException( "Element 'key' is missing attribute 'id'"); } while (xmlEventReader.hasNext()) { XMLEvent event = xmlEventReader.nextEvent(); if (event.isStartElement()) { StartElement element = (StartElement) event; String name = element.getName().getLocalPart(); if(GraphMLConstants.DESC_NAME.equals(name)) { String desc = (String)getParser(name).parse(xmlEventReader, element); key.setDescription(desc); } else if(GraphMLConstants.DEFAULT_NAME.equals(name)) { String defaultValue = (String)getParser(name).parse(xmlEventReader, element); key.setDefaultValue(defaultValue); } else { // Treat anything else as unknown getUnknownParser().parse(xmlEventReader, element); } } if (event.isEndElement()) { EndElement end = (EndElement) event; verifyMatch(start, end); break; } } return key; } catch (Exception e) { ExceptionConverter.convert(e); } return null; } static public Key.ForType convertFor(String value) { if (value != null) { if (GraphMLConstants.GRAPH_NAME.equals(value)) { return Key.ForType.GRAPH; } if (GraphMLConstants.EDGE_NAME.equals(value)) { return Key.ForType.EDGE; } if (GraphMLConstants.ENDPOINT_NAME.equals(value)) { return Key.ForType.ENDPOINT; } if (GraphMLConstants.HYPEREDGE_NAME.equals(value)) { return Key.ForType.HYPEREDGE; } if (GraphMLConstants.NODE_NAME.equals(value)) { return Key.ForType.NODE; } if (GraphMLConstants.PORT_NAME.equals(value)) { return Key.ForType.PORT; } } return Key.ForType.ALL; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/NodeElementParser.java000066400000000000000000000067751276402340000322720ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import java.util.Iterator; import javax.xml.stream.XMLEventReader; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.*; /** * Parses node elements. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class NodeElementParser,V,E> extends AbstractElementParser { public NodeElementParser(ParserContext parserContext) { super(parserContext); } @SuppressWarnings("unchecked") public NodeMetadata parse(XMLEventReader xmlEventReader, StartElement start) throws GraphIOException { try { // Create the new node. NodeMetadata node = new NodeMetadata(); // Parse the attributes. Iterator iterator = start.getAttributes(); while (iterator.hasNext()) { Attribute attribute = iterator.next(); String name = attribute.getName().getLocalPart(); String value = attribute.getValue(); if (node.getId() == null && GraphMLConstants.ID_NAME.equals(name)) { node.setId(value); } else { node.setProperty(name, value); } } // Make sure the name has been set. if (node.getId() == null) { throw new GraphIOException( "Element 'node' is missing attribute 'id'"); } while (xmlEventReader.hasNext()) { XMLEvent event = xmlEventReader.nextEvent(); if (event.isStartElement()) { StartElement element = (StartElement) event; String name = element.getName().getLocalPart(); if(GraphMLConstants.DESC_NAME.equals(name)) { String desc = (String)getParser(name).parse(xmlEventReader, element); node.setDescription(desc); } else if(GraphMLConstants.DATA_NAME.equals(name)) { DataMetadata data = (DataMetadata)getParser(name).parse(xmlEventReader, element); node.addData(data); } else if(GraphMLConstants.PORT_NAME.equals(name)) { PortMetadata port = (PortMetadata)getParser(name).parse(xmlEventReader, element); node.addPort(port); } else { // Treat anything else as unknown getUnknownParser().parse(xmlEventReader, element); } } else if (event.isEndElement()) { EndElement end = (EndElement) event; verifyMatch(start, end); break; } } // Apply the keys to this object. applyKeys(node); return node; } catch (Exception e) { ExceptionConverter.convert(e); } return null; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/ParserContext.java000066400000000000000000000050441276402340000315030ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.io.graphml.EdgeMetadata; import edu.uci.ics.jung.io.graphml.GraphMetadata; import edu.uci.ics.jung.io.graphml.HyperEdgeMetadata; import edu.uci.ics.jung.io.graphml.KeyMap; import edu.uci.ics.jung.io.graphml.NodeMetadata; /** * Provides resources related to the current parsing context. * * @author Nathan Mittler - nathan.mittler@gmail.com * * @param The graph type * @param The vertex type * @param The edge type */ public class ParserContext, V, E> { private final KeyMap keyMap; private final ElementParserRegistry elementParserRegistry; private final Function graphTransformer; private final Function vertexTransformer; private final Function edgeTransformer; private final Function hyperEdgeTransformer; public ParserContext(ElementParserRegistry elementParserRegistry, KeyMap keyMap, Function graphTransformer, Function vertexTransformer, Function edgeTransformer, Function hyperEdgeTransformer ) { this.elementParserRegistry = elementParserRegistry; this.keyMap = keyMap; this.graphTransformer = graphTransformer; this.vertexTransformer = vertexTransformer; this.edgeTransformer = edgeTransformer; this.hyperEdgeTransformer = hyperEdgeTransformer; } public ElementParserRegistry getElementParserRegistry() { return elementParserRegistry; } public KeyMap getKeyMap() { return keyMap; } public G createGraph(GraphMetadata metadata) { return graphTransformer.apply(metadata); } public V createVertex(NodeMetadata metadata) { return vertexTransformer.apply(metadata); } public E createEdge(EdgeMetadata metadata) { return edgeTransformer.apply(metadata); } public E createHyperEdge(HyperEdgeMetadata metadata) { return hyperEdgeTransformer.apply(metadata); } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/PortElementParser.java000066400000000000000000000065061276402340000323210ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import java.util.Iterator; import javax.xml.stream.XMLEventReader; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.*; /** * Parses port elements. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class PortElementParser,V,E> extends AbstractElementParser { public PortElementParser(ParserContext parserContext) { super(parserContext); } public PortMetadata parse(XMLEventReader xmlEventReader, StartElement start) throws GraphIOException { try { // Create the new port. PortMetadata port = new PortMetadata(); // Parse the attributes. @SuppressWarnings("unchecked") Iterator iterator = start.getAttributes(); while (iterator.hasNext()) { Attribute attribute = iterator.next(); String name = attribute.getName().getLocalPart(); String value = attribute.getValue(); if (port.getName() == null && GraphMLConstants.NAME_NAME.equals(name)) { port.setName(value); } else { port.setProperty(name, value); } } // Make sure the name has been set. if (port.getName() == null) { throw new GraphIOException( "Element 'port' is missing attribute 'name'"); } while (xmlEventReader.hasNext()) { XMLEvent event = xmlEventReader.nextEvent(); if (event.isStartElement()) { StartElement element = (StartElement) event; String name = element.getName().getLocalPart(); if(GraphMLConstants.DESC_NAME.equals(name)) { String desc = (String)getParser(name).parse(xmlEventReader, element); port.setDescription(desc); } else if(GraphMLConstants.DATA_NAME.equals(name)) { DataMetadata data = (DataMetadata)getParser(name).parse(xmlEventReader, element); port.addData(data); } else { // Treat anything else as unknown getUnknownParser().parse(xmlEventReader, element); } } if (event.isEndElement()) { EndElement end = (EndElement) event; verifyMatch(start, end); break; } } // Apply the keys to this port. applyKeys(port); return port; } catch (Exception e) { ExceptionConverter.convert(e); } return null; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/StringElementParser.java000066400000000000000000000036461276402340000326450ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import javax.xml.stream.XMLEventReader; import javax.xml.stream.events.Characters; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.ExceptionConverter; /** * Parses an element that just contains text. * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class StringElementParser,V,E> extends AbstractElementParser { public StringElementParser(ParserContext parserContext) { super(parserContext); } public String parse(XMLEventReader xmlEventReader, StartElement start) throws GraphIOException { try { String str = null; while (xmlEventReader.hasNext()) { XMLEvent event = xmlEventReader.nextEvent(); if (event.isStartElement()) { // Parse the unknown element. getUnknownParser().parse(xmlEventReader, event .asStartElement()); } else if (event.isEndElement()) { EndElement end = (EndElement) event; verifyMatch(start, end); break; } else if (event.isCharacters()) { Characters characters = (Characters) event; str = characters.getData(); } } return str; } catch (Exception e) { ExceptionConverter.convert(e); } return null; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/UnknownElementParser.java000066400000000000000000000046241276402340000330330ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import java.util.Stack; import javax.xml.stream.XMLEventReader; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.ExceptionConverter; /** * Skips an entire unknown subtree of the XML * * @author Nathan Mittler - nathan.mittler@gmail.com */ public class UnknownElementParser implements ElementParser { /** * Skips an entire subtree starting with the provided unknown element. * * @param xmlEventReader * the event reader * @param start * the unknown element to be skipped. * @return null */ public Object parse(XMLEventReader xmlEventReader, StartElement start) throws GraphIOException { try { Stack skippedElements = new Stack(); skippedElements.add(start.getName().getLocalPart()); while (xmlEventReader.hasNext()) { XMLEvent event = xmlEventReader.nextEvent(); if (event.isStartElement()) { String name = event.asStartElement().getName() .getLocalPart(); // Push the name of the unknown element. skippedElements.push(name); } if (event.isEndElement()) { String name = event.asEndElement().getName() .getLocalPart(); if (skippedElements.size() == 0 || !skippedElements.peek().equals(name)) { throw new GraphIOException( "Failed parsing GraphML document - startTag/endTag mismatch"); } // Pop the stack. skippedElements.pop(); if( skippedElements.isEmpty() ) { break; } } } return null; } catch (Exception e) { ExceptionConverter.convert(e); } return null; } } jung-jung-2.1.1/jung-io/src/main/java/edu/uci/ics/jung/io/package.html000066400000000000000000000007111276402340000253660ustar00rootroot00000000000000

    Interfaces and classes for reading and writing graphs in various (file) formats. Current formats fully or partially supported include:

    • GraphML format
    • Pajek NET format
    jung-jung-2.1.1/jung-io/src/site/000077500000000000000000000000001276402340000165205ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/site/site.xml000066400000000000000000000004441276402340000202100ustar00rootroot00000000000000 ${project.name} jung-jung-2.1.1/jung-io/src/test/000077500000000000000000000000001276402340000165335ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/java/000077500000000000000000000000001276402340000174545ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/java/edu/000077500000000000000000000000001276402340000202315ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/java/edu/uci/000077500000000000000000000000001276402340000210115ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/000077500000000000000000000000001276402340000215675ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/000077500000000000000000000000001276402340000225325ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/000077500000000000000000000000001276402340000231415ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/PajekNetIOTest.java000066400000000000000000000354141276402340000266040ustar00rootroot00000000000000/* * Created on May 3, 2004 * * Copyright (c) 2004, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io; import java.awt.geom.Point2D; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import junit.framework.Assert; import junit.framework.TestCase; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseMultigraph; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.UndirectedSparseMultigraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; /** * Needed tests: * - edgeslist, arcslist * - unit test to catch bug in readArcsOrEdges() [was skipping until e_pred, not c_pred] * * @author Joshua O'Madadhain * @author Tom Nelson - converted to jung2 */ public class PajekNetIOTest extends TestCase { protected String[] vertex_labels = {"alpha", "beta", "gamma", "delta", "epsilon"}; Supplier> directedGraphFactory; Supplier> undirectedGraphFactory; Supplier> graphFactory; Supplier vertexFactory; Supplier edgeFactory; PajekNetReader, Number,Number> pnr; @Override protected void setUp() { directedGraphFactory = new Supplier>() { public DirectedGraph get() { return new DirectedSparseMultigraph(); } }; undirectedGraphFactory = new Supplier>() { public UndirectedGraph get() { return new UndirectedSparseMultigraph(); } }; graphFactory = new Supplier>() { public Graph get() { return new SparseMultigraph(); } }; vertexFactory = new Supplier() { int n = 0; public Number get() { return n++; } }; edgeFactory = new Supplier() { int n = 0; public Number get() { return n++; } }; pnr = new PajekNetReader, Number,Number>(vertexFactory, edgeFactory); } public void testNull() { } public void testFileNotFound() { try { pnr.load("/dev/null/foo", graphFactory); fail("File load did not fail on nonexistent file"); } catch (FileNotFoundException fnfe) { } catch (IOException ioe) { fail("unexpected IOException"); } } public void testNoLabels() throws IOException { String test = "*Vertices 3\n1\n2\n3\n*Edges\n1 2\n2 2"; Reader r = new StringReader(test); Graph g = pnr.load(r, undirectedGraphFactory); assertEquals(g.getVertexCount(), 3); assertEquals(g.getEdgeCount(), 2); } public void testDirectedSaveLoadSave() throws IOException { Graph graph1 = directedGraphFactory.get(); for(int i=0; i<5; i++) { graph1.addVertex(i); } // GraphUtils.addVertices(graph1, 5); List id = new ArrayList(graph1.getVertices());//Indexer.getIndexer(graph1); GreekLabels gl = new GreekLabels(id); int j=0; graph1.addEdge(j++, 0, 1); graph1.addEdge(j++, 0, 2); graph1.addEdge(j++, 1, 2); graph1.addEdge(j++, 1, 3); graph1.addEdge(j++, 1, 4); graph1.addEdge(j++, 4, 3); // System.err.println("graph1 = "+graph1); // for(Number edge : graph1.getEdges()) { // System.err.println("edge "+edge+" is directed? "+graph1.getEdgeType(edge)); // } // for(Number v : graph1.getVertices()) { // System.err.println(v+" outedges are "+graph1.getOutEdges(v)); // System.err.println(v+" inedges are "+graph1.getInEdges(v)); // System.err.println(v+" incidentedges are "+graph1.getIncidentEdges(v)); // } assertEquals(graph1.getEdgeCount(), 6); String testFilename = "dtest.net"; String testFilename2 = testFilename + "2"; PajekNetWriter pnw = new PajekNetWriter(); pnw.save(graph1, testFilename, gl, null, null); Graph graph2 = pnr.load(testFilename, directedGraphFactory); // System.err.println("graph2 = "+graph2); // for(Number edge : graph2.getEdges()) { // System.err.println("edge "+edge+" is directed? "+graph2.getEdgeType(edge)); // } // for(Number v : graph2.getVertices()) { // System.err.println(v+" outedges are "+graph2.getOutEdges(v)); // System.err.println(v+" inedges are "+graph2.getInEdges(v)); // System.err.println(v+" incidentedges are "+graph2.getIncidentEdges(v)); // } assertEquals(graph1.getVertexCount(), graph2.getVertexCount()); assertEquals(graph1.getEdgeCount(), graph2.getEdgeCount()); pnw.save(graph2, testFilename2, pnr.getVertexLabeller(), null, null); compareIndexedGraphs(graph1, graph2); Graph graph3 = pnr.load(testFilename2, graphFactory); // System.err.println("graph3 = "+graph3); // for(Number edge : graph3.getEdges()) { // System.err.println("edge "+edge+" is directed? "+graph3.getEdgeType(edge)); // } // for(Number v : graph3.getVertices()) { // System.err.println(v+" outedges are "+graph3.getOutEdges(v)); // System.err.println(v+" inedges are "+graph3.getInEdges(v)); // System.err.println(v+" incidentedges are "+graph3.getIncidentEdges(v)); // } compareIndexedGraphs(graph2, graph3); File file1 = new File(testFilename); File file2 = new File(testFilename2); Assert.assertTrue(file1.length() == file2.length()); file1.delete(); file2.delete(); } public void testUndirectedSaveLoadSave() throws IOException { UndirectedGraph graph1 = undirectedGraphFactory.get(); for(int i=0; i<5; i++) { graph1.addVertex(i); } List id = new ArrayList(graph1.getVertices()); int j=0; GreekLabels gl = new GreekLabels(id); graph1.addEdge(j++, 0, 1); graph1.addEdge(j++, 0, 2); graph1.addEdge(j++, 1, 2); graph1.addEdge(j++, 1, 3); graph1.addEdge(j++, 1, 4); graph1.addEdge(j++, 4, 3); assertEquals(graph1.getEdgeCount(), 6); // System.err.println("graph1 = "+graph1); // for(Number edge : graph1.getEdges()) { // System.err.println("edge "+edge+" is directed? "+graph1.getEdgeType(edge)); // } // for(Number v : graph1.getVertices()) { // System.err.println(v+" outedges are "+graph1.getOutEdges(v)); // System.err.println(v+" inedges are "+graph1.getInEdges(v)); // System.err.println(v+" incidentedges are "+graph1.getIncidentEdges(v)); // } String testFilename = "utest.net"; String testFilename2 = testFilename + "2"; PajekNetWriter pnw = new PajekNetWriter(); pnw.save(graph1, testFilename, gl, null, null); Graph graph2 = pnr.load(testFilename, undirectedGraphFactory); // System.err.println("graph2 = "+graph2); // for(Number edge : graph2.getEdges()) { // System.err.println("edge "+edge+" is directed? "+graph2.getEdgeType(edge)); // } // for(Number v : graph2.getVertices()) { // System.err.println(v+" outedges are "+graph2.getOutEdges(v)); // System.err.println(v+" inedges are "+graph2.getInEdges(v)); // System.err.println(v+" incidentedges are "+graph2.getIncidentEdges(v)); // } assertEquals(graph1.getVertexCount(), graph2.getVertexCount()); assertEquals(graph1.getEdgeCount(), graph2.getEdgeCount()); pnw.save(graph2, testFilename2, pnr.getVertexLabeller(), null, null); compareIndexedGraphs(graph1, graph2); Graph graph3 = pnr.load(testFilename2, graphFactory); // System.err.println("graph3 = "+graph3); // for(Number edge : graph3.getEdges()) { // System.err.println("edge "+edge+" is directed? "+graph3.getEdgeType(edge)); // } // for(Number v : graph3.getVertices()) { // System.err.println(v+" outedges are "+graph3.getOutEdges(v)); // System.err.println(v+" inedges are "+graph3.getInEdges(v)); // System.err.println(v+" incidentedges are "+graph3.getIncidentEdges(v)); // } compareIndexedGraphs(graph2, graph3); File file1 = new File(testFilename); File file2 = new File(testFilename2); Assert.assertTrue(file1.length() == file2.length()); file1.delete(); file2.delete(); } public void testMixedSaveLoadSave() throws IOException { Graph graph1 = new SparseMultigraph(); for(int i=0; i<5; i++) { graph1.addVertex(i); } int j=0; List id = new ArrayList(graph1.getVertices()); GreekLabels gl = new GreekLabels(id); Number[] edges = { 0,1,2,3,4,5 }; graph1.addEdge(j++, 0, 1, EdgeType.DIRECTED); graph1.addEdge(j++, 0, 2, EdgeType.DIRECTED); graph1.addEdge(j++, 1, 2, EdgeType.DIRECTED); graph1.addEdge(j++, 1, 3); graph1.addEdge(j++, 1, 4); graph1.addEdge(j++, 4, 3); Map nr = new HashMap(); for (int i = 0; i < edges.length; i++) { nr.put(edges[i], new Float(Math.random())); } assertEquals(graph1.getEdgeCount(), 6); // System.err.println(" mixed graph1 = "+graph1); // for(Number edge : graph1.getEdges()) { // System.err.println("edge "+edge+" is directed? "+graph1.getEdgeType(edge)); // } // for(Number v : graph1.getVertices()) { // System.err.println(v+" outedges are "+graph1.getOutEdges(v)); // System.err.println(v+" inedges are "+graph1.getInEdges(v)); // System.err.println(v+" incidentedges are "+graph1.getIncidentEdges(v)); // } String testFilename = "mtest.net"; String testFilename2 = testFilename + "2"; // assign arbitrary locations to each vertex Map locations = new HashMap(); for (Number v : graph1.getVertices()) { locations.put(v, new Point2D.Double(v.intValue() * v.intValue(), 1 << v.intValue())); } Function vld = Functions.forMap(locations); PajekNetWriter pnw = new PajekNetWriter(); pnw.save(graph1, testFilename, gl, Functions.forMap(nr), vld); Graph graph2 = pnr.load(testFilename, graphFactory); Function pl = pnr.getVertexLabeller(); List id2 = new ArrayList(graph2.getVertices()); Function vld2 = pnr.getVertexLocationTransformer(); assertEquals(graph1.getVertexCount(), graph2.getVertexCount()); assertEquals(graph1.getEdgeCount(), graph2.getEdgeCount()); // test vertex labels and locations for (int i = 0; i < graph1.getVertexCount(); i++) { Number v1 = id.get(i); Number v2 = id2.get(i); assertEquals(gl.apply(v1), pl.apply(v2)); assertEquals(vld.apply(v1), vld2.apply(v2)); } // test edge weights Function nr2 = pnr.getEdgeWeightTransformer(); for (Number e2 : graph2.getEdges()) { Pair endpoints = graph2.getEndpoints(e2); Number v1_2 = endpoints.getFirst(); Number v2_2 = endpoints.getSecond(); Number v1_1 = id.get(id2.indexOf(v1_2)); Number v2_1 = id.get(id2.indexOf(v2_2)); Number e1 = graph1.findEdge(v1_1, v2_1); assertNotNull(e1); assertEquals(nr.get(e1).floatValue(), nr2.apply(e2).floatValue(), 0.0001); } pnw.save(graph2, testFilename2, pl, nr2, vld2); compareIndexedGraphs(graph1, graph2); pnr.setVertexLabeller(null); Graph graph3 = pnr.load(testFilename2, graphFactory); compareIndexedGraphs(graph2, graph3); File file1 = new File(testFilename); File file2 = new File(testFilename2); Assert.assertTrue(file1.length() == file2.length()); file1.delete(); file2.delete(); } /** * Tests to see whether these two graphs are structurally equivalent, based * on the connectivity of the vertices with matching indices in each graph. * Assumes a 0-based index. * * @param g1 * @param g2 */ private void compareIndexedGraphs(Graph g1, Graph g2) { int n1 = g1.getVertexCount(); int n2 = g2.getVertexCount(); assertEquals(n1, n2); assertEquals(g1.getEdgeCount(), g2.getEdgeCount()); List id1 = new ArrayList(g1.getVertices()); List id2 = new ArrayList(g2.getVertices()); for (int i = 0; i < n1; i++) { Number v1 = id1.get(i); Number v2 = id2.get(i); assertNotNull(v1); assertNotNull(v2); checkSets(g1.getPredecessors(v1), g2.getPredecessors(v2), id1, id2); checkSets(g1.getSuccessors(v1), g2.getSuccessors(v2), id1, id2); } } private void checkSets(Collection s1, Collection s2, List id1, List id2) { for (Number u : s1) { int j = id1.indexOf(u); assertTrue(s2.contains(id2.get(j))); } } private class GreekLabels implements Function { private List id; public GreekLabels(List id) { this.id = id; } public String apply(V v) { return vertex_labels[id.indexOf(v)]; } } } jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/TestGraphMLReader.java000066400000000000000000000231251276402340000272640ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.xml.parsers.ParserConfigurationException; import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import org.xml.sax.SAXException; import com.google.common.base.Function; import com.google.common.base.Supplier; import com.google.common.collect.BiMap; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.graph.SetHypergraph; import edu.uci.ics.jung.graph.UndirectedSparseGraph; /** * @author Scott White * @author Tom Nelson - converted to jung2 */ public class TestGraphMLReader extends TestCase { Supplier> graphFactory; Supplier vertexFactory; Supplier edgeFactory; GraphMLReader, Number, Number> gmlreader; public static Test suite() { return new TestSuite(TestGraphMLReader.class); } @Override protected void setUp() throws ParserConfigurationException, SAXException { graphFactory = new Supplier>() { public Graph get() { return new DirectedSparseMultigraph(); } }; vertexFactory = new Supplier() { int n = 0; public Number get() { return n++; } }; edgeFactory = new Supplier() { int n = 0; public Number get() { return n++; } }; gmlreader = new GraphMLReader, Number, Number>( vertexFactory, edgeFactory); } public void testLoad() throws IOException { String testFilename = "toy_graph.ml"; Graph graph = loadGraph(testFilename); Assert.assertEquals(graph.getVertexCount(), 3); Assert.assertEquals(graph.getEdgeCount(), 3); BiMap vertex_ids = gmlreader.getVertexIDs(); Number joe = vertex_ids.inverse().get("1"); Number bob = vertex_ids.inverse().get("2"); Number sue = vertex_ids.inverse().get("3"); Assert.assertNotNull(joe); Assert.assertNotNull(bob); Assert.assertNotNull(sue); Map> vertex_metadata = gmlreader.getVertexMetadata(); Function name = vertex_metadata.get("name").transformer; Assert.assertEquals(name.apply(joe), "Joe"); Assert.assertEquals(name.apply(bob), "Bob"); Assert.assertEquals(name.apply(sue), "Sue"); Assert.assertTrue(graph.isPredecessor(joe, bob)); Assert.assertTrue(graph.isPredecessor(bob, joe)); Assert.assertTrue(graph.isPredecessor(sue, joe)); Assert.assertFalse(graph.isPredecessor(joe, sue)); Assert.assertFalse(graph.isPredecessor(sue, bob)); Assert.assertFalse(graph.isPredecessor(bob, sue)); File testFile = new File(testFilename); testFile.delete(); } public void testAttributes() throws IOException { Graph graph = new UndirectedSparseGraph(); gmlreader.load("src/test/resources/edu/uci/ics/jung/io/graphml/attributes.graphml", graph); Assert.assertEquals(graph.getVertexCount(), 6); Assert.assertEquals(graph.getEdgeCount(), 7); // test vertex IDs BiMap vertex_ids = gmlreader.getVertexIDs(); for (Map.Entry entry : vertex_ids.entrySet()) { Assert.assertEquals(entry.getValue().charAt(0), 'n'); Assert.assertEquals( Integer.parseInt(entry.getValue().substring(1)), entry .getKey().intValue()); } // test edge IDs BiMap edge_ids = gmlreader.getEdgeIDs(); for (Map.Entry entry : edge_ids.entrySet()) { Assert.assertEquals(entry.getValue().charAt(0), 'e'); Assert.assertEquals( Integer.parseInt(entry.getValue().substring(1)), entry .getKey().intValue()); } // test data // Map> vertex_data = gmlreader // .getVertexData(); // Map> edge_data = gmlreader // .getEdgeData(); Map> vertex_metadata = gmlreader.getVertexMetadata(); Map> edge_metadata = gmlreader.getEdgeMetadata(); // test vertex colors // Transformer vertex_color = vertex_data.get("d0"); Function vertex_color = vertex_metadata.get("d0").transformer; Assert.assertEquals(vertex_color.apply(0), "green"); Assert.assertEquals(vertex_color.apply(1), "yellow"); Assert.assertEquals(vertex_color.apply(2), "blue"); Assert.assertEquals(vertex_color.apply(3), "red"); Assert.assertEquals(vertex_color.apply(4), "yellow"); Assert.assertEquals(vertex_color.apply(5), "turquoise"); // test edge weights // Transformer edge_weight = edge_data.get("d1"); Function edge_weight = edge_metadata.get("d1").transformer; Assert.assertEquals(edge_weight.apply(0), "1.0"); Assert.assertEquals(edge_weight.apply(1), "1.0"); Assert.assertEquals(edge_weight.apply(2), "2.0"); Assert.assertEquals(edge_weight.apply(3), null); Assert.assertEquals(edge_weight.apply(4), null); Assert.assertEquals(edge_weight.apply(5), null); Assert.assertEquals(edge_weight.apply(6), "1.1"); } public void testLoadHypergraph() throws IOException, ParserConfigurationException, SAXException { Hypergraph graph = new SetHypergraph(); GraphMLReader, Number, Number> hyperreader = new GraphMLReader, Number, Number>( vertexFactory, edgeFactory); hyperreader.load("src/test/resources/edu/uci/ics/jung/io/graphml/hyper.graphml", graph); Assert.assertEquals(graph.getVertexCount(), 7); Assert.assertEquals(graph.getEdgeCount(), 4); // n0 Set incident = new HashSet(); incident.add(0); incident.add(3); Assert.assertEquals(graph.getIncidentEdges(0), incident); // n1 incident.clear(); incident.add(0); incident.add(2); Assert.assertEquals(graph.getIncidentEdges(1), incident); // n2 incident.clear(); incident.add(0); Assert.assertEquals(graph.getIncidentEdges(2), incident); // n3 incident.clear(); incident.add(1); incident.add(2); Assert.assertEquals(graph.getIncidentEdges(3), incident); // n4 incident.clear(); incident.add(1); incident.add(3); Assert.assertEquals(graph.getIncidentEdges(4), incident); // n5 incident.clear(); incident.add(1); Assert.assertEquals(graph.getIncidentEdges(5), incident); // n6 incident.clear(); incident.add(1); Assert.assertEquals(graph.getIncidentEdges(6), incident); } private Graph loadGraph(String testFilename) throws IOException { BufferedWriter writer = new BufferedWriter(new FileWriter( testFilename)); writer.write("\n"); writer.write("\n"); writer.write("\n"); writer.write("\n"); writer.write("\n"); writer.write("\n"); writer.write("\n"); writer.write("\n"); writer.write("\n"); writer.write("\n"); writer.close(); Graph graph = graphFactory.get(); gmlreader.load(testFilename, graph); return graph; } // public void testSave() { // String testFilename = "toy_graph.ml"; // Graph oldGraph = loadGraph(testFilename); //// GraphMLFile graphmlFile = new GraphMLFile(); // String newFilename = testFilename + "_save"; // gmlreader.save(oldGraph,newFilename); // Graph newGraph = gmlreader.load(newFilename); // Assert.assertEquals(oldGraph.getVertexCount(),newGraph.getVertexCount()); // Assert.assertEquals(oldGraph.getEdgeCount(),newGraph.getEdgeCount()); // File testFile = new File(testFilename); // testFile.delete(); // File newFile = new File(newFilename); // newFile.delete(); // // // } } jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/TestGraphMLWriter.java000066400000000000000000000146751276402340000273500ustar00rootroot00000000000000/* * Created on Jun 22, 2008 * * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.parsers.ParserConfigurationException; import junit.framework.Assert; import junit.framework.TestCase; import org.xml.sax.SAXException; import com.google.common.base.Function; import com.google.common.base.Functions; import edu.uci.ics.jung.graph.DirectedSparseGraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseMultigraph; import edu.uci.ics.jung.graph.util.TestGraphs; public class TestGraphMLWriter extends TestCase { public void testBasicWrite() throws IOException, ParserConfigurationException, SAXException { Graph g = TestGraphs.createTestGraph(true); GraphMLWriter gmlw = new GraphMLWriter(); Function edge_weight = new Function() { public String apply(Number n) { return String.valueOf(n.intValue()); } }; Function vertex_name = Functions.identity(); //TransformerUtils.nopTransformer(); gmlw.addEdgeData("weight", "integer value for the edge", Integer.toString(-1), edge_weight); gmlw.addVertexData("name", "identifier for the vertex", null, vertex_name); gmlw.setEdgeIDs(edge_weight); gmlw.setVertexIDs(vertex_name); gmlw.save(g, new FileWriter("src/test/resources/testbasicwrite.graphml")); // TODO: now read it back in and compare the graph connectivity // and other metadata with what's in TestGraphs.pairs[], etc. // Factory vertex_factory = null; // Factory edge_factory = FactoryUtils.instantiateFactory(Object.class); // GraphMLReader, String, Object> gmlr = // new GraphMLReader, String, Object>( // vertex_factory, edge_factory); GraphMLReader, String, Object> gmlr = new GraphMLReader, String, Object>(); Graph g2 = new DirectedSparseGraph(); gmlr.load("src/test/resources/testbasicwrite.graphml", g2); Map> edge_metadata = gmlr.getEdgeMetadata(); Function edge_weight2 = edge_metadata.get("weight").transformer; validateTopology(g, g2, edge_weight, edge_weight2); // TODO: delete graph file when done File f = new File("src/test/resources/testbasicwrite.graphml"); f.delete(); } public void testMixedGraph() throws IOException, ParserConfigurationException, SAXException { Graph g = TestGraphs.getSmallGraph(); GraphMLWriter gmlw = new GraphMLWriter(); Function edge_weight = new Function() { public String apply(Number n) { return String.valueOf(n.doubleValue()); } }; gmlw.addEdgeData("weight", "integer value for the edge", Integer.toString(-1), edge_weight); gmlw.setEdgeIDs(edge_weight); gmlw.save(g, new FileWriter("src/test/resources/testmixedgraph.graphml")); // TODO: now read it back in and compare the graph connectivity // and other metadata with what's in TestGraphs, etc. GraphMLReader,String,Object> gmlr = new GraphMLReader,String,Object>(); Graph g2 = new SparseMultigraph(); gmlr.load("src/test/resources/testmixedgraph.graphml", g2); Map> edge_metadata = gmlr.getEdgeMetadata(); Function edge_weight2 = edge_metadata.get("weight").transformer; validateTopology(g, g2, edge_weight, edge_weight2); // TODO: delete graph file when done File f = new File("src/test/resources/testmixedgraph.graphml"); f.delete(); } public > void validateTopology(Graph g, Graph g2, Function edge_weight, Function edge_weight2) { Assert.assertEquals(g2.getEdgeCount(), g.getEdgeCount()); List g_vertices = new ArrayList(g.getVertices()); List g2_vertices = new ArrayList(g2.getVertices()); Collections.sort(g_vertices); Collections.sort(g2_vertices); Assert.assertEquals(g_vertices, g2_vertices); Set g_edges = new HashSet(); for (Number n : g.getEdges()) g_edges.add(String.valueOf(n)); Set g2_edges = new HashSet(g2.getEdges()); Assert.assertEquals(g_edges, g2_edges); for (T v : g2.getVertices()) { for (T w : g2.getVertices()) { Assert.assertEquals(g.isNeighbor(v, w), g2.isNeighbor(v, w)); Set e = new HashSet(); for (Number n : g.findEdgeSet(v, w)) e.add(String.valueOf(n)); Set e2 = new HashSet(g2.findEdgeSet(v, w)); Assert.assertEquals(e.size(), e2.size()); Assert.assertEquals(e, e2); } } for (Object o : g2.getEdges()) { String weight = edge_weight.apply(new Double((String)o)); String weight2 = edge_weight2.apply(o); Assert.assertEquals(weight2, weight); } // Number n = g.findEdge(v, w); // Object o = g2.findEdge(v, w); // if (n != null) // { // String weight = edge_weight.apply(n); // String weight2 = edge_weight2.apply(o); // Assert.assertEquals(weight2, weight); // } // } // } } } jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/000077500000000000000000000000001276402340000245735ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/DummyEdge.java000066400000000000000000000012351276402340000273170ustar00rootroot00000000000000package edu.uci.ics.jung.io.graphml; import com.google.common.base.Function; public class DummyEdge extends DummyGraphObjectBase { public static class EdgeFactory implements Function { int n = 100; public DummyEdge apply(EdgeMetadata md) { return new DummyEdge(n++); } } public static class HyperEdgeFactory implements Function { int n = 0; public DummyEdge apply(HyperEdgeMetadata md) { return new DummyEdge(n++); } } public DummyEdge() { } public DummyEdge(int v) { super(v); } } jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/DummyGraphObjectBase.java000066400000000000000000000027241276402340000314420ustar00rootroot00000000000000package edu.uci.ics.jung.io.graphml; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.graph.SetHypergraph; import edu.uci.ics.jung.graph.UndirectedSparseGraph; public class DummyGraphObjectBase { public static class UndirectedSparseGraphFactory implements Function> { public Hypergraph apply(GraphMetadata arg0) { return new UndirectedSparseGraph(); } } public static class SetHypergraphFactory implements Function> { public Hypergraph apply(GraphMetadata arg0) { return new SetHypergraph(); } } public int myValue; public DummyGraphObjectBase() { } public DummyGraphObjectBase(int v) { myValue = v; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + myValue; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; DummyGraphObjectBase other = (DummyGraphObjectBase) obj; return myValue == other.myValue; } } jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/DummyVertex.java000066400000000000000000000006771276402340000277410ustar00rootroot00000000000000package edu.uci.ics.jung.io.graphml; import com.google.common.base.Function; public class DummyVertex extends DummyGraphObjectBase { public static class Factory implements Function { int n = 0; public DummyVertex apply(NodeMetadata md) { return new DummyVertex(n++); } } public DummyVertex() { } public DummyVertex(int v) { super(v); } } jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/TestGraphMLReader2.java000066400000000000000000000463001276402340000310000ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import org.junit.After; import org.junit.Assert; import org.junit.Test; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.graph.SetHypergraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.io.GraphIOException; public class TestGraphMLReader2 { static final String graphMLDocStart = "" + ""; private GraphMLReader2, DummyVertex, DummyEdge> reader; @After public void tearDown() throws Exception { if (reader != null) { reader.close(); } reader = null; } @Test(expected = GraphIOException.class) public void testEmptyFile() throws Exception { String xml = ""; readGraph(xml, new DummyGraphObjectBase.UndirectedSparseGraphFactory(), new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory()); } @Test public void testBasics() throws Exception { String xml = graphMLDocStart + "" + "yellow" + "" + "" + "" + "" + "green" + "" + "" + "" + "blue" + "" + "" + "1.0" + "" + "" + ""; // Read the graph object. Hypergraph graph = readGraph(xml, new DummyGraphObjectBase.UndirectedSparseGraphFactory(), new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory()); // Check out the graph. Assert.assertNotNull(graph); Assert.assertEquals(3, graph.getVertexCount()); Assert.assertEquals(1, graph.getEdgeCount()); Assert.assertEquals(0, graph.getEdgeCount(EdgeType.DIRECTED)); Assert.assertEquals(1, graph.getEdgeCount(EdgeType.UNDIRECTED)); // Check out metadata. Assert.assertEquals(1, reader.getGraphMLDocument().getGraphMetadata().size()); List edges = new ArrayList(reader.getGraphMLDocument().getGraphMetadata().get(0).getEdgeMap().values()); Assert.assertEquals(1, edges.size()); Assert.assertEquals("n0", edges.get(0).getSource()); Assert.assertEquals("n2", edges.get(0).getTarget()); } @Test public void testData() throws Exception { String xml = graphMLDocStart + "" + "yellow" + "" + "" + "" + "" + "green" + "" + "" + "" + "blue" + "" + "" + "1.0" + "" + "" + ""; // Read the graph object. readGraph(xml, new DummyGraphObjectBase.UndirectedSparseGraphFactory(), new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory()); // Check out metadata. Assert.assertEquals(1, reader.getGraphMLDocument().getGraphMetadata().size()); List edges = new ArrayList(reader.getGraphMLDocument().getGraphMetadata().get(0).getEdgeMap().values()); List nodes = new ArrayList(reader.getGraphMLDocument().getGraphMetadata().get(0).getNodeMap().values()); Collections.sort(nodes, new Comparator() { public int compare(NodeMetadata o1, NodeMetadata o2) { return o1.getId().compareTo(o2.getId()); } }); Assert.assertEquals(1, edges.size()); Assert.assertEquals("1.0", edges.get(0).getProperties().get("d1")); Assert.assertEquals(3, nodes.size()); Assert.assertEquals("green", nodes.get(0).getProperties().get("d0")); Assert.assertEquals("yellow", nodes.get(1).getProperties().get("d0")); Assert.assertEquals("blue", nodes.get(2).getProperties().get("d0")); } @Test(expected = GraphIOException.class) public void testEdgeWithInvalidNode() throws Exception { String xml = graphMLDocStart + "" + "yellow" + "" + "" + "" + "" + "green" + "" + "" + "" + "blue" + "" + "" + // Invalid // node: n3 "1.0" + "" + ""; readGraph(xml, new DummyGraphObjectBase.UndirectedSparseGraphFactory(), new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory()); } @Test public void testHypergraph() throws Exception { String xml = graphMLDocStart + "" + "yellow" + "" + "" + "" + "" + "green" + "" + "" + "" + "blue" + "" + "" + "" + "" + "" + "" + "" + ""; // Read the graph object. Hypergraph graph = readGraph(xml, new DummyGraphObjectBase.SetHypergraphFactory(), new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory()); // Check out the graph. Assert.assertNotNull(graph); Assert.assertEquals(3, graph.getVertexCount()); Assert.assertEquals(1, graph.getEdgeCount()); Assert.assertEquals(0, graph.getEdgeCount(EdgeType.DIRECTED)); Assert.assertEquals(1, graph.getEdgeCount(EdgeType.UNDIRECTED)); // Check out metadata. Assert.assertEquals(1, reader.getGraphMLDocument().getGraphMetadata().size()); List edges = new ArrayList(reader.getGraphMLDocument().getGraphMetadata().get(0).getHyperEdgeMap().values()); Assert.assertEquals(1, edges.size()); Assert.assertEquals(3, edges.get(0).getEndpoints().size()); Assert.assertEquals("n0", edges.get(0).getEndpoints().get(0).getNode()); Assert.assertEquals("n1", edges.get(0).getEndpoints().get(1).getNode()); Assert.assertEquals("n2", edges.get(0).getEndpoints().get(2).getNode()); } @Test(expected = IllegalArgumentException.class) public void testInvalidGraphFactory() throws Exception { // Need a hypergraph String xml = graphMLDocStart + "" + "yellow" + "" + "" + "" + "" + "green" + "" + "" + "" + "blue" + "" + "" + "" + "" + "" + "" + ""; // This will attempt to add an edge with an invalid number of incident vertices (3) // for an UndirectedGraph, which should trigger an IllegalArgumentException. readGraph(xml, new DummyGraphObjectBase.UndirectedSparseGraphFactory(), new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory()); } @Test public void testAttributesFile() throws Exception { // Read the graph object. Hypergraph graph = readGraphFromFile("attributes.graphml", new DummyGraphObjectBase.UndirectedSparseGraphFactory(), new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory()); Assert.assertEquals(6, graph.getVertexCount()); Assert.assertEquals(7, graph.getEdgeCount()); Assert.assertEquals(1, reader.getGraphMLDocument().getGraphMetadata().size()); // Test node ids int id = 0; List nodes = new ArrayList(reader.getGraphMLDocument().getGraphMetadata().get(0).getNodeMap().values()); Collections.sort(nodes, new Comparator() { public int compare(NodeMetadata o1, NodeMetadata o2) { return o1.getId().compareTo(o2.getId()); } }); Assert.assertEquals(6, nodes.size()); for (NodeMetadata md : nodes) { Assert.assertEquals('n', md.getId().charAt(0)); Assert.assertEquals(id++, Integer.parseInt(md.getId().substring(1))); } // Test edge ids id = 0; List edges = new ArrayList(reader.getGraphMLDocument().getGraphMetadata().get(0).getEdgeMap().values()); Collections.sort(edges, new Comparator() { public int compare(EdgeMetadata o1, EdgeMetadata o2) { return o1.getId().compareTo(o2.getId()); } }); Assert.assertEquals(7, edges.size()); for (EdgeMetadata md : edges) { Assert.assertEquals('e', md.getId().charAt(0)); Assert.assertEquals(id++, Integer.parseInt(md.getId().substring(1))); } Assert.assertEquals("green", nodes.get(0).getProperties().get("d0")); Assert.assertEquals("yellow", nodes.get(1).getProperties().get("d0")); Assert.assertEquals("blue", nodes.get(2).getProperties().get("d0")); Assert.assertEquals("red", nodes.get(3).getProperties().get("d0")); Assert.assertEquals("yellow", nodes.get(4).getProperties().get("d0")); Assert.assertEquals("turquoise", nodes.get(5).getProperties().get("d0")); Assert.assertEquals("1.0", edges.get(0).getProperties().get("d1")); Assert.assertEquals("1.0", edges.get(1).getProperties().get("d1")); Assert.assertEquals("2.0", edges.get(2).getProperties().get("d1")); Assert.assertEquals(null, edges.get(3).getProperties().get("d1")); Assert.assertEquals(null, edges.get(4).getProperties().get("d1")); Assert.assertEquals(null, edges.get(5).getProperties().get("d1")); Assert.assertEquals("1.1", edges.get(6).getProperties().get("d1")); } @Test public void testHypergraphFile() throws Exception { Function> graphFactory = new Function>() { public Hypergraph apply(GraphMetadata md) { return new SetHypergraph(); } }; Function vertexFactory = new Function() { int n = 0; public Number apply(NodeMetadata md) { return n++; } }; Function edgeFactory = new Function() { int n = 100; public Number apply(EdgeMetadata md) { return n++; } }; Function hyperEdgeFactory = new Function() { int n = 0; public Number apply(HyperEdgeMetadata md) { return n++; } }; // Read the graph object. Reader fileReader = new InputStreamReader(getClass().getResourceAsStream("hyper.graphml")); GraphMLReader2, Number, Number> hyperreader = new GraphMLReader2, Number, Number>(fileReader, graphFactory, vertexFactory, edgeFactory, hyperEdgeFactory); // Read the graph. Hypergraph graph = hyperreader.readGraph(); Assert.assertEquals(graph.getVertexCount(), 7); Assert.assertEquals(graph.getEdgeCount(), 4); // n0 Set incident = new HashSet(); incident.add(0); incident.add(100); Assert.assertEquals(incident, graph.getIncidentEdges(0)); // n1 incident.clear(); incident.add(0); incident.add(2); Assert.assertEquals(incident, graph.getIncidentEdges(1)); // n2 incident.clear(); incident.add(0); Assert.assertEquals(incident, graph.getIncidentEdges(2)); // n3 incident.clear(); incident.add(1); incident.add(2); Assert.assertEquals(incident, graph.getIncidentEdges(3)); // n4 incident.clear(); incident.add(1); incident.add(100); Assert.assertEquals(incident, graph.getIncidentEdges(4)); // n5 incident.clear(); incident.add(1); Assert.assertEquals(incident, graph.getIncidentEdges(5)); // n6 incident.clear(); incident.add(1); Assert.assertEquals(incident, graph.getIncidentEdges(6)); } /*@Test public void testReader1Perf() throws Exception { String fileName = "attributes.graphml"; long totalTime = 0; int numTrials = 1000; for( int ix=0; ix, DummyVertex, DummyEdge> reader = new GraphMLReader, DummyVertex, DummyEdge>(new Factory() { public DummyVertex create() { return new DummyVertex(); } }, new Factory() { public DummyEdge create() { return new DummyEdge(); } }); Thread.sleep(10); long start = System.currentTimeMillis(); Hypergraph graph = new UndirectedSparseGraph(); reader.load(fileReader, graph); long duration = System.currentTimeMillis() - start; totalTime += duration; } double avgTime = ((double)totalTime / (double)numTrials) / 1000.0; System.out.printf("Reader1: totalTime=%6d, numTrials=%6d, avgTime=%2.6f seconds", totalTime, numTrials, avgTime); System.out.println(); } @Test public void testReader2Perf() throws Exception { String fileName = "attributes.graphml"; long totalTime = 0; int numTrials = 1000; // Test reader2 for( int ix=0; ix, DummyVertex, DummyEdge>( fileReader, new DummyGraphObjectBase.UndirectedSparseGraphFactory(), new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory()); reader.init(); Thread.sleep(10); long start = System.currentTimeMillis(); reader.readGraph(); long duration = System.currentTimeMillis() - start; totalTime += duration; reader.close(); } double avgTime = ((double)totalTime / (double)numTrials) / 1000.0; System.out.printf("Reader2: totalTime=%6d, numTrials=%6d, avgTime=%2.6f seconds", totalTime, numTrials, avgTime); System.out.println(); }*/ private Hypergraph readGraph(String xml, Function> gf, DummyVertex.Factory nf, DummyEdge.EdgeFactory ef, DummyEdge.HyperEdgeFactory hef) throws GraphIOException { Reader fileReader = new StringReader(xml); reader = new GraphMLReader2, DummyVertex, DummyEdge>( fileReader, gf, nf, ef, hef); return reader.readGraph(); } private Hypergraph readGraphFromFile(String file, Function> gf, DummyVertex.Factory nf, DummyEdge.EdgeFactory ef, DummyEdge.HyperEdgeFactory hef) throws Exception { InputStream is = getClass().getResourceAsStream(file); Reader fileReader = new InputStreamReader(is); reader = new GraphMLReader2, DummyVertex, DummyEdge>( fileReader, gf, nf, ef, hef); return reader.readGraph(); } } jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/000077500000000000000000000000001276402340000260675ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/AbstractParserTest.java000066400000000000000000000046371276402340000325240ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import java.io.Reader; import java.io.StringReader; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import org.junit.Assert; import org.junit.Before; import org.junit.After; import edu.uci.ics.jung.graph.Hypergraph; import edu.uci.ics.jung.io.graphml.DummyEdge; import edu.uci.ics.jung.io.graphml.DummyGraphObjectBase; import edu.uci.ics.jung.io.graphml.KeyMap; import edu.uci.ics.jung.io.graphml.DummyVertex; public abstract class AbstractParserTest { private ElementParserRegistry,DummyVertex,DummyEdge> registry; @Before public void setUp() throws Exception { registry = new ElementParserRegistry,DummyVertex,DummyEdge>( new KeyMap(), new DummyGraphObjectBase.UndirectedSparseGraphFactory(), new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory()); } @After public void tearDown() throws Exception { registry = null; } protected Object readObject(String xml) throws Exception { Reader fileReader = new StringReader(xml); XMLInputFactory factory = XMLInputFactory.newInstance(); XMLEventReader xmlEventReader = factory.createXMLEventReader(fileReader); xmlEventReader = factory.createFilteredReader(xmlEventReader, new GraphMLEventFilter()); try { while( xmlEventReader.hasNext() ) { XMLEvent event = xmlEventReader.nextEvent(); if (event.isStartElement()) { StartElement start = event.asStartElement(); String name = start.getName().getLocalPart(); return registry.getParser(name).parse(xmlEventReader, start); } } } finally { xmlEventReader.close(); } Assert.fail("failed to read object from XML: " + xml); return null; } } jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestEdgeElementParser.java000066400000000000000000000135131276402340000331300ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import org.junit.Assert; import org.junit.Test; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.EdgeMetadata; public class TestEdgeElementParser extends AbstractParserTest { @Test(expected= GraphIOException.class) public void testNoSource() throws Exception { String xml = ""; readObject(xml); } @Test(expected= GraphIOException.class) public void testNoTarget() throws Exception { String xml = ""; readObject(xml); } @Test public void testId() throws Exception { String xml = ""; EdgeMetadata edge = (EdgeMetadata) readObject(xml); Assert.assertNotNull(edge); Assert.assertEquals("e1", edge.getId()); Assert.assertEquals(null, edge.getDescription()); Assert.assertEquals("1", edge.getSource()); Assert.assertEquals("2", edge.getTarget()); Assert.assertEquals(null, edge.isDirected()); Assert.assertEquals(null, edge.getSourcePort()); Assert.assertEquals(null, edge.getTargetPort()); } @Test public void testDirectedTrue() throws Exception { String xml = ""; EdgeMetadata edge = (EdgeMetadata) readObject(xml); Assert.assertNotNull(edge); Assert.assertEquals(null, edge.getId()); Assert.assertEquals(null, edge.getDescription()); Assert.assertEquals("1", edge.getSource()); Assert.assertEquals("2", edge.getTarget()); Assert.assertEquals(true, edge.isDirected()); Assert.assertEquals(null, edge.getSourcePort()); Assert.assertEquals(null, edge.getTargetPort()); } @Test public void testDirectedFalse() throws Exception { String xml = ""; EdgeMetadata edge = (EdgeMetadata) readObject(xml); Assert.assertNotNull(edge); Assert.assertEquals(null, edge.getId()); Assert.assertEquals(null, edge.getDescription()); Assert.assertEquals("1", edge.getSource()); Assert.assertEquals("2", edge.getTarget()); Assert.assertEquals(false, edge.isDirected()); Assert.assertEquals(null, edge.getSourcePort()); Assert.assertEquals(null, edge.getTargetPort()); } @Test public void testSourceTargetPorts() throws Exception { String xml = ""; EdgeMetadata edge = (EdgeMetadata) readObject(xml); Assert.assertNotNull(edge); Assert.assertEquals(null, edge.getId()); Assert.assertEquals(null, edge.getDescription()); Assert.assertEquals("1", edge.getSource()); Assert.assertEquals("2", edge.getTarget()); Assert.assertEquals(null, edge.isDirected()); Assert.assertEquals("a", edge.getSourcePort()); Assert.assertEquals("b", edge.getTargetPort()); } @Test public void testDesc() throws Exception { String xml = "" + "hello world" + ""; EdgeMetadata edge = (EdgeMetadata) readObject(xml); Assert.assertNotNull(edge); Assert.assertEquals(null, edge.getId()); Assert.assertEquals("hello world", edge.getDescription()); Assert.assertEquals("1", edge.getSource()); Assert.assertEquals("2", edge.getTarget()); Assert.assertEquals(null, edge.isDirected()); Assert.assertEquals(null, edge.getSourcePort()); Assert.assertEquals(null, edge.getTargetPort()); } @Test public void testUserAttributes() throws Exception { String xml = "" + ""; EdgeMetadata edge = (EdgeMetadata) readObject(xml); Assert.assertNotNull(edge); Assert.assertEquals(null, edge.getId()); Assert.assertEquals(null, edge.getDescription()); Assert.assertEquals("1", edge.getSource()); Assert.assertEquals("2", edge.getTarget()); Assert.assertEquals(null, edge.isDirected()); Assert.assertEquals(null, edge.getSourcePort()); Assert.assertEquals(null, edge.getTargetPort()); Assert.assertEquals(1, edge.getProperties().size()); Assert.assertEquals("abc123", edge.getProperty("bob")); } @Test public void testData() throws Exception { String xml = "" + "value1" + "value2" + ""; EdgeMetadata edge = (EdgeMetadata) readObject(xml); Assert.assertNotNull(edge); Assert.assertEquals(null, edge.getId()); Assert.assertEquals(null, edge.getDescription()); Assert.assertEquals("1", edge.getSource()); Assert.assertEquals("2", edge.getTarget()); Assert.assertEquals(null, edge.isDirected()); Assert.assertEquals(null, edge.getSourcePort()); Assert.assertEquals(null, edge.getTargetPort()); Assert.assertEquals(2, edge.getProperties().size()); Assert.assertEquals("value1", edge.getProperty("d1")); Assert.assertEquals("value2", edge.getProperty("d2")); } } TestEndpointElementParser.java000066400000000000000000000111741276402340000337660ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import org.junit.Assert; import org.junit.Test; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.EndpointMetadata; import edu.uci.ics.jung.io.graphml.EndpointMetadata.EndpointType; public class TestEndpointElementParser extends AbstractParserTest { @Test(expected= GraphIOException.class) public void testNoNode() throws Exception { String xml = ""; readObject(xml); } @Test public void testId() throws Exception { String xml = ""; EndpointMetadata ep = (EndpointMetadata) readObject(xml); Assert.assertNotNull(ep); Assert.assertEquals("1", ep.getNode()); Assert.assertEquals("ep1", ep.getId()); Assert.assertEquals(null, ep.getDescription()); Assert.assertEquals(null, ep.getPort()); Assert.assertEquals(EndpointType.UNDIR, ep.getEndpointType()); } @Test public void testDesc() throws Exception { String xml = "" + "hello world" + ""; EndpointMetadata ep = (EndpointMetadata) readObject(xml); Assert.assertNotNull(ep); Assert.assertEquals("1", ep.getNode()); Assert.assertEquals("ep1", ep.getId()); Assert.assertEquals("hello world", ep.getDescription()); Assert.assertEquals(null, ep.getPort()); Assert.assertEquals(EndpointType.UNDIR, ep.getEndpointType()); } @Test public void testPort() throws Exception { String xml = "" + ""; EndpointMetadata ep = (EndpointMetadata) readObject(xml); Assert.assertNotNull(ep); Assert.assertEquals("1", ep.getNode()); Assert.assertEquals("ep1", ep.getId()); Assert.assertEquals(null, ep.getDescription()); Assert.assertEquals("abc123", ep.getPort()); Assert.assertEquals(EndpointType.UNDIR, ep.getEndpointType()); } @Test public void testTypeIn() throws Exception { String xml = "" + ""; EndpointMetadata ep = (EndpointMetadata) readObject(xml); Assert.assertNotNull(ep); Assert.assertEquals("1", ep.getNode()); Assert.assertEquals(null, ep.getId()); Assert.assertEquals(null, ep.getDescription()); Assert.assertEquals(null, ep.getPort()); Assert.assertEquals(EndpointType.IN, ep.getEndpointType()); } @Test public void testTypeOut() throws Exception { String xml = "" + ""; EndpointMetadata ep = (EndpointMetadata) readObject(xml); Assert.assertNotNull(ep); Assert.assertEquals("1", ep.getNode()); Assert.assertEquals(null, ep.getId()); Assert.assertEquals(null, ep.getDescription()); Assert.assertEquals(null, ep.getPort()); Assert.assertEquals(EndpointType.OUT, ep.getEndpointType()); } @Test public void testTypeUndir() throws Exception { String xml = "" + ""; EndpointMetadata ep = (EndpointMetadata) readObject(xml); Assert.assertNotNull(ep); Assert.assertEquals("1", ep.getNode()); Assert.assertEquals(null, ep.getId()); Assert.assertEquals(null, ep.getDescription()); Assert.assertEquals(null, ep.getPort()); Assert.assertEquals(EndpointType.UNDIR, ep.getEndpointType()); } @Test public void testTypeInvalid() throws Exception { String xml = "" + ""; EndpointMetadata ep = (EndpointMetadata) readObject(xml); Assert.assertNotNull(ep); Assert.assertEquals("1", ep.getNode()); Assert.assertEquals(null, ep.getId()); Assert.assertEquals(null, ep.getDescription()); Assert.assertEquals(null, ep.getPort()); Assert.assertEquals(EndpointType.UNDIR, ep.getEndpointType()); } } jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestGraphElementParser.java000066400000000000000000000164661276402340000333370ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.junit.Assert; import org.junit.Test; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.EdgeMetadata; import edu.uci.ics.jung.io.graphml.GraphMetadata; import edu.uci.ics.jung.io.graphml.NodeMetadata; import edu.uci.ics.jung.io.graphml.GraphMetadata.EdgeDefault; public class TestGraphElementParser extends AbstractParserTest { @Test(expected= GraphIOException.class) public void testNoEdgeDefault() throws Exception { String xml = ""; readObject(xml); } @Test public void testEdgeDefaultDirected() throws Exception { String xml = ""; GraphMetadata g = (GraphMetadata) readObject(xml); Assert.assertNotNull(g); Assert.assertEquals(EdgeDefault.DIRECTED, g.getEdgeDefault()); Assert.assertEquals(null, g.getId()); Assert.assertEquals(null, g.getDescription()); Assert.assertEquals(0, g.getNodeMap().size()); Assert.assertEquals(0, g.getEdgeMap().size()); Assert.assertEquals(0, g.getHyperEdgeMap().size()); } @Test public void testEdgeDefaultUndirected() throws Exception { String xml = ""; GraphMetadata g = (GraphMetadata) readObject(xml); Assert.assertNotNull(g); Assert.assertEquals(EdgeDefault.UNDIRECTED, g.getEdgeDefault()); Assert.assertEquals(null, g.getId()); Assert.assertEquals(null, g.getDescription()); Assert.assertEquals(0, g.getNodeMap().size()); Assert.assertEquals(0, g.getEdgeMap().size()); Assert.assertEquals(0, g.getHyperEdgeMap().size()); } @Test public void testDesc() throws Exception { String xml = "" + "hello world" + ""; GraphMetadata g = (GraphMetadata) readObject(xml); Assert.assertNotNull(g); Assert.assertEquals(EdgeDefault.UNDIRECTED, g.getEdgeDefault()); Assert.assertEquals(null, g.getId()); Assert.assertEquals("hello world", g.getDescription()); Assert.assertEquals(0, g.getNodeMap().size()); Assert.assertEquals(0, g.getEdgeMap().size()); Assert.assertEquals(0, g.getHyperEdgeMap().size()); } @Test public void testNodes() throws Exception { String xml = "" + "" + "" + "" + ""; GraphMetadata g = (GraphMetadata) readObject(xml); Assert.assertNotNull(g); Assert.assertEquals(EdgeDefault.UNDIRECTED, g.getEdgeDefault()); Assert.assertEquals(null, g.getId()); Assert.assertEquals(null, g.getDescription()); Assert.assertEquals(3, g.getNodeMap().size()); List nodes = new ArrayList(g.getNodeMap().values()); Collections.sort(nodes, new Comparator() { public int compare(NodeMetadata o1, NodeMetadata o2) { return o1.getId().compareTo(o2.getId()); } }); Assert.assertEquals("1", nodes.get(0).getId()); Assert.assertEquals("2", nodes.get(1).getId()); Assert.assertEquals("3", nodes.get(2).getId()); } @Test public void testEdges() throws Exception { String xml = "" + "" + "" + "" + "" + "" + ""; GraphMetadata g = (GraphMetadata) readObject(xml); Assert.assertNotNull(g); Assert.assertEquals(EdgeDefault.UNDIRECTED, g.getEdgeDefault()); Assert.assertEquals(null, g.getId()); Assert.assertEquals(null, g.getDescription()); List edges = new ArrayList(g.getEdgeMap().values()); Collections.sort(edges, new Comparator() { public int compare(EdgeMetadata o1, EdgeMetadata o2) { return o1.getSource().compareTo(o2.getSource()); } }); Assert.assertEquals(2, edges.size()); Assert.assertEquals("1", edges.get(0).getSource()); Assert.assertEquals("2", edges.get(1).getSource()); } @Test public void testHyperEdges() throws Exception { String xml = "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; GraphMetadata g = (GraphMetadata) readObject(xml); Assert.assertNotNull(g); Assert.assertEquals(EdgeDefault.UNDIRECTED, g.getEdgeDefault()); Assert.assertEquals(null, g.getId()); Assert.assertEquals(null, g.getDescription()); Assert.assertEquals(3, g.getHyperEdgeMap().size()); } @Test public void testUserAttributes() throws Exception { String xml = "" + ""; GraphMetadata g = (GraphMetadata) readObject(xml); Assert.assertNotNull(g); Assert.assertEquals(EdgeDefault.UNDIRECTED, g.getEdgeDefault()); Assert.assertEquals(null, g.getId()); Assert.assertEquals(null, g.getDescription()); Assert.assertEquals(1, g.getProperties().size()); Assert.assertEquals("abc123", g.getProperty("bob")); } @Test public void testData() throws Exception { String xml = "" + "value1" + "value2" + ""; GraphMetadata g = (GraphMetadata) readObject(xml); Assert.assertNotNull(g); Assert.assertEquals(EdgeDefault.UNDIRECTED, g.getEdgeDefault()); Assert.assertEquals(null, g.getId()); Assert.assertEquals(null, g.getDescription()); Assert.assertEquals(2, g.getProperties().size()); Assert.assertEquals("value1", g.getProperty("d1")); Assert.assertEquals("value2", g.getProperty("d2")); } } TestHyperEdgeElementParser.java000066400000000000000000000073501276402340000340630ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import org.junit.Assert; import org.junit.Test; import edu.uci.ics.jung.io.graphml.HyperEdgeMetadata; public class TestHyperEdgeElementParser extends AbstractParserTest { @Test public void testEmpty() throws Exception { String xml = ""; HyperEdgeMetadata edge = (HyperEdgeMetadata) readObject(xml); Assert.assertNotNull(edge); Assert.assertEquals(null, edge.getId()); Assert.assertEquals(null, edge.getDescription()); Assert.assertEquals(0, edge.getEndpoints().size()); } @Test public void testId() throws Exception { String xml = ""; HyperEdgeMetadata edge = (HyperEdgeMetadata) readObject(xml); Assert.assertNotNull(edge); Assert.assertEquals("e1", edge.getId()); Assert.assertEquals(null, edge.getDescription()); Assert.assertEquals(0, edge.getEndpoints().size()); } @Test public void testDesc() throws Exception { String xml = "" + "hello world" + ""; HyperEdgeMetadata edge = (HyperEdgeMetadata) readObject(xml); Assert.assertNotNull(edge); Assert.assertEquals(null, edge.getId()); Assert.assertEquals("hello world", edge.getDescription()); Assert.assertEquals(0, edge.getEndpoints().size()); } @Test public void testEndpoints() throws Exception { String xml = "" + "" + "" + "" + ""; HyperEdgeMetadata edge = (HyperEdgeMetadata) readObject(xml); Assert.assertNotNull(edge); Assert.assertEquals(null, edge.getId()); Assert.assertEquals(null, edge.getDescription()); Assert.assertEquals(3, edge.getEndpoints().size()); Assert.assertEquals("1", edge.getEndpoints().get(0).getNode()); Assert.assertEquals("2", edge.getEndpoints().get(1).getNode()); Assert.assertEquals("3", edge.getEndpoints().get(2).getNode()); } @Test public void testUserAttributes() throws Exception { String xml = ""; HyperEdgeMetadata edge = (HyperEdgeMetadata) readObject(xml); Assert.assertNotNull(edge); Assert.assertEquals(null, edge.getId()); Assert.assertEquals(null, edge.getDescription()); Assert.assertEquals(0, edge.getEndpoints().size()); Assert.assertEquals(1, edge.getProperties().size()); Assert.assertEquals("abc123", edge.getProperty("bob")); } @Test public void testData() throws Exception { String xml = "" + "value1" + "value2" + ""; HyperEdgeMetadata edge = (HyperEdgeMetadata) readObject(xml); Assert.assertNotNull(edge); Assert.assertEquals(null, edge.getId()); Assert.assertEquals(null, edge.getDescription()); Assert.assertEquals(0, edge.getEndpoints().size()); Assert.assertEquals(2, edge.getProperties().size()); Assert.assertEquals("value1", edge.getProperty("d1")); Assert.assertEquals("value2", edge.getProperty("d2")); } } jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestKeyElementParser.java000066400000000000000000000126001276402340000330100ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import org.junit.Assert; import org.junit.Test; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.Key; public class TestKeyElementParser extends AbstractParserTest { @Test(expected= GraphIOException.class) public void testNoId() throws Exception { String xml = ""; readObject(xml); } @Test public void testId() throws Exception { String xml = ""; Key key = (Key) readObject(xml); Assert.assertNotNull(key); Assert.assertEquals("d1", key.getId()); Assert.assertEquals(null, key.getDescription()); Assert.assertEquals(null, key.getDefaultValue()); Assert.assertEquals(null, key.getAttributeName()); Assert.assertEquals(null, key.getAttributeType()); Assert.assertEquals(Key.ForType.ALL, key.getForType()); } @Test public void testDesc() throws Exception { String xml = "" + "this is my key" + ""; Key key = (Key) readObject(xml); Assert.assertNotNull(key); Assert.assertEquals("d1", key.getId()); Assert.assertEquals("this is my key", key.getDescription()); Assert.assertEquals(null, key.getDefaultValue()); Assert.assertEquals(null, key.getAttributeName()); Assert.assertEquals(null, key.getAttributeType()); Assert.assertEquals(Key.ForType.ALL, key.getForType()); } @Test public void testDefault() throws Exception { String xml = "" + "yellow" + ""; Key key = (Key) readObject(xml); Assert.assertNotNull(key); Assert.assertEquals("d1", key.getId()); Assert.assertEquals(null, key.getDescription()); Assert.assertEquals("yellow", key.getDefaultValue()); Assert.assertEquals(null, key.getAttributeName()); Assert.assertEquals(null, key.getAttributeType()); Assert.assertEquals(Key.ForType.ALL, key.getForType()); } @Test public void testAttrNameType() throws Exception { String xml = "" + ""; Key key = (Key) readObject(xml); Assert.assertNotNull(key); Assert.assertEquals("d1", key.getId()); Assert.assertEquals(null, key.getDescription()); Assert.assertEquals(null, key.getDefaultValue()); Assert.assertEquals("myattr", key.getAttributeName()); Assert.assertEquals("double", key.getAttributeType()); Assert.assertEquals(Key.ForType.ALL, key.getForType()); } @Test public void testForNode() throws Exception { String xml = "" + ""; Key key = (Key) readObject(xml); Assert.assertNotNull(key); Assert.assertEquals("d1", key.getId()); Assert.assertEquals(null, key.getDescription()); Assert.assertEquals(null, key.getDefaultValue()); Assert.assertEquals(null, key.getAttributeName()); Assert.assertEquals(null, key.getAttributeType()); Assert.assertEquals(Key.ForType.NODE, key.getForType()); } @Test public void testForEdge() throws Exception { String xml = "" + ""; Key key = (Key) readObject(xml); Assert.assertNotNull(key); Assert.assertEquals("d1", key.getId()); Assert.assertEquals(null, key.getDescription()); Assert.assertEquals(null, key.getDefaultValue()); Assert.assertEquals(null, key.getAttributeName()); Assert.assertEquals(null, key.getAttributeType()); Assert.assertEquals(Key.ForType.EDGE, key.getForType()); } @Test public void testForGraph() throws Exception { String xml = "" + ""; Key key = (Key) readObject(xml); Assert.assertNotNull(key); Assert.assertEquals("d1", key.getId()); Assert.assertEquals(null, key.getDescription()); Assert.assertEquals(null, key.getDefaultValue()); Assert.assertEquals(null, key.getAttributeName()); Assert.assertEquals(null, key.getAttributeType()); Assert.assertEquals(Key.ForType.GRAPH, key.getForType()); } @Test public void testForAll() throws Exception { String xml = "" + ""; Key key = (Key) readObject(xml); Assert.assertNotNull(key); Assert.assertEquals("d1", key.getId()); Assert.assertEquals(null, key.getDescription()); Assert.assertEquals(null, key.getDefaultValue()); Assert.assertEquals(null, key.getAttributeName()); Assert.assertEquals(null, key.getAttributeType()); Assert.assertEquals(Key.ForType.ALL, key.getForType()); } } jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestNodeElementParser.java000066400000000000000000000102521276402340000331460ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import org.junit.Assert; import org.junit.Test; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.NodeMetadata; public class TestNodeElementParser extends AbstractParserTest { @Test(expected= GraphIOException.class) public void testNoId() throws Exception { String xml = ""; readObject(xml); } @Test public void testId() throws Exception { String xml = ""; NodeMetadata node = (NodeMetadata) readObject(xml); Assert.assertNotNull(node); Assert.assertEquals("1", node.getId()); Assert.assertEquals(null, node.getDescription()); Assert.assertEquals(0, node.getPorts().size()); } @Test public void testDesc() throws Exception { String xml = "" + "this is my node" + ""; NodeMetadata node = (NodeMetadata) readObject(xml); Assert.assertNotNull(node); Assert.assertEquals("1", node.getId()); Assert.assertEquals("this is my node", node.getDescription()); Assert.assertEquals(0, node.getPorts().size()); } @Test public void testPort() throws Exception { String xml = "" + "this is my node" + "" + "port 1" + "" + ""; NodeMetadata node = (NodeMetadata) readObject(xml); Assert.assertNotNull(node); Assert.assertEquals("1", node.getId()); Assert.assertEquals("this is my node", node.getDescription()); Assert.assertEquals(1, node.getPorts().size()); Assert.assertEquals("p1", node.getPorts().get(0).getName()); } @Test public void testMultiPort() throws Exception { String xml = "" + "this is my node" + "" + "" + "" + "" + ""; NodeMetadata node = (NodeMetadata) readObject(xml); Assert.assertNotNull(node); Assert.assertEquals("1", node.getId()); Assert.assertEquals("this is my node", node.getDescription()); Assert.assertEquals(4, node.getPorts().size()); Assert.assertEquals("p1", node.getPorts().get(0).getName()); Assert.assertEquals("p2", node.getPorts().get(1).getName()); Assert.assertEquals("p3", node.getPorts().get(2).getName()); Assert.assertEquals("p4", node.getPorts().get(3).getName()); } @Test public void testUserAttributes() throws Exception { String xml = ""; NodeMetadata node = (NodeMetadata) readObject(xml); Assert.assertNotNull(node); Assert.assertEquals("1", node.getId()); Assert.assertEquals(1, node.getProperties().size()); Assert.assertEquals("abc123", node.getProperty("bob")); Assert.assertEquals(0, node.getPorts().size()); } @Test public void testData() throws Exception { String xml = "" + "value1" + "value2" + ""; NodeMetadata node = (NodeMetadata) readObject(xml); Assert.assertNotNull(node); Assert.assertEquals("1", node.getId()); Assert.assertEquals(2, node.getProperties().size()); Assert.assertEquals("value1", node.getProperty("d1")); Assert.assertEquals("value2", node.getProperty("d2")); Assert.assertEquals(0, node.getPorts().size()); } } jung-jung-2.1.1/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestPortElementParser.java000066400000000000000000000046601276402340000332130ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.io.graphml.parser; import org.junit.Assert; import org.junit.Test; import edu.uci.ics.jung.io.GraphIOException; import edu.uci.ics.jung.io.graphml.PortMetadata; public class TestPortElementParser extends AbstractParserTest { @Test(expected= GraphIOException.class) public void testNoName() throws Exception { String xml = ""; readObject(xml); } @Test public void testName() throws Exception { String xml = ""; PortMetadata port = (PortMetadata) readObject(xml); Assert.assertNotNull(port); Assert.assertEquals("p1", port.getName()); Assert.assertEquals(null, port.getDescription()); } @Test public void testDesc() throws Exception { String xml = "" + "this is my port" + ""; PortMetadata port = (PortMetadata) readObject(xml); Assert.assertNotNull(port); Assert.assertEquals("p1", port.getName()); Assert.assertEquals("this is my port", port.getDescription()); } @Test public void testUserAttributes() throws Exception { String xml = ""; PortMetadata port = (PortMetadata) readObject(xml); Assert.assertNotNull(port); Assert.assertEquals("p1", port.getName()); Assert.assertEquals(1, port.getProperties().size()); Assert.assertEquals("abc123", port.getProperty("bob")); } @Test public void testData() throws Exception { String xml = "" + "value1" + "value2" + ""; PortMetadata port = (PortMetadata) readObject(xml); Assert.assertNotNull(port); Assert.assertEquals("p1", port.getName()); Assert.assertEquals(2, port.getProperties().size()); Assert.assertEquals("value1", port.getProperty("d1")); Assert.assertEquals("value2", port.getProperty("d2")); } } jung-jung-2.1.1/jung-io/src/test/resources/000077500000000000000000000000001276402340000205455ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/resources/attributes.graphml000066400000000000000000000024221276402340000243070ustar00rootroot00000000000000 yellow green blue red turquoise 1.0 1.0 2.0 1.1 jung-jung-2.1.1/jung-io/src/test/resources/edu/000077500000000000000000000000001276402340000213225ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/resources/edu/uci/000077500000000000000000000000001276402340000221025ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/resources/edu/uci/ics/000077500000000000000000000000001276402340000226605ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/resources/edu/uci/ics/jung/000077500000000000000000000000001276402340000236235ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/resources/edu/uci/ics/jung/io/000077500000000000000000000000001276402340000242325ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/resources/edu/uci/ics/jung/io/graphml/000077500000000000000000000000001276402340000256645ustar00rootroot00000000000000jung-jung-2.1.1/jung-io/src/test/resources/edu/uci/ics/jung/io/graphml/attributes.graphml000066400000000000000000000024221276402340000314260ustar00rootroot00000000000000 yellow green blue red turquoise 1.0 1.0 2.0 1.1 jung-jung-2.1.1/jung-io/src/test/resources/edu/uci/ics/jung/io/graphml/hyper.graphml000066400000000000000000000015411276402340000303700ustar00rootroot00000000000000 jung-jung-2.1.1/jung-io/src/test/resources/edu/uci/ics/jung/io/graphml/multigraph.graphml000066400000000000000000000036431276402340000314220ustar00rootroot00000000000000 yellow green blue red turquoise 1.0 1.0 2.0 1.1 jung-jung-2.1.1/jung-io/src/test/resources/hyper.graphml000066400000000000000000000015411276402340000232510ustar00rootroot00000000000000 jung-jung-2.1.1/jung-samples/000077500000000000000000000000001276402340000160225ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/pom.xml000066400000000000000000000055231276402340000173440ustar00rootroot00000000000000 4.0.0 net.sf.jung jung-parent 2.1.1 jung-samples JUNG - Samples Sample programs using JUNG. Nearly all JUNG capabilities are demonstrated here. Please study the source code for these examples prior to asking how to do something. net.sf.jung jung-api ${project.version} net.sf.jung jung-visualization ${project.version} net.sf.jung jung-graph-impl ${project.version} net.sf.jung jung-algorithms ${project.version} net.sf.jung jung-io ${project.version} maven-jar-plugin edu.uci.ics.jung.samples.VertexImageShaperDemo true assembly true maven-dependency-plugin copy-dependencies package copy-dependencies ${project.build.directory} jung-jung-2.1.1/jung-samples/simple.graphml000066400000000000000000000020161276402340000206660ustar00rootroot00000000000000 jung-jung-2.1.1/jung-samples/src/000077500000000000000000000000001276402340000166115ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/000077500000000000000000000000001276402340000175355ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/assembly/000077500000000000000000000000001276402340000213545ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/assembly/assembly.xml000066400000000000000000000005121276402340000237130ustar00rootroot00000000000000 dependencies zip false / false runtime jung-jung-2.1.1/jung-samples/src/main/java/000077500000000000000000000000001276402340000204565ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/java/edu/000077500000000000000000000000001276402340000212335ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/000077500000000000000000000000001276402340000220135ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/000077500000000000000000000000001276402340000225715ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/000077500000000000000000000000001276402340000235345ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/000077500000000000000000000000001276402340000252005ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/AddNodeDemo.java000066400000000000000000000141051276402340000301470ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on May 10, 2004 */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Timer; import java.util.TimerTask; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JRootPane; import com.google.common.base.Functions; import edu.uci.ics.jung.algorithms.layout.AbstractLayout; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.FRLayout2; import edu.uci.ics.jung.algorithms.layout.SpringLayout; import edu.uci.ics.jung.algorithms.layout.util.Relaxer; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.ObservableGraph; import edu.uci.ics.jung.graph.event.GraphEvent; import edu.uci.ics.jung.graph.event.GraphEventListener; import edu.uci.ics.jung.graph.util.Graphs; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.renderers.Renderer; /** * Demonstrates visualization of a graph being actively updated. * * @author danyelf */ public class AddNodeDemo extends javax.swing.JApplet { /** * */ private static final long serialVersionUID = -5345319851341875800L; private Graph g = null; private VisualizationViewer vv = null; private AbstractLayout layout = null; Timer timer; boolean done; protected JButton switchLayout; // public static final LengthFunction UNITLENGTHFUNCTION = new SpringLayout.UnitLengthFunction( // 100); public static final int EDGE_LENGTH = 100; @Override public void init() { //create a graph Graph ig = Graphs.synchronizedDirectedGraph(new DirectedSparseMultigraph()); ObservableGraph og = new ObservableGraph(ig); og.addGraphEventListener(new GraphEventListener() { public void handleGraphEvent(GraphEvent evt) { System.err.println("got "+evt); }}); this.g = og; //create a graphdraw layout = new FRLayout2(g); // ((FRLayout)layout).setMaxIterations(200); vv = new VisualizationViewer(layout, new Dimension(600,600)); JRootPane rp = this.getRootPane(); rp.putClientProperty("defeatSystemEventQueueCheck", Boolean.TRUE); getContentPane().setLayout(new BorderLayout()); getContentPane().setBackground(java.awt.Color.lightGray); getContentPane().setFont(new Font("Serif", Font.PLAIN, 12)); vv.getModel().getRelaxer().setSleepTime(500); vv.setGraphMouse(new DefaultModalGraphMouse()); vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); vv.setForeground(Color.white); getContentPane().add(vv); switchLayout = new JButton("Switch to SpringLayout"); switchLayout.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { Dimension d = new Dimension(600,600); if (switchLayout.getText().indexOf("Spring") > 0) { switchLayout.setText("Switch to FRLayout"); layout = new SpringLayout(g, Functions.constant(EDGE_LENGTH)); layout.setSize(d); vv.getModel().setGraphLayout(layout, d); } else { switchLayout.setText("Switch to SpringLayout"); layout = new FRLayout(g, d); vv.getModel().setGraphLayout(layout, d); } } }); getContentPane().add(switchLayout, BorderLayout.SOUTH); timer = new Timer(); } @Override public void start() { validate(); //set timer so applet will change timer.schedule(new RemindTask(), 1000, 1000); //subsequent rate vv.repaint(); } Integer v_prev = null; public void process() { try { if (g.getVertexCount() < 100) { layout.lock(true); //add a vertex Integer v1 = new Integer(g.getVertexCount()); Relaxer relaxer = vv.getModel().getRelaxer(); relaxer.pause(); g.addVertex(v1); System.err.println("added node " + v1); // wire it to some edges if (v_prev != null) { g.addEdge(g.getEdgeCount(), v_prev, v1); // let's connect to a random vertex, too! int rand = (int) (Math.random() * g.getVertexCount()); g.addEdge(g.getEdgeCount(), v1, rand); } v_prev = v1; layout.initialize(); relaxer.resume(); layout.lock(false); } else { done = true; } } catch (Exception e) { System.out.println(e); } } class RemindTask extends TimerTask { @Override public void run() { process(); if(done) cancel(); } } public static void main(String[] args) { AddNodeDemo and = new AddNodeDemo(); JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(and); and.init(); and.start(); frame.pack(); frame.setVisible(true); } }jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/AnimatingAddNodeDemo.java000066400000000000000000000205271276402340000320040ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.util.Timer; import java.util.TimerTask; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JRootPane; import com.google.common.base.Functions; import edu.uci.ics.jung.algorithms.layout.AbstractLayout; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.SpringLayout; import edu.uci.ics.jung.algorithms.layout.StaticLayout; import edu.uci.ics.jung.algorithms.layout.util.Relaxer; import edu.uci.ics.jung.algorithms.layout.util.VisRunner; import edu.uci.ics.jung.algorithms.util.IterativeContext; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.ObservableGraph; import edu.uci.ics.jung.graph.event.GraphEvent; import edu.uci.ics.jung.graph.event.GraphEventListener; import edu.uci.ics.jung.graph.util.Graphs; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.layout.LayoutTransition; import edu.uci.ics.jung.visualization.renderers.Renderer; import edu.uci.ics.jung.visualization.util.Animator; /** * A variation of AddNodeDemo that animates transitions between graph states. * * @author Tom Nelson */ public class AnimatingAddNodeDemo extends javax.swing.JApplet { /** * */ private static final long serialVersionUID = -5345319851341875800L; private Graph g = null; private VisualizationViewer vv = null; private AbstractLayout layout = null; Timer timer; boolean done; protected JButton switchLayout; public static final int EDGE_LENGTH = 100; @Override public void init() { //create a graph Graph ig = Graphs.synchronizedDirectedGraph(new DirectedSparseMultigraph()); ObservableGraph og = new ObservableGraph(ig); og.addGraphEventListener(new GraphEventListener() { public void handleGraphEvent(GraphEvent evt) { System.err.println("got "+evt); }}); this.g = og; //create a graphdraw layout = new FRLayout(g); layout.setSize(new Dimension(600,600)); Relaxer relaxer = new VisRunner((IterativeContext)layout); relaxer.stop(); relaxer.prerelax(); Layout staticLayout = new StaticLayout(g, layout); vv = new VisualizationViewer(staticLayout, new Dimension(600,600)); JRootPane rp = this.getRootPane(); rp.putClientProperty("defeatSystemEventQueueCheck", Boolean.TRUE); getContentPane().setLayout(new BorderLayout()); getContentPane().setBackground(java.awt.Color.lightGray); getContentPane().setFont(new Font("Serif", Font.PLAIN, 12)); vv.setGraphMouse(new DefaultModalGraphMouse()); vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); vv.setForeground(Color.white); vv.addComponentListener(new ComponentAdapter() { /** * @see java.awt.event.ComponentAdapter#componentResized(java.awt.event.ComponentEvent) */ @Override public void componentResized(ComponentEvent arg0) { super.componentResized(arg0); System.err.println("resized"); layout.setSize(arg0.getComponent().getSize()); }}); getContentPane().add(vv); switchLayout = new JButton("Switch to SpringLayout"); switchLayout.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { Dimension d = vv.getSize();//new Dimension(600,600); if (switchLayout.getText().indexOf("Spring") > 0) { switchLayout.setText("Switch to FRLayout"); layout = new SpringLayout(g, Functions.constant(EDGE_LENGTH)); layout.setSize(d); Relaxer relaxer = new VisRunner((IterativeContext)layout); relaxer.stop(); relaxer.prerelax(); StaticLayout staticLayout = new StaticLayout(g, layout); LayoutTransition lt = new LayoutTransition(vv, vv.getGraphLayout(), staticLayout); Animator animator = new Animator(lt); animator.start(); vv.repaint(); } else { switchLayout.setText("Switch to SpringLayout"); layout = new FRLayout(g, d); layout.setSize(d); Relaxer relaxer = new VisRunner((IterativeContext)layout); relaxer.stop(); relaxer.prerelax(); StaticLayout staticLayout = new StaticLayout(g, layout); LayoutTransition lt = new LayoutTransition(vv, vv.getGraphLayout(), staticLayout); Animator animator = new Animator(lt); animator.start(); vv.repaint(); } } }); getContentPane().add(switchLayout, BorderLayout.SOUTH); timer = new Timer(); } @Override public void start() { validate(); //set timer so applet will change timer.schedule(new RemindTask(), 1000, 1000); //subsequent rate vv.repaint(); } Integer v_prev = null; public void process() { vv.getRenderContext().getPickedVertexState().clear(); vv.getRenderContext().getPickedEdgeState().clear(); try { if (g.getVertexCount() < 100) { //add a vertex Integer v1 = new Integer(g.getVertexCount()); g.addVertex(v1); vv.getRenderContext().getPickedVertexState().pick(v1, true); // wire it to some edges if (v_prev != null) { Integer edge = g.getEdgeCount(); vv.getRenderContext().getPickedEdgeState().pick(edge, true); g.addEdge(edge, v_prev, v1); // let's connect to a random vertex, too! int rand = (int) (Math.random() * g.getVertexCount()); edge = g.getEdgeCount(); vv.getRenderContext().getPickedEdgeState().pick(edge, true); g.addEdge(edge, v1, rand); } v_prev = v1; layout.initialize(); Relaxer relaxer = new VisRunner((IterativeContext)layout); relaxer.stop(); relaxer.prerelax(); StaticLayout staticLayout = new StaticLayout(g, layout); LayoutTransition lt = new LayoutTransition(vv, vv.getGraphLayout(), staticLayout); Animator animator = new Animator(lt); animator.start(); // vv.getRenderContext().getMultiLayerTransformer().setToIdentity(); vv.repaint(); } else { done = true; } } catch (Exception e) { System.out.println(e); } } class RemindTask extends TimerTask { @Override public void run() { process(); if(done) cancel(); } } public static void main(String[] args) { AnimatingAddNodeDemo and = new AnimatingAddNodeDemo(); JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(and); and.init(); and.start(); frame.pack(); frame.setVisible(true); } }jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/AnnotationsDemo.java000066400000000000000000000174141276402340000311540ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.BorderFactory; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.TestGraphs; import edu.uci.ics.jung.visualization.DefaultVisualizationModel; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.VisualizationModel; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.VisualizationServer.Paintable; import edu.uci.ics.jung.visualization.annotations.AnnotatingGraphMousePlugin; import edu.uci.ics.jung.visualization.annotations.AnnotatingModalGraphMouse; import edu.uci.ics.jung.visualization.annotations.AnnotationControls; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.renderers.Renderer; /** * Demonstrates annotation of graph elements. * * @author Tom Nelson * */ @SuppressWarnings("serial") public class AnnotationsDemo extends JApplet { static final String instructions = ""+ "

    Instructions for Annotations

    "+ "

    The Annotation Controls allow you to select:"+ "

      "+ "
    • Shape"+ "
    • Color"+ "
    • Fill (or outline)"+ "
    • Above or below (UPPER/LOWER) the graph display"+ "
    "+ "

    Mouse Button one press starts a Shape,"+ "

    drag and release to complete."+ "

    Mouse Button three pops up an input dialog"+ "

    for text. This will create a text annotation."+ "

    You may use html for multi-line, etc."+ "

    You may even use an image tag and image url"+ "

    to put an image in the annotation."+ "

    "+ "

    To remove an annotation, shift-click on it"+ "

    in the Annotations mode."+ "

    If there is overlap, the Annotation with center"+ "

    closest to the mouse point will be removed."; JDialog helpDialog; Paintable viewGrid; /** * create an instance of a simple graph in two views with controls to * demo the features. * */ public AnnotationsDemo() { // create a simple graph for the demo Graph graph = TestGraphs.getOneComponentGraph(); // the preferred sizes for the two views Dimension preferredSize1 = new Dimension(600,600); // create one layout for the graph FRLayout layout = new FRLayout(graph); layout.setMaxIterations(500); VisualizationModel vm = new DefaultVisualizationModel(layout, preferredSize1); // create 2 views that share the same model final VisualizationViewer vv = new VisualizationViewer(vm, preferredSize1); vv.setBackground(Color.white); vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv.getPickedEdgeState(), Color.black, Color.cyan)); vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(vv.getPickedVertexState(), Color.red, Color.yellow)); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR); // add default listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); Container content = getContentPane(); Container panel = new JPanel(new BorderLayout()); GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv); panel.add(gzsp); helpDialog = new JDialog(); helpDialog.getContentPane().add(new JLabel(instructions)); RenderContext rc = vv.getRenderContext(); AnnotatingGraphMousePlugin annotatingPlugin = new AnnotatingGraphMousePlugin(rc); // create a GraphMouse for the main view // final AnnotatingModalGraphMouse graphMouse = new AnnotatingModalGraphMouse(rc, annotatingPlugin); vv.setGraphMouse(graphMouse); vv.addKeyListener(graphMouse.getModeKeyListener()); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JComboBox modeBox = graphMouse.getModeComboBox(); modeBox.setSelectedItem(ModalGraphMouse.Mode.ANNOTATING); JButton help = new JButton("Help"); help.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { helpDialog.pack(); helpDialog.setVisible(true); } }); JPanel controls = new JPanel(); JPanel zoomControls = new JPanel(); zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom")); zoomControls.add(plus); zoomControls.add(minus); controls.add(zoomControls); JPanel modeControls = new JPanel(); modeControls.setBorder(BorderFactory.createTitledBorder("Mouse Mode")); modeControls.add(graphMouse.getModeComboBox()); controls.add(modeControls); JPanel annotationControlPanel = new JPanel(); annotationControlPanel.setBorder(BorderFactory.createTitledBorder("Annotation Controls")); AnnotationControls annotationControls = new AnnotationControls(annotatingPlugin); annotationControlPanel.add(annotationControls.getAnnotationsToolBar()); controls.add(annotationControlPanel); JPanel helpControls = new JPanel(); helpControls.setBorder(BorderFactory.createTitledBorder("Help")); helpControls.add(help); controls.add(helpControls); content.add(panel); content.add(controls, BorderLayout.SOUTH); } public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new AnnotationsDemo()); f.pack(); f.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/BalloonLayoutDemo.java000066400000000000000000000271611276402340000314430ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Paint; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import javax.swing.BorderFactory; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JToggleButton; import com.google.common.base.Functions; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.layout.BalloonLayout; import edu.uci.ics.jung.algorithms.layout.TreeLayout; import edu.uci.ics.jung.graph.DelegateForest; import edu.uci.ics.jung.graph.DelegateTree; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Forest; import edu.uci.ics.jung.graph.Tree; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationServer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalLensGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.layout.LayoutTransition; import edu.uci.ics.jung.visualization.transform.LensSupport; import edu.uci.ics.jung.visualization.transform.MutableTransformer; import edu.uci.ics.jung.visualization.transform.MutableTransformerDecorator; import edu.uci.ics.jung.visualization.transform.shape.HyperbolicShapeTransformer; import edu.uci.ics.jung.visualization.transform.shape.ViewLensSupport; import edu.uci.ics.jung.visualization.util.Animator; /** * Demonstrates the visualization of a Tree using TreeLayout * and BalloonLayout. An examiner lens performing a hyperbolic * transformation of the view is also included. * * @author Tom Nelson * */ @SuppressWarnings("serial") public class BalloonLayoutDemo extends JApplet { /** * the graph */ Forest graph; Supplier> graphFactory = new Supplier>() { public DirectedGraph get() { return new DirectedSparseMultigraph(); } }; Supplier> treeFactory = new Supplier> () { public Tree get() { return new DelegateTree(graphFactory); } }; Supplier edgeFactory = new Supplier() { int i=0; public Integer get() { return i++; }}; Supplier vertexFactory = new Supplier() { int i=0; public String get() { return "V"+i++; }}; /** * the visual component and renderer for the graph */ VisualizationViewer vv; VisualizationServer.Paintable rings; String root; TreeLayout layout; BalloonLayout radialLayout; /** * provides a Hyperbolic lens for the view */ LensSupport hyperbolicViewSupport; public BalloonLayoutDemo() { // create a simple graph for the demo graph = new DelegateForest(); createTree(); layout = new TreeLayout(graph); radialLayout = new BalloonLayout(graph); radialLayout.setSize(new Dimension(900,900)); vv = new VisualizationViewer(layout, new Dimension(600,600)); vv.setBackground(Color.white); vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.quadCurve(graph)); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); vv.getRenderContext().setArrowFillPaintTransformer(Functions.constant(Color.lightGray)); rings = new Rings(radialLayout); Container content = getContentPane(); final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); content.add(panel); final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); vv.addKeyListener(graphMouse.getModeKeyListener()); hyperbolicViewSupport = new ViewLensSupport(vv, new HyperbolicShapeTransformer(vv, vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)), new ModalLensGraphMouse()); graphMouse.addItemListener(hyperbolicViewSupport.getGraphMouse().getModeListener()); JComboBox modeBox = graphMouse.getModeComboBox(); modeBox.addItemListener(graphMouse.getModeListener()); graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING); final ScalingControl scaler = new CrossoverScalingControl(); vv.scaleToLayout(scaler); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JToggleButton radial = new JToggleButton("Balloon"); radial.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { LayoutTransition lt = new LayoutTransition(vv, layout, radialLayout); Animator animator = new Animator(lt); animator.start(); vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).setToIdentity(); vv.addPreRenderPaintable(rings); } else { LayoutTransition lt = new LayoutTransition(vv, radialLayout, layout); Animator animator = new Animator(lt); animator.start(); vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).setToIdentity(); vv.removePreRenderPaintable(rings); } vv.repaint(); }}); final JRadioButton hyperView = new JRadioButton("Hyperbolic View"); hyperView.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { hyperbolicViewSupport.activate(e.getStateChange() == ItemEvent.SELECTED); } }); JPanel scaleGrid = new JPanel(new GridLayout(1,0)); scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom")); JPanel controls = new JPanel(); scaleGrid.add(plus); scaleGrid.add(minus); controls.add(radial); controls.add(scaleGrid); controls.add(modeBox); controls.add(hyperView); content.add(controls, BorderLayout.SOUTH); } class Rings implements VisualizationServer.Paintable { BalloonLayout layout; public Rings(BalloonLayout layout) { this.layout = layout; } public void paint(Graphics g) { g.setColor(Color.gray); Graphics2D g2d = (Graphics2D)g; Ellipse2D ellipse = new Ellipse2D.Double(); for(String v : layout.getGraph().getVertices()) { Double radius = layout.getRadii().get(v); if(radius == null) continue; Point2D p = layout.apply(v); ellipse.setFrame(-radius, -radius, 2*radius, 2*radius); AffineTransform at = AffineTransform.getTranslateInstance(p.getX(), p.getY()); Shape shape = at.createTransformedShape(ellipse); MutableTransformer viewTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW); if(viewTransformer instanceof MutableTransformerDecorator) { shape = vv.getRenderContext().getMultiLayerTransformer().transform(shape); } else { shape = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT,shape); } g2d.draw(shape); } } public boolean useTransform() { return true; } } /** * */ private void createTree() { graph.addVertex("A0"); graph.addEdge(edgeFactory.get(), "A0", "B0"); graph.addEdge(edgeFactory.get(), "A0", "B1"); graph.addEdge(edgeFactory.get(), "A0", "B2"); graph.addEdge(edgeFactory.get(), "B0", "C0"); graph.addEdge(edgeFactory.get(), "B0", "C1"); graph.addEdge(edgeFactory.get(), "B0", "C2"); graph.addEdge(edgeFactory.get(), "B0", "C3"); graph.addEdge(edgeFactory.get(), "C2", "H0"); graph.addEdge(edgeFactory.get(), "C2", "H1"); graph.addEdge(edgeFactory.get(), "B1", "D0"); graph.addEdge(edgeFactory.get(), "B1", "D1"); graph.addEdge(edgeFactory.get(), "B1", "D2"); graph.addEdge(edgeFactory.get(), "B2", "E0"); graph.addEdge(edgeFactory.get(), "B2", "E1"); graph.addEdge(edgeFactory.get(), "B2", "E2"); graph.addEdge(edgeFactory.get(), "D0", "F0"); graph.addEdge(edgeFactory.get(), "D0", "F1"); graph.addEdge(edgeFactory.get(), "D0", "F2"); graph.addEdge(edgeFactory.get(), "D1", "G0"); graph.addEdge(edgeFactory.get(), "D1", "G1"); graph.addEdge(edgeFactory.get(), "D1", "G2"); graph.addEdge(edgeFactory.get(), "D1", "G3"); graph.addEdge(edgeFactory.get(), "D1", "G4"); graph.addEdge(edgeFactory.get(), "D1", "G5"); graph.addEdge(edgeFactory.get(), "D1", "G6"); graph.addEdge(edgeFactory.get(), "D1", "G7"); // uncomment this to make it a Forest: // graph.addVertex("K0"); // graph.addEdge(edgeFactory.get(), "K0", "K1"); // graph.addEdge(edgeFactory.get(), "K0", "K2"); // graph.addEdge(edgeFactory.get(), "K0", "K3"); // // graph.addVertex("J0"); // graph.addEdge(edgeFactory.get(), "J0", "J1"); // graph.addEdge(edgeFactory.get(), "J0", "J2"); // graph.addEdge(edgeFactory.get(), "J1", "J4"); // graph.addEdge(edgeFactory.get(), "J2", "J3"); //// graph.addEdge(edgeFactory.get(), "J2", "J5"); //// graph.addEdge(edgeFactory.get(), "J4", "J6"); //// graph.addEdge(edgeFactory.get(), "J4", "J7"); //// graph.addEdge(edgeFactory.get(), "J3", "J8"); //// graph.addEdge(edgeFactory.get(), "J6", "B9"); } public static void main(String[] args) { JFrame frame = new JFrame(); Container content = frame.getContentPane(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); content.add(new BalloonLayoutDemo()); frame.pack(); frame.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/ClusteringDemo.java000066400000000000000000000257111276402340000307750ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.samples; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.Paint; import java.awt.Stroke; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.Point2D; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.JToggleButton; import javax.swing.border.TitledBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Supplier; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import edu.uci.ics.jung.algorithms.cluster.EdgeBetweennessClusterer; import edu.uci.ics.jung.algorithms.layout.AggregateLayout; import edu.uci.ics.jung.algorithms.layout.CircleLayout; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.util.Relaxer; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseMultigraph; import edu.uci.ics.jung.io.PajekNetReader; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; /** * This simple app demonstrates how one can use our algorithms and visualization libraries in unison. * In this case, we generate use the Zachary karate club data set, widely known in the social networks literature, then * we cluster the vertices using an edge-betweenness clusterer, and finally we visualize the graph using * Fruchtermain-Rheingold layout and provide a slider so that the user can adjust the clustering granularity. * @author Scott White */ @SuppressWarnings("serial") public class ClusteringDemo extends JApplet { VisualizationViewer vv; LoadingCache vertexPaints = CacheBuilder.newBuilder().build( CacheLoader.from(Functions.constant(Color.white))); LoadingCache edgePaints = CacheBuilder.newBuilder().build( CacheLoader.from(Functions.constant(Color.blue))); public final Color[] similarColors = { new Color(216, 134, 134), new Color(135, 137, 211), new Color(134, 206, 189), new Color(206, 176, 134), new Color(194, 204, 134), new Color(145, 214, 134), new Color(133, 178, 209), new Color(103, 148, 255), new Color(60, 220, 220), new Color(30, 250, 100) }; public static void main(String[] args) throws IOException { ClusteringDemo cd = new ClusteringDemo(); cd.start(); // Add a restart button so the graph can be redrawn to fit the size of the frame JFrame jf = new JFrame(); jf.getContentPane().add(cd); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.pack(); jf.setVisible(true); } public void start() { InputStream is = this.getClass().getClassLoader().getResourceAsStream("datasets/zachary.net"); BufferedReader br = new BufferedReader( new InputStreamReader( is )); try { setUpView(br); } catch (IOException e) { System.out.println("Error in loading graph"); e.printStackTrace(); } } private void setUpView(BufferedReader br) throws IOException { Supplier vertexFactory = new Supplier() { int n = 0; public Number get() { return n++; } }; Supplier edgeFactory = new Supplier() { int n = 0; public Number get() { return n++; } }; PajekNetReader, Number,Number> pnr = new PajekNetReader, Number,Number>(vertexFactory, edgeFactory); final Graph graph = new SparseMultigraph(); pnr.load(br, graph); //Create a simple layout frame //specify the Fruchterman-Rheingold layout algorithm final AggregateLayout layout = new AggregateLayout(new FRLayout(graph)); vv = new VisualizationViewer(layout); vv.setBackground( Color.white ); //Tell the renderer to use our own customized color rendering vv.getRenderContext().setVertexFillPaintTransformer(vertexPaints); vv.getRenderContext().setVertexDrawPaintTransformer(new Function() { public Paint apply(Number v) { if(vv.getPickedVertexState().isPicked(v)) { return Color.cyan; } else { return Color.BLACK; } } }); vv.getRenderContext().setEdgeDrawPaintTransformer(edgePaints); vv.getRenderContext().setEdgeStrokeTransformer(new Function() { protected final Stroke THIN = new BasicStroke(1); protected final Stroke THICK= new BasicStroke(2); public Stroke apply(Number e) { Paint c = edgePaints.getUnchecked(e); if (c == Color.LIGHT_GRAY) return THIN; else return THICK; } }); //add restart button JButton scramble = new JButton("Restart"); scramble.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { Layout layout = vv.getGraphLayout(); layout.initialize(); Relaxer relaxer = vv.getModel().getRelaxer(); if(relaxer != null) { relaxer.stop(); relaxer.prerelax(); relaxer.relax(); } } }); DefaultModalGraphMouse gm = new DefaultModalGraphMouse(); vv.setGraphMouse(gm); final JToggleButton groupVertices = new JToggleButton("Group Clusters"); //Create slider to adjust the number of edges to remove when clustering final JSlider edgeBetweennessSlider = new JSlider(JSlider.HORIZONTAL); edgeBetweennessSlider.setBackground(Color.WHITE); edgeBetweennessSlider.setPreferredSize(new Dimension(210, 50)); edgeBetweennessSlider.setPaintTicks(true); edgeBetweennessSlider.setMaximum(graph.getEdgeCount()); edgeBetweennessSlider.setMinimum(0); edgeBetweennessSlider.setValue(0); edgeBetweennessSlider.setMajorTickSpacing(10); edgeBetweennessSlider.setPaintLabels(true); edgeBetweennessSlider.setPaintTicks(true); // edgeBetweennessSlider.setBorder(BorderFactory.createLineBorder(Color.black)); //TO DO: edgeBetweennessSlider.add(new JLabel("Node Size (PageRank With Priors):")); //I also want the slider value to appear final JPanel eastControls = new JPanel(); eastControls.setOpaque(true); eastControls.setLayout(new BoxLayout(eastControls, BoxLayout.Y_AXIS)); eastControls.add(Box.createVerticalGlue()); eastControls.add(edgeBetweennessSlider); final String COMMANDSTRING = "Edges removed for clusters: "; final String eastSize = COMMANDSTRING + edgeBetweennessSlider.getValue(); final TitledBorder sliderBorder = BorderFactory.createTitledBorder(eastSize); eastControls.setBorder(sliderBorder); //eastControls.add(eastSize); eastControls.add(Box.createVerticalGlue()); groupVertices.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { clusterAndRecolor(layout, edgeBetweennessSlider.getValue(), similarColors, e.getStateChange() == ItemEvent.SELECTED); vv.repaint(); }}); clusterAndRecolor(layout, 0, similarColors, groupVertices.isSelected()); edgeBetweennessSlider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { JSlider source = (JSlider) e.getSource(); if (!source.getValueIsAdjusting()) { int numEdgesToRemove = source.getValue(); clusterAndRecolor(layout, numEdgesToRemove, similarColors, groupVertices.isSelected()); sliderBorder.setTitle( COMMANDSTRING + edgeBetweennessSlider.getValue()); eastControls.repaint(); vv.validate(); vv.repaint(); } } }); Container content = getContentPane(); content.add(new GraphZoomScrollPane(vv)); JPanel south = new JPanel(); JPanel grid = new JPanel(new GridLayout(2,1)); grid.add(scramble); grid.add(groupVertices); south.add(grid); south.add(eastControls); JPanel p = new JPanel(); p.setBorder(BorderFactory.createTitledBorder("Mouse Mode")); p.add(gm.getModeComboBox()); south.add(p); content.add(south, BorderLayout.SOUTH); } public void clusterAndRecolor(AggregateLayout layout, int numEdgesToRemove, Color[] colors, boolean groupClusters) { //Now cluster the vertices by removing the top 50 edges with highest betweenness // if (numEdgesToRemove == 0) { // colorCluster( g.getVertices(), colors[0] ); // } else { Graph g = layout.getGraph(); layout.removeAll(); EdgeBetweennessClusterer clusterer = new EdgeBetweennessClusterer(numEdgesToRemove); Set> clusterSet = clusterer.apply(g); List edges = clusterer.getEdgesRemoved(); int i = 0; //Set the colors of each node so that each cluster's vertices have the same color for (Iterator> cIt = clusterSet.iterator(); cIt.hasNext();) { Set vertices = cIt.next(); Color c = colors[i % colors.length]; colorCluster(vertices, c); if(groupClusters == true) { groupCluster(layout, vertices); } i++; } for (Number e : g.getEdges()) { if (edges.contains(e)) { edgePaints.put(e, Color.lightGray); } else { edgePaints.put(e, Color.black); } } } private void colorCluster(Set vertices, Color c) { for (Number v : vertices) { vertexPaints.put(v, c); } } private void groupCluster(AggregateLayout layout, Set vertices) { if(vertices.size() < layout.getGraph().getVertexCount()) { Point2D center = layout.apply(vertices.iterator().next()); Graph subGraph = SparseMultigraph.getFactory().get(); for(Number v : vertices) { subGraph.addVertex(v); } Layout subLayout = new CircleLayout(subGraph); subLayout.setInitializer(vv.getGraphLayout()); subLayout.setSize(new Dimension(40,40)); layout.put(subLayout,center); vv.repaint(); } } } DemoLensVertexImageShaperDemo.java000066400000000000000000000372321276402340000336120ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Paint; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.HashMap; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JPanel; import javax.swing.JRadioButton; import com.google.common.base.Function; import com.google.common.base.Functions; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.graph.DirectedSparseGraph; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.LayeredIcon; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.decorators.VertexIconShapeTransformer; import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.renderers.Checkmark; import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer; import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer; import edu.uci.ics.jung.visualization.transform.LayoutLensSupport; import edu.uci.ics.jung.visualization.transform.LensSupport; import edu.uci.ics.jung.visualization.transform.shape.MagnifyImageLensSupport; /** * Demonstrates the use of images to represent graph vertices. * The images are added to the DefaultGraphLabelRenderer and can * either be offset from the vertex, or centered on the vertex. * Additionally, the relative positioning of the label and * image is controlled by subclassing the DefaultGraphLabelRenderer * and setting the appropriate properties on its JLabel superclass * FancyGraphLabelRenderer * * The images used in this demo (courtesy of slashdot.org) are * rectangular but with a transparent background. When vertices * are represented by these images, it looks better if the actual * shape of the opaque part of the image is computed so that the * edge arrowheads follow the visual shape of the image. This demo * uses the FourPassImageShaper class to compute the Shape from * an image with transparent background. * * @author Tom Nelson * */ public class DemoLensVertexImageShaperDemo extends JApplet { /** * */ private static final long serialVersionUID = 5432239991020505763L; /** * the graph */ DirectedSparseGraph graph; /** * the visual component and renderer for the graph */ VisualizationViewer vv; /** * some icon names to use */ String[] iconNames = { "sample2" }; LensSupport viewSupport; LensSupport modelSupport; LensSupport magnifyLayoutSupport; LensSupport magnifyViewSupport; /** * create an instance of a simple graph with controls to * demo the zoom features. * */ public DemoLensVertexImageShaperDemo() { // create a simple graph for the demo graph = new DirectedSparseGraph(); Number[] vertices = createVertices(1); // a Map for the labels Map map = new HashMap(); for(int i=0; i iconMap = new HashMap(); for(int i=0; i layout = new FRLayout(graph); layout.setMaxIterations(100); vv = new VisualizationViewer(layout, new Dimension(600,600)); Function vpf = new PickableVertexPaintTransformer(vv.getPickedVertexState(), Color.white, Color.yellow); vv.getRenderContext().setVertexFillPaintTransformer(vpf); vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv.getPickedEdgeState(), Color.black, Color.cyan)); vv.setBackground(Color.white); final Function vertexStringerImpl = new VertexStringerImpl(map); vv.getRenderContext().setVertexLabelTransformer(vertexStringerImpl); vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.cyan)); vv.getRenderContext().setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.cyan)); // features on and off. For a real application, use VertexIconAndShapeFunction instead. final VertexIconShapeTransformer vertexImageShapeFunction = new VertexIconShapeTransformer(new EllipseVertexShapeTransformer()); vertexImageShapeFunction.setIconMap(iconMap); final Function vertexIconFunction = Functions.forMap(iconMap); vv.getRenderContext().setVertexShapeTransformer(vertexImageShapeFunction); vv.getRenderContext().setVertexIconTransformer(vertexIconFunction); // Get the pickedState and add a listener that will decorate the // Vertex images with a checkmark icon when they are picked PickedState ps = vv.getPickedVertexState(); ps.addItemListener(new PickWithIconListener(vertexIconFunction)); vv.addPostRenderPaintable(new VisualizationViewer.Paintable(){ int x; int y; Font font; FontMetrics metrics; int swidth; int sheight; String str = "Thank You, slashdot.org, for the images!"; public void paint(Graphics g) { Dimension d = vv.getSize(); if(font == null) { font = new Font(g.getFont().getName(), Font.BOLD, 20); metrics = g.getFontMetrics(font); swidth = metrics.stringWidth(str); sheight = metrics.getMaxAscent()+metrics.getMaxDescent(); x = (d.width-swidth)/2; y = (int)(d.height-sheight*1.5); } g.setFont(font); Color oldColor = g.getColor(); g.setColor(Color.lightGray); g.drawString(str, x, y); g.setColor(oldColor); } public boolean useTransform() { return false; } }); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); Container content = getContentPane(); final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); content.add(panel); final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JComboBox modeBox = graphMouse.getModeComboBox(); JPanel modePanel = new JPanel(); modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode")); modePanel.add(modeBox); JPanel scaleGrid = new JPanel(new GridLayout(1,0)); scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom")); JPanel controls = new JPanel(); scaleGrid.add(plus); scaleGrid.add(minus); controls.add(scaleGrid); controls.add(modePanel); content.add(controls, BorderLayout.SOUTH); this.viewSupport = new MagnifyImageLensSupport(vv); // new ViewLensSupport(vv, new HyperbolicShapeTransformer(vv, // vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)), // new ModalLensGraphMouse()); this.modelSupport = new LayoutLensSupport(vv); graphMouse.addItemListener(modelSupport.getGraphMouse().getModeListener()); graphMouse.addItemListener(viewSupport.getGraphMouse().getModeListener()); ButtonGroup radio = new ButtonGroup(); JRadioButton none = new JRadioButton("None"); none.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { if(viewSupport != null) { viewSupport.deactivate(); } if(modelSupport != null) { modelSupport.deactivate(); } } }); none.setSelected(true); JRadioButton hyperView = new JRadioButton("View"); hyperView.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { viewSupport.activate(e.getStateChange() == ItemEvent.SELECTED); } }); JRadioButton hyperModel = new JRadioButton("Layout"); hyperModel.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { modelSupport.activate(e.getStateChange() == ItemEvent.SELECTED); } }); radio.add(none); radio.add(hyperView); radio.add(hyperModel); JMenuBar menubar = new JMenuBar(); JMenu modeMenu = graphMouse.getModeMenu(); menubar.add(modeMenu); JPanel lensPanel = new JPanel(new GridLayout(2,0)); lensPanel.setBorder(BorderFactory.createTitledBorder("Lens")); lensPanel.add(none); lensPanel.add(hyperView); lensPanel.add(hyperModel); controls.add(lensPanel); } /** * A simple implementation of VertexStringer that * gets Vertex labels from a Map * * @author Tom Nelson * * */ class VertexStringerImpl implements Function { Map map = new HashMap(); boolean enabled = true; public VertexStringerImpl(Map map) { this.map = map; } /** * @see edu.uci.ics.jung.graph.decorators.VertexStringer#getLabel(edu.uci.ics.jung.graph.Vertex) */ public String apply(V v) { if(isEnabled()) { return map.get(v); } else { return ""; } } /** * @return Returns the enabled. */ public boolean isEnabled() { return enabled; } /** * @param enabled The enabled to set. */ public void setEnabled(boolean enabled) { this.enabled = enabled; } } /** * create some vertices * @param count how many to create * @return the Vertices in an array */ private Number[] createVertices(int count) { Number[] v = new Number[count]; for (int i = 0; i < count; i++) { v[i] = new Integer(i); graph.addVertex(v[i]); } return v; } /** * create edges for this demo graph * @param v an array of Vertices to connect */ void createEdges(Number[] v) { // graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[3], v[0], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[0], v[4], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[4], v[5], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[5], v[3], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[2], v[1], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[4], v[1], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[8], v[2], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[3], v[8], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[6], v[7], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[7], v[5], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[0], v[9], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[9], v[8], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[7], v[6], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[6], v[5], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[4], v[2], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[5], v[4], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[4], v[10], EdgeType.DIRECTED); // graph.addEdge(new Double(Math.random()), v[10], v[4], EdgeType.DIRECTED); } public static class PickWithIconListener implements ItemListener { Function imager; Icon checked; public PickWithIconListener(Function imager) { this.imager = imager; checked = new Checkmark(Color.red); } public void itemStateChanged(ItemEvent e) { Icon icon = imager.apply((Number)e.getItem()); if(icon != null && icon instanceof LayeredIcon) { if(e.getStateChange() == ItemEvent.SELECTED) { ((LayeredIcon)icon).add(checked); } else { ((LayeredIcon)icon).remove(checked); } } } } public static void main(String[] args) { JFrame frame = new JFrame(); Container content = frame.getContentPane(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); content.add(new DemoLensVertexImageShaperDemo()); frame.pack(); frame.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/DrawnIconVertexDemo.java000066400000000000000000000156051276402340000317410ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.graph.DirectedSparseGraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer; import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer; /** * A demo that shows drawn Icons as vertices * * @author Tom Nelson * */ public class DrawnIconVertexDemo { /** * the graph */ Graph graph; /** * the visual component and renderer for the graph */ VisualizationViewer vv; public DrawnIconVertexDemo() { // create a simple graph for the demo graph = new DirectedSparseGraph(); Integer[] v = createVertices(10); createEdges(v); vv = new VisualizationViewer(new FRLayout(graph)); vv.getRenderContext().setVertexLabelTransformer(new Function(){ public String apply(Integer v) { return "Vertex "+v; }}); vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.cyan)); vv.getRenderContext().setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.cyan)); vv.getRenderContext().setVertexIconTransformer(new Function() { /* * Implements the Icon interface to draw an Icon with background color and * a text label */ public Icon apply(final Integer v) { return new Icon() { public int getIconHeight() { return 20; } public int getIconWidth() { return 20; } public void paintIcon(Component c, Graphics g, int x, int y) { if(vv.getPickedVertexState().isPicked(v)) { g.setColor(Color.yellow); } else { g.setColor(Color.red); } g.fillOval(x, y, 20, 20); if(vv.getPickedVertexState().isPicked(v)) { g.setColor(Color.black); } else { g.setColor(Color.white); } g.drawString(""+v, x+6, y+15); }}; }}); vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(vv.getPickedVertexState(), Color.white, Color.yellow)); vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv.getPickedEdgeState(), Color.black, Color.lightGray)); vv.setBackground(Color.white); // add my listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); // create a frome to hold the graph final JFrame frame = new JFrame(); Container content = frame.getContentPane(); final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); content.add(panel); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final DefaultModalGraphMouse gm = new DefaultModalGraphMouse(); vv.setGraphMouse(gm); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JPanel controls = new JPanel(); controls.add(plus); controls.add(minus); controls.add(gm.getModeComboBox()); content.add(controls, BorderLayout.SOUTH); frame.pack(); frame.setVisible(true); } /** * create some vertices * @param count how many to create * @return the Vertices in an array */ private Integer[] createVertices(int count) { Integer[] v = new Integer[count]; for (int i = 0; i < count; i++) { v[i] = new Integer(i); graph.addVertex(v[i]); } return v; } /** * create edges for this demo graph * @param v an array of Vertices to connect */ void createEdges(Integer[] v) { graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[0], v[3], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[0], v[4], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[4], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[3], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[1], v[2], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[1], v[4], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[8], v[2], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[3], v[8], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[6], v[7], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[7], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[0], v[9], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[9], v[8], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[7], v[6], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[6], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[4], v[2], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[5], v[4], EdgeType.DIRECTED); } public static void main(String[] args) { new DrawnIconVertexDemo(); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/EdgeLabelDemo.java000066400000000000000000000324521276402340000304620ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import javax.swing.AbstractButton; import javax.swing.BorderFactory; import javax.swing.BoundedRangeModel; import javax.swing.Box; import javax.swing.ButtonGroup; import javax.swing.DefaultBoundedRangeModel; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JSlider; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.layout.CircleLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseMultigraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.ParallelEdgeShapeTransformer; import edu.uci.ics.jung.visualization.decorators.ConstantDirectionalEdgeValueTransformer; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.renderers.EdgeLabelRenderer; import edu.uci.ics.jung.visualization.renderers.VertexLabelRenderer; /** * Demonstrates jung support for drawing edge labels that * can be positioned at any point along the edge, and can * be rotated to be parallel with the edge. * * @author Tom Nelson * */ public class EdgeLabelDemo extends JApplet { private static final long serialVersionUID = -6077157664507049647L; /** * the graph */ Graph graph; /** * the visual component and renderer for the graph */ VisualizationViewer vv; /** */ VertexLabelRenderer vertexLabelRenderer; EdgeLabelRenderer edgeLabelRenderer; ScalingControl scaler = new CrossoverScalingControl(); /** * create an instance of a simple graph with controls to * demo the label positioning features * */ @SuppressWarnings("serial") public EdgeLabelDemo() { // create a simple graph for the demo graph = new SparseMultigraph(); Integer[] v = createVertices(3); createEdges(v); Layout layout = new CircleLayout(graph); vv = new VisualizationViewer(layout, new Dimension(600,400)); vv.setBackground(Color.white); vertexLabelRenderer = vv.getRenderContext().getVertexLabelRenderer(); edgeLabelRenderer = vv.getRenderContext().getEdgeLabelRenderer(); Function stringer = new Function(){ public String apply(Number e) { return "Edge:"+graph.getEndpoints(e).toString(); } }; vv.getRenderContext().setEdgeLabelTransformer(stringer); vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv.getPickedEdgeState(), Color.black, Color.cyan)); vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(vv.getPickedVertexState(), Color.red, Color.yellow)); // add my listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); // create a frome to hold the graph final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); Container content = getContentPane(); content.add(panel); final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); ButtonGroup radio = new ButtonGroup(); JRadioButton lineButton = new JRadioButton("Line"); lineButton.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph)); vv.repaint(); } } }); JRadioButton quadButton = new JRadioButton("QuadCurve"); quadButton.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.quadCurve(graph)); vv.repaint(); } } }); JRadioButton cubicButton = new JRadioButton("CubicCurve"); cubicButton.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.cubicCurve(graph)); vv.repaint(); } } }); radio.add(lineButton); radio.add(quadButton); radio.add(cubicButton); graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING); JCheckBox rotate = new JCheckBox("

    EdgeType

    Parallel

    "); rotate.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { AbstractButton b = (AbstractButton)e.getSource(); edgeLabelRenderer.setRotateEdgeLabels(b.isSelected()); vv.repaint(); } }); rotate.setSelected(true); MutableDirectionalEdgeValue mv = new MutableDirectionalEdgeValue(.5, .7); vv.getRenderContext().setEdgeLabelClosenessTransformer(mv); JSlider directedSlider = new JSlider(mv.getDirectedModel()) { public Dimension getPreferredSize() { Dimension d = super.getPreferredSize(); d.width /= 2; return d; } }; JSlider undirectedSlider = new JSlider(mv.getUndirectedModel()) { public Dimension getPreferredSize() { Dimension d = super.getPreferredSize(); d.width /= 2; return d; } }; JSlider edgeOffsetSlider = new JSlider(0,50) { public Dimension getPreferredSize() { Dimension d = super.getPreferredSize(); d.width /= 2; return d; } }; edgeOffsetSlider.addChangeListener(new ChangeListener() { @SuppressWarnings("rawtypes") public void stateChanged(ChangeEvent e) { JSlider s = (JSlider)e.getSource(); Function edgeShapeFunction = vv.getRenderContext().getEdgeShapeTransformer(); if (edgeShapeFunction instanceof ParallelEdgeShapeTransformer) { ((ParallelEdgeShapeTransformer)edgeShapeFunction) .setControlOffsetIncrement(s.getValue()); vv.repaint(); } } }); Box controls = Box.createHorizontalBox(); JPanel zoomPanel = new JPanel(new GridLayout(0,1)); zoomPanel.setBorder(BorderFactory.createTitledBorder("Scale")); zoomPanel.add(plus); zoomPanel.add(minus); JPanel edgePanel = new JPanel(new GridLayout(0,1)); edgePanel.setBorder(BorderFactory.createTitledBorder("EdgeType Type")); edgePanel.add(lineButton); edgePanel.add(quadButton); edgePanel.add(cubicButton); JPanel rotatePanel = new JPanel(); rotatePanel.setBorder(BorderFactory.createTitledBorder("Alignment")); rotatePanel.add(rotate); JPanel labelPanel = new JPanel(new BorderLayout()); JPanel sliderPanel = new JPanel(new GridLayout(3,1)); JPanel sliderLabelPanel = new JPanel(new GridLayout(3,1)); JPanel offsetPanel = new JPanel(new BorderLayout()); offsetPanel.setBorder(BorderFactory.createTitledBorder("Offset")); sliderPanel.add(directedSlider); sliderPanel.add(undirectedSlider); sliderPanel.add(edgeOffsetSlider); sliderLabelPanel.add(new JLabel("Directed", JLabel.RIGHT)); sliderLabelPanel.add(new JLabel("Undirected", JLabel.RIGHT)); sliderLabelPanel.add(new JLabel("Edges", JLabel.RIGHT)); offsetPanel.add(sliderLabelPanel, BorderLayout.WEST); offsetPanel.add(sliderPanel); labelPanel.add(offsetPanel); labelPanel.add(rotatePanel, BorderLayout.WEST); JPanel modePanel = new JPanel(new GridLayout(2,1)); modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode")); modePanel.add(graphMouse.getModeComboBox()); controls.add(zoomPanel); controls.add(edgePanel); controls.add(labelPanel); controls.add(modePanel); content.add(controls, BorderLayout.SOUTH); quadButton.setSelected(true); } /** * subclassed to hold two BoundedRangeModel instances that * are used by JSliders to move the edge label positions * @author Tom Nelson * * */ class MutableDirectionalEdgeValue extends ConstantDirectionalEdgeValueTransformer { BoundedRangeModel undirectedModel = new DefaultBoundedRangeModel(5,0,0,10); BoundedRangeModel directedModel = new DefaultBoundedRangeModel(7,0,0,10); public MutableDirectionalEdgeValue(double undirected, double directed) { super(undirected, directed); undirectedModel.setValue((int)(undirected*10)); directedModel.setValue((int)(directed*10)); undirectedModel.addChangeListener(new ChangeListener(){ public void stateChanged(ChangeEvent e) { setUndirectedValue(new Double(undirectedModel.getValue()/10f)); vv.repaint(); } }); directedModel.addChangeListener(new ChangeListener(){ public void stateChanged(ChangeEvent e) { setDirectedValue(new Double(directedModel.getValue()/10f)); vv.repaint(); } }); } /** * @return Returns the directedModel. */ public BoundedRangeModel getDirectedModel() { return directedModel; } /** * @return Returns the undirectedModel. */ public BoundedRangeModel getUndirectedModel() { return undirectedModel; } } /** * create some vertices * @param count how many to create * @return the Vertices in an array */ private Integer[] createVertices(int count) { Integer[] v = new Integer[count]; for (int i = 0; i < count; i++) { v[i] = new Integer(i); graph.addVertex(v[i]); } return v; } /** * create edges for this demo graph * @param v an array of Vertices to connect */ void createEdges(Integer[] v) { graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[1], v[0], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[1], v[0], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[1], v[2]); graph.addEdge(new Double(Math.random()), v[1], v[2]); } public static void main(String[] args) { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container content = frame.getContentPane(); content.add(new EdgeLabelDemo()); frame.pack(); frame.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/GraphEditorDemo.java000066400000000000000000000251711276402340000310660ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.awt.print.Printable; import java.awt.print.PrinterJob; import java.io.File; import javax.imageio.ImageIO; import javax.swing.AbstractAction; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import com.google.common.base.Function; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.layout.AbstractLayout; import edu.uci.ics.jung.algorithms.layout.StaticLayout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseMultigraph; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.annotations.AnnotationControls; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.EditingModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; /** * Shows how to create a graph editor with JUNG. * Mouse modes and actions are explained in the help text. * The application version of GraphEditorDemo provides a * File menu with an option to save the visible graph as * a jpeg file. * * @author Tom Nelson * */ public class GraphEditorDemo extends JApplet implements Printable { /** * */ private static final long serialVersionUID = -2023243689258876709L; /** * the graph */ Graph graph; AbstractLayout layout; /** * the visual component and renderer for the graph */ VisualizationViewer vv; String instructions = ""+ "

    All Modes:

    "+ "
      "+ "
    • Right-click an empty area for Create Vertex popup"+ "
    • Right-click on a Vertex for Delete Vertex popup"+ "
    • Right-click on a Vertex for Add Edge menus
      (if there are selected Vertices)"+ "
    • Right-click on an Edge for Delete Edge popup"+ "
    • Mousewheel scales with a crossover value of 1.0.

      "+ " - scales the graph layout when the combined scale is greater than 1

      "+ " - scales the graph view when the combined scale is less than 1"+ "

    "+ "

    Editing Mode:

    "+ "
      "+ "
    • Left-click an empty area to create a new Vertex"+ "
    • Left-click on a Vertex and drag to another Vertex to create an Undirected Edge"+ "
    • Shift+Left-click on a Vertex and drag to another Vertex to create a Directed Edge"+ "
    "+ "

    Picking Mode:

    "+ "
      "+ "
    • Mouse1 on a Vertex selects the vertex"+ "
    • Mouse1 elsewhere unselects all Vertices"+ "
    • Mouse1+Shift on a Vertex adds/removes Vertex selection"+ "
    • Mouse1+drag on a Vertex moves all selected Vertices"+ "
    • Mouse1+drag elsewhere selects Vertices in a region"+ "
    • Mouse1+Shift+drag adds selection of Vertices in a new region"+ "
    • Mouse1+CTRL on a Vertex selects the vertex and centers the display on it"+ "
    • Mouse1 double-click on a vertex or edge allows you to edit the label"+ "
    "+ "

    Transforming Mode:

    "+ "
      "+ "
    • Mouse1+drag pans the graph"+ "
    • Mouse1+Shift+drag rotates the graph"+ "
    • Mouse1+CTRL(or Command)+drag shears the graph"+ "
    • Mouse1 double-click on a vertex or edge allows you to edit the label"+ "
    "+ "

    Annotation Mode:

    "+ "
      "+ "
    • Mouse1 begins drawing of a Rectangle"+ "
    • Mouse1+drag defines the Rectangle shape"+ "
    • Mouse1 release adds the Rectangle as an annotation"+ "
    • Mouse1+Shift begins drawing of an Ellipse"+ "
    • Mouse1+Shift+drag defines the Ellipse shape"+ "
    • Mouse1+Shift release adds the Ellipse as an annotation"+ "
    • Mouse3 shows a popup to input text, which will become"+ "
    • a text annotation on the graph at the mouse location"+ "
    "+ ""; /** * create an instance of a simple graph with popup controls to * create a graph. * */ public GraphEditorDemo() { // create a simple graph for the demo graph = new SparseMultigraph(); this.layout = new StaticLayout(graph, new Dimension(600,600)); vv = new VisualizationViewer(layout); vv.setBackground(Color.white); Function labeller = new ToStringLabeller(); vv.getRenderContext().setVertexLabelTransformer(labeller); vv.getRenderContext().setEdgeLabelTransformer(labeller); vv.setVertexToolTipTransformer(vv.getRenderContext().getVertexLabelTransformer()); Container content = getContentPane(); final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); content.add(panel); Supplier vertexFactory = new VertexFactory(); Supplier edgeFactory = new EdgeFactory(); final EditingModalGraphMouse graphMouse = new EditingModalGraphMouse(vv.getRenderContext(), vertexFactory, edgeFactory); // the EditingGraphMouse will pass mouse event coordinates to the // vertexLocations function to set the locations of the vertices as // they are created // graphMouse.setVertexLocations(vertexLocations); vv.setGraphMouse(graphMouse); vv.addKeyListener(graphMouse.getModeKeyListener()); graphMouse.setMode(ModalGraphMouse.Mode.EDITING); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JButton help = new JButton("Help"); help.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(vv, instructions); }}); AnnotationControls annotationControls = new AnnotationControls(graphMouse.getAnnotatingPlugin()); JPanel controls = new JPanel(); controls.add(plus); controls.add(minus); JComboBox modeBox = graphMouse.getModeComboBox(); controls.add(modeBox); controls.add(annotationControls.getAnnotationsToolBar()); controls.add(help); content.add(controls, BorderLayout.SOUTH); } /** * copy the visible part of the graph to a file as a jpeg image * @param file the file in which to save the graph image */ public void writeJPEGImage(File file) { int width = vv.getWidth(); int height = vv.getHeight(); BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D graphics = bi.createGraphics(); vv.paint(graphics); graphics.dispose(); try { ImageIO.write(bi, "jpeg", file); } catch (Exception e) { e.printStackTrace(); } } public int print(java.awt.Graphics graphics, java.awt.print.PageFormat pageFormat, int pageIndex) throws java.awt.print.PrinterException { if (pageIndex > 0) { return (Printable.NO_SUCH_PAGE); } else { java.awt.Graphics2D g2d = (java.awt.Graphics2D) graphics; vv.setDoubleBuffered(false); g2d.translate(pageFormat.getImageableX(), pageFormat .getImageableY()); vv.paint(g2d); vv.setDoubleBuffered(true); return (Printable.PAGE_EXISTS); } } class VertexFactory implements Supplier { int i=0; public Number get() { return i++; } } class EdgeFactory implements Supplier { int i=0; public Number get() { return i++; } } @SuppressWarnings("serial") public static void main(String[] args) { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final GraphEditorDemo demo = new GraphEditorDemo(); JMenu menu = new JMenu("File"); menu.add(new AbstractAction("Make Image") { public void actionPerformed(ActionEvent e) { JFileChooser chooser = new JFileChooser(); int option = chooser.showSaveDialog(demo); if(option == JFileChooser.APPROVE_OPTION) { File file = chooser.getSelectedFile(); demo.writeJPEGImage(file); } }}); menu.add(new AbstractAction("Print") { public void actionPerformed(ActionEvent e) { PrinterJob printJob = PrinterJob.getPrinterJob(); printJob.setPrintable(demo); if (printJob.printDialog()) { try { printJob.print(); } catch (Exception ex) { ex.printStackTrace(); } } }}); JPopupMenu.setDefaultLightWeightPopupEnabled(false); JMenuBar menuBar = new JMenuBar(); menuBar.add(menu); frame.setJMenuBar(menuBar); frame.getContentPane().add(demo); frame.pack(); frame.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/GraphFromGraphMLDemo.java000066400000000000000000000153401276402340000317530ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.io.IOException; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JMenuBar; import javax.swing.JPanel; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; import com.google.common.base.Function; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.io.GraphMLReader; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.AbstractModalGraphMouse; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.GraphMouseListener; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.renderers.GradientVertexRenderer; import edu.uci.ics.jung.visualization.renderers.Renderer; import edu.uci.ics.jung.visualization.renderers.BasicVertexLabelRenderer.InsidePositioner; /** * Demonstrates loading (and visualizing) a graph from a GraphML file. * * @author Tom Nelson * */ public class GraphFromGraphMLDemo { /** * the visual component and renderer for the graph */ VisualizationViewer vv; /** * Creates an instance showing a simple graph with controls to demonstrate the zoom features. * @param filename the file containing the graph data we're reading * @throws ParserConfigurationException if a SAX parser cannot be constructed * @throws SAXException if the SAX parser factory cannot be constructed * @throws IOException if the file cannot be read */ public GraphFromGraphMLDemo(String filename) throws ParserConfigurationException, SAXException, IOException { Supplier vertexFactory = new Supplier() { int n = 0; public Number get() { return n++; } }; Supplier edgeFactory = new Supplier() { int n = 0; public Number get() { return n++; } }; GraphMLReader, Number, Number> gmlr = new GraphMLReader, Number, Number>(vertexFactory, edgeFactory); final DirectedGraph graph = new DirectedSparseMultigraph(); gmlr.load(filename, graph); // create a simple graph for the demo vv = new VisualizationViewer(new FRLayout(graph)); vv.addGraphMouseListener(new TestGraphMouseListener()); vv.getRenderer().setVertexRenderer( new GradientVertexRenderer( Color.white, Color.red, Color.white, Color.blue, vv.getPickedVertexState(), false)); // add my listeners for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); vv.setEdgeToolTipTransformer(new Function() { public String apply(Number edge) { return "E"+graph.getEndpoints(edge).toString(); }}); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); vv.getRenderer().getVertexLabelRenderer().setPositioner(new InsidePositioner()); vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.AUTO); // create a frome to hold the graph final JFrame frame = new JFrame(); Container content = frame.getContentPane(); final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); content.add(panel); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final AbstractModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); vv.addKeyListener(graphMouse.getModeKeyListener()); JMenuBar menubar = new JMenuBar(); menubar.add(graphMouse.getModeMenu()); panel.setCorner(menubar); vv.addKeyListener(graphMouse.getModeKeyListener()); vv.setToolTipText("
    Type 'p' for Pick mode

    Type 't' for Transform mode"); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JPanel controls = new JPanel(); controls.add(plus); controls.add(minus); content.add(controls, BorderLayout.SOUTH); frame.pack(); frame.setVisible(true); } /** * A nested class to demo the GraphMouseListener finding the * right vertices after zoom/pan */ static class TestGraphMouseListener implements GraphMouseListener { public void graphClicked(V v, MouseEvent me) { System.err.println("Vertex "+v+" was clicked at ("+me.getX()+","+me.getY()+")"); } public void graphPressed(V v, MouseEvent me) { System.err.println("Vertex "+v+" was pressed at ("+me.getX()+","+me.getY()+")"); } public void graphReleased(V v, MouseEvent me) { System.err.println("Vertex "+v+" was released at ("+me.getX()+","+me.getY()+")"); } } /** * @param args if this contains at least one element, the first will be used as the file to read * @throws ParserConfigurationException if a SAX parser cannot be constructed * @throws SAXException if the SAX parser factory cannot be constructed * @throws IOException if the file cannot be read */ public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException { String filename = "simple.graphml"; if(args.length > 0) filename = args[0]; new GraphFromGraphMLDemo(filename); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/GraphZoomScrollPaneDemo.java000066400000000000000000000237511276402340000325510ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Paint; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import com.google.common.base.Function; import com.google.common.base.Functions; import edu.uci.ics.jung.algorithms.layout.KKLayout; import edu.uci.ics.jung.graph.DirectedSparseGraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.AbstractModalGraphMouse; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.GraphMouseListener; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.renderers.GradientVertexRenderer; import edu.uci.ics.jung.visualization.renderers.Renderer; import edu.uci.ics.jung.visualization.renderers.BasicVertexLabelRenderer.InsidePositioner; /** * Demonstrates the use of GraphZoomScrollPane. * This class shows the VisualizationViewer zooming * and panning capabilities, using horizontal and * vertical scrollbars. * *

    This demo also shows ToolTips on graph vertices and edges, * and a key listener to change graph mouse modes. * * @author Tom Nelson * */ public class GraphZoomScrollPaneDemo { /** * the graph */ DirectedSparseGraph graph; /** * the visual component and renderer for the graph */ VisualizationViewer vv; /** * create an instance of a simple graph with controls to * demo the zoom features. * */ public GraphZoomScrollPaneDemo() { // create a simple graph for the demo graph = new DirectedSparseGraph(); String[] v = createVertices(10); createEdges(v); ImageIcon sandstoneIcon = null; String imageLocation = "/images/Sandstone.jpg"; try { sandstoneIcon = new ImageIcon(getClass().getResource(imageLocation)); } catch(Exception ex) { System.err.println("Can't load \""+imageLocation+"\""); } final ImageIcon icon = sandstoneIcon; vv = new VisualizationViewer(new KKLayout(graph)); if(icon != null) { vv.addPreRenderPaintable(new VisualizationViewer.Paintable(){ public void paint(Graphics g) { Dimension d = vv.getSize(); g.drawImage(icon.getImage(),0,0,d.width,d.height,vv); } public boolean useTransform() { return false; } }); } vv.addPostRenderPaintable(new VisualizationViewer.Paintable(){ int x; int y; Font font; FontMetrics metrics; int swidth; int sheight; String str = "GraphZoomScrollPane Demo"; public void paint(Graphics g) { Dimension d = vv.getSize(); if(font == null) { font = new Font(g.getFont().getName(), Font.BOLD, 30); metrics = g.getFontMetrics(font); swidth = metrics.stringWidth(str); sheight = metrics.getMaxAscent()+metrics.getMaxDescent(); x = (d.width-swidth)/2; y = (int)(d.height-sheight*1.5); } g.setFont(font); Color oldColor = g.getColor(); g.setColor(Color.lightGray); g.drawString(str, x, y); g.setColor(oldColor); } public boolean useTransform() { return false; } }); vv.addGraphMouseListener(new TestGraphMouseListener()); vv.getRenderer().setVertexRenderer( new GradientVertexRenderer( Color.white, Color.red, Color.white, Color.blue, vv.getPickedVertexState(), false)); vv.getRenderContext().setEdgeDrawPaintTransformer(Functions.constant(Color.lightGray)); vv.getRenderContext().setArrowFillPaintTransformer(Functions.constant(Color.lightGray)); vv.getRenderContext().setArrowDrawPaintTransformer(Functions.constant(Color.lightGray)); // add my listeners for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); vv.setEdgeToolTipTransformer(new Function() { public String apply(Number edge) { return "E"+graph.getEndpoints(edge).toString(); }}); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); vv.getRenderer().getVertexLabelRenderer().setPositioner(new InsidePositioner()); vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.AUTO); vv.setForeground(Color.lightGray); // create a frome to hold the graph final JFrame frame = new JFrame(); Container content = frame.getContentPane(); final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); content.add(panel); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final AbstractModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); vv.addKeyListener(graphMouse.getModeKeyListener()); vv.setToolTipText("

    Type 'p' for Pick mode

    Type 't' for Transform mode"); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JButton reset = new JButton("reset"); reset.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).setToIdentity(); vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).setToIdentity(); }}); JPanel controls = new JPanel(); controls.add(plus); controls.add(minus); controls.add(reset); content.add(controls, BorderLayout.SOUTH); frame.pack(); frame.setVisible(true); } /** * create some vertices * @param count how many to create * @return the Vertices in an array */ private String[] createVertices(int count) { String[] v = new String[count]; for (int i = 0; i < count; i++) { v[i] = "V"+i; graph.addVertex(v[i]); } return v; } /** * create edges for this demo graph * @param v an array of Vertices to connect */ void createEdges(String[] v) { graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[0], v[3], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[0], v[4], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[4], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[3], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[1], v[2], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[1], v[4], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[8], v[2], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[3], v[8], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[6], v[7], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[7], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[0], v[9], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[9], v[8], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[7], v[6], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[6], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[4], v[2], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[5], v[4], EdgeType.DIRECTED); } /** * A nested class to demo the GraphMouseListener finding the * right vertices after zoom/pan */ static class TestGraphMouseListener implements GraphMouseListener { public void graphClicked(V v, MouseEvent me) { System.err.println("Vertex "+v+" was clicked at ("+me.getX()+","+me.getY()+")"); } public void graphPressed(V v, MouseEvent me) { System.err.println("Vertex "+v+" was pressed at ("+me.getX()+","+me.getY()+")"); } public void graphReleased(V v, MouseEvent me) { System.err.println("Vertex "+v+" was released at ("+me.getX()+","+me.getY()+")"); } } public static void main(String[] args) { new GraphZoomScrollPaneDemo(); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/ImageEdgeLabelDemo.java000066400000000000000000000143651276402340000314300ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.net.URL; import javax.swing.BorderFactory; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JPanel; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer; import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer; /** * Demonstrates the use of images on graph edge labels. * * @author Tom Nelson * */ public class ImageEdgeLabelDemo extends JApplet { /** * */ private static final long serialVersionUID = -4332663871914930864L; private static final int VERTEX_COUNT=11; /** * the graph */ DirectedGraph graph; /** * the visual component and renderer for the graph */ VisualizationViewer vv; public ImageEdgeLabelDemo() { // create a simple graph for the demo graph = new DirectedSparseMultigraph(); createGraph(VERTEX_COUNT); FRLayout layout = new FRLayout(graph); layout.setMaxIterations(100); vv = new VisualizationViewer(layout, new Dimension(400,400)); vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv.getPickedEdgeState(), Color.black, Color.cyan)); vv.setBackground(Color.white); vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.cyan)); vv.getRenderContext().setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.cyan)); vv.getRenderContext().setEdgeLabelTransformer(new Function() { URL url = getClass().getResource("/images/lightning-s.gif"); public String apply(Number input) { return ""; }}); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); vv.setEdgeToolTipTransformer(new ToStringLabeller()); Container content = getContentPane(); final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); content.add(panel); final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); vv.addKeyListener(graphMouse.getModeKeyListener()); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JComboBox modeBox = graphMouse.getModeComboBox(); JPanel modePanel = new JPanel(); modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode")); modePanel.add(modeBox); JPanel scaleGrid = new JPanel(new GridLayout(1,0)); scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom")); JPanel controls = new JPanel(); scaleGrid.add(plus); scaleGrid.add(minus); controls.add(scaleGrid); controls.add(modePanel); content.add(controls, BorderLayout.SOUTH); } /** * create some vertices * @param count how many to create * @return the Vertices in an array */ private void createGraph(int vertexCount) { for (int i = 0; i < vertexCount; i++) { graph.addVertex(i); } int j=0; graph.addEdge(j++, 0, 1, EdgeType.DIRECTED); graph.addEdge(j++, 3, 0, EdgeType.DIRECTED); graph.addEdge(j++, 0, 4, EdgeType.DIRECTED); graph.addEdge(j++, 4, 5, EdgeType.DIRECTED); graph.addEdge(j++, 5, 3, EdgeType.DIRECTED); graph.addEdge(j++, 2, 1, EdgeType.DIRECTED); graph.addEdge(j++, 4, 1, EdgeType.DIRECTED); graph.addEdge(j++, 8, 2, EdgeType.DIRECTED); graph.addEdge(j++, 3, 8, EdgeType.DIRECTED); graph.addEdge(j++, 6, 7, EdgeType.DIRECTED); graph.addEdge(j++, 7, 5, EdgeType.DIRECTED); graph.addEdge(j++, 0, 9, EdgeType.DIRECTED); graph.addEdge(j++, 9, 8, EdgeType.DIRECTED); graph.addEdge(j++, 7, 6, EdgeType.DIRECTED); graph.addEdge(j++, 6, 5, EdgeType.DIRECTED); graph.addEdge(j++, 4, 2, EdgeType.DIRECTED); graph.addEdge(j++, 5, 4, EdgeType.DIRECTED); graph.addEdge(j++, 4, 10, EdgeType.DIRECTED); graph.addEdge(j++, 10, 4, EdgeType.DIRECTED); } public static void main(String[] args) { JFrame frame = new JFrame(); Container content = frame.getContentPane(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); content.add(new ImageEdgeLabelDemo()); frame.pack(); frame.setVisible(true); } } InternalFrameSatelliteViewDemo.java000066400000000000000000000172231276402340000340270ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JDesktopPane; import javax.swing.JFrame; import javax.swing.JInternalFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import edu.uci.ics.jung.algorithms.layout.ISOMLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.TestGraphs; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode; import edu.uci.ics.jung.visualization.control.SatelliteVisualizationViewer; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; /** * Similar to the SatelliteViewDemo, but using JInternalFrame. * * @author Tom Nelson * */ public class InternalFrameSatelliteViewDemo { static final String instructions = ""+ "

    Instructions for Mouse Listeners

    "+ "

    There are two modes, Transforming and Picking."+ "

    The modes are selected with a toggle button."+ "

    Transforming Mode:"+ "

      "+ "
    • Mouse1+drag pans the graph"+ "
    • Mouse1+Shift+drag rotates the graph"+ "
    • Mouse1+CTRL(or Command)+drag shears the graph"+ "
    "+ "Picking Mode:"+ "
      "+ "
    • Mouse1 on a Vertex selects the vertex"+ "
    • Mouse1 elsewhere unselects all Vertices"+ "
    • Mouse1+Shift on a Vertex adds/removes Vertex selection"+ "
    • Mouse1+drag on a Vertex moves all selected Vertices"+ "
    • Mouse1+drag elsewhere selects Vertices in a region"+ "
    • Mouse1+Shift+drag adds selection of Vertices in a new region"+ "
    • Mouse1+CTRL on a Vertex selects the vertex and centers the display on it"+ "
    "+ "Both Modes:"+ "
      "+ "
    • Mousewheel scales the layout > 1 and scales the view < 1"; /** * the graph */ Graph graph; /** * the visual component and renderer for the graph */ VisualizationViewer vv; VisualizationViewer satellite; JInternalFrame dialog; JDesktopPane desktop; /** * create an instance of a simple graph with controls to * demo the zoom features. * */ public InternalFrameSatelliteViewDemo() { // create a simple graph for the demo graph = TestGraphs.getOneComponentGraph(); Layout layout = new ISOMLayout(graph); vv = new VisualizationViewer(layout, new Dimension(600,600)); vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv.getPickedEdgeState(), Color.black, Color.cyan)); vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(vv.getPickedVertexState(), Color.red, Color.yellow)); // add my listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); satellite = new SatelliteVisualizationViewer(vv, new Dimension(200,200)); satellite.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(satellite.getPickedEdgeState(), Color.black, Color.cyan)); satellite.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(satellite.getPickedVertexState(), Color.red, Color.yellow)); ScalingControl satelliteScaler = new CrossoverScalingControl(); satellite.scaleToLayout(satelliteScaler); JFrame frame = new JFrame(); desktop = new JDesktopPane(); Container content = frame.getContentPane(); JPanel panel = new JPanel(new BorderLayout()); panel.add(desktop); content.add(panel); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JInternalFrame vvFrame = new JInternalFrame(); vvFrame.getContentPane().add(vv); vvFrame.pack(); vvFrame.setVisible(true); //necessary as of 1.3 desktop.add(vvFrame); try { vvFrame.setSelected(true); } catch (java.beans.PropertyVetoException e) {} dialog = new JInternalFrame(); desktop.add(dialog); content = dialog.getContentPane(); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JButton dismiss = new JButton("Dismiss"); dismiss.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { dialog.setVisible(false); } }); JButton help = new JButton("Help"); help.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JOptionPane.showInternalMessageDialog(dialog, instructions, "Instructions", JOptionPane.PLAIN_MESSAGE); } }); JPanel controls = new JPanel(new GridLayout(2,2)); controls.add(plus); controls.add(minus); controls.add(dismiss); controls.add(help); content.add(satellite); content.add(controls, BorderLayout.SOUTH); JButton zoomer = new JButton("Show Satellite View"); zoomer.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dialog.pack(); dialog.setLocation(desktop.getWidth()-dialog.getWidth(),0); dialog.show(); try { dialog.setSelected(true); } catch (java.beans.PropertyVetoException ex) {} } }); JComboBox modeBox = graphMouse.getModeComboBox(); modeBox.addItemListener(((ModalGraphMouse)satellite.getGraphMouse()).getModeListener()); JPanel p = new JPanel(); p.add(zoomer); p.add(modeBox); frame.getContentPane().add(p, BorderLayout.SOUTH); frame.setSize(800, 800); frame.setVisible(true); } public static void main(String[] args) { new InternalFrameSatelliteViewDemo(); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/L2RTreeLayoutDemo.java000066400000000000000000000232661276402340000312760ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Paint; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.swing.BorderFactory; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JToggleButton; import com.google.common.base.Functions; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.PolarPoint; import edu.uci.ics.jung.algorithms.layout.RadialTreeLayout; import edu.uci.ics.jung.algorithms.layout.TreeLayout; import edu.uci.ics.jung.graph.DelegateForest; import edu.uci.ics.jung.graph.DelegateTree; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Forest; import edu.uci.ics.jung.graph.Tree; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationServer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.layout.LayoutTransition; import edu.uci.ics.jung.visualization.util.Animator; /** * A variant of TreeLayoutDemo that rotates the view by 90 degrees from the * default orientation. * @author Tom Nelson * */ @SuppressWarnings("serial") public class L2RTreeLayoutDemo extends JApplet { /** * the graph */ Forest graph; Supplier> graphFactory = new Supplier>() { public DirectedGraph get() { return new DirectedSparseMultigraph(); } }; Supplier> treeFactory = new Supplier> () { public Tree get() { return new DelegateTree(graphFactory); } }; Supplier edgeFactory = new Supplier() { int i=0; public Integer get() { return i++; }}; Supplier vertexFactory = new Supplier() { int i=0; public String get() { return "V"+i++; }}; /** * the visual component and renderer for the graph */ VisualizationViewer vv; VisualizationServer.Paintable rings; String root; TreeLayout treeLayout; RadialTreeLayout radialLayout; public L2RTreeLayoutDemo() { // create a simple graph for the demo graph = new DelegateForest(); createTree(); treeLayout = new TreeLayout(graph); radialLayout = new RadialTreeLayout(graph); radialLayout.setSize(new Dimension(600,600)); vv = new VisualizationViewer(treeLayout, new Dimension(600,600)); vv.setBackground(Color.white); vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.quadCurve(graph)); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); vv.getRenderContext().setArrowFillPaintTransformer(Functions.constant(Color.lightGray)); rings = new Rings(); setLtoR(vv); Container content = getContentPane(); final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); content.add(panel); final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); JComboBox modeBox = graphMouse.getModeComboBox(); modeBox.addItemListener(graphMouse.getModeListener()); graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JToggleButton radial = new JToggleButton("Radial"); radial.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { LayoutTransition lt = new LayoutTransition(vv, treeLayout, radialLayout); Animator animator = new Animator(lt); animator.start(); vv.getRenderContext().getMultiLayerTransformer().setToIdentity(); vv.addPreRenderPaintable(rings); } else { LayoutTransition lt = new LayoutTransition(vv, radialLayout, treeLayout); Animator animator = new Animator(lt); animator.start(); vv.getRenderContext().getMultiLayerTransformer().setToIdentity(); setLtoR(vv); vv.removePreRenderPaintable(rings); } vv.repaint(); }}); JPanel scaleGrid = new JPanel(new GridLayout(1,0)); scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom")); JPanel controls = new JPanel(); scaleGrid.add(plus); scaleGrid.add(minus); controls.add(radial); controls.add(scaleGrid); controls.add(modeBox); content.add(controls, BorderLayout.SOUTH); } private void setLtoR(VisualizationViewer vv) { Layout layout = vv.getModel().getGraphLayout(); Dimension d = layout.getSize(); Point2D center = new Point2D.Double(d.width/2, d.height/2); vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).rotate(-Math.PI/2, center); } class Rings implements VisualizationServer.Paintable { Collection depths; public Rings() { depths = getDepths(); } private Collection getDepths() { Set depths = new HashSet(); Map polarLocations = radialLayout.getPolarLocations(); for(String v : graph.getVertices()) { PolarPoint pp = polarLocations.get(v); depths.add(pp.getRadius()); } return depths; } public void paint(Graphics g) { g.setColor(Color.lightGray); Graphics2D g2d = (Graphics2D)g; Point2D center = radialLayout.getCenter(); Ellipse2D ellipse = new Ellipse2D.Double(); for(double d : depths) { ellipse.setFrameFromDiagonal(center.getX()-d, center.getY()-d, center.getX()+d, center.getY()+d); Shape shape = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).transform(ellipse); g2d.draw(shape); } } public boolean useTransform() { return true; } } /** * */ private void createTree() { graph.addVertex("V0"); graph.addEdge(edgeFactory.get(), "V0", "V1"); graph.addEdge(edgeFactory.get(), "V0", "V2"); graph.addEdge(edgeFactory.get(), "V1", "V4"); graph.addEdge(edgeFactory.get(), "V2", "V3"); graph.addEdge(edgeFactory.get(), "V2", "V5"); graph.addEdge(edgeFactory.get(), "V4", "V6"); graph.addEdge(edgeFactory.get(), "V4", "V7"); graph.addEdge(edgeFactory.get(), "V3", "V8"); graph.addEdge(edgeFactory.get(), "V6", "V9"); graph.addEdge(edgeFactory.get(), "V4", "V10"); graph.addVertex("A0"); graph.addEdge(edgeFactory.get(), "A0", "A1"); graph.addEdge(edgeFactory.get(), "A0", "A2"); graph.addEdge(edgeFactory.get(), "A0", "A3"); graph.addVertex("B0"); graph.addEdge(edgeFactory.get(), "B0", "B1"); graph.addEdge(edgeFactory.get(), "B0", "B2"); graph.addEdge(edgeFactory.get(), "B1", "B4"); graph.addEdge(edgeFactory.get(), "B2", "B3"); graph.addEdge(edgeFactory.get(), "B2", "B5"); graph.addEdge(edgeFactory.get(), "B4", "B6"); graph.addEdge(edgeFactory.get(), "B4", "B7"); graph.addEdge(edgeFactory.get(), "B3", "B8"); graph.addEdge(edgeFactory.get(), "B6", "B9"); } public static void main(String[] args) { JFrame frame = new JFrame(); Container content = frame.getContentPane(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); content.add(new L2RTreeLayoutDemo()); frame.pack(); frame.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/LensDemo.java000066400000000000000000000420041276402340000275510ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Insets; import java.awt.Rectangle; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.HashMap; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.ButtonGroup; import javax.swing.Icon; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenuBar; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.plaf.basic.BasicLabelUI; import com.google.common.base.Function; import com.google.common.base.Functions; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.StaticLayout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseGraph; import edu.uci.ics.jung.graph.util.TestGraphs; import edu.uci.ics.jung.visualization.DefaultVisualizationModel; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationModel; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.LensMagnificationGraphMousePlugin; import edu.uci.ics.jung.visualization.control.ModalLensGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.transform.HyperbolicTransformer; import edu.uci.ics.jung.visualization.transform.LayoutLensSupport; import edu.uci.ics.jung.visualization.transform.LensSupport; import edu.uci.ics.jung.visualization.transform.MagnifyTransformer; import edu.uci.ics.jung.visualization.transform.shape.HyperbolicShapeTransformer; import edu.uci.ics.jung.visualization.transform.shape.MagnifyShapeTransformer; import edu.uci.ics.jung.visualization.transform.shape.ViewLensSupport; /** * Demonstrates the use of HyperbolicTransform * and MagnifyTransform * applied to either the model (graph layout) or the view * (VisualizationViewer) * The hyperbolic transform is applied in an elliptical lens * that affects that part of the visualization. * * @author Tom Nelson * */ @SuppressWarnings("serial") public class LensDemo extends JApplet { /** * the graph */ Graph graph; FRLayout graphLayout; /** * a grid shaped graph */ Graph grid; Layout gridLayout; /** * the visual component and renderer for the graph */ VisualizationViewer vv; /** * provides a Hyperbolic lens for the view */ LensSupport hyperbolicViewSupport; /** * provides a magnification lens for the view */ LensSupport magnifyViewSupport; /** * provides a Hyperbolic lens for the model */ LensSupport hyperbolicLayoutSupport; /** * provides a magnification lens for the model */ LensSupport magnifyLayoutSupport; ScalingControl scaler; /** * create an instance of a simple graph with controls to * demo the zoomand hyperbolic features. * */ public LensDemo() { // create a simple graph for the demo graph = TestGraphs.getOneComponentGraph(); graphLayout = new FRLayout(graph); graphLayout.setMaxIterations(1000); Dimension preferredSize = new Dimension(600,600); Map map = new HashMap(); Function vlf = Functions.forMap(map); grid = this.generateVertexGrid(map, preferredSize, 25); gridLayout = new StaticLayout(grid, vlf, preferredSize); final VisualizationModel visualizationModel = new DefaultVisualizationModel(graphLayout, preferredSize); vv = new VisualizationViewer(visualizationModel, preferredSize); PickedState ps = vv.getPickedVertexState(); PickedState pes = vv.getPickedEdgeState(); vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(ps, Color.red, Color.yellow)); vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(pes, Color.black, Color.cyan)); vv.setBackground(Color.white); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); final Function ovals = vv.getRenderContext().getVertexShapeTransformer(); final Function squares = Functions.constant(new Rectangle2D.Float(-10,-10,20,20)); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); Container content = getContentPane(); GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv); content.add(gzsp); /** * the regular graph mouse for the normal view */ final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); vv.addKeyListener(graphMouse.getModeKeyListener()); hyperbolicViewSupport = new ViewLensSupport(vv, new HyperbolicShapeTransformer(vv, vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)), new ModalLensGraphMouse()); hyperbolicLayoutSupport = new LayoutLensSupport(vv, new HyperbolicTransformer(vv, vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT)), new ModalLensGraphMouse()); magnifyViewSupport = new ViewLensSupport(vv, new MagnifyShapeTransformer(vv, vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)), new ModalLensGraphMouse(new LensMagnificationGraphMousePlugin(1.f, 6.f, .2f))); magnifyLayoutSupport = new LayoutLensSupport(vv, new MagnifyTransformer(vv, vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT)), new ModalLensGraphMouse(new LensMagnificationGraphMousePlugin(1.f, 6.f, .2f))); hyperbolicLayoutSupport.getLensTransformer().setLensShape(hyperbolicViewSupport.getLensTransformer().getLensShape()); magnifyViewSupport.getLensTransformer().setLensShape(hyperbolicLayoutSupport.getLensTransformer().getLensShape()); magnifyLayoutSupport.getLensTransformer().setLensShape(magnifyViewSupport.getLensTransformer().getLensShape()); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); ButtonGroup radio = new ButtonGroup(); JRadioButton normal = new JRadioButton("None"); normal.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { if(hyperbolicViewSupport != null) { hyperbolicViewSupport.deactivate(); } if(hyperbolicLayoutSupport != null) { hyperbolicLayoutSupport.deactivate(); } if(magnifyViewSupport != null) { magnifyViewSupport.deactivate(); } if(magnifyLayoutSupport != null) { magnifyLayoutSupport.deactivate(); } } } }); final JRadioButton hyperView = new JRadioButton("Hyperbolic View"); hyperView.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { hyperbolicViewSupport.activate(e.getStateChange() == ItemEvent.SELECTED); } }); final JRadioButton hyperModel = new JRadioButton("Hyperbolic Layout"); hyperModel.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { hyperbolicLayoutSupport.activate(e.getStateChange() == ItemEvent.SELECTED); } }); final JRadioButton magnifyView = new JRadioButton("Magnified View"); magnifyView.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { magnifyViewSupport.activate(e.getStateChange() == ItemEvent.SELECTED); } }); final JRadioButton magnifyModel = new JRadioButton("Magnified Layout"); magnifyModel.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { magnifyLayoutSupport.activate(e.getStateChange() == ItemEvent.SELECTED); } }); JLabel modeLabel = new JLabel(" Mode Menu >>"); modeLabel.setUI(new VerticalLabelUI(false)); radio.add(normal); radio.add(hyperModel); radio.add(hyperView); radio.add(magnifyModel); radio.add(magnifyView); normal.setSelected(true); graphMouse.addItemListener(hyperbolicLayoutSupport.getGraphMouse().getModeListener()); graphMouse.addItemListener(hyperbolicViewSupport.getGraphMouse().getModeListener()); graphMouse.addItemListener(magnifyLayoutSupport.getGraphMouse().getModeListener()); graphMouse.addItemListener(magnifyViewSupport.getGraphMouse().getModeListener()); ButtonGroup graphRadio = new ButtonGroup(); JRadioButton graphButton = new JRadioButton("Graph"); graphButton.setSelected(true); graphButton.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { visualizationModel.setGraphLayout(graphLayout); vv.getRenderContext().setVertexShapeTransformer(ovals); vv.getRenderContext().setVertexLabelTransformer( new ToStringLabeller()); vv.repaint(); } }}); JRadioButton gridButton = new JRadioButton("Grid"); gridButton.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { visualizationModel.setGraphLayout(gridLayout); vv.getRenderContext().setVertexShapeTransformer(squares); vv.getRenderContext().setVertexLabelTransformer(Functions.constant(null)); vv.repaint(); } }}); graphRadio.add(graphButton); graphRadio.add(gridButton); JPanel modePanel = new JPanel(new GridLayout(3,1)); modePanel.setBorder(BorderFactory.createTitledBorder("Display")); modePanel.add(graphButton); modePanel.add(gridButton); JMenuBar menubar = new JMenuBar(); menubar.add(graphMouse.getModeMenu()); gzsp.setCorner(menubar); Box controls = Box.createHorizontalBox(); JPanel zoomControls = new JPanel(new GridLayout(2,1)); zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom")); JPanel hyperControls = new JPanel(new GridLayout(3,2)); hyperControls.setBorder(BorderFactory.createTitledBorder("Examiner Lens")); zoomControls.add(plus); zoomControls.add(minus); hyperControls.add(normal); hyperControls.add(new JLabel()); hyperControls.add(hyperModel); hyperControls.add(magnifyModel); hyperControls.add(hyperView); hyperControls.add(magnifyView); controls.add(zoomControls); controls.add(hyperControls); controls.add(modePanel); controls.add(modeLabel); content.add(controls, BorderLayout.SOUTH); } private Graph generateVertexGrid(Map vlf, Dimension d, int interval) { int count = d.width/interval * d.height/interval; Graph graph = new SparseGraph(); for(int i=0; i graph; /** * the visual component and renderer for the graph */ VisualizationViewer vv; /** * some icon names to use */ String[] iconNames = { "apple", "os", "x", "linux", "inputdevices", "wireless", "graphics3", "gamespcgames", "humor", "music", "privacy" }; LensSupport viewSupport; LensSupport modelSupport; LensSupport magnifyLayoutSupport; LensSupport magnifyViewSupport; /** * create an instance of a simple graph with controls to * demo the zoom features. * */ public LensVertexImageShaperDemo() { // create a simple graph for the demo graph = new DirectedSparseGraph(); Number[] vertices = createVertices(11); // a Map for the labels Map map = new HashMap(); for(int i=0; i iconMap = new HashMap(); for(int i=0; i layout = new FRLayout(graph); layout.setMaxIterations(100); vv = new VisualizationViewer(layout, new Dimension(600,600)); Function vpf = new PickableVertexPaintTransformer(vv.getPickedVertexState(), Color.white, Color.yellow); vv.getRenderContext().setVertexFillPaintTransformer(vpf); vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv.getPickedEdgeState(), Color.black, Color.cyan)); vv.setBackground(Color.white); final Function vertexStringerImpl = new VertexStringerImpl(map); vv.getRenderContext().setVertexLabelTransformer(vertexStringerImpl); vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.cyan)); vv.getRenderContext().setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.cyan)); // features on and off. For a real application, use VertexIconAndShapeFunction instead. final VertexIconShapeTransformer vertexImageShapeFunction = new VertexIconShapeTransformer(new EllipseVertexShapeTransformer()); final Function vertexIconFunction = Functions.forMap(iconMap); vertexImageShapeFunction.setIconMap(iconMap); vv.getRenderContext().setVertexShapeTransformer(vertexImageShapeFunction); vv.getRenderContext().setVertexIconTransformer(vertexIconFunction); // Get the pickedState and add a listener that will decorate the // Vertex images with a checkmark icon when they are picked PickedState ps = vv.getPickedVertexState(); ps.addItemListener(new PickWithIconListener(vertexIconFunction)); vv.addPostRenderPaintable(new VisualizationViewer.Paintable(){ int x; int y; Font font; FontMetrics metrics; int swidth; int sheight; String str = "Thank You, slashdot.org, for the images!"; public void paint(Graphics g) { Dimension d = vv.getSize(); if(font == null) { font = new Font(g.getFont().getName(), Font.BOLD, 20); metrics = g.getFontMetrics(font); swidth = metrics.stringWidth(str); sheight = metrics.getMaxAscent()+metrics.getMaxDescent(); x = (d.width-swidth)/2; y = (int)(d.height-sheight*1.5); } g.setFont(font); Color oldColor = g.getColor(); g.setColor(Color.lightGray); g.drawString(str, x, y); g.setColor(oldColor); } public boolean useTransform() { return false; } }); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); Container content = getContentPane(); final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); content.add(panel); final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JComboBox modeBox = graphMouse.getModeComboBox(); JPanel modePanel = new JPanel(); modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode")); modePanel.add(modeBox); JPanel scaleGrid = new JPanel(new GridLayout(1,0)); scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom")); JPanel controls = new JPanel(); scaleGrid.add(plus); scaleGrid.add(minus); controls.add(scaleGrid); controls.add(modePanel); content.add(controls, BorderLayout.SOUTH); this.viewSupport = new MagnifyImageLensSupport(vv); // new ViewLensSupport(vv, new HyperbolicShapeTransformer(vv, // vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)), // new ModalLensGraphMouse()); this.modelSupport = new LayoutLensSupport(vv); graphMouse.addItemListener(modelSupport.getGraphMouse().getModeListener()); graphMouse.addItemListener(viewSupport.getGraphMouse().getModeListener()); ButtonGroup radio = new ButtonGroup(); JRadioButton none = new JRadioButton("None"); none.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { if(viewSupport != null) { viewSupport.deactivate(); } if(modelSupport != null) { modelSupport.deactivate(); } } }); none.setSelected(true); JRadioButton hyperView = new JRadioButton("View"); hyperView.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { viewSupport.activate(e.getStateChange() == ItemEvent.SELECTED); } }); JRadioButton hyperModel = new JRadioButton("Layout"); hyperModel.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { modelSupport.activate(e.getStateChange() == ItemEvent.SELECTED); } }); radio.add(none); radio.add(hyperView); radio.add(hyperModel); JMenuBar menubar = new JMenuBar(); JMenu modeMenu = graphMouse.getModeMenu(); menubar.add(modeMenu); JPanel lensPanel = new JPanel(new GridLayout(2,0)); lensPanel.setBorder(BorderFactory.createTitledBorder("Lens")); lensPanel.add(none); lensPanel.add(hyperView); lensPanel.add(hyperModel); controls.add(lensPanel); } /** * A simple implementation of VertexStringer that * gets Vertex labels from a Map * * @author Tom Nelson * * */ class VertexStringerImpl implements Function { Map map = new HashMap(); boolean enabled = true; public VertexStringerImpl(Map map) { this.map = map; } /** * @see edu.uci.ics.jung.graph.decorators.VertexStringer#getLabel(edu.uci.ics.jung.graph.Vertex) */ public String apply(V v) { if(isEnabled()) { return map.get(v); } else { return ""; } } /** * @return Returns the enabled. */ public boolean isEnabled() { return enabled; } /** * @param enabled The enabled to set. */ public void setEnabled(boolean enabled) { this.enabled = enabled; } } /** * create some vertices * @param count how many to create * @return the Vertices in an array */ private Number[] createVertices(int count) { Number[] v = new Number[count]; for (int i = 0; i < count; i++) { v[i] = new Integer(i); graph.addVertex(v[i]); } return v; } /** * create edges for this demo graph * @param v an array of Vertices to connect */ void createEdges(Number[] v) { graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[3], v[0], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[0], v[4], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[4], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[5], v[3], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[2], v[1], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[4], v[1], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[8], v[2], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[3], v[8], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[6], v[7], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[7], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[0], v[9], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[9], v[8], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[7], v[6], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[6], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[4], v[2], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[5], v[4], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[4], v[10], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[10], v[4], EdgeType.DIRECTED); } public static class PickWithIconListener implements ItemListener { Function imager; Icon checked; public PickWithIconListener(Function imager) { this.imager = imager; checked = new Checkmark(Color.red); } public void itemStateChanged(ItemEvent e) { Icon icon = imager.apply((Number)e.getItem()); if(icon != null && icon instanceof LayeredIcon) { if(e.getStateChange() == ItemEvent.SELECTED) { ((LayeredIcon)icon).add(checked); } else { ((LayeredIcon)icon).remove(checked); } } } } public static void main(String[] args) { JFrame frame = new JFrame(); Container content = frame.getContentPane(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); content.add(new LensVertexImageShaperDemo()); frame.pack(); frame.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/MinimumSpanningTreeDemo.java000066400000000000000000000254371276402340000326140ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.BorderFactory; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import com.google.common.base.Functions; import edu.uci.ics.jung.algorithms.layout.KKLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.StaticLayout; import edu.uci.ics.jung.algorithms.layout.TreeLayout; import edu.uci.ics.jung.algorithms.shortestpath.MinimumSpanningForest2; import edu.uci.ics.jung.graph.DelegateForest; import edu.uci.ics.jung.graph.DelegateTree; import edu.uci.ics.jung.graph.Forest; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.TestGraphs; import edu.uci.ics.jung.visualization.DefaultVisualizationModel; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationModel; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.picking.MultiPickedState; import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.renderers.Renderer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * Demonstrates a single graph with 3 layouts in 3 views. * The first view is an undirected graph using KKLayout * The second view show a TreeLayout view of a MinimumSpanningTree * of the first graph. The third view shows the complete graph * of the first view, using the layout positions of the * MinimumSpanningTree tree view. * * @author Tom Nelson * */ @SuppressWarnings("serial") public class MinimumSpanningTreeDemo extends JApplet { /** * the graph */ Graph graph; Forest tree; /** * the visual components and renderers for the graph */ VisualizationViewer vv0; VisualizationViewer vv1; VisualizationViewer vv2; /** * the normal Function */ MutableTransformer layoutTransformer; Dimension preferredSize = new Dimension(300,300); Dimension preferredLayoutSize = new Dimension(400,400); Dimension preferredSizeRect = new Dimension(500,250); /** * create an instance of a simple graph in two views with controls to * demo the zoom features. * */ public MinimumSpanningTreeDemo() { // create a simple graph for the demo // both models will share one graph graph = TestGraphs.getDemoGraph(); MinimumSpanningForest2 prim = new MinimumSpanningForest2(graph, new DelegateForest(), DelegateTree.getFactory(), Functions.constant(1.0)); tree = prim.getForest(); // create two layouts for the one graph, one layout for each model Layout layout0 = new KKLayout(graph); layout0.setSize(preferredLayoutSize); Layout layout1 = new TreeLayout(tree); Layout layout2 = new StaticLayout(graph, layout1); // create the two models, each with a different layout VisualizationModel vm0 = new DefaultVisualizationModel(layout0, preferredSize); VisualizationModel vm1 = new DefaultVisualizationModel(layout1, preferredSizeRect); VisualizationModel vm2 = new DefaultVisualizationModel(layout2, preferredSizeRect); // create the two views, one for each model // they share the same renderer vv0 = new VisualizationViewer(vm0, preferredSize); vv1 = new VisualizationViewer(vm1, preferredSizeRect); vv2 = new VisualizationViewer(vm2, preferredSizeRect); vv1.getRenderContext().setMultiLayerTransformer(vv0.getRenderContext().getMultiLayerTransformer()); vv2.getRenderContext().setMultiLayerTransformer(vv0.getRenderContext().getMultiLayerTransformer()); vv1.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph)); vv0.addChangeListener(vv1); vv1.addChangeListener(vv2); vv0.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); vv2.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); Color back = Color.decode("0xffffbb"); vv0.setBackground(back); vv1.setBackground(back); vv2.setBackground(back); vv0.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR); vv0.setForeground(Color.darkGray); vv1.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR); vv1.setForeground(Color.darkGray); vv2.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR); vv2.setForeground(Color.darkGray); // share one PickedState between the two views PickedState ps = new MultiPickedState(); vv0.setPickedVertexState(ps); vv1.setPickedVertexState(ps); vv2.setPickedVertexState(ps); PickedState pes = new MultiPickedState(); vv0.setPickedEdgeState(pes); vv1.setPickedEdgeState(pes); vv2.setPickedEdgeState(pes); // set an edge paint function that will show picking for edges vv0.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv0.getPickedEdgeState(), Color.black, Color.red)); vv0.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(vv0.getPickedVertexState(), Color.red, Color.yellow)); vv1.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv1.getPickedEdgeState(), Color.black, Color.red)); vv1.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(vv1.getPickedVertexState(), Color.red, Color.yellow)); // add default listeners for ToolTips vv0.setVertexToolTipTransformer(new ToStringLabeller()); vv1.setVertexToolTipTransformer(new ToStringLabeller()); vv2.setVertexToolTipTransformer(new ToStringLabeller()); vv0.setLayout(new BorderLayout()); vv1.setLayout(new BorderLayout()); vv2.setLayout(new BorderLayout()); Font font = vv0.getFont().deriveFont(Font.BOLD, 16); JLabel vv0Label = new JLabel("Original Graph

      using KKLayout"); vv0Label.setFont(font); JLabel vv1Label = new JLabel("Minimum Spanning Trees"); vv1Label.setFont(font); JLabel vv2Label = new JLabel("Original Graph using TreeLayout"); vv2Label.setFont(font); JPanel flow0 = new JPanel(); flow0.setOpaque(false); JPanel flow1 = new JPanel(); flow1.setOpaque(false); JPanel flow2 = new JPanel(); flow2.setOpaque(false); flow0.add(vv0Label); flow1.add(vv1Label); flow2.add(vv2Label); vv0.add(flow0, BorderLayout.NORTH); vv1.add(flow1, BorderLayout.NORTH); vv2.add(flow2, BorderLayout.NORTH); Container content = getContentPane(); JPanel grid = new JPanel(new GridLayout(0,1)); JPanel panel = new JPanel(new BorderLayout()); panel.add(new GraphZoomScrollPane(vv0), BorderLayout.WEST); grid.add(new GraphZoomScrollPane(vv1)); grid.add(new GraphZoomScrollPane(vv2)); panel.add(grid); content.add(panel); // create a GraphMouse for each view DefaultModalGraphMouse gm0 = new DefaultModalGraphMouse(); DefaultModalGraphMouse gm1 = new DefaultModalGraphMouse(); DefaultModalGraphMouse gm2 = new DefaultModalGraphMouse(); vv0.setGraphMouse(gm0); vv1.setGraphMouse(gm1); vv2.setGraphMouse(gm2); // create zoom buttons for scaling the Function that is // shared between the two models. final ScalingControl scaler = new CrossoverScalingControl(); vv0.scaleToLayout(scaler); vv1.scaleToLayout(scaler); vv2.scaleToLayout(scaler); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv1, 1.1f, vv1.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv1, 1/1.1f, vv1.getCenter()); } }); JPanel zoomPanel = new JPanel(new GridLayout(1,2)); zoomPanel.setBorder(BorderFactory.createTitledBorder("Zoom")); JPanel modePanel = new JPanel(); modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode")); gm1.getModeComboBox().addItemListener(gm2.getModeListener()); gm1.getModeComboBox().addItemListener(gm0.getModeListener()); modePanel.add(gm1.getModeComboBox()); JPanel controls = new JPanel(); zoomPanel.add(plus); zoomPanel.add(minus); controls.add(zoomPanel); controls.add(modePanel); content.add(controls, BorderLayout.SOUTH); } public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new MinimumSpanningTreeDemo()); f.pack(); f.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/MultiViewDemo.java000066400000000000000000000336631276402340000306100ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.geom.Rectangle2D; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import com.google.common.base.Functions; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.TestGraphs; import edu.uci.ics.jung.visualization.DefaultVisualizationModel; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationModel; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.AnimatedPickingGraphMousePlugin; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.LayoutScalingControl; import edu.uci.ics.jung.visualization.control.PickingGraphMousePlugin; import edu.uci.ics.jung.visualization.control.RotatingGraphMousePlugin; import edu.uci.ics.jung.visualization.control.ScalingGraphMousePlugin; import edu.uci.ics.jung.visualization.control.ShearingGraphMousePlugin; import edu.uci.ics.jung.visualization.control.TranslatingGraphMousePlugin; import edu.uci.ics.jung.visualization.control.ViewScalingControl; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.picking.MultiPickedState; import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.picking.ShapePickSupport; /** * Demonstrates 3 views of one graph in one model with one layout. * Each view uses a different scaling graph mouse. * * @author Tom Nelson * */ @SuppressWarnings("serial") public class MultiViewDemo extends JApplet { /** * the graph */ Graph graph; /** * the visual components and renderers for the graph */ VisualizationViewer vv1; VisualizationViewer vv2; VisualizationViewer vv3; /** * the normal Function */ // MutableTransformer Function; Dimension preferredSize = new Dimension(300,300); final String messageOne = "The mouse wheel will scale the model's layout when activated"+ " in View 1. Since all three views share the same layout Function, all three views will"+ " show the same scaling of the layout."; final String messageTwo = "The mouse wheel will scale the view when activated in"+ " View 2. Since all three views share the same view Function, all three views will be affected."; final String messageThree = " The mouse wheel uses a 'crossover' feature in View 3."+ " When the combined layout and view scale is greater than '1', the model's layout will be scaled."+ " Since all three views share the same layout Function, all three views will show the same "+ " scaling of the layout.\n When the combined scale is less than '1', the scaling function"+ " crosses over to the view, and then, since all three views share the same view Function,"+ " all three views will show the same scaling."; JTextArea textArea; JScrollPane scrollPane; /** * create an instance of a simple graph in two views with controls to * demo the zoom features. * */ public MultiViewDemo() { // create a simple graph for the demo graph = TestGraphs.getOneComponentGraph(); // create one layout for the graph FRLayout layout = new FRLayout(graph); layout.setMaxIterations(1000); // create one model that all 3 views will share VisualizationModel visualizationModel = new DefaultVisualizationModel(layout, preferredSize); // create 3 views that share the same model vv1 = new VisualizationViewer(visualizationModel, preferredSize); vv2 = new VisualizationViewer(visualizationModel, preferredSize); vv3 = new VisualizationViewer(visualizationModel, preferredSize); vv1.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph)); vv2.getRenderContext().setVertexShapeTransformer( Functions.constant(new Rectangle2D.Float(-6,-6,12,12))); vv2.getRenderContext().setEdgeShapeTransformer(EdgeShape.quadCurve(graph)); vv3.getRenderContext().setEdgeShapeTransformer(EdgeShape.cubicCurve(graph)); // Function = vv1.getLayoutTransformer(); // vv2.setLayoutTransformer(Function); // vv3.setLayoutTransformer(Function); // // vv2.setViewTransformer(vv1.getViewTransformer()); // vv3.setViewTransformer(vv1.getViewTransformer()); vv2.getRenderContext().setMultiLayerTransformer(vv1.getRenderContext().getMultiLayerTransformer()); vv3.getRenderContext().setMultiLayerTransformer(vv1.getRenderContext().getMultiLayerTransformer()); vv1.getRenderContext().getMultiLayerTransformer().addChangeListener(vv1); vv2.getRenderContext().getMultiLayerTransformer().addChangeListener(vv2); vv3.getRenderContext().getMultiLayerTransformer().addChangeListener(vv3); vv1.setBackground(Color.white); vv2.setBackground(Color.white); vv3.setBackground(Color.white); // create one pick support for all 3 views to share GraphElementAccessor pickSupport = new ShapePickSupport(vv1); vv1.setPickSupport(pickSupport); vv2.setPickSupport(pickSupport); vv3.setPickSupport(pickSupport); // create one picked state for all 3 views to share PickedState pes = new MultiPickedState(); PickedState pvs = new MultiPickedState(); vv1.setPickedVertexState(pvs); vv2.setPickedVertexState(pvs); vv3.setPickedVertexState(pvs); vv1.setPickedEdgeState(pes); vv2.setPickedEdgeState(pes); vv3.setPickedEdgeState(pes); // set an edge paint function that shows picked edges vv1.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(pes, Color.black, Color.red)); vv2.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(pes, Color.black, Color.red)); vv3.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(pes, Color.black, Color.red)); vv1.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(pvs, Color.red, Color.yellow)); vv2.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(pvs, Color.blue, Color.cyan)); vv3.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(pvs, Color.red, Color.yellow)); // add default listener for ToolTips vv1.setVertexToolTipTransformer(new ToStringLabeller()); vv2.setVertexToolTipTransformer(new ToStringLabeller()); vv3.setVertexToolTipTransformer(new ToStringLabeller()); Container content = getContentPane(); JPanel panel = new JPanel(new GridLayout(1,0)); final JPanel p1 = new JPanel(new BorderLayout()); final JPanel p2 = new JPanel(new BorderLayout()); final JPanel p3 = new JPanel(new BorderLayout()); p1.add(new GraphZoomScrollPane(vv1)); p2.add(new GraphZoomScrollPane(vv2)); p3.add(new GraphZoomScrollPane(vv3)); JButton h1 = new JButton("?"); h1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { textArea.setText(messageOne); JOptionPane.showMessageDialog(p1, scrollPane, "View 1", JOptionPane.PLAIN_MESSAGE); }}); JButton h2 = new JButton("?"); h2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { textArea.setText(messageTwo); JOptionPane.showMessageDialog(p2, scrollPane, "View 2", JOptionPane.PLAIN_MESSAGE); }}); JButton h3 = new JButton("?"); h3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { textArea.setText(messageThree); textArea.setCaretPosition(0); JOptionPane.showMessageDialog(p3, scrollPane, "View 3", JOptionPane.PLAIN_MESSAGE); }}); // create a GraphMouse for each view // each one has a different scaling plugin DefaultModalGraphMouse gm1 = new DefaultModalGraphMouse() { protected void loadPlugins() { pickingPlugin = new PickingGraphMousePlugin(); animatedPickingPlugin = new AnimatedPickingGraphMousePlugin(); translatingPlugin = new TranslatingGraphMousePlugin(InputEvent.BUTTON1_MASK); scalingPlugin = new ScalingGraphMousePlugin(new LayoutScalingControl(), 0); rotatingPlugin = new RotatingGraphMousePlugin(); shearingPlugin = new ShearingGraphMousePlugin(); add(scalingPlugin); setMode(Mode.TRANSFORMING); } }; DefaultModalGraphMouse gm2 = new DefaultModalGraphMouse() { protected void loadPlugins() { pickingPlugin = new PickingGraphMousePlugin(); animatedPickingPlugin = new AnimatedPickingGraphMousePlugin(); translatingPlugin = new TranslatingGraphMousePlugin(InputEvent.BUTTON1_MASK); scalingPlugin = new ScalingGraphMousePlugin(new ViewScalingControl(), 0); rotatingPlugin = new RotatingGraphMousePlugin(); shearingPlugin = new ShearingGraphMousePlugin(); add(scalingPlugin); setMode(Mode.TRANSFORMING); } }; DefaultModalGraphMouse gm3 = new DefaultModalGraphMouse() {}; vv1.setGraphMouse(gm1); vv2.setGraphMouse(gm2); vv3.setGraphMouse(gm3); vv1.setToolTipText("

      MouseWheel Scales Layout
      "); vv2.setToolTipText("
      MouseWheel Scales View
      "); vv3.setToolTipText("
      MouseWheel Scales Layout and

      crosses over to view

      ctrl+MouseWheel scales view

      "); vv1.addPostRenderPaintable(new BannerLabel(vv1, "View 1")); vv2.addPostRenderPaintable(new BannerLabel(vv2, "View 2")); vv3.addPostRenderPaintable(new BannerLabel(vv3, "View 3")); textArea = new JTextArea(6,30); scrollPane = new JScrollPane(textArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); textArea.setLineWrap(true); textArea.setWrapStyleWord(true); textArea.setEditable(false); JPanel flow = new JPanel(); flow.add(h1); flow.add(gm1.getModeComboBox()); p1.add(flow, BorderLayout.SOUTH); flow = new JPanel(); flow.add(h2); flow.add(gm2.getModeComboBox()); p2.add(flow, BorderLayout.SOUTH); flow = new JPanel(); flow.add(h3); flow.add(gm3.getModeComboBox()); p3.add(flow, BorderLayout.SOUTH); panel.add(p1); panel.add(p2); panel.add(p3); content.add(panel); } class BannerLabel implements VisualizationViewer.Paintable { int x; int y; Font font; FontMetrics metrics; int swidth; int sheight; String str; VisualizationViewer vv; public BannerLabel(VisualizationViewer vv, String label) { this.vv = vv; this.str = label; } public void paint(Graphics g) { Dimension d = vv.getSize(); if(font == null) { font = new Font(g.getFont().getName(), Font.BOLD, 30); metrics = g.getFontMetrics(font); swidth = metrics.stringWidth(str); sheight = metrics.getMaxAscent()+metrics.getMaxDescent(); x = (3*d.width/2-swidth)/2; y = d.height-sheight; } g.setFont(font); Color oldColor = g.getColor(); g.setColor(Color.gray); g.drawString(str, x, y); g.setColor(oldColor); } public boolean useTransform() { return false; } } public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new MultiViewDemo()); f.pack(); f.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/PersistentLayoutDemo.java000066400000000000000000000107471276402340000322170ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.TestGraphs; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.layout.PersistentLayout; import edu.uci.ics.jung.visualization.layout.PersistentLayoutImpl; /** * Demonstrates the use of PersistentLayout * and PersistentLayoutImpl. * * @author Tom Nelson * */ public class PersistentLayoutDemo { /** * the graph */ Graph graph = TestGraphs.getOneComponentGraph(); /** * the name of the file where the layout is saved */ String fileName; /** * the visual component and renderer for the graph */ VisualizationViewer vv; PersistentLayout persistentLayout; /** * create an instance of a simple graph with controls to * demo the persistence and zoom features. * * @param fileName where to save/restore the graph positions */ public PersistentLayoutDemo(final String fileName) { this.fileName = fileName; // create a simple graph for the demo persistentLayout = new PersistentLayoutImpl(new FRLayout(graph)); vv = new VisualizationViewer(persistentLayout); // add my listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); DefaultModalGraphMouse gm = new DefaultModalGraphMouse(); vv.setGraphMouse(gm); final ScalingControl scaler = new CrossoverScalingControl(); vv.scaleToLayout(scaler); // create a frome to hold the graph final JFrame frame = new JFrame(); frame.getContentPane().add(new GraphZoomScrollPane(vv)); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // create a control panel and buttons for demo // functions JPanel p = new JPanel(); JButton persist = new JButton("Save Layout"); // saves the graph vertex positions to a file persist.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { persistentLayout.persist(fileName); } catch (IOException e1) { System.err.println("got "+e1); } } }); p.add(persist); JButton restore = new JButton("Restore Layout"); // restores the graph vertex positions from a file // if new vertices were added since the last 'persist', // they will be placed at random locations restore.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // PersistentLayout pl = (PersistentLayout) vv.getGraphLayout(); try { persistentLayout.restore(fileName); } catch (Exception e1) { e1.printStackTrace(); } } }); p.add(restore); p.add(gm.getModeComboBox()); frame.getContentPane().add(p, BorderLayout.SOUTH); frame.pack();//setSize(600, 600); frame.setVisible(true); } /** * a driver for this demo * @param args should hold the filename for the persistence demo */ public static void main(String[] args) { String filename; if (args.length >= 1) filename = args[0]; else filename = "PersistentLayoutDemo.out"; new PersistentLayoutDemo(filename); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/PluggableRendererDemo.java000066400000000000000000001264321276402340000322510ustar00rootroot00000000000000/* * Copyright (c) 2004, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * * Created on Nov 7, 2004 */ package edu.uci.ics.jung.samples; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.GradientPaint; import java.awt.GridLayout; import java.awt.Paint; import java.awt.Shape; import java.awt.Stroke; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.geom.Point2D; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.ButtonGroup; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButton; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.generators.random.MixedRandomGraphGenerator; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.scoring.VoltageScorer; import edu.uci.ics.jung.algorithms.scoring.util.VertexScoreTransformer; import edu.uci.ics.jung.algorithms.util.SelfLoopEdgePredicate; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseMultigraph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.AbstractPopupGraphMousePlugin; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.AbstractVertexShapeTransformer; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.decorators.GradientEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.NumberFormattingTransformer; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.picking.PickedInfo; import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.renderers.BasicEdgeArrowRenderingSupport; import edu.uci.ics.jung.visualization.renderers.CachingEdgeRenderer; import edu.uci.ics.jung.visualization.renderers.CachingVertexRenderer; import edu.uci.ics.jung.visualization.renderers.CenterEdgeArrowRenderingSupport; import edu.uci.ics.jung.visualization.renderers.Renderer; import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position; /** * Shows off some of the capabilities of PluggableRenderer. * This code provides examples of different ways to provide and * change the various functions that provide property information * to the renderer. * *

      This demo creates a random mixed-mode graph with random edge * weights using TestGraph.generateMixedRandomGraph. * It then runs VoltageRanker on this graph, using half * of the "seed" vertices from the random graph generation as * voltage sources, and half of them as voltage sinks. * *

      What the controls do: *

        *
      • Mouse controls: *
          *
        • If your mouse has a scroll wheel, scrolling forward zooms out and * scrolling backward zooms in. *
        • Left-clicking on a vertex or edge selects it, and unselects all others. *
        • Middle-clicking on a vertex or edge toggles its selection state. *
        • Right-clicking on a vertex brings up a pop-up menu that allows you to * increase or decrease that vertex's transparency. *
        • Left-clicking on the background allows you to drag the image around. *
        • Hovering over a vertex tells you what its voltage is; hovering over an * edge shows its identity; hovering over the background shows an informational * message. *
        *
      • Vertex stuff: *
          *
        • "vertex seed coloring": if checked, the seed vertices are colored blue, * and all other vertices are colored red. Otherwise, all vertices are colored * a slightly transparent red (except the currently "picked" vertex, which is * colored transparent purple). *
        • "vertex selection stroke highlighting": if checked, the picked vertex * and its neighbors are all drawn with heavy borders. Otherwise, all vertices * are drawn with light borders. *
        • "show vertex ranks (voltages)": if checked, each vertex is labeled with its * calculated 'voltage'. Otherwise, vertices are unlabeled. *
        • "vertex degree shapes": if checked, vertices are drawn with a polygon with * number of sides proportional to its degree. Otherwise, vertices are drawn * as ellipses. *
        • "vertex voltage size": if checked, vertices are drawn with a size * proportional to their voltage ranking. Otherwise, all vertices are drawn * at the same size. *
        • "vertex degree ratio stretch": if checked, vertices are drawn with an * aspect ratio (height/width ratio) proportional to the ratio of their indegree to * their outdegree. Otherwise, vertices are drawn with an aspect ratio of 1. *
        • "filter vertices of degree < 4": if checked, does not display any vertices * (or their incident edges) whose degree in the original graph is less than 4; * otherwise, all vertices are drawn. *
        *
      • Edge stuff: *
          *
        • "edge shape": selects between lines, wedges, quadratic curves, and cubic curves * for drawing edges. *
        • "fill edge shapes": if checked, fills the edge shapes. This will have no effect * if "line" is selected. *
        • "edge paint": selects between solid colored edges, and gradient-painted edges. * Gradient painted edges are darkest in the middle for undirected edges, and darkest * at the destination for directed edges. *
        • "show edges": only edges of the checked types are drawn. *
        • "show arrows": only arrows whose edges are of the checked types are drawn. *
        • "edge weight highlighting": if checked, edges with weight greater than * a threshold value are drawn using thick solid lines, and other edges are drawn * using thin gray dotted lines. (This combines edge stroke and paint.) Otherwise, * all edges are drawn with thin solid lines. *
        • "show edge weights": if checked, edges are labeled with their weights. * Otherwise, edges are not labeled. *
        *
      • Miscellaneous (center panel) *
          *
        • "bold text": if checked, all vertex and edge labels are drawn using a * boldface font. Otherwise, a normal-weight font is used. (Has no effect if * no labels are currently visible.) *
        • zoom controls: *
            *
          • "+" zooms in, "-" zooms out *
          • "zoom at mouse (wheel only)": if checked, zooming (using the mouse * scroll wheel) is centered on the location of the mouse pointer; otherwise, * it is centered on the center of the visualization pane. *
          *
        *
      * * * @author Danyel Fisher, Joshua O'Madadhain, Tom Nelson */ @SuppressWarnings("serial") public class PluggableRendererDemo extends JApplet implements ActionListener { protected JCheckBox v_color; protected JCheckBox e_color; protected JCheckBox v_stroke; protected JCheckBox e_uarrow_pred; protected JCheckBox e_darrow_pred; protected JCheckBox e_arrow_centered; protected JCheckBox v_shape; protected JCheckBox v_size; protected JCheckBox v_aspect; protected JCheckBox v_labels; protected JRadioButton e_line; protected JRadioButton e_bent; protected JRadioButton e_wedge; protected JRadioButton e_quad; protected JRadioButton e_ortho; protected JRadioButton e_cubic; protected JCheckBox e_labels; protected JCheckBox font; protected JCheckBox e_show_d; protected JCheckBox e_show_u; protected JCheckBox v_small; protected JCheckBox zoom_at_mouse; protected JCheckBox fill_edges; protected JRadioButton no_gradient; protected JRadioButton gradient_relative; protected static final int GRADIENT_NONE = 0; protected static final int GRADIENT_RELATIVE = 1; protected static int gradient_level = GRADIENT_NONE; protected SeedFillColor seedFillColor; protected SeedDrawColor seedDrawColor; protected EdgeWeightStrokeFunction ewcs; protected VertexStrokeHighlight vsh; protected Function vs; protected Function vs_none; protected Function es; protected Function es_none; protected VertexFontTransformer vff; protected EdgeFontTransformer eff; protected VertexShapeSizeAspect vssa; protected DirectionDisplayPredicate show_edge; protected DirectionDisplayPredicate show_arrow; protected VertexDisplayPredicate show_vertex; protected Predicate,Number>> self_loop; protected GradientPickedEdgePaintFunction edgeDrawPaint; protected GradientPickedEdgePaintFunction edgeFillPaint; protected final static Object VOLTAGE_KEY = "voltages"; protected final static Object TRANSPARENCY = "transparency"; protected Map edge_weight = new HashMap(); protected Function voltages; protected Map transparency = new HashMap(); protected VisualizationViewer vv; protected DefaultModalGraphMouse gm; protected Set seedVertices = new HashSet(); private Graph graph; public void start() { getContentPane().add( startFunction() ); } public static void main(String[] s ) { JFrame jf = new JFrame(); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JPanel jp = new PluggableRendererDemo().startFunction(); jf.getContentPane().add(jp); jf.pack(); jf.setVisible(true); } public JPanel startFunction() { this.graph = buildGraph(); Layout layout = new FRLayout(graph); vv = new VisualizationViewer(layout); vv.getRenderer().setVertexRenderer(new CachingVertexRenderer(vv)); vv.getRenderer().setEdgeRenderer(new CachingEdgeRenderer(vv)); PickedState picked_state = vv.getPickedVertexState(); self_loop = new SelfLoopEdgePredicate(); // create decorators seedFillColor = new SeedFillColor(picked_state); seedDrawColor = new SeedDrawColor(); ewcs = new EdgeWeightStrokeFunction(edge_weight); vsh = new VertexStrokeHighlight(graph, picked_state); vff = new VertexFontTransformer(); eff = new EdgeFontTransformer(); vs_none = Functions.constant(null); es_none = Functions.constant(null); vssa = new VertexShapeSizeAspect(graph, voltages); show_edge = new DirectionDisplayPredicate(true, true); show_arrow = new DirectionDisplayPredicate(true, false); show_vertex = new VertexDisplayPredicate(false); // uses a gradient edge if unpicked, otherwise uses picked selection edgeDrawPaint = new GradientPickedEdgePaintFunction( new PickableEdgePaintTransformer( vv.getPickedEdgeState(),Color.black,Color.cyan), vv); edgeFillPaint = new GradientPickedEdgePaintFunction( new PickableEdgePaintTransformer( vv.getPickedEdgeState(),Color.black,Color.cyan), vv); vv.getRenderContext().setVertexFillPaintTransformer(seedFillColor); vv.getRenderContext().setVertexDrawPaintTransformer(seedDrawColor); vv.getRenderContext().setVertexStrokeTransformer(vsh); vv.getRenderContext().setVertexLabelTransformer(vs_none); vv.getRenderContext().setVertexFontTransformer(vff); vv.getRenderContext().setVertexShapeTransformer(vssa); vv.getRenderContext().setVertexIncludePredicate(show_vertex); vv.getRenderContext().setEdgeDrawPaintTransformer( edgeDrawPaint ); vv.getRenderContext().setEdgeLabelTransformer(es_none); vv.getRenderContext().setEdgeFontTransformer(eff); vv.getRenderContext().setEdgeStrokeTransformer(ewcs); vv.getRenderContext().setEdgeIncludePredicate(show_edge); vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph)); vv.getRenderContext().setEdgeArrowPredicate(show_arrow); vv.getRenderContext().setArrowFillPaintTransformer(Functions.constant(Color.lightGray)); vv.getRenderContext().setArrowDrawPaintTransformer(Functions.constant(Color.black)); JPanel jp = new JPanel(); jp.setLayout(new BorderLayout()); vv.setBackground(Color.white); GraphZoomScrollPane scrollPane = new GraphZoomScrollPane(vv); jp.add(scrollPane); gm = new DefaultModalGraphMouse(); vv.setGraphMouse(gm); gm.add(new PopupGraphMousePlugin()); addBottomControls( jp ); vssa.setScaling(true); vv.setVertexToolTipTransformer(new VoltageTips()); vv.setToolTipText("
      Use the mouse wheel to zoom

      Click and Drag the mouse to pan

      Shift-click and Drag to Rotate

      "); return jp; } /** * Generates a mixed-mode random graph, runs VoltageRanker on it, and * returns the resultant graph. * @return the generated graph */ public Graph buildGraph() { Supplier> graphFactory = new Supplier>() { public Graph get() { return new SparseMultigraph(); } }; Supplier vertexFactory = new Supplier() { int count; public Integer get() { return count++; }}; Supplier edgeFactory = new Supplier() { int count; public Number get() { return count++; }}; Graph g = MixedRandomGraphGenerator.generateMixedRandomGraph(graphFactory, vertexFactory, edgeFactory, edge_weight, 20, seedVertices); es = new NumberFormattingTransformer(Functions.forMap(edge_weight)); // collect the seeds used to define the random graph if (seedVertices.size() < 2) System.out.println("need at least 2 seeds (one source, one sink)"); // use these seeds as source and sink vertices, run VoltageRanker boolean source = true; Set sources = new HashSet(); Set sinks = new HashSet(); for(Integer v : seedVertices) { if (source) sources.add(v); else sinks.add(v); source = !source; } VoltageScorer voltage_scores = new VoltageScorer(g, Functions.forMap(edge_weight), sources, sinks); voltage_scores.evaluate(); voltages = new VertexScoreTransformer(voltage_scores); vs = new NumberFormattingTransformer(voltages); Collection verts = g.getVertices(); // assign a transparency value of 0.9 to all vertices for(Integer v : verts) { transparency.put(v, new Double(0.9)); } // add a couple of self-loops (sanity check on rendering) Integer v = verts.iterator().next(); Number e = new Float(Math.random()); edge_weight.put(e, e); g.addEdge(e, v, v); e = new Float(Math.random()); edge_weight.put(e, e); g.addEdge(e, v, v); return g; } /** * @param jp panel to which controls will be added */ protected void addBottomControls(final JPanel jp) { final JPanel control_panel = new JPanel(); jp.add(control_panel, BorderLayout.EAST); control_panel.setLayout(new BorderLayout()); final Box vertex_panel = Box.createVerticalBox(); vertex_panel.setBorder(BorderFactory.createTitledBorder("Vertices")); final Box edge_panel = Box.createVerticalBox(); edge_panel.setBorder(BorderFactory.createTitledBorder("Edges")); final Box both_panel = Box.createVerticalBox(); control_panel.add(vertex_panel, BorderLayout.NORTH); control_panel.add(edge_panel, BorderLayout.SOUTH); control_panel.add(both_panel, BorderLayout.CENTER); // set up vertex controls v_color = new JCheckBox("seed highlight"); v_color.addActionListener(this); v_stroke = new JCheckBox("stroke highlight on selection"); v_stroke.addActionListener(this); v_labels = new JCheckBox("show voltage values"); v_labels.addActionListener(this); v_shape = new JCheckBox("shape by degree"); v_shape.addActionListener(this); v_size = new JCheckBox("size by voltage"); v_size.addActionListener(this); v_size.setSelected(true); v_aspect = new JCheckBox("stretch by degree ratio"); v_aspect.addActionListener(this); v_small = new JCheckBox("filter when degree < " + VertexDisplayPredicate.MIN_DEGREE); v_small.addActionListener(this); vertex_panel.add(v_color); vertex_panel.add(v_stroke); vertex_panel.add(v_labels); vertex_panel.add(v_shape); vertex_panel.add(v_size); vertex_panel.add(v_aspect); vertex_panel.add(v_small); // set up edge controls JPanel gradient_panel = new JPanel(new GridLayout(1, 0)); gradient_panel.setBorder(BorderFactory.createTitledBorder("Edge paint")); no_gradient = new JRadioButton("Solid color"); no_gradient.addActionListener(this); no_gradient.setSelected(true); // gradient_absolute = new JRadioButton("Absolute gradient"); // gradient_absolute.addActionListener(this); gradient_relative = new JRadioButton("Gradient"); gradient_relative.addActionListener(this); ButtonGroup bg_grad = new ButtonGroup(); bg_grad.add(no_gradient); bg_grad.add(gradient_relative); //bg_grad.add(gradient_absolute); gradient_panel.add(no_gradient); //gradientGrid.add(gradient_absolute); gradient_panel.add(gradient_relative); JPanel shape_panel = new JPanel(new GridLayout(3,2)); shape_panel.setBorder(BorderFactory.createTitledBorder("Edge shape")); e_line = new JRadioButton("line"); e_line.addActionListener(this); e_line.setSelected(true); // e_bent = new JRadioButton("bent line"); // e_bent.addActionListener(this); e_wedge = new JRadioButton("wedge"); e_wedge.addActionListener(this); e_quad = new JRadioButton("quad curve"); e_quad.addActionListener(this); e_cubic = new JRadioButton("cubic curve"); e_cubic.addActionListener(this); e_ortho = new JRadioButton("orthogonal"); e_ortho.addActionListener(this); ButtonGroup bg_shape = new ButtonGroup(); bg_shape.add(e_line); // bg.add(e_bent); bg_shape.add(e_wedge); bg_shape.add(e_quad); bg_shape.add(e_ortho); bg_shape.add(e_cubic); shape_panel.add(e_line); // shape_panel.add(e_bent); shape_panel.add(e_wedge); shape_panel.add(e_quad); shape_panel.add(e_cubic); shape_panel.add(e_ortho); fill_edges = new JCheckBox("fill edge shapes"); fill_edges.setSelected(false); fill_edges.addActionListener(this); shape_panel.add(fill_edges); shape_panel.setOpaque(true); e_color = new JCheckBox("highlight edge weights"); e_color.addActionListener(this); e_labels = new JCheckBox("show edge weight values"); e_labels.addActionListener(this); e_uarrow_pred = new JCheckBox("undirected"); e_uarrow_pred.addActionListener(this); e_darrow_pred = new JCheckBox("directed"); e_darrow_pred.addActionListener(this); e_darrow_pred.setSelected(true); e_arrow_centered = new JCheckBox("centered"); e_arrow_centered.addActionListener(this); JPanel arrow_panel = new JPanel(new GridLayout(1,0)); arrow_panel.setBorder(BorderFactory.createTitledBorder("Show arrows")); arrow_panel.add(e_uarrow_pred); arrow_panel.add(e_darrow_pred); arrow_panel.add(e_arrow_centered); e_show_d = new JCheckBox("directed"); e_show_d.addActionListener(this); e_show_d.setSelected(true); e_show_u = new JCheckBox("undirected"); e_show_u.addActionListener(this); e_show_u.setSelected(true); JPanel show_edge_panel = new JPanel(new GridLayout(1,0)); show_edge_panel.setBorder(BorderFactory.createTitledBorder("Show edges")); show_edge_panel.add(e_show_u); show_edge_panel.add(e_show_d); shape_panel.setAlignmentX(Component.LEFT_ALIGNMENT); edge_panel.add(shape_panel); gradient_panel.setAlignmentX(Component.LEFT_ALIGNMENT); edge_panel.add(gradient_panel); show_edge_panel.setAlignmentX(Component.LEFT_ALIGNMENT); edge_panel.add(show_edge_panel); arrow_panel.setAlignmentX(Component.LEFT_ALIGNMENT); edge_panel.add(arrow_panel); e_color.setAlignmentX(Component.LEFT_ALIGNMENT); edge_panel.add(e_color); e_labels.setAlignmentX(Component.LEFT_ALIGNMENT); edge_panel.add(e_labels); // set up zoom controls zoom_at_mouse = new JCheckBox("
      zoom at mouse

      (wheel only)

      "); zoom_at_mouse.addActionListener(this); zoom_at_mouse.setSelected(true); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JPanel zoomPanel = new JPanel(); zoomPanel.setBorder(BorderFactory.createTitledBorder("Zoom")); plus.setAlignmentX(Component.CENTER_ALIGNMENT); zoomPanel.add(plus); minus.setAlignmentX(Component.CENTER_ALIGNMENT); zoomPanel.add(minus); zoom_at_mouse.setAlignmentX(Component.CENTER_ALIGNMENT); zoomPanel.add(zoom_at_mouse); JPanel fontPanel = new JPanel(); // add font and zoom controls to center panel font = new JCheckBox("bold text"); font.addActionListener(this); font.setAlignmentX(Component.CENTER_ALIGNMENT); fontPanel.add(font); both_panel.add(zoomPanel); both_panel.add(fontPanel); JComboBox modeBox = gm.getModeComboBox(); modeBox.setAlignmentX(Component.CENTER_ALIGNMENT); JPanel modePanel = new JPanel(new BorderLayout()) { public Dimension getMaximumSize() { return getPreferredSize(); } }; modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode")); modePanel.add(modeBox); JPanel comboGrid = new JPanel(new GridLayout(0,1)); comboGrid.add(modePanel); fontPanel.add(comboGrid); JComboBox cb = new JComboBox(); cb.addItem(Renderer.VertexLabel.Position.N); cb.addItem(Renderer.VertexLabel.Position.NE); cb.addItem(Renderer.VertexLabel.Position.E); cb.addItem(Renderer.VertexLabel.Position.SE); cb.addItem(Renderer.VertexLabel.Position.S); cb.addItem(Renderer.VertexLabel.Position.SW); cb.addItem(Renderer.VertexLabel.Position.W); cb.addItem(Renderer.VertexLabel.Position.NW); cb.addItem(Renderer.VertexLabel.Position.N); cb.addItem(Renderer.VertexLabel.Position.CNTR); cb.addItem(Renderer.VertexLabel.Position.AUTO); cb.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { Renderer.VertexLabel.Position position = (Renderer.VertexLabel.Position)e.getItem(); vv.getRenderer().getVertexLabelRenderer().setPosition(position); vv.repaint(); }}); cb.setSelectedItem(Renderer.VertexLabel.Position.SE); JPanel positionPanel = new JPanel(); positionPanel.setBorder(BorderFactory.createTitledBorder("Label Position")); positionPanel.add(cb); comboGrid.add(positionPanel); } public void actionPerformed(ActionEvent e) { AbstractButton source = (AbstractButton)e.getSource(); if (source == v_color) { seedFillColor.setSeedColoring(source.isSelected()); } else if (source == e_color) { ewcs.setWeighted(source.isSelected()); } else if (source == v_stroke) { vsh.setHighlight(source.isSelected()); } else if (source == v_labels) { if (source.isSelected()) vv.getRenderContext().setVertexLabelTransformer(vs); else vv.getRenderContext().setVertexLabelTransformer(vs_none); } else if (source == e_labels) { if (source.isSelected()) vv.getRenderContext().setEdgeLabelTransformer(es); else vv.getRenderContext().setEdgeLabelTransformer(es_none); } else if (source == e_uarrow_pred) { show_arrow.showUndirected(source.isSelected()); } else if (source == e_darrow_pred) { show_arrow.showDirected(source.isSelected()); } else if (source == e_arrow_centered) { if(source.isSelected()) { vv.getRenderer().getEdgeRenderer().setEdgeArrowRenderingSupport( new CenterEdgeArrowRenderingSupport()); } else { vv.getRenderer().getEdgeRenderer().setEdgeArrowRenderingSupport( new BasicEdgeArrowRenderingSupport()); } } else if (source == font) { vff.setBold(source.isSelected()); eff.setBold(source.isSelected()); } else if (source == v_shape) { vssa.useFunnyShapes(source.isSelected()); } else if (source == v_size) { vssa.setScaling(source.isSelected()); } else if (source == v_aspect) { vssa.setStretching(source.isSelected()); } else if (source == e_line) { if(source.isSelected()) { vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph)); } } else if (source == e_ortho) { if (source.isSelected()) vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.orthogonal(graph)); } else if (source == e_wedge) { if (source.isSelected()) vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.wedge(graph, 10)); } // else if (source == e_bent) // { // if(source.isSelected()) // { // vv.getRenderContext().setEdgeShapeFunction(new EdgeShape.BentLine()); // } // } else if (source == e_quad) { if(source.isSelected()) { vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.quadCurve(graph)); } } else if (source == e_cubic) { if(source.isSelected()) { vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.cubicCurve(graph)); } } else if (source == e_show_d) { show_edge.showDirected(source.isSelected()); } else if (source == e_show_u) { show_edge.showUndirected(source.isSelected()); } else if (source == v_small) { show_vertex.filterSmall(source.isSelected()); } else if(source == zoom_at_mouse) { gm.setZoomAtMouse(source.isSelected()); } else if (source == no_gradient) { if (source.isSelected()) { gradient_level = GRADIENT_NONE; } } else if (source == gradient_relative) { if (source.isSelected()) { gradient_level = GRADIENT_RELATIVE; } } else if (source == fill_edges) { if(source.isSelected()) { vv.getRenderContext().setEdgeFillPaintTransformer( edgeFillPaint ); } else { vv.getRenderContext().setEdgeFillPaintTransformer( Functions.constant(null) ); } } vv.repaint(); } private final class SeedDrawColor implements Function { public Paint apply(V v) { return Color.BLACK; } } private final class SeedFillColor implements Function { protected PickedInfo pi; protected final static float dark_value = 0.8f; protected final static float light_value = 0.2f; protected boolean seed_coloring; public SeedFillColor(PickedInfo pi) { this.pi = pi; seed_coloring = false; } public void setSeedColoring(boolean b) { this.seed_coloring = b; } public Paint apply(V v) { float alpha = transparency.get(v).floatValue(); if (pi.isPicked(v)) { return new Color(1f, 1f, 0, alpha); } else { if (seed_coloring && seedVertices.contains(v)) { Color dark = new Color(0, 0, dark_value, alpha); Color light = new Color(0, 0, light_value, alpha); return new GradientPaint( 0, 0, dark, 10, 0, light, true); } else return new Color(1f, 0, 0, alpha); } } } private final static class EdgeWeightStrokeFunction implements Function { protected static final Stroke basic = new BasicStroke(1); protected static final Stroke heavy = new BasicStroke(2); protected static final Stroke dotted = RenderContext.DOTTED; protected boolean weighted = false; protected Map edge_weight; public EdgeWeightStrokeFunction(Map edge_weight) { this.edge_weight = edge_weight; } public void setWeighted(boolean weighted) { this.weighted = weighted; } public Stroke apply(E e) { if (weighted) { if (drawHeavy(e)) return heavy; else return dotted; } else return basic; } protected boolean drawHeavy(E e) { double value = edge_weight.get(e).doubleValue(); if (value > 0.7) return true; else return false; } } private final static class VertexStrokeHighlight implements Function { protected boolean highlight = false; protected Stroke heavy = new BasicStroke(5); protected Stroke medium = new BasicStroke(3); protected Stroke light = new BasicStroke(1); protected PickedInfo pi; protected Graph graph; public VertexStrokeHighlight(Graph graph, PickedInfo pi) { this.graph = graph; this.pi = pi; } public void setHighlight(boolean highlight) { this.highlight = highlight; } public Stroke apply(V v) { if (highlight) { if (pi.isPicked(v)) return heavy; else { for(V w : graph.getNeighbors(v)) { // for (Iterator iter = graph.getNeighbors(v)v.getNeighbors().iterator(); iter.hasNext(); ) // { // Vertex w = (Vertex)iter.next(); if (pi.isPicked(w)) return medium; } return light; } } else return light; } } private final static class VertexFontTransformer implements Function { protected boolean bold = false; Font f = new Font("Helvetica", Font.PLAIN, 12); Font b = new Font("Helvetica", Font.BOLD, 12); public void setBold(boolean bold) { this.bold = bold; } public Font apply(V v) { if (bold) return b; else return f; } } private final static class EdgeFontTransformer implements Function { protected boolean bold = false; Font f = new Font("Helvetica", Font.PLAIN, 12); Font b = new Font("Helvetica", Font.BOLD, 12); public void setBold(boolean bold) { this.bold = bold; } public Font apply(E e) { if (bold) return b; else return f; } } private final static class DirectionDisplayPredicate implements Predicate,E>> //extends AbstractGraphPredicate { protected boolean show_d; protected boolean show_u; public DirectionDisplayPredicate(boolean show_d, boolean show_u) { this.show_d = show_d; this.show_u = show_u; } public void showDirected(boolean b) { show_d = b; } public void showUndirected(boolean b) { show_u = b; } public boolean apply(Context,E> context) { Graph graph = context.graph; E e = context.element; if (graph.getEdgeType(e) == EdgeType.DIRECTED && show_d) { return true; } if (graph.getEdgeType(e) == EdgeType.UNDIRECTED && show_u) { return true; } return false; } } private final static class VertexDisplayPredicate implements Predicate,V>> // extends AbstractGraphPredicate { protected boolean filter_small; protected final static int MIN_DEGREE = 4; public VertexDisplayPredicate(boolean filter) { this.filter_small = filter; } public void filterSmall(boolean b) { filter_small = b; } public boolean apply(Context,V> context) { Graph graph = context.graph; V v = context.element; // Vertex v = (Vertex)arg0; if (filter_small) return (graph.degree(v) >= MIN_DEGREE); else return true; } } /** * Controls the shape, size, and aspect ratio for each vertex. * * @author Joshua O'Madadhain */ private final static class VertexShapeSizeAspect extends AbstractVertexShapeTransformer implements Function { protected boolean stretch = false; protected boolean scale = false; protected boolean funny_shapes = false; protected Function voltages; protected Graph graph; // protected AffineTransform scaleTransform = new AffineTransform(); public VertexShapeSizeAspect(Graph graphIn, Function voltagesIn) { this.graph = graphIn; this.voltages = voltagesIn; setSizeTransformer(new Function() { public Integer apply(V v) { if (scale) return (int)(voltages.apply(v) * 30) + 20; else return 20; }}); setAspectRatioTransformer(new Function() { public Float apply(V v) { if (stretch) { return (float)(graph.inDegree(v) + 1) / (graph.outDegree(v) + 1); } else { return 1.0f; } }}); } public void setStretching(boolean stretch) { this.stretch = stretch; } public void setScaling(boolean scale) { this.scale = scale; } public void useFunnyShapes(boolean use) { this.funny_shapes = use; } public Shape apply(V v) { if (funny_shapes) { if (graph.degree(v) < 5) { int sides = Math.max(graph.degree(v), 3); return factory.getRegularPolygon(v, sides); } else return factory.getRegularStar(v, graph.degree(v)); } else return factory.getEllipse(v); } } /** * a GraphMousePlugin that offers popup * menu support */ protected class PopupGraphMousePlugin extends AbstractPopupGraphMousePlugin implements MouseListener { public PopupGraphMousePlugin() { this(MouseEvent.BUTTON3_MASK); } public PopupGraphMousePlugin(int modifiers) { super(modifiers); } /** * If this event is over a Vertex, pop up a menu to * allow the user to increase/decrease the voltage * attribute of this Vertex * @param e the event to be handled */ @SuppressWarnings("unchecked") protected void handlePopup(MouseEvent e) { final VisualizationViewer vv = (VisualizationViewer)e.getSource(); Point2D p = e.getPoint();//vv.getRenderContext().getBasicTransformer().inverseViewTransform(e.getPoint()); GraphElementAccessor pickSupport = vv.getPickSupport(); if(pickSupport != null) { final Integer v = pickSupport.getVertex(vv.getGraphLayout(), p.getX(), p.getY()); if(v != null) { JPopupMenu popup = new JPopupMenu(); popup.add(new AbstractAction("Decrease Transparency") { public void actionPerformed(ActionEvent e) { Double value = Math.min(1, transparency.get(v).doubleValue()+0.1); transparency.put(v, value); // transparency.put(v, )transparency.get(v); // MutableDouble value = (MutableDouble)transparency.getNumber(v); // value.setDoubleValue(Math.min(1, value.doubleValue() + 0.1)); vv.repaint(); } }); popup.add(new AbstractAction("Increase Transparency"){ public void actionPerformed(ActionEvent e) { Double value = Math.max(0, transparency.get(v).doubleValue()-0.1); transparency.put(v, value); // MutableDouble value = (MutableDouble)transparency.getNumber(v); // value.setDoubleValue(Math.max(0, value.doubleValue() - 0.1)); vv.repaint(); } }); popup.show(vv, e.getX(), e.getY()); } else { final Number edge = pickSupport.getEdge(vv.getGraphLayout(), p.getX(), p.getY()); if(edge != null) { JPopupMenu popup = new JPopupMenu(); popup.add(new AbstractAction(edge.toString()) { public void actionPerformed(ActionEvent e) { System.err.println("got "+edge); } }); popup.show(vv, e.getX(), e.getY()); } } } } } public class VoltageTips implements Function { public String apply(Integer vertex) { return "Voltage:"+voltages.apply(vertex); } } public class GradientPickedEdgePaintFunction extends GradientEdgePaintTransformer { private Function defaultFunc; protected boolean fill_edge = false; Predicate,E>> selfLoop = new SelfLoopEdgePredicate(); public GradientPickedEdgePaintFunction(Function defaultEdgePaintFunction, VisualizationViewer vv) { super(Color.WHITE, Color.BLACK, vv); this.defaultFunc = defaultEdgePaintFunction; } public void useFill(boolean b) { fill_edge = b; } public Paint apply(E e) { if (gradient_level == GRADIENT_NONE) { return defaultFunc.apply(e); } else { return super.apply(e); } } protected Color getColor2(E e) { return vv.getPickedEdgeState().isPicked(e)? Color.CYAN : c2; } // public Paint getFillPaint(E e) // { // if (selfLoop.evaluateEdge(vv.getGraphLayout().getGraph(), e) || !fill_edge) // return null; // else // return getDrawPaint(e); // } } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/RadialTreeLensDemo.java000066400000000000000000000246061276402340000315160ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.swing.BorderFactory; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JMenuBar; import javax.swing.JPanel; import javax.swing.JRadioButton; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.layout.PolarPoint; import edu.uci.ics.jung.algorithms.layout.RadialTreeLayout; import edu.uci.ics.jung.algorithms.layout.TreeLayout; import edu.uci.ics.jung.graph.DelegateForest; import edu.uci.ics.jung.graph.DelegateTree; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseGraph; import edu.uci.ics.jung.graph.Forest; import edu.uci.ics.jung.graph.Tree; import edu.uci.ics.jung.visualization.DefaultVisualizationModel; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationModel; import edu.uci.ics.jung.visualization.VisualizationServer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalLensGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.transform.LensSupport; import edu.uci.ics.jung.visualization.transform.shape.HyperbolicShapeTransformer; import edu.uci.ics.jung.visualization.transform.shape.ViewLensSupport; /** * Shows a RadialTreeLayout view of a Forest. * A hyperbolic projection lens may also be applied * to the view * * @author Tom Nelson * */ @SuppressWarnings("serial") public class RadialTreeLensDemo extends JApplet { Forest graph; Supplier> graphFactory = new Supplier>() { public DirectedGraph get() { return new DirectedSparseGraph(); } }; Supplier> treeFactory = new Supplier> () { public Tree get() { return new DelegateTree(graphFactory); } }; Supplier edgeFactory = new Supplier() { int i=0; public Integer get() { return i++; } }; Supplier vertexFactory = new Supplier() { int i=0; public String get() { return "V"+i++; } }; VisualizationServer.Paintable rings; String root; TreeLayout layout; RadialTreeLayout radialLayout; /** * the visual component and renderer for the graph */ VisualizationViewer vv; /** * provides a Hyperbolic lens for the view */ LensSupport hyperbolicViewSupport; ScalingControl scaler; /** * create an instance of a simple graph with controls to * demo the zoomand hyperbolic features. * */ public RadialTreeLensDemo() { // create a simple graph for the demo // create a simple graph for the demo graph = new DelegateForest(); createTree(); layout = new TreeLayout(graph); radialLayout = new RadialTreeLayout(graph); radialLayout.setSize(new Dimension(600,600)); Dimension preferredSize = new Dimension(600,600); final VisualizationModel visualizationModel = new DefaultVisualizationModel(radialLayout, preferredSize); vv = new VisualizationViewer(visualizationModel, preferredSize); PickedState ps = vv.getPickedVertexState(); PickedState pes = vv.getPickedEdgeState(); vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(ps, Color.red, Color.yellow)); vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(pes, Color.black, Color.cyan)); vv.setBackground(Color.white); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph)); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); Container content = getContentPane(); GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv); content.add(gzsp); final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); vv.addKeyListener(graphMouse.getModeKeyListener()); rings = new Rings(); vv.addPreRenderPaintable(rings); hyperbolicViewSupport = new ViewLensSupport(vv, new HyperbolicShapeTransformer(vv, vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)), new ModalLensGraphMouse()); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); final JRadioButton hyperView = new JRadioButton("Hyperbolic View"); hyperView.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { hyperbolicViewSupport.activate(e.getStateChange() == ItemEvent.SELECTED); } }); graphMouse.addItemListener(hyperbolicViewSupport.getGraphMouse().getModeListener()); JMenuBar menubar = new JMenuBar(); menubar.add(graphMouse.getModeMenu()); gzsp.setCorner(menubar); JPanel controls = new JPanel(); JPanel zoomControls = new JPanel(new GridLayout(2,1)); zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom")); JPanel hyperControls = new JPanel(new GridLayout(3,2)); hyperControls.setBorder(BorderFactory.createTitledBorder("Examiner Lens")); zoomControls.add(plus); zoomControls.add(minus); JPanel modeControls = new JPanel(new BorderLayout()); modeControls.setBorder(BorderFactory.createTitledBorder("Mouse Mode")); modeControls.add(graphMouse.getModeComboBox()); hyperControls.add(hyperView); controls.add(zoomControls); controls.add(hyperControls); controls.add(modeControls); content.add(controls, BorderLayout.SOUTH); } private void createTree() { graph.addVertex("V0"); graph.addEdge(edgeFactory.get(), "V0", "V1"); graph.addEdge(edgeFactory.get(), "V0", "V2"); graph.addEdge(edgeFactory.get(), "V1", "V4"); graph.addEdge(edgeFactory.get(), "V2", "V3"); graph.addEdge(edgeFactory.get(), "V2", "V5"); graph.addEdge(edgeFactory.get(), "V4", "V6"); graph.addEdge(edgeFactory.get(), "V4", "V7"); graph.addEdge(edgeFactory.get(), "V3", "V8"); graph.addEdge(edgeFactory.get(), "V6", "V9"); graph.addEdge(edgeFactory.get(), "V4", "V10"); graph.addVertex("A0"); graph.addEdge(edgeFactory.get(), "A0", "A1"); graph.addEdge(edgeFactory.get(), "A0", "A2"); graph.addEdge(edgeFactory.get(), "A0", "A3"); graph.addVertex("B0"); graph.addEdge(edgeFactory.get(), "B0", "B1"); graph.addEdge(edgeFactory.get(), "B0", "B2"); graph.addEdge(edgeFactory.get(), "B1", "B4"); graph.addEdge(edgeFactory.get(), "B2", "B3"); graph.addEdge(edgeFactory.get(), "B2", "B5"); graph.addEdge(edgeFactory.get(), "B4", "B6"); graph.addEdge(edgeFactory.get(), "B4", "B7"); graph.addEdge(edgeFactory.get(), "B3", "B8"); graph.addEdge(edgeFactory.get(), "B6", "B9"); } class Rings implements VisualizationServer.Paintable { Collection depths; public Rings() { depths = getDepths(); } private Collection getDepths() { Set depths = new HashSet(); Map polarLocations = radialLayout.getPolarLocations(); for(String v : graph.getVertices()) { PolarPoint pp = polarLocations.get(v); depths.add(pp.getRadius()); } return depths; } public void paint(Graphics g) { g.setColor(Color.gray); Graphics2D g2d = (Graphics2D)g; Point2D center = radialLayout.getCenter(); Ellipse2D ellipse = new Ellipse2D.Double(); for(double d : depths) { ellipse.setFrameFromDiagonal(center.getX()-d, center.getY()-d, center.getX()+d, center.getY()+d); Shape shape = vv.getRenderContext().getMultiLayerTransformer().transform(ellipse); g2d.draw(shape); } } public boolean useTransform() { return true; } } public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new RadialTreeLensDemo()); f.pack(); f.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/SatelliteViewDemo.java000066400000000000000000000301021276402340000314250ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Rectangle; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.GeneralPath; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.ToolTipManager; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.TestGraphs; import edu.uci.ics.jung.visualization.DefaultVisualizationModel; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationModel; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.VisualizationServer.Paintable; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.SatelliteVisualizationViewer; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.renderers.GradientVertexRenderer; import edu.uci.ics.jung.visualization.renderers.Renderer; import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer; /** * Demonstrates the construction of a graph visualization with a main and * a satellite view. The satellite * view is smaller, always contains the entire graph, and contains * a lens shape that shows the boundaries of the visible part of the * graph in the main view. Using the mouse, you can pick, translate, * layout-scale, view-scale, rotate, shear, and region-select in either * view. Using the mouse in either window affects only the main view * and the lens shape in the satellite view. * * @author Tom Nelson * */ @SuppressWarnings("serial") public class SatelliteViewDemo extends JApplet { static final String instructions = ""+ "

      Instructions for Mouse Listeners

      "+ "

      There are two modes, Transforming and Picking."+ "

      The modes are selected with a combo box."+ "

      Transforming Mode:"+ "

        "+ "
      • Mouse1+drag pans the graph"+ "
      • Mouse1+Shift+drag rotates the graph"+ "
      • Mouse1+CTRL(or Command)+drag shears the graph"+ "
      "+ "Picking Mode:"+ "
        "+ "
      • Mouse1 on a Vertex selects the vertex"+ "
      • Mouse1 elsewhere unselects all Vertices"+ "
      • Mouse1+Shift on a Vertex adds/removes Vertex selection"+ "
      • Mouse1+drag on a Vertex moves all selected Vertices"+ "
      • Mouse1+drag elsewhere selects Vertices in a region"+ "
      • Mouse1+Shift+drag adds selection of Vertices in a new region"+ "
      • Mouse1+CTRL on a Vertex selects the vertex and centers the display on it"+ "
      "+ "Both Modes:"+ "
        "+ "
      • Mousewheel scales with a crossover value of 1.0.

        "+ " - scales the graph layout when the combined scale is greater than 1

        "+ " - scales the graph view when the combined scale is less than 1"; JDialog helpDialog; Paintable viewGrid; /** * create an instance of a simple graph in two views with controls to * demo the features. * */ public SatelliteViewDemo() { // create a simple graph for the demo Graph graph = TestGraphs.getOneComponentGraph(); // the preferred sizes for the two views Dimension preferredSize1 = new Dimension(600,600); Dimension preferredSize2 = new Dimension(300, 300); // create one layout for the graph FRLayout layout = new FRLayout(graph); layout.setMaxIterations(500); // create one model that both views will share VisualizationModel vm = new DefaultVisualizationModel(layout, preferredSize1); // create 2 views that share the same model final VisualizationViewer vv1 = new VisualizationViewer(vm, preferredSize1); final SatelliteVisualizationViewer vv2 = new SatelliteVisualizationViewer(vv1, preferredSize2); vv1.setBackground(Color.white); vv1.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv1.getPickedEdgeState(), Color.black, Color.cyan)); vv1.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(vv1.getPickedVertexState(), Color.red, Color.yellow)); vv2.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv2.getPickedEdgeState(), Color.black, Color.cyan)); vv2.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(vv2.getPickedVertexState(), Color.red, Color.yellow)); vv1.getRenderer().setVertexRenderer(new GradientVertexRenderer(Color.red, Color.white, true)); vv1.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); vv1.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR); ScalingControl vv2Scaler = new CrossoverScalingControl(); vv2.scaleToLayout(vv2Scaler); viewGrid = new ViewGrid(vv2, vv1); // add default listener for ToolTips vv1.setVertexToolTipTransformer(new ToStringLabeller()); vv2.setVertexToolTipTransformer(new ToStringLabeller()); vv2.getRenderContext().setVertexLabelTransformer(vv1.getRenderContext().getVertexLabelTransformer()); ToolTipManager.sharedInstance().setDismissDelay(10000); Container content = getContentPane(); Container panel = new JPanel(new BorderLayout()); Container rightPanel = new JPanel(new GridLayout(2,1)); GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv1); panel.add(gzsp); rightPanel.add(new JPanel()); rightPanel.add(vv2); panel.add(rightPanel, BorderLayout.EAST); helpDialog = new JDialog(); helpDialog.getContentPane().add(new JLabel(instructions)); // create a GraphMouse for the main view final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv1.setGraphMouse(graphMouse); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv1, 1.1f, vv1.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv1, 1/1.1f, vv1.getCenter()); } }); JComboBox modeBox = graphMouse.getModeComboBox(); modeBox.addItemListener(((DefaultModalGraphMouse)vv2.getGraphMouse()) .getModeListener()); JCheckBox gridBox = new JCheckBox("Show Grid"); gridBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { showGrid(vv2, e.getStateChange() == ItemEvent.SELECTED); }}); JButton help = new JButton("Help"); help.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { helpDialog.pack(); helpDialog.setVisible(true); } }); JPanel controls = new JPanel(); controls.add(plus); controls.add(minus); controls.add(modeBox); controls.add(gridBox); controls.add(help); content.add(panel); content.add(controls, BorderLayout.SOUTH); } protected void showGrid(VisualizationViewer vv, boolean state) { if(state == true) { vv.addPreRenderPaintable(viewGrid); } else { vv.removePreRenderPaintable(viewGrid); } vv.repaint(); } /** * draws a grid on the SatelliteViewer's lens * @author Tom Nelson * */ static class ViewGrid implements Paintable { VisualizationViewer master; VisualizationViewer vv; public ViewGrid(VisualizationViewer vv, VisualizationViewer master) { this.vv = vv; this.master = master; } public void paint(Graphics g) { ShapeTransformer masterViewTransformer = master.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW); ShapeTransformer masterLayoutTransformer = master.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); ShapeTransformer vvLayoutTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); Rectangle rect = master.getBounds(); GeneralPath path = new GeneralPath(); path.moveTo(rect.x, rect.y); path.lineTo(rect.width,rect.y); path.lineTo(rect.width, rect.height); path.lineTo(rect.x, rect.height); path.lineTo(rect.x, rect.y); for(int i=0; i<=rect.width; i+=rect.width/10) { path.moveTo(rect.x+i, rect.y); path.lineTo(rect.x+i, rect.height); } for(int i=0; i<=rect.height; i+=rect.height/10) { path.moveTo(rect.x, rect.y+i); path.lineTo(rect.width, rect.y+i); } Shape lens = path; lens = masterViewTransformer.inverseTransform(lens); lens = masterLayoutTransformer.inverseTransform(lens); lens = vvLayoutTransformer.transform(lens); Graphics2D g2d = (Graphics2D)g; Color old = g.getColor(); g.setColor(Color.cyan); g2d.draw(lens); path = new GeneralPath(); path.moveTo((float)rect.getMinX(), (float)rect.getCenterY()); path.lineTo((float)rect.getMaxX(), (float)rect.getCenterY()); path.moveTo((float)rect.getCenterX(), (float)rect.getMinY()); path.lineTo((float)rect.getCenterX(), (float)rect.getMaxY()); Shape crosshairShape = path; crosshairShape = masterViewTransformer.inverseTransform(crosshairShape); crosshairShape = masterLayoutTransformer.inverseTransform(crosshairShape); crosshairShape = vvLayoutTransformer.transform(crosshairShape); g.setColor(Color.black); g2d.setStroke(new BasicStroke(3)); g2d.draw(crosshairShape); g.setColor(old); } public boolean useTransform() { return true; } } public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new SatelliteViewDemo()); f.pack(); f.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/ShortestPathDemo.java000066400000000000000000000206261276402340000313060ustar00rootroot00000000000000/* * Created on Jan 2, 2004 */ package edu.uci.ics.jung.samples; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Paint; import java.awt.Stroke; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Point2D; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingConstants; import com.google.common.base.Function; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.generators.random.EppsteinPowerLawGenerator; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.shortestpath.BFSDistanceLabeler; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseMultigraph; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.renderers.Renderer; /** * Demonstrates use of the shortest path algorithm and visualization of the * results. * * @author danyelf */ public class ShortestPathDemo extends JPanel { /** * */ private static final long serialVersionUID = 7526217664458188502L; /** * Starting vertex */ private String mFrom; /** * Ending vertex */ private String mTo; private Graph mGraph; private Set mPred; public ShortestPathDemo() { this.mGraph = getGraph(); setBackground(Color.WHITE); // show graph final Layout layout = new FRLayout(mGraph); final VisualizationViewer vv = new VisualizationViewer(layout); vv.setBackground(Color.WHITE); vv.getRenderContext().setVertexDrawPaintTransformer(new MyVertexDrawPaintFunction()); vv.getRenderContext().setVertexFillPaintTransformer(new MyVertexFillPaintFunction()); vv.getRenderContext().setEdgeDrawPaintTransformer(new MyEdgePaintFunction()); vv.getRenderContext().setEdgeStrokeTransformer(new MyEdgeStrokeFunction()); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); vv.setGraphMouse(new DefaultModalGraphMouse()); vv.addPostRenderPaintable(new VisualizationViewer.Paintable(){ public boolean useTransform() { return true; } public void paint(Graphics g) { if(mPred == null) return; // for all edges, paint edges that are in shortest path for (Number e : layout.getGraph().getEdges()) { if(isBlessed(e)) { String v1 = mGraph.getEndpoints(e).getFirst(); String v2 = mGraph.getEndpoints(e).getSecond(); Point2D p1 = layout.apply(v1); Point2D p2 = layout.apply(v2); p1 = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p1); p2 = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p2); Renderer renderer = vv.getRenderer(); renderer.renderEdge( vv.getRenderContext(), layout, e); } } } }); setLayout(new BorderLayout()); add(vv, BorderLayout.CENTER); // set up controls add(setUpControls(), BorderLayout.SOUTH); } boolean isBlessed( Number e ) { Pair endpoints = mGraph.getEndpoints(e); String v1= endpoints.getFirst() ; String v2= endpoints.getSecond() ; return v1.equals(v2) == false && mPred.contains(v1) && mPred.contains(v2); } /** * @author danyelf */ public class MyEdgePaintFunction implements Function { public Paint apply(Number e) { if ( mPred == null || mPred.size() == 0) return Color.BLACK; if( isBlessed( e )) { return new Color(0.0f, 0.0f, 1.0f, 0.5f);//Color.BLUE; } else { return Color.LIGHT_GRAY; } } } public class MyEdgeStrokeFunction implements Function { protected final Stroke THIN = new BasicStroke(1); protected final Stroke THICK = new BasicStroke(1); public Stroke apply(Number e) { if ( mPred == null || mPred.size() == 0) return THIN; if (isBlessed( e ) ) { return THICK; } else return THIN; } } /** * @author danyelf */ public class MyVertexDrawPaintFunction implements Function { public Paint apply(V v) { return Color.black; } } public class MyVertexFillPaintFunction implements Function { public Paint apply( V v ) { if ( v == mFrom) { return Color.BLUE; } if ( v == mTo ) { return Color.BLUE; } if ( mPred == null ) { return Color.LIGHT_GRAY; } else { if ( mPred.contains(v)) { return Color.RED; } else { return Color.LIGHT_GRAY; } } } } /** * */ private JPanel setUpControls() { JPanel jp = new JPanel(); jp.setBackground(Color.WHITE); jp.setLayout(new BoxLayout(jp, BoxLayout.PAGE_AXIS)); jp.setBorder(BorderFactory.createLineBorder(Color.black, 3)); jp.add( new JLabel("Select a pair of vertices for which a shortest path will be displayed")); JPanel jp2 = new JPanel(); jp2.add(new JLabel("vertex from", SwingConstants.LEFT)); jp2.add(getSelectionBox(true)); jp2.setBackground(Color.white); JPanel jp3 = new JPanel(); jp3.add(new JLabel("vertex to", SwingConstants.LEFT)); jp3.add(getSelectionBox(false)); jp3.setBackground(Color.white); jp.add( jp2 ); jp.add( jp3 ); return jp; } private Component getSelectionBox(final boolean from) { Set s = new TreeSet(); for (String v : mGraph.getVertices()) { s.add(v); } final JComboBox choices = new JComboBox((String[]) s.toArray()); choices.setSelectedIndex(-1); choices.setBackground(Color.WHITE); choices.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String v = (String) choices.getSelectedItem(); if (from) { mFrom = v; } else { mTo = v; } drawShortest(); repaint(); } }); return choices; } /** * */ protected void drawShortest() { if (mFrom == null || mTo == null) { return; } BFSDistanceLabeler bdl = new BFSDistanceLabeler(); bdl.labelDistances(mGraph, mFrom); mPred = new HashSet(); // grab a predecessor String v = mTo; Set prd = bdl.getPredecessors(v); mPred.add( mTo ); while( prd != null && prd.size() > 0) { v = prd.iterator().next(); mPred.add( v ); if ( v == mFrom ) return; prd = bdl.getPredecessors(v); } } public static void main(String[] s) { JFrame jf = new JFrame(); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.getContentPane().add(new ShortestPathDemo()); jf.pack(); jf.setVisible(true); } /** * @return the graph for this demo */ Graph getGraph() { Graph g = new EppsteinPowerLawGenerator( new GraphFactory(), new VertexFactory(), new EdgeFactory(), 26, 50, 50).get(); Set removeMe = new HashSet(); for (String v : g.getVertices()) { if ( g.degree(v) == 0 ) { removeMe.add( v ); } } for(String v : removeMe) { g.removeVertex(v); } return g; } static class GraphFactory implements Supplier> { public Graph get() { return new SparseMultigraph(); } } static class VertexFactory implements Supplier { char a = 'a'; public String get() { return Character.toString(a++); } } static class EdgeFactory implements Supplier { int count; public Number get() { return count++; } } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/ShowLayouts.java000066400000000000000000000237031276402340000303510ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import javax.swing.DefaultListCellRenderer; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JPanel; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.generators.random.MixedRandomGraphGenerator; import edu.uci.ics.jung.algorithms.layout.CircleLayout; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.ISOMLayout; import edu.uci.ics.jung.algorithms.layout.KKLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.SpringLayout; import edu.uci.ics.jung.algorithms.layout.SpringLayout2; import edu.uci.ics.jung.algorithms.layout.util.Relaxer; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseMultigraph; import edu.uci.ics.jung.graph.util.TestGraphs; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.layout.LayoutTransition; import edu.uci.ics.jung.visualization.util.Animator; /** * Demonstrates several of the graph layout algorithms. * Allows the user to interactively select one of several graphs, and one of * several layouts, and visualizes the combination. * * @author Danyel Fisher * @author Joshua O'Madadhain */ @SuppressWarnings("serial") public class ShowLayouts extends JApplet { protected static Graph[] g_array; protected static int graph_index; protected static String[] graph_names = {"Two component graph", "Random mixed-mode graph", "Miscellaneous multicomponent graph", "Random directed acyclic graph", "One component graph", "Chain+isolate graph", "Trivial (disconnected) graph"}; public static class GraphChooser implements ActionListener { private JComboBox layout_combo; public GraphChooser(JComboBox layout_combo) { this.layout_combo = layout_combo; } public void actionPerformed(ActionEvent e) { JComboBox cb = (JComboBox)e.getSource(); graph_index = cb.getSelectedIndex(); layout_combo.setSelectedIndex(layout_combo.getSelectedIndex()); // rebuild the layout } } /** * * @author danyelf */ private static final class LayoutChooser implements ActionListener { private final JComboBox jcb; private final VisualizationViewer vv; private LayoutChooser(JComboBox jcb, VisualizationViewer vv) { super(); this.jcb = jcb; this.vv = vv; } @SuppressWarnings("unchecked") public void actionPerformed(ActionEvent arg0) { Object[] constructorArgs = { g_array[graph_index]}; Class> layoutC = (Class>) jcb.getSelectedItem(); try { Constructor> constructor = layoutC .getConstructor(new Class[] {Graph.class}); Object o = constructor.newInstance(constructorArgs); Layout l = (Layout) o; l.setInitializer(vv.getGraphLayout()); l.setSize(vv.getSize()); LayoutTransition lt = new LayoutTransition(vv, vv.getGraphLayout(), l); Animator animator = new Animator(lt); animator.start(); vv.getRenderContext().getMultiLayerTransformer().setToIdentity(); vv.repaint(); } catch (Exception e) { e.printStackTrace(); } } } @SuppressWarnings({ "rawtypes", "unchecked" }) private static JPanel getGraphPanel() { g_array = (Graph[]) new Graph[graph_names.length]; Supplier> graphFactory = new Supplier>() { public Graph get() { return new SparseMultigraph(); } }; Supplier vertexFactory = new Supplier() { int count; public Integer get() { return count++; }}; Supplier edgeFactory = new Supplier() { int count; public Number get() { return count++; }}; g_array[0] = TestGraphs.createTestGraph(false); g_array[1] = MixedRandomGraphGenerator.generateMixedRandomGraph(graphFactory, vertexFactory, edgeFactory, new HashMap(), 20, new HashSet()); g_array[2] = TestGraphs.getDemoGraph(); g_array[3] = TestGraphs.createDirectedAcyclicGraph(4, 4, 0.3); g_array[4] = TestGraphs.getOneComponentGraph(); g_array[5] = TestGraphs.createChainPlusIsolates(18, 5); g_array[6] = TestGraphs.createChainPlusIsolates(0, 20); Graph g = g_array[4]; // initial graph final VisualizationViewer vv = new VisualizationViewer(new FRLayout(g)); vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(vv.getPickedVertexState(), Color.red, Color.yellow)); final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JButton reset = new JButton("reset"); reset.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Layout layout = vv.getGraphLayout(); layout.initialize(); Relaxer relaxer = vv.getModel().getRelaxer(); if(relaxer != null) { // if(layout instanceof IterativeContext) { relaxer.stop(); relaxer.prerelax(); relaxer.relax(); } }}); JComboBox modeBox = graphMouse.getModeComboBox(); modeBox.addItemListener(((DefaultModalGraphMouse)vv.getGraphMouse()).getModeListener()); JPanel jp = new JPanel(); jp.setBackground(Color.WHITE); jp.setLayout(new BorderLayout()); jp.add(vv, BorderLayout.CENTER); Class[] combos = getCombos(); final JComboBox jcb = new JComboBox(combos); // use a renderer to shorten the layout name presentation jcb.setRenderer(new DefaultListCellRenderer() { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { String valueString = value.toString(); valueString = valueString.substring(valueString.lastIndexOf('.')+1); return super.getListCellRendererComponent(list, valueString, index, isSelected, cellHasFocus); } }); jcb.addActionListener(new LayoutChooser(jcb, vv)); jcb.setSelectedItem(FRLayout.class); JPanel control_panel = new JPanel(new GridLayout(2,1)); JPanel topControls = new JPanel(); JPanel bottomControls = new JPanel(); control_panel.add(topControls); control_panel.add(bottomControls); jp.add(control_panel, BorderLayout.NORTH); final JComboBox graph_chooser = new JComboBox(graph_names); graph_chooser.addActionListener(new GraphChooser(jcb)); topControls.add(jcb); topControls.add(graph_chooser); bottomControls.add(plus); bottomControls.add(minus); bottomControls.add(modeBox); bottomControls.add(reset); return jp; } public void start() { this.getContentPane().add(getGraphPanel()); } /** * @return */ @SuppressWarnings({ "unchecked", "rawtypes" }) private static Class[] getCombos() { List> layouts = new ArrayList>(); layouts.add(KKLayout.class); layouts.add(FRLayout.class); layouts.add(CircleLayout.class); layouts.add(SpringLayout.class); layouts.add(SpringLayout2.class); layouts.add(ISOMLayout.class); return layouts.toArray(new Class[0]); } public static void main(String[] args) { JPanel jp = getGraphPanel(); JFrame jf = new JFrame(); jf.getContentPane().add(jp); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.pack(); jf.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/SimpleGraphDraw.java000066400000000000000000000033141276402340000310750ustar00rootroot00000000000000/* * Copyright (c) 2008, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.samples; import java.io.IOException; import javax.swing.JFrame; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedSparseGraph; import edu.uci.ics.jung.io.PajekNetReader; import edu.uci.ics.jung.visualization.VisualizationViewer; /** * A class that shows the minimal work necessary to load and visualize a graph. */ public class SimpleGraphDraw { @SuppressWarnings({ "rawtypes", "unchecked" }) public static void main(String[] args) throws IOException { JFrame jf = new JFrame(); Graph g = getGraph(); VisualizationViewer vv = new VisualizationViewer(new FRLayout(g)); jf.getContentPane().add(vv); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.pack(); jf.setVisible(true); } /** * Generates a graph: in this case, reads it from the file * "samples/datasetsgraph/simple.net" * @return A sample undirected graph * @throws IOException if there is an error in reading the file */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static Graph getGraph() throws IOException { PajekNetReader pnr = new PajekNetReader(new Supplier(){ public Object get() { return new Object(); }}); Graph g = new UndirectedSparseGraph(); pnr.load("src/main/resources/datasets/simple.net", g); return g; } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/SubLayoutDemo.java000066400000000000000000000361601276402340000306050ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.Point2D; import java.lang.reflect.Constructor; import java.util.Collection; import java.util.HashMap; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.DefaultListCellRenderer; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import edu.uci.ics.jung.algorithms.layout.AggregateLayout; import edu.uci.ics.jung.algorithms.layout.CircleLayout; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.KKLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.SpringLayout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.graph.util.TestGraphs; import edu.uci.ics.jung.visualization.DefaultVisualizationModel; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationModel; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.picking.PickedState; /** * Demonstrates the AggregateLayout * class. In this demo, vertices are visually clustered as they * are selected. The cluster is formed in a new Layout centered at the * middle locations of the selected vertices. The size and layout * algorithm for each new cluster is selectable. * * @author Tom Nelson * */ @SuppressWarnings("serial") public class SubLayoutDemo extends JApplet { String instructions = ""+ "Use the Layout combobox to select the "+ "

        underlying layout."+ "

        Use the SubLayout combobox to select "+ "

        the type of layout for any clusters you create."+ "

        To create clusters, use the mouse to select "+ "

        multiple vertices, either by dragging a region, "+ "

        or by shift-clicking on multiple vertices."+ "

        After you select vertices, use the "+ "

        Cluster Picked button to cluster them using the "+ "

        layout and size specified in the Sublayout comboboxen."+ "

        Use the Uncluster All button to remove all"+ "

        clusters."+ "

        You can drag the cluster with the mouse." + "

        Use the 'Picking'/'Transforming' combo-box to switch"+ "

        between picking and transforming mode."; /** * the graph */ Graph graph; Map,Dimension> sizes = new HashMap,Dimension>(); @SuppressWarnings({ "unchecked", "rawtypes" }) Class[] layoutClasses = new Class[] { CircleLayout.class,SpringLayout.class,FRLayout.class,KKLayout.class }; /** * the visual component and renderer for the graph */ VisualizationViewer vv; AggregateLayout clusteringLayout; Dimension subLayoutSize; PickedState ps; @SuppressWarnings("rawtypes") Class subLayoutType = CircleLayout.class; /** * create an instance of a simple graph with controls to * demo the zoomand hyperbolic features. * */ public SubLayoutDemo() { // create a simple graph for the demo graph = TestGraphs.getOneComponentGraph(); // ClusteringLayout is a decorator class that delegates // to another layout, but can also sepately manage the // layout of sub-sets of vertices in circular clusters. clusteringLayout = new AggregateLayout(new FRLayout(graph)); //new SubLayoutDecorator(new FRLayout(graph)); Dimension preferredSize = new Dimension(600,600); final VisualizationModel visualizationModel = new DefaultVisualizationModel(clusteringLayout, preferredSize); vv = new VisualizationViewer(visualizationModel, preferredSize); ps = vv.getPickedVertexState(); vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv.getPickedEdgeState(), Color.black, Color.red)); vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(vv.getPickedVertexState(), Color.red, Color.yellow)); vv.setBackground(Color.white); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); /** * the regular graph mouse for the normal view */ final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); Container content = getContentPane(); GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv); content.add(gzsp); JComboBox modeBox = graphMouse.getModeComboBox(); modeBox.addItemListener(graphMouse.getModeListener()); graphMouse.setMode(ModalGraphMouse.Mode.PICKING); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JButton cluster = new JButton("Cluster Picked"); cluster.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { clusterPicked(); }}); JButton uncluster = new JButton("UnCluster All"); uncluster.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { uncluster(); }}); JComboBox layoutTypeComboBox = new JComboBox(layoutClasses); layoutTypeComboBox.setRenderer(new DefaultListCellRenderer() { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { String valueString = value.toString(); valueString = valueString.substring(valueString.lastIndexOf('.')+1); return super.getListCellRendererComponent(list, valueString, index, isSelected, cellHasFocus); } }); layoutTypeComboBox.setSelectedItem(FRLayout.class); layoutTypeComboBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { @SuppressWarnings({ "unchecked", "rawtypes" }) Class clazz = (Class)e.getItem(); try { Layout layout = getLayoutFor(clazz, graph); layout.setInitializer(vv.getGraphLayout()); clusteringLayout.setDelegate(layout); vv.setGraphLayout(clusteringLayout); } catch(Exception ex) { ex.printStackTrace(); } } }}); JComboBox subLayoutTypeComboBox = new JComboBox(layoutClasses); subLayoutTypeComboBox.setRenderer(new DefaultListCellRenderer() { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { String valueString = value.toString(); valueString = valueString.substring(valueString.lastIndexOf('.')+1); return super.getListCellRendererComponent(list, valueString, index, isSelected, cellHasFocus); } }); subLayoutTypeComboBox.addItemListener(new ItemListener() { @SuppressWarnings({ "unchecked", "rawtypes" }) public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { subLayoutType = (Class)e.getItem(); } }}); JComboBox subLayoutDimensionComboBox = new JComboBox(new Dimension[]{ new Dimension(75,75), new Dimension(100,100), new Dimension(150,150), new Dimension(200,200), new Dimension(250,250), new Dimension(300,300) } ); subLayoutDimensionComboBox.setRenderer(new DefaultListCellRenderer() { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { String valueString = value.toString(); valueString = valueString.substring(valueString.lastIndexOf('[')); valueString = valueString.replaceAll("idth", ""); valueString = valueString.replaceAll("eight",""); return super.getListCellRendererComponent(list, valueString, index, isSelected, cellHasFocus); } }); subLayoutDimensionComboBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { subLayoutSize = (Dimension)e.getItem(); } }}); subLayoutDimensionComboBox.setSelectedIndex(1); JButton help = new JButton("Help"); help.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog((JComponent)e.getSource(), instructions, "Help", JOptionPane.PLAIN_MESSAGE); } }); Dimension space = new Dimension(20,20); Box controls = Box.createVerticalBox(); controls.add(Box.createRigidArea(space)); JPanel zoomControls = new JPanel(new GridLayout(1,2)); zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom")); zoomControls.add(plus); zoomControls.add(minus); heightConstrain(zoomControls); controls.add(zoomControls); controls.add(Box.createRigidArea(space)); JPanel clusterControls = new JPanel(new GridLayout(0,1)); clusterControls.setBorder(BorderFactory.createTitledBorder("Clustering")); clusterControls.add(cluster); clusterControls.add(uncluster); heightConstrain(clusterControls); controls.add(clusterControls); controls.add(Box.createRigidArea(space)); JPanel layoutControls = new JPanel(new GridLayout(0,1)); layoutControls.setBorder(BorderFactory.createTitledBorder("Layout")); layoutControls.add(layoutTypeComboBox); heightConstrain(layoutControls); controls.add(layoutControls); JPanel subLayoutControls = new JPanel(new GridLayout(0,1)); subLayoutControls.setBorder(BorderFactory.createTitledBorder("SubLayout")); subLayoutControls.add(subLayoutTypeComboBox); subLayoutControls.add(subLayoutDimensionComboBox); heightConstrain(subLayoutControls); controls.add(subLayoutControls); controls.add(Box.createRigidArea(space)); JPanel modePanel = new JPanel(new GridLayout(1,1)); modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode")); modePanel.add(modeBox); heightConstrain(modePanel); controls.add(modePanel); controls.add(Box.createRigidArea(space)); controls.add(help); controls.add(Box.createVerticalGlue()); content.add(controls, BorderLayout.EAST); } private void heightConstrain(Component component) { Dimension d = new Dimension(component.getMaximumSize().width, component.getMinimumSize().height); component.setMaximumSize(d); } @SuppressWarnings({ "rawtypes", "unchecked" }) private Layout getLayoutFor(Class layoutClass, Graph graph) throws Exception { Object[] args = new Object[]{graph}; Constructor constructor = layoutClass.getConstructor(new Class[] {Graph.class}); return constructor.newInstance(args); } private void clusterPicked() { cluster(true); } private void uncluster() { cluster(false); } @SuppressWarnings("unchecked") private void cluster(boolean state) { if(state == true) { // put the picked vertices into a new sublayout Collection picked = ps.getPicked(); if(picked.size() > 1) { Point2D center = new Point2D.Double(); double x = 0; double y = 0; for(String vertex : picked) { Point2D p = clusteringLayout.apply(vertex); x += p.getX(); y += p.getY(); } x /= picked.size(); y /= picked.size(); center.setLocation(x,y); Graph subGraph; try { subGraph = graph.getClass().newInstance(); for(String vertex : picked) { subGraph.addVertex(vertex); Collection incidentEdges = graph.getIncidentEdges(vertex); for(Number edge : incidentEdges) { Pair endpoints = graph.getEndpoints(edge); if(picked.containsAll(endpoints)) { // put this edge into the subgraph subGraph.addEdge(edge, endpoints.getFirst(), endpoints.getSecond()); } } } Layout subLayout = getLayoutFor(subLayoutType, subGraph); subLayout.setInitializer(vv.getGraphLayout()); subLayout.setSize(subLayoutSize); clusteringLayout.put(subLayout,center); vv.setGraphLayout(clusteringLayout); } catch (Exception e) { e.printStackTrace(); } } } else { // remove all sublayouts this.clusteringLayout.removeAll(); vv.setGraphLayout(clusteringLayout); } } public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new SubLayoutDemo()); f.pack(); f.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/TreeCollapseDemo.java000066400000000000000000000303741276402340000312410ustar00rootroot00000000000000package edu.uci.ics.jung.samples; /* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Paint; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.swing.BorderFactory; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JToggleButton; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.PolarPoint; import edu.uci.ics.jung.algorithms.layout.RadialTreeLayout; import edu.uci.ics.jung.algorithms.layout.TreeLayout; import edu.uci.ics.jung.graph.DelegateForest; import edu.uci.ics.jung.graph.DelegateTree; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Forest; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.Tree; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationServer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.subLayout.TreeCollapser; /** * Demonstrates "collapsing"/"expanding" of a tree's subtrees. * @author Tom Nelson * */ @SuppressWarnings("serial") public class TreeCollapseDemo extends JApplet { /** * the graph */ Forest graph; Supplier> graphFactory = new Supplier>() { public DirectedGraph get() { return new DirectedSparseMultigraph(); } }; Supplier> treeFactory = new Supplier> () { public Tree get() { return new DelegateTree(graphFactory); } }; Supplier edgeFactory = new Supplier() { int i=0; public Integer get() { return i++; }}; Supplier vertexFactory = new Supplier() { int i=0; public String get() { return "V"+i++; }}; /** * the visual component and renderer for the graph */ VisualizationViewer vv; VisualizationServer.Paintable rings; String root; TreeLayout layout; FRLayout layout1; TreeCollapser collapser; RadialTreeLayout radialLayout; public TreeCollapseDemo() { // create a simple graph for the demo graph = new DelegateForest(); createTree(); layout = new TreeLayout(graph); collapser = new TreeCollapser(); radialLayout = new RadialTreeLayout(graph); radialLayout.setSize(new Dimension(600,600)); vv = new VisualizationViewer(layout, new Dimension(600,600)); vv.setBackground(Color.white); vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph)); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); vv.getRenderContext().setVertexShapeTransformer(new ClusterVertexShapeFunction()); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); vv.getRenderContext().setArrowFillPaintTransformer(Functions.constant(Color.lightGray)); rings = new Rings(); Container content = getContentPane(); final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); content.add(panel); final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); JComboBox modeBox = graphMouse.getModeComboBox(); modeBox.addItemListener(graphMouse.getModeListener()); graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JToggleButton radial = new JToggleButton("Radial"); radial.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { vv.setGraphLayout(radialLayout); vv.getRenderContext().getMultiLayerTransformer().setToIdentity(); vv.addPreRenderPaintable(rings); } else { vv.setGraphLayout(layout); vv.getRenderContext().getMultiLayerTransformer().setToIdentity(); vv.removePreRenderPaintable(rings); } vv.repaint(); }}); JButton collapse = new JButton("Collapse"); collapse.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Collection picked = new HashSet(vv.getPickedVertexState().getPicked()); if(picked.size() == 1) { Object root = picked.iterator().next(); Forest inGraph = (Forest)layout.getGraph(); try { collapser.collapse(vv.getGraphLayout(), inGraph, root); } catch (InstantiationException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (IllegalAccessException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } vv.getPickedVertexState().clear(); vv.repaint(); } }}); JButton expand = new JButton("Expand"); expand.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Collection picked = vv.getPickedVertexState().getPicked(); for(Object v : picked) { if(v instanceof Forest) { Forest inGraph = (Forest)layout.getGraph(); collapser.expand(inGraph, (Forest)v); } vv.getPickedVertexState().clear(); vv.repaint(); } }}); JPanel scaleGrid = new JPanel(new GridLayout(1,0)); scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom")); JPanel controls = new JPanel(); scaleGrid.add(plus); scaleGrid.add(minus); controls.add(radial); controls.add(scaleGrid); controls.add(modeBox); controls.add(collapse); controls.add(expand); content.add(controls, BorderLayout.SOUTH); } class Rings implements VisualizationServer.Paintable { Collection depths; public Rings() { depths = getDepths(); } private Collection getDepths() { Set depths = new HashSet(); Map polarLocations = radialLayout.getPolarLocations(); for(String v : graph.getVertices()) { PolarPoint pp = polarLocations.get(v); depths.add(pp.getRadius()); } return depths; } public void paint(Graphics g) { g.setColor(Color.lightGray); Graphics2D g2d = (Graphics2D)g; Point2D center = radialLayout.getCenter(); Ellipse2D ellipse = new Ellipse2D.Double(); for(double d : depths) { ellipse.setFrameFromDiagonal(center.getX()-d, center.getY()-d, center.getX()+d, center.getY()+d); Shape shape = vv.getRenderContext(). getMultiLayerTransformer().getTransformer(Layer.LAYOUT).transform(ellipse); g2d.draw(shape); } } public boolean useTransform() { return true; } } /** * */ private void createTree() { graph.addVertex("V0"); graph.addEdge(edgeFactory.get(), "V0", "V1"); graph.addEdge(edgeFactory.get(), "V0", "V2"); graph.addEdge(edgeFactory.get(), "V1", "V4"); graph.addEdge(edgeFactory.get(), "V2", "V3"); graph.addEdge(edgeFactory.get(), "V2", "V5"); graph.addEdge(edgeFactory.get(), "V4", "V6"); graph.addEdge(edgeFactory.get(), "V4", "V7"); graph.addEdge(edgeFactory.get(), "V3", "V8"); graph.addEdge(edgeFactory.get(), "V6", "V9"); graph.addEdge(edgeFactory.get(), "V4", "V10"); graph.addVertex("A0"); graph.addEdge(edgeFactory.get(), "A0", "A1"); graph.addEdge(edgeFactory.get(), "A0", "A2"); graph.addEdge(edgeFactory.get(), "A0", "A3"); graph.addVertex("B0"); graph.addEdge(edgeFactory.get(), "B0", "B1"); graph.addEdge(edgeFactory.get(), "B0", "B2"); graph.addEdge(edgeFactory.get(), "B1", "B4"); graph.addEdge(edgeFactory.get(), "B2", "B3"); graph.addEdge(edgeFactory.get(), "B2", "B5"); graph.addEdge(edgeFactory.get(), "B4", "B6"); graph.addEdge(edgeFactory.get(), "B4", "B7"); graph.addEdge(edgeFactory.get(), "B3", "B8"); graph.addEdge(edgeFactory.get(), "B6", "B9"); } /** * a demo class that will create a vertex shape that is either a * polygon or star. The number of sides corresponds to the number * of vertices that were collapsed into the vertex represented by * this shape. * * @author Tom Nelson * * @param the vertex type */ class ClusterVertexShapeFunction extends EllipseVertexShapeTransformer { ClusterVertexShapeFunction() { setSizeTransformer(new ClusterVertexSizeFunction(20)); } @Override public Shape apply(V v) { if(v instanceof Graph) { @SuppressWarnings("rawtypes") int size = ((Graph)v).getVertexCount(); if (size < 8) { int sides = Math.max(size, 3); return factory.getRegularPolygon(v, sides); } else { return factory.getRegularStar(v, size); } } return super.apply(v); } } /** * A demo class that will make vertices larger if they represent * a collapsed collection of original vertices * @author Tom Nelson * * @param the vertex type */ class ClusterVertexSizeFunction implements Function { int size; public ClusterVertexSizeFunction(Integer size) { this.size = size; } public Integer apply(V v) { if(v instanceof Graph) { return 30; } return size; } } public static void main(String[] args) { JFrame frame = new JFrame(); Container content = frame.getContentPane(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); content.add(new TreeCollapseDemo()); frame.pack(); frame.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/TreeLayoutDemo.java000066400000000000000000000222371276402340000307530ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Paint; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.swing.BorderFactory; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JToggleButton; import com.google.common.base.Functions; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.layout.PolarPoint; import edu.uci.ics.jung.algorithms.layout.RadialTreeLayout; import edu.uci.ics.jung.algorithms.layout.TreeLayout; import edu.uci.ics.jung.graph.DelegateForest; import edu.uci.ics.jung.graph.DelegateTree; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Forest; import edu.uci.ics.jung.graph.Tree; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationServer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.layout.LayoutTransition; import edu.uci.ics.jung.visualization.util.Animator; /** * Demonsrates TreeLayout and RadialTreeLayout. * @author Tom Nelson * */ @SuppressWarnings("serial") public class TreeLayoutDemo extends JApplet { /** * the graph */ Forest graph; Supplier> graphFactory = new Supplier>() { public DirectedGraph get() { return new DirectedSparseMultigraph(); } }; Supplier> treeFactory = new Supplier> () { public Tree get() { return new DelegateTree(graphFactory); } }; Supplier edgeFactory = new Supplier() { int i=0; public Integer get() { return i++; }}; Supplier vertexFactory = new Supplier() { int i=0; public String get() { return "V"+i++; }}; /** * the visual component and renderer for the graph */ VisualizationViewer vv; VisualizationServer.Paintable rings; String root; TreeLayout treeLayout; RadialTreeLayout radialLayout; public TreeLayoutDemo() { // create a simple graph for the demo graph = new DelegateForest(); createTree(); treeLayout = new TreeLayout(graph); radialLayout = new RadialTreeLayout(graph); radialLayout.setSize(new Dimension(600,600)); vv = new VisualizationViewer(treeLayout, new Dimension(600,600)); vv.setBackground(Color.white); vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph)); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); vv.getRenderContext().setArrowFillPaintTransformer(Functions.constant(Color.lightGray)); rings = new Rings(); Container content = getContentPane(); final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); content.add(panel); final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); JComboBox modeBox = graphMouse.getModeComboBox(); modeBox.addItemListener(graphMouse.getModeListener()); graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JToggleButton radial = new JToggleButton("Radial"); radial.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { LayoutTransition lt = new LayoutTransition(vv, treeLayout, radialLayout); Animator animator = new Animator(lt); animator.start(); vv.getRenderContext().getMultiLayerTransformer().setToIdentity(); vv.addPreRenderPaintable(rings); } else { LayoutTransition lt = new LayoutTransition(vv, radialLayout, treeLayout); Animator animator = new Animator(lt); animator.start(); vv.getRenderContext().getMultiLayerTransformer().setToIdentity(); vv.removePreRenderPaintable(rings); } vv.repaint(); }}); JPanel scaleGrid = new JPanel(new GridLayout(1,0)); scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom")); JPanel controls = new JPanel(); scaleGrid.add(plus); scaleGrid.add(minus); controls.add(radial); controls.add(scaleGrid); controls.add(modeBox); content.add(controls, BorderLayout.SOUTH); } class Rings implements VisualizationServer.Paintable { Collection depths; public Rings() { depths = getDepths(); } private Collection getDepths() { Set depths = new HashSet(); Map polarLocations = radialLayout.getPolarLocations(); for(String v : graph.getVertices()) { PolarPoint pp = polarLocations.get(v); depths.add(pp.getRadius()); } return depths; } public void paint(Graphics g) { g.setColor(Color.lightGray); Graphics2D g2d = (Graphics2D)g; Point2D center = radialLayout.getCenter(); Ellipse2D ellipse = new Ellipse2D.Double(); for(double d : depths) { ellipse.setFrameFromDiagonal(center.getX()-d, center.getY()-d, center.getX()+d, center.getY()+d); Shape shape = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).transform(ellipse); g2d.draw(shape); } } public boolean useTransform() { return true; } } /** * */ private void createTree() { graph.addVertex("V0"); graph.addEdge(edgeFactory.get(), "V0", "V1"); graph.addEdge(edgeFactory.get(), "V0", "V2"); graph.addEdge(edgeFactory.get(), "V1", "V4"); graph.addEdge(edgeFactory.get(), "V2", "V3"); graph.addEdge(edgeFactory.get(), "V2", "V5"); graph.addEdge(edgeFactory.get(), "V4", "V6"); graph.addEdge(edgeFactory.get(), "V4", "V7"); graph.addEdge(edgeFactory.get(), "V3", "V8"); graph.addEdge(edgeFactory.get(), "V6", "V9"); graph.addEdge(edgeFactory.get(), "V4", "V10"); graph.addVertex("A0"); graph.addEdge(edgeFactory.get(), "A0", "A1"); graph.addEdge(edgeFactory.get(), "A0", "A2"); graph.addEdge(edgeFactory.get(), "A0", "A3"); graph.addVertex("B0"); graph.addEdge(edgeFactory.get(), "B0", "B1"); graph.addEdge(edgeFactory.get(), "B0", "B2"); graph.addEdge(edgeFactory.get(), "B1", "B4"); graph.addEdge(edgeFactory.get(), "B2", "B3"); graph.addEdge(edgeFactory.get(), "B2", "B5"); graph.addEdge(edgeFactory.get(), "B4", "B6"); graph.addEdge(edgeFactory.get(), "B4", "B7"); graph.addEdge(edgeFactory.get(), "B3", "B8"); graph.addEdge(edgeFactory.get(), "B6", "B9"); } public static void main(String[] args) { JFrame frame = new JFrame(); Container content = frame.getContentPane(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); content.add(new TreeLayoutDemo()); frame.pack(); frame.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/TwoModelDemo.java000066400000000000000000000157001276402340000304050ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.BorderFactory; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.ISOMLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.TestGraphs; import edu.uci.ics.jung.visualization.DefaultVisualizationModel; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationModel; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.picking.MultiPickedState; import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * Demonstrates a single graph with 2 layouts in 2 views. * They share picking, transforms, and a pluggable renderer * * @author Tom Nelson * */ @SuppressWarnings("serial") public class TwoModelDemo extends JApplet { /** * the graph */ Graph graph; /** * the visual components and renderers for the graph */ VisualizationViewer vv1; VisualizationViewer vv2; /** * the normal Function */ MutableTransformer layoutTransformer; Dimension preferredSize = new Dimension(300,300); /** * create an instance of a simple graph in two views with controls to * demo the zoom features. * */ public TwoModelDemo() { // create a simple graph for the demo // both models will share one graph graph = TestGraphs.getOneComponentGraph(); // create two layouts for the one graph, one layout for each model Layout layout1 = new FRLayout(graph); Layout layout2 = new ISOMLayout(graph); // create the two models, each with a different layout VisualizationModel vm1 = new DefaultVisualizationModel(layout1, preferredSize); VisualizationModel vm2 = new DefaultVisualizationModel(layout2, preferredSize); // create the two views, one for each model // they share the same renderer vv1 = new VisualizationViewer(vm1, preferredSize); vv2 = new VisualizationViewer(vm2, preferredSize); vv1.setRenderContext(vv2.getRenderContext()); // share the model Function between the two models // layoutTransformer = vv1.getLayoutTransformer(); // vv2.setLayoutTransformer(layoutTransformer); // // // share the view Function between the two models // vv2.setViewTransformer(vv1.getViewTransformer()); vv2.getRenderContext().setMultiLayerTransformer(vv1.getRenderContext().getMultiLayerTransformer()); vv2.getRenderContext().getMultiLayerTransformer().addChangeListener(vv1); vv1.setBackground(Color.white); vv2.setBackground(Color.white); // share one PickedState between the two views PickedState ps = new MultiPickedState(); vv1.setPickedVertexState(ps); vv2.setPickedVertexState(ps); PickedState pes = new MultiPickedState(); vv1.setPickedEdgeState(pes); vv2.setPickedEdgeState(pes); // set an edge paint function that will show picking for edges vv1.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv1.getPickedEdgeState(), Color.black, Color.red)); vv1.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(vv1.getPickedVertexState(), Color.red, Color.yellow)); // add default listeners for ToolTips vv1.setVertexToolTipTransformer(new ToStringLabeller()); vv2.setVertexToolTipTransformer(new ToStringLabeller()); Container content = getContentPane(); JPanel panel = new JPanel(new GridLayout(1,0)); panel.add(new GraphZoomScrollPane(vv1)); panel.add(new GraphZoomScrollPane(vv2)); content.add(panel); // create a GraphMouse for each view final DefaultModalGraphMouse gm1 = new DefaultModalGraphMouse(); DefaultModalGraphMouse gm2 = new DefaultModalGraphMouse(); vv1.setGraphMouse(gm1); vv2.setGraphMouse(gm2); // create zoom buttons for scaling the Function that is // shared between the two models. final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv1, 1.1f, vv1.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv1, 1/1.1f, vv1.getCenter()); } }); JPanel zoomPanel = new JPanel(new GridLayout(1,2)); zoomPanel.setBorder(BorderFactory.createTitledBorder("Zoom")); JPanel modePanel = new JPanel(); modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode")); gm1.getModeComboBox().addItemListener(gm2.getModeListener()); modePanel.add(gm1.getModeComboBox()); JPanel controls = new JPanel(); zoomPanel.add(plus); zoomPanel.add(minus); controls.add(zoomPanel); controls.add(modePanel); content.add(controls, BorderLayout.SOUTH); } public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new TwoModelDemo()); f.pack(); f.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/UnicodeLabelDemo.java000066400000000000000000000235751276402340000312120ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.HashMap; import java.util.Map; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JPanel; import com.google.common.base.Function; import com.google.common.base.Functions; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.graph.DirectedSparseGraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.decorators.VertexIconShapeTransformer; import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer; import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer; /** * A demo that shows flag images as vertices, and uses unicode * to render vertex labels. * * @author Tom Nelson * */ public class UnicodeLabelDemo { /** * the graph */ Graph graph; /** * the visual component and renderer for the graph */ VisualizationViewer vv; boolean showLabels; public UnicodeLabelDemo() { // create a simple graph for the demo graph = new DirectedSparseGraph(); Integer[] v = createVertices(10); createEdges(v); Map iconMap = new HashMap(); vv = new VisualizationViewer(new FRLayout(graph)); vv.getRenderContext().setVertexLabelTransformer(new UnicodeVertexStringer(v)); vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.cyan)); vv.getRenderContext().setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.cyan)); VertexIconShapeTransformer vertexIconShapeFunction = new VertexIconShapeTransformer(new EllipseVertexShapeTransformer()); Function vertexIconFunction = Functions.forMap(iconMap); vv.getRenderContext().setVertexShapeTransformer(vertexIconShapeFunction); vv.getRenderContext().setVertexIconTransformer(vertexIconFunction); loadImages(v, iconMap); vertexIconShapeFunction.setIconMap(iconMap); vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(vv.getPickedVertexState(), Color.white, Color.yellow)); vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv.getPickedEdgeState(), Color.black, Color.lightGray)); vv.setBackground(Color.white); // add my listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); // create a frome to hold the graph final JFrame frame = new JFrame(); Container content = frame.getContentPane(); final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); content.add(panel); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final DefaultModalGraphMouse gm = new DefaultModalGraphMouse(); vv.setGraphMouse(gm); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JCheckBox lo = new JCheckBox("Show Labels"); lo.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { showLabels = e.getStateChange() == ItemEvent.SELECTED; vv.repaint(); } }); lo.setSelected(true); JPanel controls = new JPanel(); controls.add(plus); controls.add(minus); controls.add(lo); controls.add(gm.getModeComboBox()); content.add(controls, BorderLayout.SOUTH); frame.pack(); frame.setVisible(true); } class UnicodeVertexStringer implements Function { Map map = new HashMap(); Map iconMap = new HashMap(); String[] labels = { "\u0057\u0065\u006C\u0063\u006F\u006D\u0065\u0020\u0074\u006F\u0020JUNG\u0021", "\u6B22\u8FCE\u4F7F\u7528\u0020\u0020JUNG\u0021", "\u0414\u043E\u0431\u0440\u043E\u0020\u043F\u043E\u0436\u0430\u043B\u043E\u0432\u0430\u0422\u044A\u0020\u0432\u0020JUNG\u0021", "\u0042\u0069\u0065\u006E\u0076\u0065\u006E\u0075\u0065\u0020\u0061\u0075\u0020JUNG\u0021", "\u0057\u0069\u006C\u006B\u006F\u006D\u006D\u0065\u006E\u0020\u007A\u0075\u0020JUNG\u0021", "JUNG\u3078\u3087\u3045\u3053\u305D\u0021", // "\u0053\u00E9\u006A\u0061\u0020\u0042\u0065\u006D\u0076\u0069\u006E\u0064\u006F\u0020JUNG\u0021", "\u0042\u0069\u0065\u006E\u0076\u0065\u006E\u0069\u0064\u0061\u0020\u0061\u0020JUNG\u0021" }; public UnicodeVertexStringer(V[] vertices) { for(int i=0; i imageMap) { ImageIcon[] icons = null; try { icons = new ImageIcon[] { new ImageIcon(getClass().getResource("/images/united-states.gif")), new ImageIcon(getClass().getResource("/images/china.gif")), new ImageIcon(getClass().getResource("/images/russia.gif")), new ImageIcon(getClass().getResource("/images/france.gif")), new ImageIcon(getClass().getResource("/images/germany.gif")), new ImageIcon(getClass().getResource("/images/japan.gif")), new ImageIcon(getClass().getResource("/images/spain.gif")) }; } catch(Exception ex) { System.err.println("You need flags.jar in your classpath to see the flag icons."); } for(int i=0; icons != null && i} for the collapsed vertices. * * @author Tom Nelson * */ @SuppressWarnings({"serial", "rawtypes", "unchecked"}) public class VertexCollapseDemo extends JApplet { String instructions = "Use the mouse to select multiple vertices"+ "

        either by dragging a region, or by shift-clicking"+ "

        on multiple vertices."+ "

        After you select vertices, use the Collapse button"+ "

        to combine them into a single vertex."+ "

        Select a 'collapsed' vertex and use the Expand button"+ "

        to restore the collapsed vertices."+ "

        The Restore button will restore the original graph."+ "

        If you select 2 (and only 2) vertices, then press"+ "

        the Compress Edges button, parallel edges between"+ "

        those two vertices will no longer be expanded."+ "

        If you select 2 (and only 2) vertices, then press"+ "

        the Expand Edges button, parallel edges between"+ "

        those two vertices will be expanded."+ "

        You can drag the vertices with the mouse." + "

        Use the 'Picking'/'Transforming' combo-box to switch"+ "

        between picking and transforming mode."; /** * the graph */ Graph graph; /** * the visual component and renderer for the graph */ VisualizationViewer vv; Layout layout; GraphCollapser collapser; public VertexCollapseDemo() { // create a simple graph for the demo graph = TestGraphs.getOneComponentGraph(); collapser = new GraphCollapser(graph); layout = new FRLayout(graph); Dimension preferredSize = new Dimension(400,400); final VisualizationModel visualizationModel = new DefaultVisualizationModel(layout, preferredSize); vv = new VisualizationViewer(visualizationModel, preferredSize); vv.getRenderContext().setVertexShapeTransformer(new ClusterVertexShapeFunction()); final PredicatedParallelEdgeIndexFunction eif = PredicatedParallelEdgeIndexFunction.getInstance(); final Set exclusions = new HashSet(); eif.setPredicate(new Predicate() { public boolean apply(Object e) { return exclusions.contains(e); }}); vv.getRenderContext().setParallelEdgeIndexFunction(eif); vv.setBackground(Color.white); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller() { @Override public String apply(Object v) { if(v instanceof Graph) { return ((Graph)v).getVertices().toString(); } return super.apply(v); }}); /** * the regular graph mouse for the normal view */ final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); Container content = getContentPane(); GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv); content.add(gzsp); JComboBox modeBox = graphMouse.getModeComboBox(); modeBox.addItemListener(graphMouse.getModeListener()); graphMouse.setMode(ModalGraphMouse.Mode.PICKING); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JButton collapse = new JButton("Collapse"); collapse.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Collection picked = new HashSet(vv.getPickedVertexState().getPicked()); if(picked.size() > 1) { Graph inGraph = layout.getGraph(); Graph clusterGraph = collapser.getClusterGraph(inGraph, picked); Graph g = collapser.collapse(layout.getGraph(), clusterGraph); double sumx = 0; double sumy = 0; for(Object v : picked) { Point2D p = (Point2D)layout.apply(v); sumx += p.getX(); sumy += p.getY(); } Point2D cp = new Point2D.Double(sumx/picked.size(), sumy/picked.size()); vv.getRenderContext().getParallelEdgeIndexFunction().reset(); layout.setGraph(g); layout.setLocation(clusterGraph, cp); vv.getPickedVertexState().clear(); vv.repaint(); } }}); JButton compressEdges = new JButton("Compress Edges"); compressEdges.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Collection picked = vv.getPickedVertexState().getPicked(); if(picked.size() == 2) { Pair pair = new Pair(picked); Graph graph = layout.getGraph(); Collection edges = new HashSet(graph.getIncidentEdges(pair.getFirst())); edges.retainAll(graph.getIncidentEdges(pair.getSecond())); exclusions.addAll(edges); vv.repaint(); } }}); JButton expandEdges = new JButton("Expand Edges"); expandEdges.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Collection picked = vv.getPickedVertexState().getPicked(); if(picked.size() == 2) { Pair pair = new Pair(picked); Graph graph = layout.getGraph(); Collection edges = new HashSet(graph.getIncidentEdges(pair.getFirst())); edges.retainAll(graph.getIncidentEdges(pair.getSecond())); exclusions.removeAll(edges); vv.repaint(); } }}); JButton expand = new JButton("Expand"); expand.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Collection picked = new HashSet(vv.getPickedVertexState().getPicked()); for(Object v : picked) { if(v instanceof Graph) { Graph g = collapser.expand(layout.getGraph(), (Graph)v); vv.getRenderContext().getParallelEdgeIndexFunction().reset(); layout.setGraph(g); } vv.getPickedVertexState().clear(); vv.repaint(); } }}); JButton reset = new JButton("Reset"); reset.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { layout.setGraph(graph); exclusions.clear(); vv.repaint(); }}); JButton help = new JButton("Help"); help.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog((JComponent)e.getSource(), instructions, "Help", JOptionPane.PLAIN_MESSAGE); } }); JPanel controls = new JPanel(); JPanel zoomControls = new JPanel(new GridLayout(2,1)); zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom")); zoomControls.add(plus); zoomControls.add(minus); controls.add(zoomControls); JPanel collapseControls = new JPanel(new GridLayout(3,1)); collapseControls.setBorder(BorderFactory.createTitledBorder("Picked")); collapseControls.add(collapse); collapseControls.add(expand); collapseControls.add(compressEdges); collapseControls.add(expandEdges); collapseControls.add(reset); controls.add(collapseControls); controls.add(modeBox); controls.add(help); content.add(controls, BorderLayout.SOUTH); } /** * a demo class that will create a vertex shape that is either a * polygon or star. The number of sides corresponds to the number * of vertices that were collapsed into the vertex represented by * this shape. * * @author Tom Nelson * * @param the vertex type */ class ClusterVertexShapeFunction extends EllipseVertexShapeTransformer { ClusterVertexShapeFunction() { setSizeTransformer(new ClusterVertexSizeFunction(20)); } @Override public Shape apply(V v) { if(v instanceof Graph) { int size = ((Graph)v).getVertexCount(); if (size < 8) { int sides = Math.max(size, 3); return factory.getRegularPolygon(v, sides); } else { return factory.getRegularStar(v, size); } } return super.apply(v); } } /** * A demo class that will make vertices larger if they represent * a collapsed collection of original vertices * @author Tom Nelson * * @param the vertex type */ class ClusterVertexSizeFunction implements Function { int size; public ClusterVertexSizeFunction(Integer size) { this.size = size; } public Integer apply(V v) { if(v instanceof Graph) { return 30; } return size; } } public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new VertexCollapseDemo()); f.pack(); f.setVisible(true); } } VertexCollapseDemoWithLayouts.java000066400000000000000000000375731276402340000337650ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Point2D; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.BorderFactory; import javax.swing.DefaultListCellRenderer; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import com.google.common.base.Function; import com.google.common.base.Predicate; import edu.uci.ics.jung.algorithms.layout.CircleLayout; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.ISOMLayout; import edu.uci.ics.jung.algorithms.layout.KKLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.SpringLayout; import edu.uci.ics.jung.algorithms.layout.SpringLayout2; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.graph.util.TestGraphs; import edu.uci.ics.jung.visualization.DefaultVisualizationModel; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationModel; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.layout.LayoutTransition; import edu.uci.ics.jung.visualization.subLayout.GraphCollapser; import edu.uci.ics.jung.visualization.util.Animator; import edu.uci.ics.jung.visualization.util.PredicatedParallelEdgeIndexFunction; /** * A demo that shows how collections of vertices can be collapsed * into a single vertex. In this demo, the vertices that are * collapsed are those mouse-picked by the user. Any criteria * could be used to form the vertex collections to be collapsed, * perhaps some common characteristic of those vertex objects. * * Note that the collection types don't use generics in this * demo, because the vertices are of two types: String for plain * vertices, and {@code Graph} for the collapsed vertices. * * @author Tom Nelson * */ @SuppressWarnings("serial") public class VertexCollapseDemoWithLayouts extends JApplet { String instructions = "Use the mouse to select multiple vertices"+ "

        either by dragging a region, or by shift-clicking"+ "

        on multiple vertices."+ "

        After you select vertices, use the Collapse button"+ "

        to combine them into a single vertex."+ "

        Select a 'collapsed' vertex and use the Expand button"+ "

        to restore the collapsed vertices."+ "

        The Restore button will restore the original graph."+ "

        If you select 2 (and only 2) vertices, then press"+ "

        the Compress Edges button, parallel edges between"+ "

        those two vertices will no longer be expanded."+ "

        If you select 2 (and only 2) vertices, then press"+ "

        the Expand Edges button, parallel edges between"+ "

        those two vertices will be expanded."+ "

        You can drag the vertices with the mouse." + "

        Use the 'Picking'/'Transforming' combo-box to switch"+ "

        between picking and transforming mode."; /** * the graph */ @SuppressWarnings("rawtypes") Graph graph; @SuppressWarnings("rawtypes") Graph collapsedGraph; /** * the visual component and renderer for the graph */ @SuppressWarnings("rawtypes") VisualizationViewer vv; @SuppressWarnings("rawtypes") Layout layout; GraphCollapser collapser; @SuppressWarnings({ "unchecked", "rawtypes" }) public VertexCollapseDemoWithLayouts() { // create a simple graph for the demo graph = TestGraphs.getOneComponentGraph(); collapsedGraph = graph; collapser = new GraphCollapser(graph); layout = new FRLayout(graph); Dimension preferredSize = new Dimension(400,400); final VisualizationModel visualizationModel = new DefaultVisualizationModel(layout, preferredSize); vv = new VisualizationViewer(visualizationModel, preferredSize); vv.getRenderContext().setVertexShapeTransformer(new ClusterVertexShapeFunction()); final PredicatedParallelEdgeIndexFunction eif = PredicatedParallelEdgeIndexFunction.getInstance(); final Set exclusions = new HashSet(); eif.setPredicate(new Predicate() { public boolean apply(Object e) { return exclusions.contains(e); }}); vv.getRenderContext().setParallelEdgeIndexFunction(eif); vv.setBackground(Color.white); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller() { /* (non-Javadoc) * @see edu.uci.ics.jung.visualization.decorators.DefaultToolTipFunction#getToolTipText(java.lang.Object) */ @Override public String apply(Object v) { if(v instanceof Graph) { return ((Graph)v).getVertices().toString(); } return super.apply(v); }}); /** * the regular graph mouse for the normal view */ final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); Container content = getContentPane(); GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv); content.add(gzsp); JComboBox modeBox = graphMouse.getModeComboBox(); modeBox.addItemListener(graphMouse.getModeListener()); graphMouse.setMode(ModalGraphMouse.Mode.PICKING); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JButton collapse = new JButton("Collapse"); collapse.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Collection picked = new HashSet(vv.getPickedVertexState().getPicked()); if(picked.size() > 1) { Graph inGraph = layout.getGraph(); Graph clusterGraph = collapser.getClusterGraph(inGraph, picked); Graph g = collapser.collapse(layout.getGraph(), clusterGraph); collapsedGraph = g; double sumx = 0; double sumy = 0; for(Object v : picked) { Point2D p = (Point2D)layout.apply(v); sumx += p.getX(); sumy += p.getY(); } Point2D cp = new Point2D.Double(sumx/picked.size(), sumy/picked.size()); vv.getRenderContext().getParallelEdgeIndexFunction().reset(); layout.setGraph(g); layout.setLocation(clusterGraph, cp); vv.getPickedVertexState().clear(); vv.repaint(); } }}); JButton compressEdges = new JButton("Compress Edges"); compressEdges.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Collection picked = vv.getPickedVertexState().getPicked(); if(picked.size() == 2) { Pair pair = new Pair(picked); Graph graph = layout.getGraph(); Collection edges = new HashSet(graph.getIncidentEdges(pair.getFirst())); edges.retainAll(graph.getIncidentEdges(pair.getSecond())); exclusions.addAll(edges); vv.repaint(); } }}); JButton expandEdges = new JButton("Expand Edges"); expandEdges.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Collection picked = vv.getPickedVertexState().getPicked(); if(picked.size() == 2) { Pair pair = new Pair(picked); Graph graph = layout.getGraph(); Collection edges = new HashSet(graph.getIncidentEdges(pair.getFirst())); edges.retainAll(graph.getIncidentEdges(pair.getSecond())); exclusions.removeAll(edges); vv.repaint(); } }}); JButton expand = new JButton("Expand"); expand.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Collection picked = new HashSet(vv.getPickedVertexState().getPicked()); for(Object v : picked) { if(v instanceof Graph) { Graph g = collapser.expand(layout.getGraph(), (Graph)v); vv.getRenderContext().getParallelEdgeIndexFunction().reset(); layout.setGraph(g); } vv.getPickedVertexState().clear(); vv.repaint(); } }}); JButton reset = new JButton("Reset"); reset.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { layout.setGraph(graph); exclusions.clear(); vv.repaint(); }}); JButton help = new JButton("Help"); help.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog((JComponent)e.getSource(), instructions, "Help", JOptionPane.PLAIN_MESSAGE); } }); Class[] combos = getCombos(); final JComboBox jcb = new JComboBox(combos); // use a renderer to shorten the layout name presentation jcb.setRenderer(new DefaultListCellRenderer() { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { String valueString = value.toString(); valueString = valueString.substring(valueString.lastIndexOf('.')+1); return super.getListCellRendererComponent(list, valueString, index, isSelected, cellHasFocus); } }); jcb.addActionListener(new LayoutChooser(jcb, vv)); jcb.setSelectedItem(FRLayout.class); JPanel controls = new JPanel(); JPanel zoomControls = new JPanel(new GridLayout(2,1)); zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom")); zoomControls.add(plus); zoomControls.add(minus); controls.add(zoomControls); JPanel collapseControls = new JPanel(new GridLayout(3,1)); collapseControls.setBorder(BorderFactory.createTitledBorder("Picked")); collapseControls.add(collapse); collapseControls.add(expand); collapseControls.add(compressEdges); collapseControls.add(expandEdges); collapseControls.add(reset); controls.add(collapseControls); controls.add(modeBox); controls.add(help); controls.add(jcb); content.add(controls, BorderLayout.SOUTH); } /** * a demo class that will create a vertex shape that is either a * polygon or star. The number of sides corresponds to the number * of vertices that were collapsed into the vertex represented by * this shape. * * @author Tom Nelson * * @param the vertex type */ class ClusterVertexShapeFunction extends EllipseVertexShapeTransformer { ClusterVertexShapeFunction() { setSizeTransformer(new ClusterVertexSizeFunction(20)); } @Override public Shape apply(V v) { if(v instanceof Graph) { @SuppressWarnings("rawtypes") int size = ((Graph)v).getVertexCount(); if (size < 8) { int sides = Math.max(size, 3); return factory.getRegularPolygon(v, sides); } else { return factory.getRegularStar(v, size); } } return super.apply(v); } } /** * A demo class that will make vertices larger if they represent * a collapsed collection of original vertices * @author Tom Nelson * * @param the vertex type */ class ClusterVertexSizeFunction implements Function { int size; public ClusterVertexSizeFunction(Integer size) { this.size = size; } public Integer apply(V v) { if(v instanceof Graph) { return 30; } return size; } } private class LayoutChooser implements ActionListener { private final JComboBox jcb; @SuppressWarnings("rawtypes") private final VisualizationViewer vv; private LayoutChooser(JComboBox jcb, VisualizationViewer vv) { super(); this.jcb = jcb; this.vv = vv; } @SuppressWarnings({ "unchecked", "rawtypes" }) public void actionPerformed(ActionEvent arg0) { Object[] constructorArgs = { collapsedGraph }; Class layoutC = (Class) jcb.getSelectedItem(); try { Constructor constructor = layoutC .getConstructor(new Class[] {Graph.class}); Object o = constructor.newInstance(constructorArgs); Layout l = (Layout) o; l.setInitializer(vv.getGraphLayout()); l.setSize(vv.getSize()); layout = l; LayoutTransition lt = new LayoutTransition(vv, vv.getGraphLayout(), l); Animator animator = new Animator(lt); animator.start(); vv.getRenderContext().getMultiLayerTransformer().setToIdentity(); vv.repaint(); } catch (Exception e) { e.printStackTrace(); } } } /** * @return */ @SuppressWarnings({ "unchecked", "rawtypes" }) private Class[] getCombos() { List> layouts = new ArrayList>(); layouts.add(KKLayout.class); layouts.add(FRLayout.class); layouts.add(CircleLayout.class); layouts.add(SpringLayout.class); layouts.add(SpringLayout2.class); layouts.add(ISOMLayout.class); return layouts.toArray(new Class[0]); } public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new VertexCollapseDemoWithLayouts()); f.pack(); f.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexImageShaperDemo.java000066400000000000000000000473701276402340000322460ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Image; import java.awt.Paint; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.util.HashMap; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JPanel; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer; import edu.uci.ics.jung.graph.DirectedSparseGraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.LayeredIcon; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.decorators.VertexIconShapeTransformer; import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.renderers.BasicVertexRenderer; import edu.uci.ics.jung.visualization.renderers.Checkmark; import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer; import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer; import edu.uci.ics.jung.visualization.util.ImageShapeUtils; /** * Demonstrates the use of images to represent graph vertices. * The images are supplied via the VertexShapeFunction so that * both the image and its shape can be utilized. * * The images used in this demo (courtesy of slashdot.org) are * rectangular but with a transparent background. When vertices * are represented by these images, it looks better if the actual * shape of the opaque part of the image is computed so that the * edge arrowheads follow the visual shape of the image. This demo * uses the FourPassImageShaper class to compute the Shape from * an image with transparent background. * * @author Tom Nelson * */ public class VertexImageShaperDemo extends JApplet { /** * */ private static final long serialVersionUID = -4332663871914930864L; private static final int VERTEX_COUNT=11; /** * the graph */ DirectedSparseGraph graph; /** * the visual component and renderer for the graph */ VisualizationViewer vv; /** * some icon names to use */ String[] iconNames = { "apple", "os", "x", "linux", "inputdevices", "wireless", "graphics3", "gamespcgames", "humor", "music", "privacy" }; public VertexImageShaperDemo() { // create a simple graph for the demo graph = new DirectedSparseGraph(); createGraph(VERTEX_COUNT); // a Map for the labels Map map = new HashMap(); for(int i=0; i iconMap = new HashMap(); for(int i=0; i layout = new FRLayout(graph); layout.setMaxIterations(100); layout.setInitializer(new RandomLocationTransformer(new Dimension(400,400), 0)); vv = new VisualizationViewer(layout, new Dimension(400,400)); // This demo uses a special renderer to turn outlines on and off. // you do not need to do this in a real application. // Instead, just let vv use the Renderer it already has vv.getRenderer().setVertexRenderer(new DemoRenderer()); Function vpf = new PickableVertexPaintTransformer(vv.getPickedVertexState(), Color.white, Color.yellow); vv.getRenderContext().setVertexFillPaintTransformer(vpf); vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(vv.getPickedEdgeState(), Color.black, Color.cyan)); vv.setBackground(Color.white); final Function vertexStringerImpl = new VertexStringerImpl(map); vv.getRenderContext().setVertexLabelTransformer(vertexStringerImpl); vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.cyan)); vv.getRenderContext().setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.cyan)); // vv.getRenderContext().setEdgeLabelTransformer(new Function() { // URL url = getClass().getResource("/images/lightning-s.gif"); // public String transform(Number input) { // // return ""+input.toString(); // }}); // For this demo only, I use a special class that lets me turn various // features on and off. For a real application, use VertexIconShapeTransformer instead. final DemoVertexIconShapeTransformer vertexIconShapeTransformer = new DemoVertexIconShapeTransformer(new EllipseVertexShapeTransformer()); vertexIconShapeTransformer.setIconMap(iconMap); final DemoVertexIconTransformer vertexIconTransformer = new DemoVertexIconTransformer(iconMap); vv.getRenderContext().setVertexShapeTransformer(vertexIconShapeTransformer); vv.getRenderContext().setVertexIconTransformer(vertexIconTransformer); // un-comment for RStar Tree visual testing //vv.addPostRenderPaintable(new BoundingRectanglePaintable(vv.getRenderContext(), vv.getGraphLayout())); // Get the pickedState and add a listener that will decorate the // Vertex images with a checkmark icon when they are picked PickedState ps = vv.getPickedVertexState(); ps.addItemListener(new PickWithIconListener(vertexIconTransformer)); vv.addPostRenderPaintable(new VisualizationViewer.Paintable(){ int x; int y; Font font; FontMetrics metrics; int swidth; int sheight; String str = "Thank You, slashdot.org, for the images!"; public void paint(Graphics g) { Dimension d = vv.getSize(); if(font == null) { font = new Font(g.getFont().getName(), Font.BOLD, 20); metrics = g.getFontMetrics(font); swidth = metrics.stringWidth(str); sheight = metrics.getMaxAscent()+metrics.getMaxDescent(); x = (d.width-swidth)/2; y = (int)(d.height-sheight*1.5); } g.setFont(font); Color oldColor = g.getColor(); g.setColor(Color.lightGray); g.drawString(str, x, y); g.setColor(oldColor); } public boolean useTransform() { return false; } }); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); Container content = getContentPane(); final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); content.add(panel); final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); vv.addKeyListener(graphMouse.getModeKeyListener()); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JCheckBox shape = new JCheckBox("Shape"); shape.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { vertexIconShapeTransformer.setShapeImages(e.getStateChange()==ItemEvent.SELECTED); vv.repaint(); } }); shape.setSelected(true); JCheckBox fill = new JCheckBox("Fill"); fill.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { vertexIconTransformer.setFillImages(e.getStateChange()==ItemEvent.SELECTED); vv.repaint(); } }); fill.setSelected(true); JCheckBox drawOutlines = new JCheckBox("Outline"); drawOutlines.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e) { vertexIconTransformer.setOutlineImages(e.getStateChange()==ItemEvent.SELECTED); vv.repaint(); } }); JComboBox modeBox = graphMouse.getModeComboBox(); JPanel modePanel = new JPanel(); modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode")); modePanel.add(modeBox); JPanel scaleGrid = new JPanel(new GridLayout(1,0)); scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom")); JPanel labelFeatures = new JPanel(new GridLayout(1,0)); labelFeatures.setBorder(BorderFactory.createTitledBorder("Image Effects")); JPanel controls = new JPanel(); scaleGrid.add(plus); scaleGrid.add(minus); controls.add(scaleGrid); labelFeatures.add(shape); labelFeatures.add(fill); labelFeatures.add(drawOutlines); controls.add(labelFeatures); controls.add(modePanel); content.add(controls, BorderLayout.SOUTH); } /** * When Vertices are picked, add a checkmark icon to the imager. * Remove the icon when a Vertex is unpicked * @author Tom Nelson * */ public static class PickWithIconListener implements ItemListener { Function imager; Icon checked; public PickWithIconListener(Function imager) { this.imager = imager; checked = new Checkmark(); } public void itemStateChanged(ItemEvent e) { @SuppressWarnings("unchecked") Icon icon = imager.apply((V)e.getItem()); if(icon != null && icon instanceof LayeredIcon) { if(e.getStateChange() == ItemEvent.SELECTED) { ((LayeredIcon)icon).add(checked); } else { ((LayeredIcon)icon).remove(checked); } } } } /** * A simple implementation of VertexStringer that * gets Vertex labels from a Map * * @author Tom Nelson * * */ public static class VertexStringerImpl implements Function { Map map = new HashMap(); boolean enabled = true; public VertexStringerImpl(Map map) { this.map = map; } /* (non-Javadoc) * @see edu.uci.ics.jung.graph.decorators.VertexStringer#getLabel(edu.uci.ics.jung.graph.Vertex) */ public String apply(V v) { if(isEnabled()) { return map.get(v); } else { return ""; } } /** * @return Returns the enabled. */ public boolean isEnabled() { return enabled; } /** * @param enabled The enabled to set. */ public void setEnabled(boolean enabled) { this.enabled = enabled; } } /** * create some vertices * @param count how many to create * @return the Vertices in an array */ private void createGraph(int vertexCount) { for (int i = 0; i < vertexCount; i++) { graph.addVertex(i); } int j=0; graph.addEdge(j++, 0, 1, EdgeType.DIRECTED); graph.addEdge(j++, 3, 0, EdgeType.DIRECTED); graph.addEdge(j++, 0, 4, EdgeType.DIRECTED); graph.addEdge(j++, 4, 5, EdgeType.DIRECTED); graph.addEdge(j++, 5, 3, EdgeType.DIRECTED); graph.addEdge(j++, 2, 1, EdgeType.DIRECTED); graph.addEdge(j++, 4, 1, EdgeType.DIRECTED); graph.addEdge(j++, 8, 2, EdgeType.DIRECTED); graph.addEdge(j++, 3, 8, EdgeType.DIRECTED); graph.addEdge(j++, 6, 7, EdgeType.DIRECTED); graph.addEdge(j++, 7, 5, EdgeType.DIRECTED); graph.addEdge(j++, 0, 9, EdgeType.DIRECTED); graph.addEdge(j++, 9, 8, EdgeType.DIRECTED); graph.addEdge(j++, 7, 6, EdgeType.DIRECTED); graph.addEdge(j++, 6, 5, EdgeType.DIRECTED); graph.addEdge(j++, 4, 2, EdgeType.DIRECTED); graph.addEdge(j++, 5, 4, EdgeType.DIRECTED); graph.addEdge(j++, 4, 10, EdgeType.DIRECTED); graph.addEdge(j++, 10, 4, EdgeType.DIRECTED); } /** * This class exists only to provide settings to turn on/off shapes and image fill * in this demo. * *

        For a real application, just use {@code Functions.forMap(iconMap)} to provide a * {@code Function}. */ public static class DemoVertexIconTransformer implements Function { boolean fillImages = true; boolean outlineImages = false; Map iconMap = new HashMap(); public DemoVertexIconTransformer(Map iconMap) { this.iconMap = iconMap; } /** * @return Returns the fillImages. */ public boolean isFillImages() { return fillImages; } /** * @param fillImages The fillImages to set. */ public void setFillImages(boolean fillImages) { this.fillImages = fillImages; } public boolean isOutlineImages() { return outlineImages; } public void setOutlineImages(boolean outlineImages) { this.outlineImages = outlineImages; } public Icon apply(V v) { if(fillImages) { return (Icon)iconMap.get(v); } else { return null; } } } /** * this class exists only to provide settings to turn on/off shapes and image fill * in this demo. * In a real application, use VertexIconShapeTransformer instead. * */ public static class DemoVertexIconShapeTransformer extends VertexIconShapeTransformer { boolean shapeImages = true; public DemoVertexIconShapeTransformer(Function delegate) { super(delegate); } /** * @return Returns the shapeImages. */ public boolean isShapeImages() { return shapeImages; } /** * @param shapeImages The shapeImages to set. */ public void setShapeImages(boolean shapeImages) { shapeMap.clear(); this.shapeImages = shapeImages; } public Shape transform(V v) { Icon icon = (Icon) iconMap.get(v); if (icon != null && icon instanceof ImageIcon) { Image image = ((ImageIcon) icon).getImage(); Shape shape = shapeMap.get(image); if (shape == null) { if (shapeImages) { shape = ImageShapeUtils.getShape(image, 30); } else { shape = new Rectangle2D.Float(0, 0, image.getWidth(null), image.getHeight(null)); } if(shape.getBounds().getWidth() > 0 && shape.getBounds().getHeight() > 0) { int width = image.getWidth(null); int height = image.getHeight(null); AffineTransform transform = AffineTransform.getTranslateInstance(-width / 2, -height / 2); shape = transform.createTransformedShape(shape); shapeMap.put(image, shape); } } return shape; } else { return delegate.apply(v); } } } /** * a special renderer that can turn outlines on and off * in this demo. * You won't need this for a real application. * Use BasicVertexRenderer instead * * @author Tom Nelson * */ class DemoRenderer extends BasicVertexRenderer { // public void paintIconForVertex(RenderContext rc, V v, Layout layout) { // // Point2D p = layout.transform(v); // p = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p); // float x = (float)p.getX(); // float y = (float)p.getY(); // // GraphicsDecorator g = rc.getGraphicsContext(); // boolean outlineImages = false; // Function vertexIconFunction = rc.getVertexIconTransformer(); // // if(vertexIconFunction instanceof DemoVertexIconTransformer) { // outlineImages = ((DemoVertexIconTransformer)vertexIconFunction).isOutlineImages(); // } // Icon icon = vertexIconFunction.transform(v); // if(icon == null || outlineImages) { // // Shape s = AffineTransform.getTranslateInstance(x,y). // createTransformedShape(rc.getVertexShapeTransformer().transform(v)); // paintShapeForVertex(rc, v, s); // } // if(icon != null) { // int xLoc = (int) (x - icon.getIconWidth()/2); // int yLoc = (int) (y - icon.getIconHeight()/2); // icon.paintIcon(rc.getScreenDevice(), g.getDelegate(), xLoc, yLoc); // } // } } public static void main(String[] args) { JFrame frame = new JFrame(); Container content = frame.getContentPane(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); content.add(new VertexImageShaperDemo()); frame.pack(); frame.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexLabelAsShapeDemo.java000066400000000000000000000136171276402340000323420ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.Paint; import java.awt.Stroke; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.BorderFactory; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JPanel; import com.google.common.base.Function; import com.google.common.base.Functions; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.TestGraphs; import edu.uci.ics.jung.visualization.DefaultVisualizationModel; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationModel; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer; import edu.uci.ics.jung.visualization.renderers.GradientVertexRenderer; import edu.uci.ics.jung.visualization.renderers.VertexLabelAsShapeRenderer; /** * This demo shows how to use the vertex labels themselves as * the vertex shapes. Additionally, it shows html labels * so they are multi-line, and gradient painting of the * vertex labels. * * @author Tom Nelson * */ public class VertexLabelAsShapeDemo extends JApplet { /** * */ private static final long serialVersionUID = 1017336668368978842L; Graph graph; VisualizationViewer vv; Layout layout; /** * create an instance of a simple graph with basic controls */ public VertexLabelAsShapeDemo() { // create a simple graph for the demo graph = TestGraphs.getOneComponentGraph(); layout = new FRLayout(graph); Dimension preferredSize = new Dimension(400,400); final VisualizationModel visualizationModel = new DefaultVisualizationModel(layout, preferredSize); vv = new VisualizationViewer(visualizationModel, preferredSize); // this class will provide both label drawing and vertex shapes VertexLabelAsShapeRenderer vlasr = new VertexLabelAsShapeRenderer(vv.getRenderContext()); // customize the render context vv.getRenderContext().setVertexLabelTransformer( // this chains together Functions so that the html tags // are prepended to the toString method output Functions.compose( new Function(){ public String apply(String input) { return "

        Vertex

        "+input; }}, new ToStringLabeller())); vv.getRenderContext().setVertexShapeTransformer(vlasr); vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.red)); vv.getRenderContext().setEdgeDrawPaintTransformer(Functions.constant(Color.yellow)); vv.getRenderContext().setEdgeStrokeTransformer(Functions.constant(new BasicStroke(2.5f))); // customize the renderer vv.getRenderer().setVertexRenderer(new GradientVertexRenderer(Color.gray, Color.white, true)); vv.getRenderer().setVertexLabelRenderer(vlasr); vv.setBackground(Color.black); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); vv.addKeyListener(graphMouse.getModeKeyListener()); Container content = getContentPane(); GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv); content.add(gzsp); JComboBox modeBox = graphMouse.getModeComboBox(); modeBox.addItemListener(graphMouse.getModeListener()); graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JPanel controls = new JPanel(); JPanel zoomControls = new JPanel(new GridLayout(2,1)); zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom")); zoomControls.add(plus); zoomControls.add(minus); controls.add(zoomControls); controls.add(modeBox); content.add(controls, BorderLayout.SOUTH); } public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new VertexLabelAsShapeDemo()); f.pack(); f.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexLabelPositionDemo.java000066400000000000000000000147661276402340000326300ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import javax.swing.BorderFactory; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JMenuBar; import javax.swing.JPanel; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.TestGraphs; import edu.uci.ics.jung.visualization.DefaultVisualizationModel; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.VisualizationModel; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.renderers.Renderer; import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position; /** * Demonstrates vertex label positioning * controlled by the user. * In the AUTO setting, labels are placed according to * which quadrant the vertex is in * * @author Tom Nelson * */ @SuppressWarnings("serial") public class VertexLabelPositionDemo extends JApplet { /** * the graph */ Graph graph; FRLayout graphLayout; /** * the visual component and renderer for the graph */ VisualizationViewer vv; ScalingControl scaler; /** * create an instance of a simple graph with controls to * demo the zoomand hyperbolic features. * */ public VertexLabelPositionDemo() { // create a simple graph for the demo graph = TestGraphs.getOneComponentGraph(); graphLayout = new FRLayout(graph); graphLayout.setMaxIterations(1000); Dimension preferredSize = new Dimension(600,600); final VisualizationModel visualizationModel = new DefaultVisualizationModel(graphLayout, preferredSize); vv = new VisualizationViewer(visualizationModel, preferredSize); PickedState ps = vv.getPickedVertexState(); PickedState pes = vv.getPickedEdgeState(); vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer(ps, Color.red, Color.yellow)); vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(pes, Color.black, Color.cyan)); vv.setBackground(Color.white); vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.W); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); // add a listener for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); Container content = getContentPane(); GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv); content.add(gzsp); /** * the regular graph mouse for the normal view */ final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); vv.addKeyListener(graphMouse.getModeKeyListener()); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JPanel positionPanel = new JPanel(); positionPanel.setBorder(BorderFactory.createTitledBorder("Label Position")); JMenuBar menubar = new JMenuBar(); menubar.add(graphMouse.getModeMenu()); gzsp.setCorner(menubar); JComboBox cb = new JComboBox(); cb.addItem(Renderer.VertexLabel.Position.N); cb.addItem(Renderer.VertexLabel.Position.NE); cb.addItem(Renderer.VertexLabel.Position.E); cb.addItem(Renderer.VertexLabel.Position.SE); cb.addItem(Renderer.VertexLabel.Position.S); cb.addItem(Renderer.VertexLabel.Position.SW); cb.addItem(Renderer.VertexLabel.Position.W); cb.addItem(Renderer.VertexLabel.Position.NW); cb.addItem(Renderer.VertexLabel.Position.N); cb.addItem(Renderer.VertexLabel.Position.CNTR); cb.addItem(Renderer.VertexLabel.Position.AUTO); cb.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { Renderer.VertexLabel.Position position = (Renderer.VertexLabel.Position)e.getItem(); vv.getRenderer().getVertexLabelRenderer().setPosition(position); vv.repaint(); }}); cb.setSelectedItem(Renderer.VertexLabel.Position.SE); positionPanel.add(cb); JPanel controls = new JPanel(); JPanel zoomControls = new JPanel(new GridLayout(2,1)); zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom")); zoomControls.add(plus); zoomControls.add(minus); controls.add(zoomControls); controls.add(positionPanel); content.add(controls, BorderLayout.SOUTH); } public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new VertexLabelPositionDemo()); f.pack(); f.setVisible(true); } } VisualizationImageServerDemo.java000066400000000000000000000117021276402340000335650ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Image; import java.awt.Paint; import java.awt.geom.Point2D; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import com.google.common.base.Functions; import edu.uci.ics.jung.algorithms.layout.KKLayout; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.VisualizationImageServer; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.renderers.GradientVertexRenderer; import edu.uci.ics.jung.visualization.renderers.Renderer; import edu.uci.ics.jung.visualization.renderers.BasicVertexLabelRenderer.InsidePositioner; /** * Demonstrates VisualizationImageServer. * @author Tom Nelson * */ public class VisualizationImageServerDemo { /** * the graph */ DirectedSparseMultigraph graph; /** * the visual component and renderer for the graph */ VisualizationImageServer vv; /** * */ public VisualizationImageServerDemo() { // create a simple graph for the demo graph = new DirectedSparseMultigraph(); String[] v = createVertices(10); createEdges(v); vv = new VisualizationImageServer(new KKLayout(graph), new Dimension(600,600)); vv.getRenderer().setVertexRenderer( new GradientVertexRenderer( Color.white, Color.red, Color.white, Color.blue, vv.getPickedVertexState(), false)); vv.getRenderContext().setEdgeDrawPaintTransformer(Functions.constant(Color.lightGray)); vv.getRenderContext().setArrowFillPaintTransformer(Functions.constant(Color.lightGray)); vv.getRenderContext().setArrowDrawPaintTransformer(Functions.constant(Color.lightGray)); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); vv.getRenderer().getVertexLabelRenderer().setPositioner(new InsidePositioner()); vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.AUTO); // create a frome to hold the graph final JFrame frame = new JFrame(); Container content = frame.getContentPane(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Image im = vv.getImage(new Point2D.Double(300,300), new Dimension(600,600)); Icon icon = new ImageIcon(im); JLabel label = new JLabel(icon); content.add(label); frame.pack(); frame.setVisible(true); } /** * create some vertices * @param count how many to create * @return the Vertices in an array */ private String[] createVertices(int count) { String[] v = new String[count]; for (int i = 0; i < count; i++) { v[i] = "V"+i; graph.addVertex(v[i]); } return v; } /** * create edges for this demo graph * @param v an array of Vertices to connect */ void createEdges(String[] v) { graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[0], v[3], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[0], v[4], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[4], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[3], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[1], v[2], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[1], v[4], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[8], v[2], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[3], v[8], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[6], v[7], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[7], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[0], v[9], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[9], v[8], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[7], v[6], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[6], v[5], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[4], v[2], EdgeType.DIRECTED); graph.addEdge(new Double(Math.random()), v[5], v[4], EdgeType.DIRECTED); } public static void main(String[] args) { new VisualizationImageServerDemo(); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/WorldMapGraphDemo.java000066400000000000000000000246571276402340000313750ustar00rootroot00000000000000/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.samples; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.ImageIcon; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import com.google.common.base.Function; import com.google.common.base.Functions; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.StaticLayout; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.AbstractModalGraphMouse; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.renderers.GradientVertexRenderer; import edu.uci.ics.jung.visualization.renderers.Renderer; import edu.uci.ics.jung.visualization.renderers.BasicVertexLabelRenderer.InsidePositioner; /** * Shows a graph overlaid on a world map image. * Scaling of the graph also scales the image background. * @author Tom Nelson * */ @SuppressWarnings("serial") public class WorldMapGraphDemo extends JApplet { /** * the graph */ Graph graph; /** * the visual component and renderer for the graph */ VisualizationViewer vv; Map map = new HashMap(); List cityList; /** * create an instance of a simple graph with controls to * demo the zoom features. * */ public WorldMapGraphDemo() { setLayout(new BorderLayout()); map.put("TYO", new String[] {"35 40 N", "139 45 E"}); map.put("PEK", new String[] {"39 55 N", "116 26 E"}); map.put("MOW", new String[] {"55 45 N", "37 42 E"}); map.put("JRS", new String[] {"31 47 N", "35 13 E"}); map.put("CAI", new String[] {"30 03 N", "31 15 E"}); map.put("CPT", new String[] {"33 55 S", "18 22 E"}); map.put("PAR", new String[] {"48 52 N", "2 20 E"}); map.put("LHR", new String[] {"51 30 N", "0 10 W"}); map.put("HNL", new String[] {"21 18 N", "157 51 W"}); map.put("NYC", new String[] {"40 77 N", "73 98 W"}); map.put("SFO", new String[] {"37 62 N", "122 38 W"}); map.put("AKL", new String[] {"36 55 S", "174 47 E"}); map.put("BNE", new String[] {"27 28 S", "153 02 E"}); map.put("HKG", new String[] {"22 15 N", "114 10 E"}); map.put("KTM", new String[] {"27 42 N", "85 19 E"}); map.put("IST", new String[] {"41 01 N", "28 58 E"}); map.put("STO", new String[] {"59 20 N", "18 03 E"}); map.put("RIO", new String[] {"22 54 S", "43 14 W"}); map.put("LIM", new String[] {"12 03 S", "77 03 W"}); map.put("YTO", new String[] {"43 39 N", "79 23 W"}); cityList = new ArrayList(map.keySet()); // create a simple graph for the demo graph = new DirectedSparseMultigraph(); createVertices(); createEdges(); ImageIcon mapIcon = null; String imageLocation = "/images/political_world_map.jpg"; try { mapIcon = new ImageIcon(getClass().getResource(imageLocation)); } catch(Exception ex) { System.err.println("Can't load \""+imageLocation+"\""); } final ImageIcon icon = mapIcon; Dimension layoutSize = new Dimension(2000,1000); Layout layout = new StaticLayout(graph, Functions.compose( new LatLonPixelTransformer(new Dimension(2000,1000)), new CityTransformer(map)) ); // new ChainedTransformer(new Function[]{ // new CityTransformer(map), // new LatLonPixelTransformer(new Dimension(2000,1000)) // })); layout.setSize(layoutSize); vv = new VisualizationViewer(layout, new Dimension(800,400)); if(icon != null) { vv.addPreRenderPaintable(new VisualizationViewer.Paintable(){ public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; AffineTransform oldXform = g2d.getTransform(); AffineTransform lat = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getTransform(); AffineTransform vat = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getTransform(); AffineTransform at = new AffineTransform(); at.concatenate(g2d.getTransform()); at.concatenate(vat); at.concatenate(lat); g2d.setTransform(at); g.drawImage(icon.getImage(), 0, 0, icon.getIconWidth(),icon.getIconHeight(),vv); g2d.setTransform(oldXform); } public boolean useTransform() { return false; } }); } vv.getRenderer().setVertexRenderer( new GradientVertexRenderer( Color.white, Color.red, Color.white, Color.blue, vv.getPickedVertexState(), false)); // add my listeners for ToolTips vv.setVertexToolTipTransformer(new ToStringLabeller()); vv.setEdgeToolTipTransformer(new Function() { public String apply(Number edge) { return "E"+graph.getEndpoints(edge).toString(); }}); vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); vv.getRenderer().getVertexLabelRenderer().setPositioner(new InsidePositioner()); vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.AUTO); final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv); add(panel); final AbstractModalGraphMouse graphMouse = new DefaultModalGraphMouse(); vv.setGraphMouse(graphMouse); vv.addKeyListener(graphMouse.getModeKeyListener()); vv.setToolTipText("

        Type 'p' for Pick mode

        Type 't' for Transform mode"); final ScalingControl scaler = new CrossoverScalingControl(); JButton plus = new JButton("+"); plus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); } }); JButton minus = new JButton("-"); minus.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1/1.1f, vv.getCenter()); } }); JButton reset = new JButton("reset"); reset.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).setToIdentity(); vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).setToIdentity(); }}); JPanel controls = new JPanel(); controls.add(plus); controls.add(minus); controls.add(reset); add(controls, BorderLayout.SOUTH); } /** * create some vertices * @param count how many to create * @return the Vertices in an array */ private void createVertices() { for (String city : map.keySet()) { graph.addVertex(city); } } /** * create edges for this demo graph * @param v an array of Vertices to connect */ void createEdges() { for(int i=0; i { Map map; public CityTransformer(Map map) { this.map = map; } /** * transform airport code to latlon string */ public String[] apply(String city) { return map.get(city); } } static class LatLonPixelTransformer implements Function { Dimension d; int startOffset; public LatLonPixelTransformer(Dimension d) { this.d = d; } /** * transform a lat */ public Point2D apply(String[] latlon) { double latitude = 0; double longitude = 0; String[] lat = latlon[0].split(" "); String[] lon = latlon[1].split(" "); latitude = Integer.parseInt(lat[0]) + Integer.parseInt(lat[1])/60f; latitude *= d.height/180f; longitude = Integer.parseInt(lon[0]) + Integer.parseInt(lon[1])/60f; longitude *= d.width/360f; if(lat[2].equals("N")) { latitude = d.height / 2 - latitude; } else { // assume S latitude = d.height / 2 + latitude; } if(lon[2].equals("W")) { longitude = d.width / 2 - longitude; } else { // assume E longitude = d.width / 2 + longitude; } return new Point2D.Double(longitude,latitude); } } public static void main(String[] args) { // create a frome to hold the graph final JFrame frame = new JFrame(); Container content = frame.getContentPane(); content.add(new WorldMapGraphDemo()); frame.pack(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } } jung-jung-2.1.1/jung-samples/src/main/java/edu/uci/ics/jung/samples/package.html000066400000000000000000000015751276402340000274710ustar00rootroot00000000000000

        Sample applications created using JUNG, largely focused on visualization. Current features demonstrated in the samples include:

        • visualization of changing graphs
        • visualization animation
        • layouts: BalloonLayout, TreeLayout, RadialTreeLayout
        • clustering, shortest path, minimum spanning tree
        • sublayouts
        • label variations: orientation, position
        • customization of color, shape, stroke
        • on-the-fly visualization filtering
        • graph editor
        • vertex 'collapsing'
        • multiple models, multiple views with the same model
        • vertices as Icons
        • magnifying lens overlay
        • satellite view
        jung-jung-2.1.1/jung-samples/src/main/resources/000077500000000000000000000000001276402340000215475ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/resources/datasets/000077500000000000000000000000001276402340000233575ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/resources/datasets/simple.net000066400000000000000000000005121276402340000253560ustar00rootroot00000000000000*Vertices 16 1 "in1" 2 "in2" 3 "in3" 4 "in4" 5 "in5" 6 "in6" 7 "in7" 8 "in8" 9 "out1" 10 "out2" 11 "out3" 12 "out4" 13 "out5" 14 "out6" 15 "out7" 16 "out8" *Edgeslist 1 2 5 8 9 16 2 3 6 9 10 3 4 7 10 11 4 5 8 11 12 5 6 12 13 6 7 13 14 7 8 14 15 8 15 16 9 10 16 10 11 11 12 12 13 13 14 14 15 15 16 jung-jung-2.1.1/jung-samples/src/main/resources/datasets/smyth.net000066400000000000000000001060711276402340000252400ustar00rootroot00000000000000*Vertices 286 1 "M Pazzani" 0.0000 0.0000 0.5000 2 "M Fernandez" 0.0000 0.0000 0.5000 3 "O Mangasarian" 0.0000 0.0000 0.5000 4 "M Hearst" 0.0000 0.0000 0.5000 5 "M Jordan" 0.0000 0.0000 0.5000 6 "C Lai" 0.0000 0.0000 0.5000 7 "T Richardson" 0.0000 0.0000 0.5000 8 "C Meek" 0.0000 0.0000 0.5000 9 "C Glymour" 0.0000 0.0000 0.5000 10 "P Sabes" 0.0000 0.0000 0.5000 11 "J Frank" 0.0000 0.0000 0.5000 12 "P Cheeseman" 0.0000 0.0000 0.5000 13 "J Stutz" 0.0000 0.0000 0.5000 14 "M Wellman" 0.0000 0.0000 0.5000 15 "D Cohn" 0.0000 0.0000 0.5000 16 "A Smith" 0.0000 0.0000 0.5000 17 "B Draper" 0.0000 0.0000 0.5000 18 "J Erickson" 0.0000 0.0000 0.5000 19 "C Brodley" 0.0000 0.0000 0.5000 20 "P Smyth" 0.0000 0.0000 0.5000 21 "J Weber" 0.0000 0.0000 0.5000 22 "J Malik" 0.0000 0.0000 0.5000 23 "D Heckerman" 0.0000 0.0000 0.5000 24 "A Jones" 0.0000 0.0000 0.5000 25 "E Knill" 0.0000 0.0000 0.5000 26 "B Bollobas" 0.0000 0.0000 0.5000 27 "G Das" 0.0000 0.0000 0.5000 28 "D Gunopulos" 0.0000 0.0000 0.5000 29 "H Mannila" 0.0000 0.0000 0.5000 30 "S Teng" 0.0000 0.0000 0.5000 31 "D Mitchell" 0.0000 0.0000 0.5000 32 "R Khardon" 0.0000 0.0000 0.5000 33 "D Roth" 0.0000 0.0000 0.5000 34 "A Raftery" 0.0000 0.0000 0.5000 35 "D Madigan" 0.0000 0.0000 0.5000 36 "J Hoeting" 0.0000 0.0000 0.5000 37 "M Kersten" 0.0000 0.0000 0.5000 38 "O Miglino" 0.0000 0.0000 0.5000 39 "K Nafasi" 0.0000 0.0000 0.5000 40 "C Taylor" 0.0000 0.0000 0.5000 41 "D Wolpert" 0.0000 0.0000 0.5000 42 "W Macready" 0.0000 0.0000 0.5000 43 "A Hill" 0.0000 0.0000 0.5000 44 "T Cootes" 0.0000 0.0000 0.5000 45 "Z Ghahramani" 0.0000 0.0000 0.5000 46 "S Dumais" 0.0000 0.0000 0.5000 47 "P Chan" 0.0000 0.0000 0.5000 48 "S Soatto" 0.0000 0.0000 0.5000 49 "P Perona" 0.0000 0.0000 0.5000 50 "R Frezza" 0.0000 0.0000 0.5000 51 "G Picci" 0.0000 0.0000 0.5000 52 "M Mitchell" 0.0000 0.0000 0.5000 53 "T Jaakkola" 0.0000 0.0000 0.5000 54 "D Haussler" 0.0000 0.0000 0.5000 55 "P Orponen" 0.0000 0.0000 0.5000 56 "K Murphy" 0.0000 0.0000 0.5000 57 "H Greenspan" 0.0000 0.0000 0.5000 58 "S Belongie" 0.0000 0.0000 0.5000 59 "R Goodman" 0.0000 0.0000 0.5000 60 "M Holsheimer" 0.0000 0.0000 0.5000 61 "J Matousek" 0.0000 0.0000 0.5000 62 "L Xu" 0.0000 0.0000 0.5000 63 "M Paterson" 0.0000 0.0000 0.5000 64 "M Klemettinen" 0.0000 0.0000 0.5000 65 "H Toivonen" 0.0000 0.0000 0.5000 66 "A Weigend" 0.0000 0.0000 0.5000 67 "S Jung" 0.0000 0.0000 0.5000 68 "J Boulicaut" 0.0000 0.0000 0.5000 69 "S Casadei" 0.0000 0.0000 0.5000 70 "S Mitter" 0.0000 0.0000 0.5000 71 "J Adams" 0.0000 0.0000 0.5000 72 "J Kosecka" 0.0000 0.0000 0.5000 73 "R Paul" 0.0000 0.0000 0.5000 74 "A Farquhar" 0.0000 0.0000 0.5000 75 "R Kohavi" 0.0000 0.0000 0.5000 76 "M Jaeger" 0.0000 0.0000 0.5000 77 "J Ostrowski" 0.0000 0.0000 0.5000 78 "K Bennett" 0.0000 0.0000 0.5000 79 "A Ng" 0.0000 0.0000 0.5000 80 "P Ronkainen" 0.0000 0.0000 0.5000 81 "L Guibas" 0.0000 0.0000 0.5000 82 "B Dom" 0.0000 0.0000 0.5000 83 "M Sahami" 0.0000 0.0000 0.5000 84 "J Bouguet" 0.0000 0.0000 0.5000 85 "J Rice" 0.0000 0.0000 0.5000 86 "D Dobkin" 0.0000 0.0000 0.5000 87 "D Eppstein" 0.0000 0.0000 0.5000 88 "T Hastie" 0.0000 0.0000 0.5000 89 "P Agarwal" 0.0000 0.0000 0.5000 90 "E Ukkonen" 0.0000 0.0000 0.5000 91 "G Overton" 0.0000 0.0000 0.5000 92 "J Aaronson" 0.0000 0.0000 0.5000 93 "J Haas" 0.0000 0.0000 0.5000 94 "Z Wang" 0.0000 0.0000 0.5000 95 "D Hart" 0.0000 0.0000 0.5000 96 "G Gottlob" 0.0000 0.0000 0.5000 97 "P Stolorz" 0.0000 0.0000 0.5000 98 "F Yao" 0.0000 0.0000 0.5000 99 "A Robertson" 0.0000 0.0000 0.5000 100 "E Horvitz" 0.0000 0.0000 0.5000 101 "S Singh" 0.0000 0.0000 0.5000 102 "B Fischer" 0.0000 0.0000 0.5000 103 "T Leung" 0.0000 0.0000 0.5000 104 "H Ahonen" 0.0000 0.0000 0.5000 105 "O Heinonen" 0.0000 0.0000 0.5000 106 "P Kilpelainen" 0.0000 0.0000 0.5000 107 "M Atkinson" 0.0000 0.0000 0.5000 108 "D Peel" 0.0000 0.0000 0.5000 109 "G Miller" 0.0000 0.0000 0.5000 110 "S Russell" 0.0000 0.0000 0.5000 111 "W Buntine" 0.0000 0.0000 0.5000 112 "J Fortes" 0.0000 0.0000 0.5000 113 "L Saul" 0.0000 0.0000 0.5000 114 "M Dillencourt" 0.0000 0.0000 0.5000 115 "Y Weiss" 0.0000 0.0000 0.5000 116 "C Volinsky" 0.0000 0.0000 0.5000 117 "R Kronmal" 0.0000 0.0000 0.5000 118 "A Gray" 0.0000 0.0000 0.5000 119 "S Hanks" 0.0000 0.0000 0.5000 120 "V Tsaoussidis" 0.0000 0.0000 0.5000 121 "H Badr" 0.0000 0.0000 0.5000 122 "D Kriegman" 0.0000 0.0000 0.5000 123 "T Lane" 0.0000 0.0000 0.5000 124 "J Gilbert" 0.0000 0.0000 0.5000 125 "P Debevec" 0.0000 0.0000 0.5000 126 "J Kivinen" 0.0000 0.0000 0.5000 127 "J Crowcroft" 0.0000 0.0000 0.5000 128 "R Beigel" 0.0000 0.0000 0.5000 129 "A Mayer" 0.0000 0.0000 0.5000 130 "B MacLennan" 0.0000 0.0000 0.5000 131 "G Edwards" 0.0000 0.0000 0.5000 132 "C Matheus" 0.0000 0.0000 0.5000 133 "G Piatetsky-Shapiro" 0.0000 0.0000 0.5000 134 "D McNeill" 0.0000 0.0000 0.5000 135 "P Utgoff" 0.0000 0.0000 0.5000 136 "R Fikes" 0.0000 0.0000 0.5000 137 "R Jacobs" 0.0000 0.0000 0.5000 138 "M Weber" 0.0000 0.0000 0.5000 139 "J Gavrin" 0.0000 0.0000 0.5000 140 "K Chang" 0.0000 0.0000 0.5000 141 "D Hirschberg" 0.0000 0.0000 0.5000 142 "E Hunt" 0.0000 0.0000 0.5000 143 "S MacDonell" 0.0000 0.0000 0.5000 144 "Z Galil" 0.0000 0.0000 0.5000 145 "E Weydert" 0.0000 0.0000 0.5000 146 "D Thomas" 0.0000 0.0000 0.5000 147 "M Henzinger" 0.0000 0.0000 0.5000 148 "M Burl" 0.0000 0.0000 0.5000 149 "M Bern" 0.0000 0.0000 0.5000 150 "P Sozou" 0.0000 0.0000 0.5000 151 "J Graham" 0.0000 0.0000 0.5000 152 "E Demaine" 0.0000 0.0000 0.5000 153 "M Demaine" 0.0000 0.0000 0.5000 154 "S Spence" 0.0000 0.0000 0.5000 155 "D Geiger" 0.0000 0.0000 0.5000 156 "M Friedl" 0.0000 0.0000 0.5000 157 "T Eiter" 0.0000 0.0000 0.5000 158 "E Alpaydin" 0.0000 0.0000 0.5000 159 "S Solloway" 0.0000 0.0000 0.5000 160 "C Hutchinson" 0.0000 0.0000 0.5000 161 "J Waterton" 0.0000 0.0000 0.5000 162 "D Cooper" 0.0000 0.0000 0.5000 163 "A Barto" 0.0000 0.0000 0.5000 164 "E Nikunen" 0.0000 0.0000 0.5000 165 "E Dimauro" 0.0000 0.0000 0.5000 166 "G Linden" 0.0000 0.0000 0.5000 167 "A Verkamo" 0.0000 0.0000 0.5000 168 "I Verkamo" 0.0000 0.0000 0.5000 169 "L Goncalves" 0.0000 0.0000 0.5000 170 "G Cooper" 0.0000 0.0000 0.5000 171 "A Newton" 0.0000 0.0000 0.5000 172 "R Kraft" 0.0000 0.0000 0.5000 173 "J Breese" 0.0000 0.0000 0.5000 174 "N Amenta" 0.0000 0.0000 0.5000 175 "I Cadez" 0.0000 0.0000 0.5000 176 "C McLaren" 0.0000 0.0000 0.5000 177 "G McLachlan" 0.0000 0.0000 0.5000 178 "G Barequet" 0.0000 0.0000 0.5000 179 "M Meila" 0.0000 0.0000 0.5000 180 "Q Morris" 0.0000 0.0000 0.5000 181 "Y Song" 0.0000 0.0000 0.5000 182 "S Andersson" 0.0000 0.0000 0.5000 183 "M Perlman" 0.0000 0.0000 0.5000 184 "P Bradley" 0.0000 0.0000 0.5000 185 "U Fayyad" 0.0000 0.0000 0.5000 186 "D Wolf" 0.0000 0.0000 0.5000 187 "G Italiano" 0.0000 0.0000 0.5000 188 "K Tumer" 0.0000 0.0000 0.5000 189 "E Keogh" 0.0000 0.0000 0.5000 190 "E Bernardo" 0.0000 0.0000 0.5000 191 "E Ursella" 0.0000 0.0000 0.5000 192 "C Triggs" 0.0000 0.0000 0.5000 193 "D Sornette" 0.0000 0.0000 0.5000 194 "E Friedman" 0.0000 0.0000 0.5000 195 "L Daynes" 0.0000 0.0000 0.5000 196 "T Printezis" 0.0000 0.0000 0.5000 197 "T Pressburger" 0.0000 0.0000 0.5000 198 "B Tuttle" 0.0000 0.0000 0.5000 199 "J Haslam" 0.0000 0.0000 0.5000 200 "G Page" 0.0000 0.0000 0.5000 201 "C Jackson" 0.0000 0.0000 0.5000 202 "C Bishop" 0.0000 0.0000 0.5000 203 "D Pavlov" 0.0000 0.0000 0.5000 204 "M Dickerson" 0.0000 0.0000 0.5000 205 "N de Freitas" 0.0000 0.0000 0.5000 206 "C Stauss" 0.0000 0.0000 0.5000 207 "R Kilgour" 0.0000 0.0000 0.5000 208 "R Manduchi" 0.0000 0.0000 0.5000 209 "G Graefe" 0.0000 0.0000 0.5000 210 "R Shachter" 0.0000 0.0000 0.5000 211 "K Mosurski" 0.0000 0.0000 0.5000 212 "R Almond" 0.0000 0.0000 0.5000 213 "D Young" 0.0000 0.0000 0.5000 214 "A Lapedes" 0.0000 0.0000 0.5000 215 "R Blasi" 0.0000 0.0000 0.5000 216 "D Chickering" 0.0000 0.0000 0.5000 217 "R Giancarlo" 0.0000 0.0000 0.5000 218 "K Wheeler" 0.0000 0.0000 0.5000 219 "A Lanitis" 0.0000 0.0000 0.5000 220 "P Prado" 0.0000 0.0000 0.5000 221 "X Ge" 0.0000 0.0000 0.5000 222 "S Payandeh" 0.0000 0.0000 0.5000 223 "M Ghil" 0.0000 0.0000 0.5000 224 "K Ide" 0.0000 0.0000 0.5000 225 "H King" 0.0000 0.0000 0.5000 226 "I Dryden" 0.0000 0.0000 0.5000 227 "B Thiesson" 0.0000 0.0000 0.5000 228 "D Pregibon" 0.0000 0.0000 0.5000 229 "A Korhola" 0.0000 0.0000 0.5000 230 "H Olander" 0.0000 0.0000 0.5000 231 "W Pratt" 0.0000 0.0000 0.5000 232 "R Engelmore" 0.0000 0.0000 0.5000 233 "A Brett" 0.0000 0.0000 0.5000 234 "C Sayers" 0.0000 0.0000 0.5000 235 "J Mao" 0.0000 0.0000 0.5000 236 "M Salmenkivi" 0.0000 0.0000 0.5000 237 "A Mamdani" 0.0000 0.0000 0.5000 238 "J Iv" 0.0000 0.0000 0.5000 239 "M Welling" 0.0000 0.0000 0.5000 240 "B Kanefsky" 0.0000 0.0000 0.5000 241 "W Taylor" 0.0000 0.0000 0.5000 242 "C Strauss" 0.0000 0.0000 0.5000 243 "K Walker" 0.0000 0.0000 0.5000 244 "M Faghihi" 0.0000 0.0000 0.5000 245 "E Di Bernardo" 0.0000 0.0000 0.5000 246 "P Kube" 0.0000 0.0000 0.5000 247 "K Rommelse" 0.0000 0.0000 0.5000 248 "D Rumelhart" 0.0000 0.0000 0.5000 249 "S Gaffney" 0.0000 0.0000 0.5000 250 "C Reina" 0.0000 0.0000 0.5000 251 "S Chaudhuiri" 0.0000 0.0000 0.5000 252 "P Sallis" 0.0000 0.0000 0.5000 253 "K Laakso" 0.0000 0.0000 0.5000 254 "B Levidow" 0.0000 0.0000 0.5000 255 "D Donnell" 0.0000 0.0000 0.5000 256 "M Tartagni" 0.0000 0.0000 0.5000 257 "M Vanter" 0.0000 0.0000 0.5000 258 "N Lawrence" 0.0000 0.0000 0.5000 259 "C Fowlkes" 0.0000 0.0000 0.5000 260 "J Roden" 0.0000 0.0000 0.5000 261 "G Ridgeway" 0.0000 0.0000 0.5000 262 "E Kuo" 0.0000 0.0000 0.5000 263 "L Su" 0.0000 0.0000 0.5000 264 "M Munich" 0.0000 0.0000 0.5000 265 "D Golinelli" 0.0000 0.0000 0.5000 266 "G Consonni" 0.0000 0.0000 0.5000 267 "D Cunliffe" 0.0000 0.0000 0.5000 268 "D Psaltis" 0.0000 0.0000 0.5000 269 "G Steckman" 0.0000 0.0000 0.5000 270 "P Courtier" 0.0000 0.0000 0.5000 271 "M Latif" 0.0000 0.0000 0.5000 272 "I Sim" 0.0000 0.0000 0.5000 273 "L Cordero" 0.0000 0.0000 0.5000 274 "L Ugarte" 0.0000 0.0000 0.5000 275 "K Pentikousis" 0.0000 0.0000 0.5000 276 "N Kapadia" 0.0000 0.0000 0.5000 277 "J seck" 0.0000 0.0000 0.5000 278 "F Lott" 0.0000 0.0000 0.5000 279 "D Chudova" 0.0000 0.0000 0.5000 280 "P Yiou" 0.0000 0.0000 0.5000 281 "W Einhauser" 0.0000 0.0000 0.5000 282 "P Vetter" 0.0000 0.0000 0.5000 283 "S Goodbody" 0.0000 0.0000 0.5000 284 "M Kawato" 0.0000 0.0000 0.5000 285 "P Hrensen" 0.0000 0.0000 0.5000 286 "M Levitz" 0.0000 0.0000 0.5000 *Edges 23 237 1 203 279 2 171 263 1 116 117 1 41 214 1 87 124 1 54 126 2 265 266 1 40 67 2 5 53 17 166 167 3 87 217 2 63 87 1 49 169 3 75 83 2 49 70 1 49 69 1 71 222 1 223 280 1 49 246 1 231 232 1 40 215 2 5 158 1 29 60 1 20 228 1 17 19 1 16 127 1 152 194 1 58 59 1 108 220 1 30 109 4 74 232 1 40 200 1 29 166 1 29 167 3 40 125 3 20 40 1 49 138 8 89 174 2 11 13 1 11 12 1 169 181 1 28 29 4 29 105 1 44 219 4 29 55 1 65 167 3 133 185 1 20 49 1 8 216 2 5 202 3 148 185 1 41 284 1 44 162 1 83 100 1 22 58 7 22 57 4 16 71 1 20 41 1 193 223 1 151 162 1 102 111 1 121 275 1 184 250 3 29 65 13 29 64 4 82 203 1 40 199 2 118 207 1 87 149 14 136 231 3 40 165 2 30 174 2 54 185 1 20 221 5 41 188 5 5 20 2 41 130 3 6 224 1 182 192 2 268 269 1 203 221 1 86 87 1 172 240 1 5 115 1 144 187 4 228 238 1 155 185 1 36 116 1 35 255 1 159 160 2 159 161 2 105 167 4 49 148 9 175 249 1 96 157 7 49 269 1 104 164 1 1 114 1 35 228 1 87 141 3 54 97 1 142 254 1 142 255 1 87 114 1 35 254 1 5 163 1 5 154 1 175 177 3 175 176 3 149 262 1 150 165 2 183 192 2 26 28 2 26 27 2 26 29 2 74 136 7 152 262 1 94 127 9 71 127 1 88 228 1 149 174 6 56 205 1 1 189 5 101 113 2 49 239 4 84 138 1 182 183 7 99 278 1 57 58 6 200 201 1 30 87 3 236 253 1 29 126 3 129 263 1 119 166 2 87 194 1 71 146 1 27 28 3 27 29 5 71 93 1 71 91 1 47 132 1 47 133 1 18 89 4 24 40 1 29 203 2 29 76 1 187 217 1 137 163 1 193 280 1 5 23 2 205 285 1 71 92 1 40 150 3 282 283 1 118 143 3 19 156 1 16 94 1 223 224 3 48 72 2 1 141 1 5 179 6 5 180 2 7 8 3 29 80 3 41 45 10 13 172 1 154 195 1 40 277 1 40 72 1 29 37 1 23 179 1 20 203 5 124 149 1 129 171 1 12 20 1 46 100 1 63 98 1 40 244 1 185 251 1 18 87 4 69 70 8 223 278 1 20 231 1 259 260 1 35 36 4 9 228 1 133 134 1 273 274 1 14 23 1 20 249 2 20 29 3 44 151 1 29 229 1 29 164 2 111 197 1 35 117 1 21 40 1 8 23 6 56 115 2 19 135 4 49 181 1 64 68 2 40 162 1 13 240 1 15 101 2 35 211 1 35 212 2 56 110 5 25 41 1 28 86 3 87 221 1 87 109 1 2 118 1 5 10 4 155 225 1 87 95 1 25 54 1 203 235 1 37 60 2 49 50 3 6 140 1 41 283 1 46 83 1 40 233 3 49 57 2 32 33 12 71 73 1 87 262 1 71 234 1 35 261 2 29 104 2 15 45 4 12 13 3 29 106 1 87 178 2 44 165 2 43 233 2 3 185 1 65 253 1 65 236 1 87 98 1 35 265 1 87 89 2 221 249 1 149 152 1 120 221 1 9 20 1 49 58 2 44 199 2 179 180 2 34 116 3 23 225 1 40 160 2 40 159 2 44 201 1 111 129 1 49 51 1 8 225 1 129 144 1 79 110 3 5 107 3 18 81 3 20 185 3 81 147 1 106 166 2 20 279 2 35 183 8 35 182 7 111 171 1 49 281 1 185 250 3 40 44 20 40 43 5 27 80 2 29 96 1 264 269 1 264 268 1 203 249 1 221 275 1 183 286 1 23 46 1 73 234 4 49 268 1 5 62 2 76 145 1 49 185 1 5 248 1 138 281 1 178 204 6 58 103 3 105 106 1 19 20 1 209 251 1 20 71 1 22 277 1 160 161 2 22 103 7 57 59 2 28 32 1 169 191 1 169 190 1 48 50 4 61 87 1 60 65 1 41 218 3 5 195 1 5 196 1 74 231 3 22 72 1 29 68 2 138 148 3 3 78 4 12 172 1 22 215 2 10 41 2 5 101 5 19 112 2 110 285 1 164 166 1 7 35 2 121 221 1 5 258 2 22 110 2 226 244 1 20 224 2 103 148 4 140 223 1 14 237 1 29 168 1 49 59 1 53 113 4 23 247 2 81 89 4 23 155 11 240 241 1 64 104 8 101 163 3 20 189 1 5 56 1 43 44 2 65 168 1 6 223 1 29 90 2 45 53 2 111 263 1 50 51 2 5 137 3 100 170 1 35 139 2 211 212 1 23 100 2 44 200 1 44 131 6 21 22 6 49 208 1 53 54 3 254 255 1 148 260 1 148 259 1 153 194 1 87 147 1 35 192 2 23 216 5 181 245 1 49 191 1 49 190 1 19 276 2 4 83 2 92 93 3 49 103 4 89 147 1 71 198 1 136 232 1 5 45 21 64 167 4 19 123 7 40 151 1 20 118 1 35 286 1 112 276 2 40 77 2 239 281 1 81 87 1 81 86 1 23 170 2 5 110 1 185 209 1 91 93 3 91 92 3 82 235 1 29 236 1 155 216 1 40 243 3 40 122 2 53 179 4 107 154 1 173 247 2 110 205 2 104 106 1 184 185 5 41 206 1 87 144 4 34 117 1 107 196 1 30 124 4 23 173 4 118 252 1 5 205 1 67 77 2 41 186 3 120 275 1 215 277 1 143 252 1 20 148 1 18 152 1 20 223 2 64 105 10 99 271 1 23 227 1 29 157 2 2 273 1 2 274 1 20 175 5 20 176 3 20 177 3 61 89 6 49 256 1 177 220 1 41 75 1 20 133 1 87 152 2 5 79 1 78 155 1 20 23 3 35 116 4 22 40 6 35 266 1 43 199 1 116 182 1 116 183 1 85 136 4 140 224 1 66 111 1 107 195 3 29 253 1 34 36 4 34 35 7 5 15 3 5 257 1 87 153 1 74 85 5 118 213 1 106 164 1 4 231 1 229 230 1 41 282 1 138 239 4 29 32 3 29 33 2 44 150 3 40 71 1 104 167 3 40 201 1 53 101 7 221 231 1 131 219 1 23 83 1 45 113 4 231 272 1 64 65 2 190 191 1 90 126 2 71 94 1 29 145 1 3 184 8 29 230 1 34 139 1 5 285 1 40 219 4 22 125 6 72 215 1 23 210 1 20 35 1 31 87 1 31 86 1 53 258 1 152 153 5 85 231 1 41 42 8 65 229 1 65 230 1 35 142 1 120 121 6 169 245 2 61 81 1 40 52 1 30 149 2 87 204 3 49 264 5 40 161 2 7 261 2 12 111 1 20 87 1 44 243 3 99 223 2 97 185 1 35 119 1 8 227 1 12 240 2 12 241 1 39 40 1 8 155 2 216 227 1 102 197 1 17 135 1 78 185 1 104 105 10 202 258 4 103 138 1 5 113 12 108 177 7 48 49 11 48 51 2 224 270 1 87 174 4 5 41 10 20 111 2 40 226 1 119 139 1 223 271 1 87 187 3 9 35 1 49 245 2 118 274 1 118 273 1 8 170 1 176 177 3 49 84 9 37 65 1 114 141 1 40 267 1 118 185 1 41 242 1 132 133 3 132 134 1 53 202 1 144 217 2 40 131 6 188 218 2 87 128 2 38 39 1 38 40 1 jung-jung-2.1.1/jung-samples/src/main/resources/datasets/weighted.net000066400000000000000000000001151276402340000256640ustar00rootroot00000000000000*Vertices 3 1 "in1" 2 "in2" 3 "in3" *Edges 1 2 10 10 1 3 1 1 2 3 5 5 jung-jung-2.1.1/jung-samples/src/main/resources/datasets/zachary.net000066400000000000000000000110771276402340000255360ustar00rootroot00000000000000*Vertices 34 1 "1" 0.6680 0.3596 0.5000 2 "2" 0.7146 0.5745 0.5000 3 "3" 0.5470 0.4607 0.5000 4 "4" 0.7270 0.4813 0.5000 5 "5" 0.7150 0.1929 0.5000 6 "6" 0.7813 0.1330 0.5000 7 "7" 0.8013 0.1929 0.5000 8 "8" 0.7424 0.5463 0.5000 9 "9" 0.5510 0.5262 0.5000 10 "10" 0.4630 0.5356 0.5000 11 "11" 0.6873 0.1338 0.5000 12 "12" 0.8413 0.3596 0.5000 13 "13" 0.8036 0.4532 0.5000 14 "14" 0.6116 0.5443 0.5000 15 "15" 0.2306 0.8916 0.5000 16 "16" 0.3470 0.9505 0.5000 17 "17" 0.8663 0.1330 0.5000 18 "18" 0.8100 0.5468 0.5000 19 "19" 0.2077 0.8501 0.5000 20 "20" 0.6283 0.5955 0.5000 21 "21" 0.3115 0.9469 0.5000 22 "22" 0.7942 0.5784 0.5000 23 "23" 0.2660 0.9264 0.5000 24 "24" 0.2383 0.5472 0.5000 25 "25" 0.2823 0.2970 0.5000 26 "26" 0.2306 0.3616 0.5000 27 "27" 0.1850 0.7828 0.5000 28 "28" 0.3373 0.4704 0.5000 29 "29" 0.3827 0.5160 0.5000 30 "30" 0.2060 0.6628 0.5000 31 "31" 0.5619 0.7032 0.5000 32 "32" 0.4127 0.4366 0.5000 33 "33" 0.4016 0.6960 0.5000 34 "34" 0.3830 0.6161 0.5000 *Edges 1 2 2 1 3 2 1 4 2 1 5 2 1 6 2 1 7 2 1 8 2 1 9 2 1 11 2 1 12 2 1 13 2 1 14 2 1 18 2 1 20 2 1 22 2 1 32 2 2 3 2 2 4 2 2 8 2 2 14 2 2 18 2 2 20 2 2 22 2 2 31 2 3 4 2 3 8 2 3 9 2 3 10 2 3 14 2 3 28 2 3 29 2 3 33 2 4 8 2 4 13 2 4 14 2 5 7 2 5 11 2 6 7 2 6 11 2 6 17 2 7 17 2 9 31 2 9 33 2 9 34 2 10 34 2 14 34 2 15 33 2 15 34 2 16 33 2 16 34 2 19 33 2 19 34 2 20 34 2 21 33 2 21 34 2 23 33 2 23 34 2 24 26 2 24 28 2 24 30 2 24 33 2 24 34 2 25 26 2 25 28 2 25 32 2 26 32 2 27 30 2 27 34 2 28 34 2 29 32 2 29 34 2 30 33 2 30 34 2 31 33 2 31 34 2 32 33 2 32 34 2 33 34 2 jung-jung-2.1.1/jung-samples/src/main/resources/images/000077500000000000000000000000001276402340000230145ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/main/resources/images/Sandstone.jpg000066400000000000000000001452761276402340000254730ustar00rootroot00000000000000ÿØÿàJFIF,,ÿáAExifMM*bj(1r2‡i¤Ð,,Adobe Photoshop Elements 2.02005:03:30 09:19:01 ÿÿ X X&(. HHÿØÿàJFIFHHÿí Adobe_CMÿîAdobed€ÿÛ„            ÿÀ€€"ÿÝÿÄ?   3!1AQa"q2‘¡±B#$RÁb34r‚ÑC%’Sðáñcs5¢²ƒ&D“TdE£t6ÒUâeò³„ÃÓuãóF'”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷5!1AQaq"2‘¡±B#ÁRÑð3$bár‚’CScs4ñ%¢²ƒ&5ÂÒD“T£dEU6teâò³„ÃÓuãóF”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö'7GWgw‡—§·ÇÿÚ ?çô˜>†æÇ»ý=#§‰>_”¶Ïê½–&Ò;Ç:ëôßÍÜïô~¯ý¾ž;¼¯ùî[Mk>'CÝ¿0çnÒú ÿÔ^¢;Ç̇6³þ—üoü:ð83Ç~ ¶3ùoHDùé0{~k`9ÿæþ›ÿE¤¥çI“·¾…Úø)ßæàik¬L'P´6úÿŒwý‡‰>Dê`oÓMÚyñ&@×Ý·ÿõRð|p'_Þú%¿Gý*±Ÿñu¥ÁÐëØkø6=¿›ÿžÓÒÇÄ÷dµÅÏÿ_瓉κò]1÷oc[ùûìÿ­¤¥yIïÉüæÿÂÕÿn%x™‘¯>zOé=¿I4vÇ„ßëùéâgÚ#Igù;¡á»·~fÿúîÿѤ¥iâcÄ“?ߤÿÏŸñIxK´.Ýÿ’•î h|ƒ„ÑÒ½îþ§þ üâ×ÌLpI€83»þ•ÿSÔ±4væcS¸ÿe²k±ÏH€ Ö1¯ç;Wé/ø´”¸çGkß_ÇQµ­þdzùĆº ÄxÁÿÎës¿sÙ_ü\鮚Ǹè{–û\×»ýlõçY.ŽÐóÿ餧ÿÐççRâNœ¸ê@:;S±û?wgý7¤töè#@Ý?Õ ÿÒé?ÓTŒøDÇÑxís¡ÞÔŒ€ywùK¿wý›[M¶Ïi>$'î~ïýþ ïzÍíí<ä¸ÿ6ôÚsĈìïÍÝÿ¾Äàw‰žðuÿ·?꾞Ïûq%*#Ȉk´oóZßæÿaûÿš³ù¤Úãáxïþ¿ñ•§ @ø7C÷m-öÿ#bG¸×´ŽN¿î÷#ÿQ¤¥ iÐG’J_ƒ´OÂ>>Ýίþ®¿ôŸ¥JGžCH'AôGæÿÖëßþ‘6€sÄžõCœížïôŸ×ôÓóí1ÚDAÔîÛ´·Ûþ¿ñv%//ºÀÐñ¯õ๿ùð_èÔD@tÌh×Gáú?£ÿÿžÿÀ¥ â™äjc¡­»ú¿÷ôüëâ4÷FžnÿÌ?ëµ$§ÿÓçõ‰qÖæw5­ú?ù‚R§ÝÜá?ÖÖ?é¥'XÐοÊnÝííÿþÔKO}I‚ѯýõß¿üçó>šÚh*INž— }ßD·oöq#'Sã$ý)?ÚkíüÏýV—?ÊLÌéô·nÿ¯»ÿ;K¿sÜ@ãÏý_oõÒR§NH_góáÞÔ¸ 4F±£Œëù­Û»þ—þ¤J9׿´;YÓ[aŽ÷FÏûsÓM È@ÑÞö{7ûUÿÆoIKë:jyð×÷¦6»úÉNÓ¤‚5ÐÄŸ¦ 7ݹ#¤Ï¶=ÄGðgö¿íÿô+Ü5Ø$Þöû›þ¾šJW·¸ñ«‡Íöÿ[óRèI3:$HýßnïóShƒ-í©Û¯†§oöYÿ ê'I#ƒÜ%ßù ÿúMéKDLÛIÿ¾þwõÓŸå#M¤Ä'ÝîþºZø™¨m‡·üÏSþ1-®‚"¤áúÛ6ÿ£õ”©3ÀƒÃµÓ’í»›·ýàÒÔéÈÈ:êGÒþ§þ“Láí‡t‚4mÚï?ÑýOùÊý$ñØNžÝ£Ç÷f?7þµ³ýJP=„릚c€Ç7ó¾‚m5"'ÇpíßÛû©áÇAÒ!Î1ôšßw·þŸúOU)Ö'ÝÈ“îùÛ\CÝûÿ×ÿ‹IOÿÔçö΄<ˆ&@çù?ëÿ\H‚ýO©ÿ|üÄ€ÿ ÃC§·w·ÝûŸOý&ÏzJP–ý·@ÒAø;MÕÿ#ôi´Ò`@1±ðs¾‡òÞÿç?íÄä;’<õ÷IýþOö¿ð4¦p:LÈ‚|½Ícü½ÞŸú;w¤¥AçO"8×ó}»ÚïëïÿÌÛ` ÛY#ó½ƒó×ý"\ë¡™$€Iÿ9ŸKù^ÏSý'èý;SÁàLŽ=ÿ’w~oÑwîƒIJqq$’g_¤âãþwîÿR­ÿú1£°ƒ¤DFŸÕk·ÖßÜÿÀ¿àÜ vˆjÖ€~!²×mþSÓL鸓wþ¨?I¿¿ì·þ3ô‰)x™$O<<Ü×;é[ÛÿM(pºÄ‰Ô‡¨Ù÷Ÿÿ[ÿF€ðÐùH2<œàßoò›ÿKþ4@˜ ‰´廨’ŸÿÕçaölˆñ Ú÷7é~sZïOÔÿJ” tãY"µGùîÿÔ©Ð ìe­'ìÿ¯«ú?N´¤j{·^x>{\ZÏõÿ­í4;‰ ŽIìú[?Öê­©"IΆ]&#÷[?™ûÞ§þš}¾z?µµ¿E®þWèÓ‘qy’ Ï|îïþ´¤¥´æ'žúëûÏý/çê¿ÌO¬·AÞ?½ùßúV¿ÑÖôÒ®œÆ¿ºZ _æVÏúßé‰1¡ w׃¹É)y1´ÌôAïôOø?ÎÿÒ¿ÎØÝõ™ï'Ë]é~çì}‰À‘¤‘¡$ ½¹ÿCwÑßô—Rinðá¾ý¿Gþ¯þéïzJ_@‚4üº~nßä7¿ôŸÍý½Ó»Px&ŸǵŸÉþGé=oÑúm΢ >ÙäGî¶\æcÿ=§"ŸÝüÐßæîßúOøßM%(ŸÎ3 “Ÿ‹½ŸÔÿ ÿš;Dè>õ[¿íÏü£¯ïHs]ý¦àɆӠ‚ºéçïIKëù¿´ÇòGÒÿÑ_ák·ÖKÝôL’"ºyíöÿÖý?ü 6§Y¦¢}º{_þ¿£O´Ä´OæËbx Õ¬ýßüü’–æyù{Ÿ Û;ó?¯bq¤5-ÙÿgübJT»ãÛæÇù®wùÿø:iD‚m¤Èótmÿ¢ŸvÏåþ©¥ÿž¿á,JIäø˜çOúáÿ·6Wÿn$¥iN€3¨öÏý[+ücÓÉí0x‚?Õßíÿ1¿öò@ÒDìÇöw;oö=$»xƒ<í3ÿRßtý?üûüÚJQwïG÷é_øÄ¢dAúD?9Žú_ÛözèÒÔú1É÷û‹?×ý'èýF†÷ðÔè4ídzÓþS¬IKˆäF§Cå>ݾÏìÿÅúžŸ¦‘$êf$í>^ÒÖ¿ýÑ%¬Ì’yˆüéÿÉY_ú4¼à1#þ§÷—úGÿ£IK»O¤|<ÿYÞÝßë_¦ŸóµÕÇ]DñùÛ£{¿’ïûi1ÖwìA/¦ç¿þ-›í´ð5ÔùÝ¿.ÿú߬ßûúJT»˜ú—î~Ïõýi×R=¢uòkôoþOšG†„~?¼Ý¿õËàëKX¢<9Ÿ½Û?Îüô”ÿÿÙÿí72Photoshop 3.08BIM%8BIMê° com.apple.print.PageFormat.PMHorizontalRes com.apple.print.ticket.creator com.apple.printingmanager com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMHorizontalRes 72 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2005-03-30T14:05:38Z com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMOrientation com.apple.print.ticket.creator com.apple.printingmanager com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMOrientation 1 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2005-03-30T14:05:38Z com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMScaling com.apple.print.ticket.creator com.apple.printingmanager com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMScaling 1 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2005-03-30T14:05:38Z com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMVerticalRes com.apple.print.ticket.creator com.apple.printingmanager com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMVerticalRes 72 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2005-03-30T14:05:38Z com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMVerticalScaling com.apple.print.ticket.creator com.apple.printingmanager com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMVerticalScaling 1 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2005-03-30T14:05:38Z com.apple.print.ticket.stateFlag 0 com.apple.print.subTicket.paper_info_ticket com.apple.print.PageFormat.PMAdjustedPageRect com.apple.print.ticket.creator com.apple.printingmanager com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMAdjustedPageRect 0.0 0.0 734 576 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2005-03-30T14:05:38Z com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMAdjustedPaperRect com.apple.print.ticket.creator com.apple.printingmanager com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMAdjustedPaperRect -18 -18 774 594 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2005-03-30T14:05:38Z com.apple.print.ticket.stateFlag 0 com.apple.print.PaperInfo.PMPaperName com.apple.print.ticket.creator com.apple.print.pm.PostScript com.apple.print.ticket.itemArray com.apple.print.PaperInfo.PMPaperName na-letter com.apple.print.ticket.client com.apple.print.pm.PostScript com.apple.print.ticket.modDate 2003-07-01T17:49:36Z com.apple.print.ticket.stateFlag 1 com.apple.print.PaperInfo.PMUnadjustedPageRect com.apple.print.ticket.creator com.apple.print.pm.PostScript com.apple.print.ticket.itemArray com.apple.print.PaperInfo.PMUnadjustedPageRect 0.0 0.0 734 576 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2005-03-30T14:05:38Z com.apple.print.ticket.stateFlag 0 com.apple.print.PaperInfo.PMUnadjustedPaperRect com.apple.print.ticket.creator com.apple.print.pm.PostScript com.apple.print.ticket.itemArray com.apple.print.PaperInfo.PMUnadjustedPaperRect -18 -18 774 594 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2005-03-30T14:05:38Z com.apple.print.ticket.stateFlag 0 com.apple.print.PaperInfo.ppd.PMPaperName com.apple.print.ticket.creator com.apple.print.pm.PostScript com.apple.print.ticket.itemArray com.apple.print.PaperInfo.ppd.PMPaperName US Letter com.apple.print.ticket.client com.apple.print.pm.PostScript com.apple.print.ticket.modDate 2003-07-01T17:49:36Z com.apple.print.ticket.stateFlag 1 com.apple.print.ticket.APIVersion 00.20 com.apple.print.ticket.privateLock com.apple.print.ticket.type com.apple.print.PaperInfoTicket com.apple.print.ticket.APIVersion 00.20 com.apple.print.ticket.privateLock com.apple.print.ticket.type com.apple.print.PageFormatTicket 8BIMéxHHÞ@ÿîÿîRg(üHHØ(dÿh 8BIMí,,8BIM&?€8BIM x8BIM8BIMó 8BIM 8BIM' 8BIMõH/fflff/ff¡™š2Z5-8BIMøpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿè8BIM@@8BIM8BIMAXXMosaicXXnullboundsObjcRct1Top longLeftlongBtomlongXRghtlongXslicesVlLsObjcslicesliceIDlonggroupIDlongoriginenum ESliceOrigin autoGeneratedTypeenum ESliceTypeImg boundsObjcRct1Top longLeftlongBtomlongXRghtlongXurlTEXTnullTEXTMsgeTEXTaltTagTEXTcellTextIsHTMLboolcellTextTEXT horzAlignenumESliceHorzAligndefault vertAlignenumESliceVertAligndefault bgColorTypeenumESliceBGColorTypeNone topOutsetlong leftOutsetlong bottomOutsetlong rightOutsetlong8BIM8BIM8BIM '€€€À ÿØÿàJFIFHHÿí Adobe_CMÿîAdobed€ÿÛ„            ÿÀ€€"ÿÝÿÄ?   3!1AQa"q2‘¡±B#$RÁb34r‚ÑC%’Sðáñcs5¢²ƒ&D“TdE£t6ÒUâeò³„ÃÓuãóF'”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷5!1AQaq"2‘¡±B#ÁRÑð3$bár‚’CScs4ñ%¢²ƒ&5ÂÒD“T£dEU6teâò³„ÃÓuãóF”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö'7GWgw‡—§·ÇÿÚ ?çô˜>†æÇ»ý=#§‰>_”¶Ïê½–&Ò;Ç:ëôßÍÜïô~¯ý¾ž;¼¯ùî[Mk>'CÝ¿0çnÒú ÿÔ^¢;Ç̇6³þ—üoü:ð83Ç~ ¶3ùoHDùé0{~k`9ÿæþ›ÿE¤¥çI“·¾…Úø)ßæàik¬L'P´6úÿŒwý‡‰>Dê`oÓMÚyñ&@×Ý·ÿõRð|p'_Þú%¿Gý*±Ÿñu¥ÁÐëØkø6=¿›ÿžÓÒÇÄ÷dµÅÏÿ_瓉κò]1÷oc[ùûìÿ­¤¥yIïÉüæÿÂÕÿn%x™‘¯>zOé=¿I4vÇ„ßëùéâgÚ#Igù;¡á»·~fÿúîÿѤ¥iâcÄ“?ߤÿÏŸñIxK´.Ýÿ’•î h|ƒ„ÑÒ½îþ§þ üâ×ÌLpI€83»þ•ÿSÔ±4væcS¸ÿe²k±ÏH€ Ö1¯ç;Wé/ø´”¸çGkß_ÇQµ­þdzùĆº ÄxÁÿÎës¿sÙ_ü\鮚Ǹè{–û\×»ýlõçY.ŽÐóÿ餧ÿÐççRâNœ¸ê@:;S±û?wgý7¤töè#@Ý?Õ ÿÒé?ÓTŒøDÇÑxís¡ÞÔŒ€ywùK¿wý›[M¶Ïi>$'î~ïýþ ïzÍíí<ä¸ÿ6ôÚsĈìïÍÝÿ¾Äàw‰žðuÿ·?꾞Ïûq%*#Ȉk´oóZßæÿaûÿš³ù¤Úãáxïþ¿ñ•§ @ø7C÷m-öÿ#bG¸×´ŽN¿î÷#ÿQ¤¥ iÐG’J_ƒ´OÂ>>Ýίþ®¿ôŸ¥JGžCH'AôGæÿÖëßþ‘6€sÄžõCœížïôŸ×ôÓóí1ÚDAÔîÛ´·Ûþ¿ñv%//ºÀÐñ¯õ๿ùð_èÔD@tÌh×Gáú?£ÿÿžÿÀ¥ â™äjc¡­»ú¿÷ôüëâ4÷FžnÿÌ?ëµ$§ÿÓçõ‰qÖæw5­ú?ù‚R§ÝÜá?ÖÖ?é¥'XÐοÊnÝííÿþÔKO}I‚ѯýõß¿üçó>šÚh*INž— }ßD·oöq#'Sã$ý)?ÚkíüÏýV—?ÊLÌéô·nÿ¯»ÿ;K¿sÜ@ãÏý_oõÒR§NH_góáÞÔ¸ 4F±£Œëù­Û»þ—þ¤J9׿´;YÓ[aŽ÷FÏûsÓM È@ÑÞö{7ûUÿÆoIKë:jyð×÷¦6»úÉNÓ¤‚5ÐÄŸ¦ 7ݹ#¤Ï¶=ÄGðgö¿íÿô+Ü5Ø$Þöû›þ¾šJW·¸ñ«‡Íöÿ[óRèI3:$HýßnïóShƒ-í©Û¯†§oöYÿ ê'I#ƒÜ%ßù ÿúMéKDLÛIÿ¾þwõÓŸå#M¤Ä'ÝîþºZø™¨m‡·üÏSþ1-®‚"¤áúÛ6ÿ£õ”©3ÀƒÃµÓ’í»›·ýàÒÔéÈÈ:êGÒþ§þ“Láí‡t‚4mÚï?ÑýOùÊý$ñØNžÝ£Ç÷f?7þµ³ýJP=„릚c€Ç7ó¾‚m5"'ÇpíßÛû©áÇAÒ!Î1ôšßw·þŸúOU)Ö'ÝÈ“îùÛ\CÝûÿ×ÿ‹IOÿÔçö΄<ˆ&@çù?ëÿ\H‚ýO©ÿ|üÄ€ÿ ÃC§·w·ÝûŸOý&ÏzJP–ý·@ÒAø;MÕÿ#ôi´Ò`@1±ðs¾‡òÞÿç?íÄä;’<õ÷IýþOö¿ð4¦p:LÈ‚|½Ícü½ÞŸú;w¤¥AçO"8×ó}»ÚïëïÿÌÛ` ÛY#ó½ƒó×ý"\ë¡™$€Iÿ9ŸKù^ÏSý'èý;SÁàLŽ=ÿ’w~oÑwîƒIJqq$’g_¤âãþwîÿR­ÿú1£°ƒ¤DFŸÕk·ÖßÜÿÀ¿àÜ vˆjÖ€~!²×mþSÓL鸓wþ¨?I¿¿ì·þ3ô‰)x™$O<<Ü×;é[ÛÿM(pºÄ‰Ô‡¨Ù÷Ÿÿ[ÿF€ðÐùH2<œàßoò›ÿKþ4@˜ ‰´廨’ŸÿÕçaölˆñ Ú÷7é~sZïOÔÿJ” tãY"µGùîÿÔ©Ð ìe­'ìÿ¯«ú?N´¤j{·^x>{\ZÏõÿ­í4;‰ ŽIìú[?Öê­©"IΆ]&#÷[?™ûÞ§þš}¾z?µµ¿E®þWèÓ‘qy’ Ï|îïþ´¤¥´æ'žúëûÏý/çê¿ÌO¬·AÞ?½ùßúV¿ÑÖôÒ®œÆ¿ºZ _æVÏúßé‰1¡ w׃¹É)y1´ÌôAïôOø?ÎÿÒ¿ÎØÝõ™ï'Ë]é~çì}‰À‘¤‘¡$ ½¹ÿCwÑßô—Rinðá¾ý¿Gþ¯þéïzJ_@‚4üº~nßä7¿ôŸÍý½Ó»Px&ŸǵŸÉþGé=oÑúm΢ >ÙäGî¶\æcÿ=§"ŸÝüÐßæîßúOøßM%(ŸÎ3 “Ÿ‹½ŸÔÿ ÿš;Dè>õ[¿íÏü£¯ïHs]ý¦àɆӠ‚ºéçïIKëù¿´ÇòGÒÿÑ_ák·ÖKÝôL’"ºyíöÿÖý?ü 6§Y¦¢}º{_þ¿£O´Ä´OæËbx Õ¬ýßüü’–æyù{Ÿ Û;ó?¯bq¤5-ÙÿgübJT»ãÛæÇù®wùÿø:iD‚m¤Èótmÿ¢ŸvÏåþ©¥ÿž¿á,JIäø˜çOúáÿ·6Wÿn$¥iN€3¨öÏý[+ücÓÉí0x‚?Õßíÿ1¿öò@ÒDìÇöw;oö=$»xƒ<í3ÿRßtý?üûüÚJQwïG÷é_øÄ¢dAúD?9Žú_ÛözèÒÔú1É÷û‹?×ý'èýF†÷ðÔè4ídzÓþS¬IKˆäF§Cå>ݾÏìÿÅúžŸ¦‘$êf$í>^ÒÖ¿ýÑ%¬Ì’yˆüéÿÉY_ú4¼à1#þ§÷—úGÿ£IK»O¤|<ÿYÞÝßë_¦ŸóµÕÇ]DñùÛ£{¿’ïûi1ÖwìA/¦ç¿þ-›í´ð5ÔùÝ¿.ÿú߬ßûúJT»˜ú—î~Ïõýi×R=¢uòkôoþOšG†„~?¼Ý¿õËàëKX¢<9Ÿ½Û?Îüô”ÿÿÙ8BIM!yAdobe Photoshop ElementsAdobe Photoshop Elements 2.08BIMÿüÿáÍhttp://ns.adobe.com/xap/1.0/ Adobe Photoshop Elements for Macintosh, version 2.0 adobe:docid:photoshop:1cce8657-a28d-11d9-930b-f12e5256bb70 ÿîAdobed€ÿÛ„ !!3$3Q00QB///B''""  "334&4""   ÿÀXX"ÿÝ&ÿÄ  5!1AQ"aq2‘±B¡ÑÁðR#r3bá‚ñC4’¢²ÒS$sÂcƒ“âò£DTd%5E&t6Ue³„ÃÓuãóF”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö/!1AQaq‘"2ð¡±ÁÑáñBR#br’3‚C$¢²4SDcsÂÒƒ“£Tâò%&5dEU6te³„ÃÓuãóF”¤…´•ÄÔäô¥µÅÕåõVfv†ÿÚ ?ãçÙþ”X®Õ¢óàJ~¿ßþéõA ö“©_¼ÿÙZúPk’?òI5ûü*iË x]ÌŠ7àÞïéø«Qÿ5«=øÿ’€×‡ý¤9E~÷²_ùµ Wó¿ø*JxÿÎTsíA5¡( ¡ÉH>5ÿ’Xßâ’Ÿ£ú¾) ?ýÔ»¿}÷Òwƒ\yûÄwìÿJ,W?êÑyö§ëýÿî€ÚN¥~óü_eké@I®HÿÉ$×ïð¨§,áuÿ2(ß‚7{¿§àZ­GüÖ¬÷ãþJ^ötåûÞÉæÔ\Îÿਠ*5ãÿ9QϵÖ„ &‡% ø×þIc|?ŠJ~êø¤€8ýÿ÷RîýõÿßIÜ qåýÿïß³ý(±\ÿ«EçÚŸ¯÷ÿº@ÿi:•ûÏñ}•¯¥&¹#ÿ$“_¿Â Vœ²…×üÈ ~ÝîþŸjµóZ³ßù( xÚAÓ”Wï{%ÿ›Rqÿ;ÿ‚ $¨×üåG>ÔZ€š”ƒã_ù%ð8þ))ú?«â’ã÷ÿÝK»÷×ÿ}'pH5Ç—÷ÿ¼@~Ïô¢Åsþ­Ÿj~¿ßþéhý¤êWï?ÅöV¾”šäü’M~ÿ ZrÈ_ó"€Mø#w»ú~ªÔÍjÏ~?ä 5áÿiNQ_½ì—þmHÇüïþ €’£^?ó•ûPMhJhrRä–7Àãø¤§èþ¯ŠHßÿu.ïß_ýôÁ ×_ßþñ]û?Ò‹Ïú´^}©úÿû¤ ö“©_¼ÿÙZúPk’?òI5ûü*iË x]ÌŠ7àÞïéø«Qÿ5«=øÿ’€×‡ý¤9E~÷²_ùµ Wó¿ø*Oï%QÏ·þj ÿÐã>ÒºÒ]މºÐpúŽ#÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ ¦š}éÖ<ÈÓ*e '_Ü-׊}ßò¢·ZZîF‡á]·ûù²€O‡‚ê4J=Ü n´ ?ü™-|—Güôh€kÃþJÒÐä„Â4ÿ‘4 ®çþj>ñü[÷b(x_ø·-uú±Çý„Ý|!åÊ:i§ÞcÉ 8ò¨öZuýÂÝx§Ýÿ*+u¥ ^äh~Û¿™÷û(øx.£@Gô£ÝÇð¦ëAÂ÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ ¦š}éÖ<ÈÓ*e '_Ü-׊}ßò¢·ZZîF‡á]·ûù²€O‡‚ê4J=Ü n´ ?ü™-|—Güôh€kÃþJÒÐä„Â4ÿ‘4 ®çþj>ñü[÷b(x_ø·-uú±Çý„Ý|!åÊ:i§ÞcÉ 8ò¨öZuýÂÝx§Ýÿ*+u¥ ^äh~Û¿™÷û(øx.£@Gô£ÝÇð¦ëAÂ÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ §?òµT >/õ*ÿÑã$B}Ÿó  ÿÉÿß,ßþuüo¨â69?öš:kÙn–åï@ÃÇþ“D“íH'¶ˆ¾ÀÿÉÿàˆljÿ’ž9ÑwxŸü’*½§ü>Tïÿ—µ}Ÿ-~“ýeŠ5í¢í¥£ì?/:ÝíÿÈ´†Ÿ—ï‘{èQ^:O™å:…¢}ß¿ýâ>óP@å¯güÄ ÅÿÉÿßLßþuüh ŽOý¦Žšö[¥¹{Ð'pñÿ¤Ñ$ûR í¢/°?òø"±âä§Žt]Þ'ÿ$Нiÿ•ûÿåí_gÄ‹_¤ÿGÙ@¢{h»ihûË΀7{ò-!§åûäEžúWŽŸÓæ@yN¡hŸwïÿxŸüÔ9kÙÿ1Dqò÷Ó7ÿc“ÿi£¦½–én^ô Ü<é4I>Ô‚{h‹ìüŸþ€,xŸù)ãw‰ÿÉ"«ÚÃå@>ÿù{WÙñ"×é?ÑöP(£^Ú.ÚZ>Ãòó  Þßü‹@Hiù~ùg¾…ã§ôùS¨Z'ÝûÿÞ#çÿ5$ZöÌ@‘_üŸýôÍÿç_Æ€ØäÿÚhé¯eº[—½wúMOµ žÚ"ûÿ'ÿ‚ 'þJxçEÝâòHªöŸðùP¿þ^Õö|HµúOô}” (×¶‹¶–°ü¼èw·ÿ"Ð~_¾DYï¡Exéý>d”ê‰÷~ÿ÷ˆùÿÍ@I–½Ÿó$Gÿ'ÿ}3ù×ñ 69?öš:kÙn–åï@ÃÇþ“D“íH'¶ˆ¾ÀÿÉÿàˆljÿ’ž9ÑwxŸü’*½§ü>Tïÿ—µ}Ÿ-~“ýeг¶¹ÕP?ÿÒä/ßÿ‚ ŸüÔ%ÊA¿cê8„_aÿ+ÌŠ÷ü*EëèZ¯wøÐyý+}®×kþRýÿóPï ÿR€/Cæû[Z¿ßnÄØ ñÿ™ñ#Cÿž®ÐóÕӵ߸‚ø¿Â“îÿœ‚|é(µÎ@løÿÊN£Çý+4Uqÿ+M¨„ëæ?Óÿ¿R+ÃOßûH°F¿¿þbMw Ê@"åã_¿÷=Tïÿš‚D¹H7ì@"ûù^dW¸áR/[ÿBÕ{¿Æ€ëÈ¿é[ív¼ë_ò—ïÿš€7xWú”z7ÚÚÕþûv ŽÅ×üωüõvƒÿž®®ÿÄÀþ˜ŸwüäãÿI@«þrgÇþRu?é[ù¢«ùZm@t'_1þŸýú‘^~ÿÚE‚5ýÿók¹þR/ýÿ¹ê ŸüÔ%ÊA¿bØÊó"½Àÿ ‘zßú«Ýþ4^EÿJßkµçZÿ”¿üԻ¿Ԡ Ðù¾ÖÖ¯÷Û±v(¼æ|HÐÿç«´üõtíwþ €¾¯ð¤Äû¿ç ŸúJí_ó>?ò“¨ñÿJßÍ\ÊÓj¡:ùôÿïÔŠðÓ÷þÒ,¯ïÿ˜“]Èò¹x×ïýÏUûÿæ ‘.R û¾ÃþW™îøT‹Öÿе^ïñ :ò/úVû]¯:×ü¥ûÿæ  Þþ¥^‡Íö¶µ¾Ýˆ#±@5ãÿ3âF‡ÿ=] ÿç«§k¿ñð…&'Ýÿ9øÿÒPjÿœ€Ùñÿ”GúVþhªãþV›P ×̧ÿ~¤W†Ÿ¿ö‘`üÄšî@ÿ”€EËÆ¿îz¨'ßÿ5‰roØ€Eöò¼È¯p?¤^·þ…ª÷בÒ·ÚíyÖ¿å/ßÿ5nð¯õ(ô>oµµ«ýöìAНùŸ4?ùêíÿ=];]ÿˆ /€?ÒªOñÒÿÝjÿÓã¿ù¿i'^ó¿üÔ‹÷WºH ;>£ˆ@½kþR/°ÿ O?òS@€Ñäû(Z£Çú£ÿÅ“¯éÿ݈ÇþI}•ÿ'Ê“%ÕiâÔ¿5öú5ì€Ew¯ù*tçÿ8ROTF¼PhHOïÿ˜‘­÷‰ÿ•¿ÿ} ßÿ5 V‡¾¿jLÔ{þµÔû‡ÙOÏý( ö?óuçÿ;ÿÍHºÓJ÷Idë_ò‘}€×øT‚yÿ’šx$ÙBÕ?Õþ,|ÿOþì@<òKì¯ù>T™.¨Oþ¤íù¨'·Ô¯d+½ÉS§?ùÂ’x¢¢5â€ûB@úüÄo¼Oü­ÿûíxþÿù­´=õûRf£Øßõ®§Ü>Ê~é@o±ÿ˜“¯?ùßþjEÖšWºH ; /Zÿ”‹ì¿Â¤Ïü”Ð#À 4y#þʨñþ¨ÿñdëàGú÷b±ãÿ’_eÉò¤Éu@xŸõ'oÍA=¾¤ { ]ëþJ9ÿΓůÚÓûÿæ$k}âåoÿßh#Ç÷ÿÍh¡ï¯Ú“5Æÿ­u>áöSóÿJ}üÄyÿÎÿóR.´Ò½ÒEÙz×ü¤_`5þ žä¦£ÉöPµGõGÿ‹'_?Óÿ»ü’û+þO•&KªÓÄÿ©;~j íõ kÙŠï_òTéÏþp¤ž(¨x >Ð>Ÿßÿ1#[ïÿ+þûA?¿þk@­}~Ô™¨ö7ýk©÷²ŸŸúPìæ$ëÏþwÿš‘u¦•î’(È Ö¿å"û¯ð©óÿ%4ð Hÿ²…ª<ª?üY:øþŸýØ€,xÿä—Ù_ò|©2]Pž'ýIÛóPOo©^ÈWzÿ“æU$ñEP?ÿÔã$Çßÿ5®ž:û¿÷âĊЫöùϨâKb^ßêòñTƒàuÿ˜€*´ø‡ø·y]û{ü‹J+¿é¹³ã§ø§‰'÷ÿx¶}è“4]iÝûÇÊ€~wïýÆEKŽûÅÛßþjI¿û?gÿ6 5~ßõ.§Åh ü‡þeA‹ ’Ø—·ú¼ŸüU øæ ­>!þ-ÞD×~Þÿ"ßÒŠïún@løéþéâIýÿÞ-£_z$ÇO}ßûñEZ¿hÿœ€ñÚÿÅñ©ÿêFŸ?ê]½ÿÅ9 B,ç7%¯‡Ošw~ñò ‡Ýûÿq‘RãŸßþñv÷ÿš’oþÏÙÿͨ _·ýK©ñZ'CGÿ!ÿ™P@@"Ç#ä¶%íþ¯'ÿH>_ùˆ«Oˆ‹w‘5ß·¿È·ô¢»þ›>::x’÷‹h×Þ€I1ãEÓÇ_wþüQC€V¯Ú?ç Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Ú?¿ûÅQþUÿÖã×ÃþoÿMûù¶¼?ò,Ðà>£‰U|ž?¼÷ãýZ^‡„ ð»ñ9"ÿóå¡ÇÅþ”΀ZñØi-?ó„Š>Ôdû“¯~?Ò¶}§ü>_ýØ‹ôÿ ªñÿÈ£?æ­öçü1)7ûù¿ór´ u{XÝK·Ç÷ÿÍkTlš@|ù_ËZ­GþI$ƒÿž¯'Ú€ëÿmMûù¶¼¡À@ª¾Oá^{ñþˆ­‚/CÂø ]¿øœ‘ùòÐãâÿJç@-xì4–ŸùÂEj²}É׿é[>Óþ/þìEŽú…ÕxÿäQΟóVûsþ”›ýüßù¹Úº€½¬n¥Ûãûÿ浪6M ¾ü¯å­V£ÿ$’AÿÏW“í@uÿζ¦ˆýüˆÛ^ Ðà U_'ð¯=øÿDV€A¡áƒ|®ßüNH¿üùhqñ¥ó ¼vKOüá"µY>äëßô­Ÿiÿ—ÿv"Ç}?€j¼ò(çOù«}¹ÿ JMþþoüÜ€í]@^Ö7RíñýÿóZÕ&ßþWòÖ«Qÿ’I ÿç«Éö :ÿç[SD~þDm¯hp*¯“ÇøWžü¢+@ ‹Ðð€A¾×oþ'$_þ|´8ø¿Ò€yÐ ^;%§þp‘GÚ€,ŸruïÇúVÏ´ÿ‡Ëÿ»c¾Ÿá@5^?ùs§üÕ¾Üÿ†%&ÿ7þn@v® /k©vøþÿù­j“Hïƒÿ+ùkU¨ÿÉ$óÕäûPó­©¢?"6׃48WÉãü+Ï~?Ñ Eèx@ ßk·ÿ’/ÿ>Z|_é@<诀F’Óÿ8H£í@O¹:÷ãý+gÚÃåÿ݈±ßOð +ÅPa¯ømP?ÿ×âû+ü)Ýzßú‘Ï)úÿû§Ôq€xÿŠIÙ~óü_eké@I®LòK@{?è$ Ó–@ðºÿ™oÂѺôÓú>ªÔÍjÏ~?ä  ðçþRMÇ÷ÚÍ~÷²_ùµ Wó¿ø*JóÇþp£Ÿj ­ @MÜ ÿœ˜ßâ’Ÿ£ú¾) 7\qûÿi’Aä·ƒ\yûÄ û+ü)Ýzßú‘ϵ?_ïÿt€4ÇüRH¾Ë÷Ÿâû+_JMrcÿ’ZÙÿA Vœ²…×üÈ ~צŸÑð-V£þkV{ñÿ%W‡?ò’n?¾Ök÷½’ÿÍ©¸ÿÿÁPWž?ó…ûPMhJh(®àWü䯸”ýÕñIºãßûL’ 5¸$ãËûÿÞ H×Ù_áNëÖÿÔŽ}©úÿû¤ ?â’Eö_¼ÿÙZúPk“ü’ÐÏú ´å<.¿æE›ð´n½4þjµóZ³ßù(¼9ÿ”“qýö³_½ì—þmHÇüïþ €’¼ñÿœ(çÚ‚kBPAEw¿ç&7Àãø¤§èþ¯ŠH ×~ÿÚdy­Á ×_ßþñF¾Êÿ w^·þ¤síO×ûÿÝ ñÿ’/²ýçø¾Ê×Ò€“\˜ÿä–€öÐH§,áuÿ2(ß…£ué§ô| U¨ÿšÕžüÉ@áÏü¤›ïµšýïd¿ój@®?çðT•çüáG>ÔZ€š +¸ÿ91¾Å%?Gõ|R@n¸ã÷þÓ$ƒÈ n ¸òþÿ÷ˆ5öWøSºõ¿õ#Ÿj~¿ßþéhø¤‘}—ï?ÅöV¾”šäÇÿ$´³þ‚@­9d ¯ù‘@&ü-¯M?£àZ­GüÖ¬÷ãþJ¯å$Ü}¬×ï{%ÿ›Rqÿ;ÿ‚ $þòUûæªÿÐã>ÒºÒ]މºÐpúŽ#÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ ¦š}éÖ<ÈÓ*e '_Ü-׊}ßò¢·ZZîF‡á]·ûù²€O‡‚ê4J=Ü n´ ?ü™-|—Güôh€kÃþJÒÐä„Â4ÿ‘4 ®çþj>ñü[÷b(x_ø·-uú±Çý„Ý|!åÊ:i§ÞcÉ 8ò¨öZuýÂÝx§Ýÿ*+u¥ ^äh~Û¿™÷û(øx.£@Gô£ÝÇð¦ëAÂ÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ ¦š}éÖ<ÈÓ*e '_Ü-׊}ßò¢·ZZîF‡á]·ûù²€O‡‚ê4J=Ü n´ ?ü™-|—Güôh€kÃþJÒÐä„Â4ÿ‘4 ®çþj>ñü[÷b(x_ø·-uú±Çý„Ý|!åÊ:i§ÞcÉ 8ò¨öZuýÂÝx§Ýÿ*+u¥ ^äh~Û¿™÷û(øx.£@Gô£ÝÇð¦ëAÂ÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ §?òµT >/õ*ÿÑã$B}Ÿó  ÿÉÿß,ßþuüo¨â69?öš:kÙn–åï@ÃÇþ“D“íH'¶ˆ¾ÀÿÉÿàˆljÿ’ž9ÑwxŸü’*½§ü>Tïÿ—µ}Ÿ-~“ýeŠ5í¢í¥£ì?/:ÝíÿÈ´†Ÿ—ï‘{èQ^:O™å:…¢}ß¿ýâ>óP@å¯güÄ ÅÿÉÿßLßþuüh ŽOý¦Žšö[¥¹{Ð'pñÿ¤Ñ$ûR í¢/°?òø"±âä§Žt]Þ'ÿ$Нiÿ•ûÿåí_gÄ‹_¤ÿGÙ@¢{h»ihûË΀7{ò-!§åûäEžúWŽŸÓæ@yN¡hŸwïÿxŸüÔ9kÙÿ1Dqò÷Ó7ÿc“ÿi£¦½–én^ô Ü<é4I>Ô‚{h‹ìüŸþ€,xŸù)ãw‰ÿÉ"«ÚÃå@>ÿù{WÙñ"×é?ÑöP(£^Ú.ÚZ>Ãòó  Þßü‹@Hiù~ùg¾…ã§ôùS¨Z'ÝûÿÞ#çÿ5$ZöÌ@‘_üŸýôÍÿç_Æ€ØäÿÚhé¯eº[—½wúMOµ žÚ"ûÿ'ÿ‚ 'þJxçEÝâòHªöŸðùP¿þ^Õö|HµúOô}” (×¶‹¶–°ü¼èw·ÿ"Ð~_¾DYï¡Exéý>d”ê‰÷~ÿ÷ˆùÿÍ@I–½Ÿó$Gÿ'ÿ}3ù×ñ 69?öš:kÙn–åï@ÃÇþ“D“íH'¶ˆ¾ÀÿÉÿàˆljÿ’ž9ÑwxŸü’*½§ü>Tïÿ—µ}Ÿ-~“ýeг¶¹ÕP?ÿÒä/ßÿ‚ ŸüÔ%ÊA¿cê8„_aÿ+ÌŠ÷ü*EëèZ¯wøÐyý+}®×kþRýÿóPï ÿR€/Cæû[Z¿ßnÄØ ñÿ™ñ#Cÿž®ÐóÕӵ߸‚ø¿Â“îÿœ‚|é(µÎ@løÿÊN£Çý+4Uqÿ+M¨„ëæ?Óÿ¿R+ÃOßûH°F¿¿þbMw Ê@"åã_¿÷=Tïÿš‚D¹H7ì@"ûù^dW¸áR/[ÿBÕ{¿Æ€ëÈ¿é[ív¼ë_ò—ïÿš€7xWú”z7ÚÚÕþûv ŽÅ×üωüõvƒÿž®®ÿÄÀþ˜ŸwüäãÿI@«þrgÇþRu?é[ù¢«ùZm@t'_1þŸýú‘^~ÿÚE‚5ýÿók¹þR/ýÿ¹ê ŸüÔ%ÊA¿bØÊó"½Àÿ ‘zßú«Ýþ4^EÿJßkµçZÿ”¿üԻ¿Ԡ Ðù¾ÖÖ¯÷Û±v(¼æ|HÐÿç«´üõtíwþ €¾¯ð¤Äû¿ç ŸúJí_ó>?ò“¨ñÿJßÍ\ÊÓj¡:ùôÿïÔŠðÓ÷þÒ,¯ïÿ˜“]Èò¹x×ïýÏUûÿæ ‘.R û¾ÃþW™îøT‹Öÿе^ïñ :ò/úVû]¯:×ü¥ûÿæ  Þþ¥^‡Íö¶µ¾Ýˆ#±@5ãÿ3âF‡ÿ=] ÿç«§k¿ñð…&'Ýÿ9øÿÒPjÿœ€Ùñÿ”GúVþhªãþV›P ×̧ÿ~¤W†Ÿ¿ö‘`üÄšî@ÿ”€EËÆ¿îz¨'ßÿ5‰roØ€Eöò¼È¯p?¤^·þ…ª÷בÒ·ÚíyÖ¿å/ßÿ5nð¯õ(ô>oµµ«ýöìAНùŸ4?ùêíÿ=];]ÿˆ /€?ÒªOñÒÿÝjÿÓã¿ù¿i'^ó¿üÔ‹÷WºH ;>£ˆ@½kþR/°ÿ O?òS@€Ñäû(Z£Çú£ÿÅ“¯éÿ݈ÇþI}•ÿ'Ê“%ÕiâÔ¿5öú5ì€Ew¯ù*tçÿ8ROTF¼PhHOïÿ˜‘­÷‰ÿ•¿ÿ} ßÿ5 V‡¾¿jLÔ{þµÔû‡ÙOÏý( ö?óuçÿ;ÿÍHºÓJ÷Idë_ò‘}€×øT‚yÿ’šx$ÙBÕ?Õþ,|ÿOþì@<òKì¯ù>T™.¨Oþ¤íù¨'·Ô¯d+½ÉS§?ùÂ’x¢¢5â€ûB@úüÄo¼Oü­ÿûíxþÿù­´=õûRf£Øßõ®§Ü>Ê~é@o±ÿ˜“¯?ùßþjEÖšWºH ; /Zÿ”‹ì¿Â¤Ïü”Ð#À 4y#þʨñþ¨ÿñdëàGú÷b±ãÿ’_eÉò¤Éu@xŸõ'oÍA=¾¤ { ]ëþJ9ÿΓůÚÓûÿæ$k}âåoÿßh#Ç÷ÿÍh¡ï¯Ú“5Æÿ­u>áöSóÿJ}üÄyÿÎÿóR.´Ò½ÒEÙz×ü¤_`5þ žä¦£ÉöPµGõGÿ‹'_?Óÿ»ü’û+þO•&KªÓÄÿ©;~j íõ kÙŠï_òTéÏþp¤ž(¨x >Ð>Ÿßÿ1#[ïÿ+þûA?¿þk@­}~Ô™¨ö7ýk©÷²ŸŸúPìæ$ëÏþwÿš‘u¦•î’(È Ö¿å"û¯ð©óÿ%4ð Hÿ²…ª<ª?üY:øþŸýØ€,xÿä—Ù_ò|©2]Pž'ýIÛóPOo©^ÈWzÿ“æU$ñEP?ÿÔã$Çßÿ5®ž:û¿÷âĊЫöùϨâKb^ßêòñTƒàuÿ˜€*´ø‡ø·y]û{ü‹J+¿é¹³ã§ø§‰'÷ÿx¶}è“4]iÝûÇÊ€~wïýÆEKŽûÅÛßþjI¿û?gÿ6 5~ßõ.§Åh ü‡þeA‹ ’Ø—·ú¼ŸüU øæ ­>!þ-ÞD×~Þÿ"ßÒŠïún@løéþéâIýÿÞ-£_z$ÇO}ßûñEZ¿hÿœ€ñÚÿÅñ©ÿêFŸ?ê]½ÿÅ9 B,ç7%¯‡Ošw~ñò ‡Ýûÿq‘RãŸßþñv÷ÿš’oþÏÙÿͨ _·ýK©ñZ'CGÿ!ÿ™P@@"Ç#ä¶%íþ¯'ÿH>_ùˆ«Oˆ‹w‘5ß·¿È·ô¢»þ›>::x’÷‹h×Þ€I1ãEÓÇ_wþüQC€V¯Ú?ç Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Ú?¿ûÅQþUÿÖã×ÃþoÿMûù¶¼?ò,Ðà>£‰U|ž?¼÷ãýZ^‡„ ð»ñ9"ÿóå¡ÇÅþ”΀ZñØi-?ó„Š>Ôdû“¯~?Ò¶}§ü>_ýØ‹ôÿ ªñÿÈ£?æ­öçü1)7ûù¿ór´ u{XÝK·Ç÷ÿÍkTlš@|ù_ËZ­GþI$ƒÿž¯'Ú€ëÿmMûù¶¼¡À@ª¾Oá^{ñþˆ­‚/CÂø ]¿øœ‘ùòÐãâÿJç@-xì4–ŸùÂEj²}É׿é[>Óþ/þìEŽú…ÕxÿäQΟóVûsþ”›ýüßù¹Úº€½¬n¥Ûãûÿ浪6M ¾ü¯å­V£ÿ$’AÿÏW“í@uÿζ¦ˆýüˆÛ^ Ðà U_'ð¯=øÿDV€A¡áƒ|®ßüNH¿üùhqñ¥ó ¼vKOüá"µY>äëßô­Ÿiÿ—ÿv"Ç}?€j¼ò(çOù«}¹ÿ JMþþoüÜ€í]@^Ö7RíñýÿóZÕ&ßþWòÖ«Qÿ’I ÿç«Éö :ÿç[SD~þDm¯hp*¯“ÇøWžü¢+@ ‹Ðð€A¾×oþ'$_þ|´8ø¿Ò€yÐ ^;%§þp‘GÚ€,ŸruïÇúVÏ´ÿ‡Ëÿ»c¾Ÿá@5^?ùs§üÕ¾Üÿ†%&ÿ7þn@v® /k©vøþÿù­j“Hïƒÿ+ùkU¨ÿÉ$óÕäûPó­©¢?"6׃48WÉãü+Ï~?Ñ Eèx@ ßk·ÿ’/ÿ>Z|_é@<诀F’Óÿ8H£í@O¹:÷ãý+gÚÃåÿ݈±ßOð +ÅPa¯ømP?ÿ×âû+ü)Ýzßú‘Ï)úÿû§Ôq€xÿŠIÙ~óü_eké@I®LòK@{?è$ Ó–@ðºÿ™oÂѺôÓú>ªÔÍjÏ~?ä  ðçþRMÇ÷ÚÍ~÷²_ùµ Wó¿ø*JóÇþp£Ÿj ­ @MÜ ÿœ˜ßâ’Ÿ£ú¾) 7\qûÿi’Aä·ƒ\yûÄ û+ü)Ýzßú‘ϵ?_ïÿt€4ÇüRH¾Ë÷Ÿâû+_JMrcÿ’ZÙÿA Vœ²…×üÈ ~צŸÑð-V£þkV{ñÿ%W‡?ò’n?¾Ök÷½’ÿÍ©¸ÿÿÁPWž?ó…ûPMhJh(®àWü䯸”ýÕñIºãßûL’ 5¸$ãËûÿÞ H×Ù_áNëÖÿÔŽ}©úÿû¤ ?â’Eö_¼ÿÙZúPk“ü’ÐÏú ´å<.¿æE›ð´n½4þjµóZ³ßù(¼9ÿ”“qýö³_½ì—þmHÇüïþ €’¼ñÿœ(çÚ‚kBPAEw¿ç&7Àãø¤§èþ¯ŠH ×~ÿÚdy­Á ×_ßþñF¾Êÿ w^·þ¤síO×ûÿÝ ñÿ’/²ýçø¾Ê×Ò€“\˜ÿä–€öÐH§,áuÿ2(ß…£ué§ô| U¨ÿšÕžüÉ@áÏü¤›ïµšýïd¿ój@®?çðT•çüáG>ÔZ€š +¸ÿ91¾Å%?Gõ|R@n¸ã÷þÓ$ƒÈ n ¸òþÿ÷ˆ5öWøSºõ¿õ#Ÿj~¿ßþéhø¤‘}—ï?ÅöV¾”šäÇÿ$´³þ‚@­9d ¯ù‘@&ü-¯M?£àZ­GüÖ¬÷ãþJ¯å$Ü}¬×ï{%ÿ›Rqÿ;ÿ‚ $þòUûæªÿÐã>ÒºÒ]މºÐpúŽ#÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ ¦š}éÖ<ÈÓ*e '_Ü-׊}ßò¢·ZZîF‡á]·ûù²€O‡‚ê4J=Ü n´ ?ü™-|—Güôh€kÃþJÒÐä„Â4ÿ‘4 ®çþj>ñü[÷b(x_ø·-uú±Çý„Ý|!åÊ:i§ÞcÉ 8ò¨öZuýÂÝx§Ýÿ*+u¥ ^äh~Û¿™÷û(øx.£@Gô£ÝÇð¦ëAÂ÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ ¦š}éÖ<ÈÓ*e '_Ü-׊}ßò¢·ZZîF‡á]·ûù²€O‡‚ê4J=Ü n´ ?ü™-|—Güôh€kÃþJÒÐä„Â4ÿ‘4 ®çþj>ñü[÷b(x_ø·-uú±Çý„Ý|!åÊ:i§ÞcÉ 8ò¨öZuýÂÝx§Ýÿ*+u¥ ^äh~Û¿™÷û(øx.£@Gô£ÝÇð¦ëAÂ÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ §?òµT >/õ*ÿÑã$B}Ÿó  ÿÉÿß,ßþuüo¨â69?öš:kÙn–åï@ÃÇþ“D“íH'¶ˆ¾ÀÿÉÿàˆljÿ’ž9ÑwxŸü’*½§ü>Tïÿ—µ}Ÿ-~“ýeŠ5í¢í¥£ì?/:ÝíÿÈ´†Ÿ—ï‘{èQ^:O™å:…¢}ß¿ýâ>óP@å¯güÄ ÅÿÉÿßLßþuüh ŽOý¦Žšö[¥¹{Ð'pñÿ¤Ñ$ûR í¢/°?òø"±âä§Žt]Þ'ÿ$Нiÿ•ûÿåí_gÄ‹_¤ÿGÙ@¢{h»ihûË΀7{ò-!§åûäEžúWŽŸÓæ@yN¡hŸwïÿxŸüÔ9kÙÿ1Dqò÷Ó7ÿc“ÿi£¦½–én^ô Ü<é4I>Ô‚{h‹ìüŸþ€,xŸù)ãw‰ÿÉ"«ÚÃå@>ÿù{WÙñ"×é?ÑöP(£^Ú.ÚZ>Ãòó  Þßü‹@Hiù~ùg¾…ã§ôùS¨Z'ÝûÿÞ#çÿ5$ZöÌ@‘_üŸýôÍÿç_Æ€ØäÿÚhé¯eº[—½wúMOµ žÚ"ûÿ'ÿ‚ 'þJxçEÝâòHªöŸðùP¿þ^Õö|HµúOô}” (×¶‹¶–°ü¼èw·ÿ"Ð~_¾DYï¡Exéý>d”ê‰÷~ÿ÷ˆùÿÍ@I–½Ÿó$Gÿ'ÿ}3ù×ñ 69?öš:kÙn–åï@ÃÇþ“D“íH'¶ˆ¾ÀÿÉÿàˆljÿ’ž9ÑwxŸü’*½§ü>Tïÿ—µ}Ÿ-~“ýeг¶¹ÕP?ÿÒä/ßÿ‚ ŸüÔ%ÊA¿cê8„_aÿ+ÌŠ÷ü*EëèZ¯wøÐyý+}®×kþRýÿóPï ÿR€/Cæû[Z¿ßnÄØ ñÿ™ñ#Cÿž®ÐóÕӵ߸‚ø¿Â“îÿœ‚|é(µÎ@løÿÊN£Çý+4Uqÿ+M¨„ëæ?Óÿ¿R+ÃOßûH°F¿¿þbMw Ê@"åã_¿÷=Tïÿš‚D¹H7ì@"ûù^dW¸áR/[ÿBÕ{¿Æ€ëÈ¿é[ív¼ë_ò—ïÿš€7xWú”z7ÚÚÕþûv ŽÅ×üωüõvƒÿž®®ÿÄÀþ˜ŸwüäãÿI@«þrgÇþRu?é[ù¢«ùZm@t'_1þŸýú‘^~ÿÚE‚5ýÿók¹þR/ýÿ¹ê ŸüÔ%ÊA¿bØÊó"½Àÿ ‘zßú«Ýþ4^EÿJßkµçZÿ”¿üԻ¿Ԡ Ðù¾ÖÖ¯÷Û±v(¼æ|HÐÿç«´üõtíwþ €¾¯ð¤Äû¿ç ŸúJí_ó>?ò“¨ñÿJßÍ\ÊÓj¡:ùôÿïÔŠðÓ÷þÒ,¯ïÿ˜“]Èò¹x×ïýÏUûÿæ ‘.R û¾ÃþW™îøT‹Öÿе^ïñ :ò/úVû]¯:×ü¥ûÿæ  Þþ¥^‡Íö¶µ¾Ýˆ#±@5ãÿ3âF‡ÿ=] ÿç«§k¿ñð…&'Ýÿ9øÿÒPjÿœ€Ùñÿ”GúVþhªãþV›P ×̧ÿ~¤W†Ÿ¿ö‘`üÄšî@ÿ”€EËÆ¿îz¨'ßÿ5‰roØ€Eöò¼È¯p?¤^·þ…ª÷בÒ·ÚíyÖ¿å/ßÿ5nð¯õ(ô>oµµ«ýöìAНùŸ4?ùêíÿ=];]ÿˆ /€?ÒªOñÒÿÝjÿÓã¿ù¿i'^ó¿üÔ‹÷WºH ;>£ˆ@½kþR/°ÿ O?òS@€Ñäû(Z£Çú£ÿÅ“¯éÿ݈ÇþI}•ÿ'Ê“%ÕiâÔ¿5öú5ì€Ew¯ù*tçÿ8ROTF¼PhHOïÿ˜‘­÷‰ÿ•¿ÿ} ßÿ5 V‡¾¿jLÔ{þµÔû‡ÙOÏý( ö?óuçÿ;ÿÍHºÓJ÷Idë_ò‘}€×øT‚yÿ’šx$ÙBÕ?Õþ,|ÿOþì@<òKì¯ù>T™.¨Oþ¤íù¨'·Ô¯d+½ÉS§?ùÂ’x¢¢5â€ûB@úüÄo¼Oü­ÿûíxþÿù­´=õûRf£Øßõ®§Ü>Ê~é@o±ÿ˜“¯?ùßþjEÖšWºH ; /Zÿ”‹ì¿Â¤Ïü”Ð#À 4y#þʨñþ¨ÿñdëàGú÷b±ãÿ’_eÉò¤Éu@xŸõ'oÍA=¾¤ { ]ëþJ9ÿΓůÚÓûÿæ$k}âåoÿßh#Ç÷ÿÍh¡ï¯Ú“5Æÿ­u>áöSóÿJ}üÄyÿÎÿóR.´Ò½ÒEÙz×ü¤_`5þ žä¦£ÉöPµGõGÿ‹'_?Óÿ»ü’û+þO•&KªÓÄÿ©;~j íõ kÙŠï_òTéÏþp¤ž(¨x >Ð>Ÿßÿ1#[ïÿ+þûA?¿þk@­}~Ô™¨ö7ýk©÷²ŸŸúPìæ$ëÏþwÿš‘u¦•î’(È Ö¿å"û¯ð©óÿ%4ð Hÿ²…ª<ª?üY:øþŸýØ€,xÿä—Ù_ò|©2]Pž'ýIÛóPOo©^ÈWzÿ“æU$ñEP?ÿÔã$Çßÿ5®ž:û¿÷âĊЫöùϨâKb^ßêòñTƒàuÿ˜€*´ø‡ø·y]û{ü‹J+¿é¹³ã§ø§‰'÷ÿx¶}è“4]iÝûÇÊ€~wïýÆEKŽûÅÛßþjI¿û?gÿ6 5~ßõ.§Åh ü‡þeA‹ ’Ø—·ú¼ŸüU øæ ­>!þ-ÞD×~Þÿ"ßÒŠïún@løéþéâIýÿÞ-£_z$ÇO}ßûñEZ¿hÿœ€ñÚÿÅñ©ÿêFŸ?ê]½ÿÅ9 B,ç7%¯‡Ošw~ñò ‡Ýûÿq‘RãŸßþñv÷ÿš’oþÏÙÿͨ _·ýK©ñZ'CGÿ!ÿ™P@@"Ç#ä¶%íþ¯'ÿH>_ùˆ«Oˆ‹w‘5ß·¿È·ô¢»þ›>::x’÷‹h×Þ€I1ãEÓÇ_wþüQC€V¯Ú?ç Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Ú?¿ûÅQþUÿÖã×ÃþoÿMûù¶¼?ò,Ðà>£‰U|ž?¼÷ãýZ^‡„ ð»ñ9"ÿóå¡ÇÅþ”΀ZñØi-?ó„Š>Ôdû“¯~?Ò¶}§ü>_ýØ‹ôÿ ªñÿÈ£?æ­öçü1)7ûù¿ór´ u{XÝK·Ç÷ÿÍkTlš@|ù_ËZ­GþI$ƒÿž¯'Ú€ëÿmMûù¶¼¡À@ª¾Oá^{ñþˆ­‚/CÂø ]¿øœ‘ùòÐãâÿJç@-xì4–ŸùÂEj²}É׿é[>Óþ/þìEŽú…ÕxÿäQΟóVûsþ”›ýüßù¹Úº€½¬n¥Ûãûÿ浪6M ¾ü¯å­V£ÿ$’AÿÏW“í@uÿζ¦ˆýüˆÛ^ Ðà U_'ð¯=øÿDV€A¡áƒ|®ßüNH¿üùhqñ¥ó ¼vKOüá"µY>äëßô­Ÿiÿ—ÿv"Ç}?€j¼ò(çOù«}¹ÿ JMþþoüÜ€í]@^Ö7RíñýÿóZÕ&ßþWòÖ«Qÿ’I ÿç«Éö :ÿç[SD~þDm¯hp*¯“ÇøWžü¢+@ ‹Ðð€A¾×oþ'$_þ|´8ø¿Ò€yÐ ^;%§þp‘GÚ€,ŸruïÇúVÏ´ÿ‡Ëÿ»c¾Ÿá@5^?ùs§üÕ¾Üÿ†%&ÿ7þn@v® /k©vøþÿù­j“Hïƒÿ+ùkU¨ÿÉ$óÕäûPó­©¢?"6׃48WÉãü+Ï~?Ñ Eèx@ ßk·ÿ’/ÿ>Z|_é@<诀F’Óÿ8H£í@O¹:÷ãý+gÚÃåÿ݈±ßOð +ÅPa¯ømP?ÿ×âû+ü)Ýzßú‘Ï)úÿû§Ôq€xÿŠIÙ~óü_eké@I®LòK@{?è$ Ó–@ðºÿ™oÂѺôÓú>ªÔÍjÏ~?ä  ðçþRMÇ÷ÚÍ~÷²_ùµ Wó¿ø*JóÇþp£Ÿj ­ @MÜ ÿœ˜ßâ’Ÿ£ú¾) 7\qûÿi’Aä·ƒ\yûÄ û+ü)Ýzßú‘ϵ?_ïÿt€4ÇüRH¾Ë÷Ÿâû+_JMrcÿ’ZÙÿA Vœ²…×üÈ ~צŸÑð-V£þkV{ñÿ%W‡?ò’n?¾Ök÷½’ÿÍ©¸ÿÿÁPWž?ó…ûPMhJh(®àWü䯸”ýÕñIºãßûL’ 5¸$ãËûÿÞ H×Ù_áNëÖÿÔŽ}©úÿû¤ ?â’Eö_¼ÿÙZúPk“ü’ÐÏú ´å<.¿æE›ð´n½4þjµóZ³ßù(¼9ÿ”“qýö³_½ì—þmHÇüïþ €’¼ñÿœ(çÚ‚kBPAEw¿ç&7Àãø¤§èþ¯ŠH ×~ÿÚdy­Á ×_ßþñF¾Êÿ w^·þ¤síO×ûÿÝ ñÿ’/²ýçø¾Ê×Ò€“\˜ÿä–€öÐH§,áuÿ2(ß…£ué§ô| U¨ÿšÕžüÉ@áÏü¤›ïµšýïd¿ój@®?çðT•çüáG>ÔZ€š +¸ÿ91¾Å%?Gõ|R@n¸ã÷þÓ$ƒÈ n ¸òþÿ÷ˆ5öWøSºõ¿õ#Ÿj~¿ßþéhø¤‘}—ï?ÅöV¾”šäÇÿ$´³þ‚@­9d ¯ù‘@&ü-¯M?£àZ­GüÖ¬÷ãþJ¯å$Ü}¬×ï{%ÿ›Rqÿ;ÿ‚ $þòUûæªÿÐã>ÒºÒ]މºÐpúŽ#÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ ¦š}éÖ<ÈÓ*e '_Ü-׊}ßò¢·ZZîF‡á]·ûù²€O‡‚ê4J=Ü n´ ?ü™-|—Güôh€kÃþJÒÐä„Â4ÿ‘4 ®çþj>ñü[÷b(x_ø·-uú±Çý„Ý|!åÊ:i§ÞcÉ 8ò¨öZuýÂÝx§Ýÿ*+u¥ ^äh~Û¿™÷û(øx.£@Gô£ÝÇð¦ëAÂ÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ ¦š}éÖ<ÈÓ*e '_Ü-׊}ßò¢·ZZîF‡á]·ûù²€O‡‚ê4J=Ü n´ ?ü™-|—Güôh€kÃþJÒÐä„Â4ÿ‘4 ®çþj>ñü[÷b(x_ø·-uú±Çý„Ý|!åÊ:i§ÞcÉ 8ò¨öZuýÂÝx§Ýÿ*+u¥ ^äh~Û¿™÷û(øx.£@Gô£ÝÇð¦ëAÂ÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ §?òµT >/õ*ÿÑã$B}Ÿó  ÿÉÿß,ßþuüo¨â69?öš:kÙn–åï@ÃÇþ“D“íH'¶ˆ¾ÀÿÉÿàˆljÿ’ž9ÑwxŸü’*½§ü>Tïÿ—µ}Ÿ-~“ýeŠ5í¢í¥£ì?/:ÝíÿÈ´†Ÿ—ï‘{èQ^:O™å:…¢}ß¿ýâ>óP@å¯güÄ ÅÿÉÿßLßþuüh ŽOý¦Žšö[¥¹{Ð'pñÿ¤Ñ$ûR í¢/°?òø"±âä§Žt]Þ'ÿ$Нiÿ•ûÿåí_gÄ‹_¤ÿGÙ@¢{h»ihûË΀7{ò-!§åûäEžúWŽŸÓæ@yN¡hŸwïÿxŸüÔ9kÙÿ1Dqò÷Ó7ÿc“ÿi£¦½–én^ô Ü<é4I>Ô‚{h‹ìüŸþ€,xŸù)ãw‰ÿÉ"«ÚÃå@>ÿù{WÙñ"×é?ÑöP(£^Ú.ÚZ>Ãòó  Þßü‹@Hiù~ùg¾…ã§ôùS¨Z'ÝûÿÞ#çÿ5$ZöÌ@‘_üŸýôÍÿç_Æ€ØäÿÚhé¯eº[—½wúMOµ žÚ"ûÿ'ÿ‚ 'þJxçEÝâòHªöŸðùP¿þ^Õö|HµúOô}” (×¶‹¶–°ü¼èw·ÿ"Ð~_¾DYï¡Exéý>d”ê‰÷~ÿ÷ˆùÿÍ@I–½Ÿó$Gÿ'ÿ}3ù×ñ 69?öš:kÙn–åï@ÃÇþ“D“íH'¶ˆ¾ÀÿÉÿàˆljÿ’ž9ÑwxŸü’*½§ü>Tïÿ—µ}Ÿ-~“ýeг¶¹ÕP?ÿÒä/ßÿ‚ ŸüÔ%ÊA¿cê8„_aÿ+ÌŠ÷ü*EëèZ¯wøÐyý+}®×kþRýÿóPï ÿR€/Cæû[Z¿ßnÄØ ñÿ™ñ#Cÿž®ÐóÕӵ߸‚ø¿Â“îÿœ‚|é(µÎ@løÿÊN£Çý+4Uqÿ+M¨„ëæ?Óÿ¿R+ÃOßûH°F¿¿þbMw Ê@"åã_¿÷=Tïÿš‚D¹H7ì@"ûù^dW¸áR/[ÿBÕ{¿Æ€ëÈ¿é[ív¼ë_ò—ïÿš€7xWú”z7ÚÚÕþûv ŽÅ×üωüõvƒÿž®®ÿÄÀþ˜ŸwüäãÿI@«þrgÇþRu?é[ù¢«ùZm@t'_1þŸýú‘^~ÿÚE‚5ýÿók¹þR/ýÿ¹ê ŸüÔ%ÊA¿bØÊó"½Àÿ ‘zßú«Ýþ4^EÿJßkµçZÿ”¿üԻ¿Ԡ Ðù¾ÖÖ¯÷Û±v(¼æ|HÐÿç«´üõtíwþ €¾¯ð¤Äû¿ç ŸúJí_ó>?ò“¨ñÿJßÍ\ÊÓj¡:ùôÿïÔŠðÓ÷þÒ,¯ïÿ˜“]Èò¹x×ïýÏUûÿæ ‘.R û¾ÃþW™îøT‹Öÿе^ïñ :ò/úVû]¯:×ü¥ûÿæ  Þþ¥^‡Íö¶µ¾Ýˆ#±@5ãÿ3âF‡ÿ=] ÿç«§k¿ñð…&'Ýÿ9øÿÒPjÿœ€Ùñÿ”GúVþhªãþV›P ×̧ÿ~¤W†Ÿ¿ö‘`üÄšî@ÿ”€EËÆ¿îz¨'ßÿ5‰roØ€Eöò¼È¯p?¤^·þ…ª÷בÒ·ÚíyÖ¿å/ßÿ5nð¯õ(ô>oµµ«ýöìAНùŸ4?ùêíÿ=];]ÿˆ /€?ÒªOñÒÿÝjÿÓã¿ù¿i'^ó¿üÔ‹÷WºH ;>£ˆ@½kþR/°ÿ O?òS@€Ñäû(Z£Çú£ÿÅ“¯éÿ݈ÇþI}•ÿ'Ê“%ÕiâÔ¿5öú5ì€Ew¯ù*tçÿ8ROTF¼PhHOïÿ˜‘­÷‰ÿ•¿ÿ} ßÿ5 V‡¾¿jLÔ{þµÔû‡ÙOÏý( ö?óuçÿ;ÿÍHºÓJ÷Idë_ò‘}€×øT‚yÿ’šx$ÙBÕ?Õþ,|ÿOþì@<òKì¯ù>T™.¨Oþ¤íù¨'·Ô¯d+½ÉS§?ùÂ’x¢¢5â€ûB@úüÄo¼Oü­ÿûíxþÿù­´=õûRf£Øßõ®§Ü>Ê~é@o±ÿ˜“¯?ùßþjEÖšWºH ; /Zÿ”‹ì¿Â¤Ïü”Ð#À 4y#þʨñþ¨ÿñdëàGú÷b±ãÿ’_eÉò¤Éu@xŸõ'oÍA=¾¤ { ]ëþJ9ÿΓůÚÓûÿæ$k}âåoÿßh#Ç÷ÿÍh¡ï¯Ú“5Æÿ­u>áöSóÿJ}üÄyÿÎÿóR.´Ò½ÒEÙz×ü¤_`5þ žä¦£ÉöPµGõGÿ‹'_?Óÿ»ü’û+þO•&KªÓÄÿ©;~j íõ kÙŠï_òTéÏþp¤ž(¨x >Ð>Ÿßÿ1#[ïÿ+þûA?¿þk@­}~Ô™¨ö7ýk©÷²ŸŸúPìæ$ëÏþwÿš‘u¦•î’(È Ö¿å"û¯ð©óÿ%4ð Hÿ²…ª<ª?üY:øþŸýØ€,xÿä—Ù_ò|©2]Pž'ýIÛóPOo©^ÈWzÿ“æU$ñEP?ÿÔã$Çßÿ5®ž:û¿÷âĊЫöùϨâKb^ßêòñTƒàuÿ˜€*´ø‡ø·y]û{ü‹J+¿é¹³ã§ø§‰'÷ÿx¶}è“4]iÝûÇÊ€~wïýÆEKŽûÅÛßþjI¿û?gÿ6 5~ßõ.§Åh ü‡þeA‹ ’Ø—·ú¼ŸüU øæ ­>!þ-ÞD×~Þÿ"ßÒŠïún@løéþéâIýÿÞ-£_z$ÇO}ßûñEZ¿hÿœ€ñÚÿÅñ©ÿêFŸ?ê]½ÿÅ9 B,ç7%¯‡Ošw~ñò ‡Ýûÿq‘RãŸßþñv÷ÿš’oþÏÙÿͨ _·ýK©ñZ'CGÿ!ÿ™P@@"Ç#ä¶%íþ¯'ÿH>_ùˆ«Oˆ‹w‘5ß·¿È·ô¢»þ›>::x’÷‹h×Þ€I1ãEÓÇ_wþüQC€V¯Ú?ç Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Õ±Çþ|¾îW,ÿ«÷þZC¿ü¤ £ùÖµþÝÓ’Ù<ù¿ûí6?ýÖ‹®OÜ£Nü–¯Ø€=·þ¯öÛÈ÷©ÿ G7þŸ"7$WrANîÜÿ‰«ôÿ©A®‰íÿ%ªíÿ$(=¾þ-ÓG¸ÿ‘5¾ÝÐqþ¤9ò¢ïÂÿÔ+Ùÿ:H >Ú?¿ûÅQþUÿÖã×ÃþoÿMûù¶¼?ò,Ðà>£‰U|ž?¼÷ãýZ^‡„ ð»ñ9"ÿóå¡ÇÅþ”΀ZñØi-?ó„Š>Ôdû“¯~?Ò¶}§ü>_ýØ‹ôÿ ªñÿÈ£?æ­öçü1)7ûù¿ór´ u{XÝK·Ç÷ÿÍkTlš@|ù_ËZ­GþI$ƒÿž¯'Ú€ëÿmMûù¶¼¡À@ª¾Oá^{ñþˆ­‚/CÂø ]¿øœ‘ùòÐãâÿJç@-xì4–ŸùÂEj²}É׿é[>Óþ/þìEŽú…ÕxÿäQΟóVûsþ”›ýüßù¹Úº€½¬n¥Ûãûÿ浪6M ¾ü¯å­V£ÿ$’AÿÏW“í@uÿζ¦ˆýüˆÛ^ Ðà U_'ð¯=øÿDV€A¡áƒ|®ßüNH¿üùhqñ¥ó ¼vKOüá"µY>äëßô­Ÿiÿ—ÿv"Ç}?€j¼ò(çOù«}¹ÿ JMþþoüÜ€í]@^Ö7RíñýÿóZÕ&ßþWòÖ«Qÿ’I ÿç«Éö :ÿç[SD~þDm¯hp*¯“ÇøWžü¢+@ ‹Ðð€A¾×oþ'$_þ|´8ø¿Ò€yÐ ^;%§þp‘GÚ€,ŸruïÇúVÏ´ÿ‡Ëÿ»c¾Ÿá@5^?ùs§üÕ¾Üÿ†%&ÿ7þn@v® /k©vøþÿù­j“Hïƒÿ+ùkU¨ÿÉ$óÕäûPó­©¢?"6׃48WÉãü+Ï~?Ñ Eèx@ ßk·ÿ’/ÿ>Z|_é@<诀F’Óÿ8H£í@O¹:÷ãý+gÚÃåÿ݈±ßOð +ÅPa¯ømP?ÿ×âû+ü)Ýzßú‘Ï)úÿû§Ôq€xÿŠIÙ~óü_eké@I®LòK@{?è$ Ó–@ðºÿ™oÂѺôÓú>ªÔÍjÏ~?ä  ðçþRMÇ÷ÚÍ~÷²_ùµ Wó¿ø*JóÇþp£Ÿj ­ @MÜ ÿœ˜ßâ’Ÿ£ú¾) 7\qûÿi’Aä·ƒ\yûÄ û+ü)Ýzßú‘ϵ?_ïÿt€4ÇüRH¾Ë÷Ÿâû+_JMrcÿ’ZÙÿA Vœ²…×üÈ ~צŸÑð-V£þkV{ñÿ%W‡?ò’n?¾Ök÷½’ÿÍ©¸ÿÿÁPWž?ó…ûPMhJh(®àWü䯸”ýÕñIºãßûL’ 5¸$ãËûÿÞ H×Ù_áNëÖÿÔŽ}©úÿû¤ ?â’Eö_¼ÿÙZúPk“ü’ÐÏú ´å<.¿æE›ð´n½4þjµóZ³ßù(¼9ÿ”“qýö³_½ì—þmHÇüïþ €’¼ñÿœ(çÚ‚kBPAEw¿ç&7Àãø¤§èþ¯ŠH ×~ÿÚdy­Á ×_ßþñF¾Êÿ w^·þ¤síO×ûÿÝ ñÿ’/²ýçø¾Ê×Ò€“\˜ÿä–€öÐH§,áuÿ2(ß…£ué§ô| U¨ÿšÕžüÉ@áÏü¤›ïµšýïd¿ój@®?çðT•çüáG>ÔZ€š +¸ÿ91¾Å%?Gõ|R@n¸ã÷þÓ$ƒÈ n ¸òþÿ÷ˆ5öWøSºõ¿õ#Ÿj~¿ßþéhø¤‘}—ï?ÅöV¾”šäÇÿ$´³þ‚@­9d ¯ù‘@&ü-¯M?£àZ­GüÖ¬÷ãþJ¯å$Ü}¬×ï{%ÿ›Rqÿ;ÿ‚ $þòUûæªÿÐã>ÒºÒ]މºÐpúŽ#÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ ¦š}éÖ<ÈÓ*e '_Ü-׊}ßò¢·ZZîF‡á]·ûù²€O‡‚ê4J=Ü n´ ?ü™-|—Güôh€kÃþJÒÐä„Â4ÿ‘4 ®çþj>ñü[÷b(x_ø·-uú±Çý„Ý|!åÊ:i§ÞcÉ 8ò¨öZuýÂÝx§Ýÿ*+u¥ ^äh~Û¿™÷û(øx.£@Gô£ÝÇð¦ëAÂ÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ ¦š}éÖ<ÈÓ*e '_Ü-׊}ßò¢·ZZîF‡á]·ûù²€O‡‚ê4J=Ü n´ ?ü™-|—Güôh€kÃþJÒÐä„Â4ÿ‘4 ®çþj>ñü[÷b(x_ø·-uú±Çý„Ý|!åÊ:i§ÞcÉ 8ò¨öZuýÂÝx§Ýÿ*+u¥ ^äh~Û¿™÷û(øx.£@Gô£ÝÇð¦ëAÂ÷ÿÉ’×ÉxÔÏFˆ¼?ä­-H@ü#Où@ªîæ£ïÅ·ÿv"‡…ÿ‹rÐçQÿ¡ÑQxM×Ân\ §?òµT >/õ*ÿÑã$B}Ÿó  ÿÉÿß,ßþuüo¨â69?öš:kÙn–åï@ÃÇþ“D“íH'¶ˆ¾ÀÿÉÿàˆljÿ’ž9ÑwxŸü’*½§ü>Tïÿ—µ}Ÿ-~“ýeŠ5í¢í¥£ì?/:ÝíÿÈ´†Ÿ—ï‘{èQ^:O™å:…¢}ß¿ýâ>óP@å¯güÄ ÅÿÉÿßLßþuüh ŽOý¦Žšö[¥¹{Ð'pñÿ¤Ñ$ûR í¢/°?òø"±âä§Žt]Þ'ÿ$Нiÿ•ûÿåí_gÄ‹_¤ÿGÙ@¢{h»ihûË΀7{ò-!§åûäEžúWŽŸÓæ@yN¡hŸwïÿxŸüÔ9kÙÿ1Dqò÷Ó7ÿc“ÿi£¦½–én^ô Ü<é4I>Ô‚{h‹ìüŸþ€,xŸù)ãw‰ÿÉ"«ÚÃå@>ÿù{WÙñ"×é?ÑöP(£^Ú.ÚZ>Ãòó  Þßü‹@Hiù~ùg¾…ã§ôùS¨Z'ÝûÿÞ#çÿ5$ZöÌ@‘_üŸýôÍÿç_Æ€ØäÿÚhé¯eº[—½wúMOµ žÚ"ûÿ'ÿ‚ 'þJxçEÝâòHªöŸðùP¿þ^Õö|HµúOô}” (×¶‹¶–°ü¼èw·ÿ"Ð~_¾DYï¡Exéý>d”ê‰÷~ÿ÷ˆùÿÍ@I–½Ÿó$Gÿ'ÿ}3ù×ñ 69?öš:kÙn–åï@ÃÇþ“D“íH'¶ˆ¾ÀÿÉÿàˆljÿ’ž9ÑwxŸü’*½§ü>Tïÿ—µ}Ÿ-~“ýeг¶¹ÕP?ÿÒä/ßÿ‚ ŸüÔ%ÊA¿cê8„_aÿ+ÌŠ÷ü*EëèZ¯wøÐyý+}®×kþRýÿóPï ÿR€/Cæû[Z¿ßnÄØ ñÿ™ñ#Cÿž®ÐóÕӵ߸‚ø¿Â“îÿœ‚|é(µÎ@løÿÊN£Çý+4Uqÿ+M¨„ëæ?Óÿ¿R+ÃOßûH°F¿¿þbMw Ê@"åã_¿÷=Tïÿš‚D¹H7ì@"ûù^dW¸áR/[ÿBÕ{¿Æ€ëÈ¿é[ív¼ë_ò—ïÿš€7xWú”z7ÚÚÕþûv ŽÅ×üωüõvƒÿž®®ÿÄÀþ˜ŸwüäãÿI@«þrgÇþRu?é[ù¢«ùZm@t'_1þŸýú‘^~ÿÚE‚5ýÿók¹þR/ýÿ¹ê ŸüÔ%ÊA¿bØÊó"½Àÿ ‘zßú«Ýþ4^EÿJßkµçZÿ”¿üԻ¿Ԡ Ðù¾ÖÖ¯÷Û±v(¼æ|HÐÿç«´üõtíwþ €¾¯ð¤Äû¿ç ŸúJí_ó>?ò“¨ñÿJßÍ\ÊÓj¡:ùôÿïÔŠðÓ÷þÒ,¯ïÿ˜“]Èò¹x×ïýÏUûÿæ ‘.R û¾ÃþW™îøT‹Öÿе^ïñ :ò/úVû]¯:×ü¥ûÿæ  Þþ¥^‡Íö¶µ¾Ýˆ#±@5ãÿ3âF‡ÿ=] ÿç«§k¿ñð…&'Ýÿ9øÿÒPjÿœ€Ùñÿ”GúVþhªãþV›P ×̧ÿ~¤W†Ÿ¿ö‘`üÄšî@ÿ”€EËÆ¿îz¨'ßÿ5‰roØ€Eöò¼È¯p?¤^·þ…ª÷בÒ·ÚíyÖ¿å/ßÿ5nð¯õ(ô>oµµ«ýöìAНùŸ4?ùêíÿ=];]ÿˆ /€?ÒªOñÒÿÝjÿÓã¿ù¿i'^ó¿üÔ‹÷WºH ;>£ˆ@½kþR/°ÿ O?òS@€Ñäû(Z£Çú£ÿÅ“¯éÿ݈ÇþI}•ÿ'Ê“%ÕiâÔ¿5öú5ì€Ew¯ù*tçÿ8ROTF¼PhHOïÿ˜‘­÷‰ÿ•¿ÿ} ßÿ5 V‡¾¿jLÔ{þµÔû‡ÙOÏý( ö?óuçÿ;ÿÍHºÓJ÷Idë_ò‘}€×øT‚yÿ’šx$ÙBÕ?Õþ,|ÿOþì@<òKì¯ù>T™.¨Oþ¤íù¨'·Ô¯d+½ÉS§?ùÂ’x¢¢5â€ûB@úüÄo¼Oü­ÿûíxþÿù­´=õûRf£Øßõ®§Ü>Ê~é@o±ÿ˜“¯?ùßþjEÖšWºH ; /Zÿ”‹ì¿Â¤Ïü”Ð#À 4y#þʨñþ¨ÿñdëàGú÷b±ãÿ’_eÉò¤Éu@xŸõ'oÍA=¾¤ { ]ëþJ9ÿΓůÚÓûÿæ$k}âåoÿßh#Ç÷ÿÍh¡ï¯Ú“5Æÿ­u>áöSóÿJ}üÄyÿÎÿóR.´Ò½ÒEÙz×ü¤_`5þ žä¦£ÉöPµGõGÿ‹'_?Óÿ»ü’û+þO•&KªÓÄÿ©;~j íõ kÙŠï_òTéÏþp¤ž(¨x >Ð>Ÿßÿ1#[ïÿ+þûA?¿þk@­}~Ô™¨ö7ýk©÷²ŸŸúPìæ$ëÏþwÿš‘u¦•î’(È Ö¿å"û¯ð©óÿ%4ð Hÿ²…ª<ª?üY:øþŸýØ€,xÿä—Ù_ò|©2]Pž'ýIÛóPOo©^ÈWzÿ“æU$ñEP?ÿÔã$Ž?üÔ¿=}ßûñòÕõORë·ú¾4ÿΟ1P=MW÷Ûçÿão–¨§ïzaýÿï_1P=B/ÿ>[¾åòÕÔŽ?ç"»>b zuZ|_ջʚýïÊùjêYñÿš$Ÿßýëå*¨I~ÿù­~zû¿÷ã媩uÛý_ çO˜¨¦«ûíóÿñ·ËTSw½°þÿ÷¯˜¨¡ÿŸ-ßrùjêGó‘]Ÿ1P=:­>/êÝåM~÷å|µ@õ,øÿÈMOïþõò•Ô$Ž?üÖ¿=}ßûñòÕÔºíþ¯¿ó§ÌTSUýöùÿøÛ媩ǻÞGØû×ÌTP‹ÿÏ–ï¹|µ@õ#ùȮϘ¨VŸõnò¦¿{ò¾Z z–|ä&‡‰'÷ÿzùJêG¿þk_ž¾ïýøùjê]vÿWƃ_ùÓæ*©ªþû|ÿümòÕÔãÝï@£ì?¿ýëæ*¨EÿçËwܾZ z€‘ÇüäWgÌTN«O‹ú·yS_½ù_-P=K>?òCÄ“ûÿ½|¥@õ #ßÿ5¯Ï_wþü|µ@õ.»«ãA¯üéóÔÕ}¾þ6ùjêqî÷ QößþõóÔ"ÿóå»î_-P=@Hãþr¾Z ÿÙjung-jung-2.1.1/jung-samples/src/main/resources/images/china.gif000066400000000000000000000015261276402340000245710ustar00rootroot00000000000000GIF89a÷üþüwP4ØÀ|  D€Ä>£9V°¢CC°æõwf¨¢S!çw"Ðìýœ£8"¤7Éä¶¥KõwfE÷wP"ð¤Õöw$ÿ¤ÿÿÿrærìNýõwè"xÿÿ$õÿw²£õwXµKÐæÈ°õ,wEPx¤$¤¦Ð0°­÷wØÀ¢,Ð@A9õwÿ lÿ¥[ÿçÿw÷wðÐÕöwÿÐÿÿÿkæõwXxõw²öæõwwçwìýHP£È(çwx¡l[çw<ùPÿÿ÷ÿwÿÈÕ)öçwwÿhÐÿ£°ÿÿjd/õèwwàp¤çw®KP¤ÐsPrNl„¤¤d˜/èçww4¥§N³<æ<¥ÿõNÿwø¦hУ°0­z€Ð£û°Oo5çwi¯nG"aø¦.iø¦c!ù,3H° Áƒ(\Ȱ¡C2Œ(±a€‹ )JÔXñ!ÇŽ '†I²¤É†Sª,;jung-jung-2.1.1/jung-samples/src/main/resources/images/france.gif000066400000000000000000000015531276402340000247450ustar00rootroot00000000000000GIF89a÷üþüüüP4ØÀ|  D€Ä>£9V°¢CC°æõwf¨¢S!çw$Ðìýœ£8$¤7Éä¶¥KõwfE÷wP$ð¤Õöw$ÿ¤ÿÿÿrærìNýõwç$xÿÿ&õÿw²£õwXµKÐæÈ°õ,wEPx¤$¤¦Ð0°­÷wØÀ¢,Ð@A9õwÿ lÿ¥[ÿçÿw÷wðÐÕöwÿÐÿÿÿkæõwXxõw²öæõwwçwìýHP£È(çwx¡l[çw<ùPÿÿ÷ÿwÿÈÕ)öçwwÿhÐÿ£°ÿÿjd/õèwwàp¤çw®KP¤ÐsPrNl„¤¤d˜/èçww4¥§N³<å<¥ÿõNÿwø¦hУ°0­|€Ð£û°Oo5çwa¯nG$cø¦e.ø¦i!ù,HH° ÁƒÀa€‡H” ¡Cˆ&R´(cF-zÔ8 dÑ 9¢œhò"D’-;b„©rfJ‘6Y*Üɳ'€€;jung-jung-2.1.1/jung-samples/src/main/resources/images/germany.gif000066400000000000000000000001151276402340000251420ustar00rootroot00000000000000GIF89a‘üüþÿÿÿ!ù,œ9Àí£œ´Úû‚Þ¼û†b(”扦êʶ¬;jung-jung-2.1.1/jung-samples/src/main/resources/images/japan.gif000066400000000000000000000015371276402340000246020ustar00rootroot00000000000000GIF89a÷üþüüwP4ØÀ|  D€Ä>£9V°¢CC°æõwf¨¢S!çw"Ðìýœ£8"¤7Éä¶¥KõwfE÷wP"ð¤Õöw$ÿ¤ÿÿÿrærìNýõwè"xÿÿ$õÿw²£õwXµKÐæÈ°õ,wEPx¤$¤¦Ð0°­÷wØÀ¢,Ð@A9õwÿ lÿ¥[ÿçÿw÷wðÐÕöwÿÐÿÿÿkæõwXxõw²öæõwwçwìýHP£È(çwx¡l[çw<ùPÿÿ÷ÿwÿÈÕ)öçwwÿhÐÿ£°ÿÿjd/õèwwàp¤çw®KP¤ÐsPrNl„¤¤d˜/èçww4¥§N³<æ<¥ÿõNÿwø¦hУ°0­z€Ð£û°Oo5çwp¯aG"nø¦.iø¦c!ù,<H° Áƒ(\Ȱ!€†>Œq"C!ZTˆ±£Ã†3Jò#I)VT™’¥D„0cÊŒ;jung-jung-2.1.1/jung-samples/src/main/resources/images/lightning-s.gif000066400000000000000000000020371276402340000257300ustar00rootroot00000000000000GIF89a÷ÿÿÿ™†Œ¤™§”‹šbUoÝÜÞ­¥·”´|q›¹­æPNc˜•³¯­Â»»¼´´µ†ˆ”ÅÇÑŸ£¨©§ÄÅÃÇÍ»´·­ž —‹Œ‡iqHŠq£«{•šz\a<¼À¥Ÿ¢5: ]^UAF7: æô#-/‘„äíÍÕ´»óþëõ¬³÷ÿÆÎ¹Áèð öþ óüËÐJ•—m©ª’Ö×ÂüÿòøÕÛÞヅôø WYüÿïñÓÖçìØÞòõàä"¤¨¸º$šœÏÑ.©¬*äé>äèX™›>ÿÿùúðïøøþþ))úø ÿþŸŸ ûúrq ÿþ°¯ýþ&ïð2ýý=÷÷M¢¡4úùd‰ˆ8ººVýü|š™TkkW––“ÔÔÐÿúûõ¿ùò¬§ òíàÚ$¶´Dqo5ÚÖiâà|¨§ogfE‹ŠaB>41ýô†‚ |@úõÉÅuÎËhb uqCDB'LJ4ONBõèsƒo~yX¤£œvqRSRLvsdbY6‡`$ˆ†”‹vªšy±®¨–‹1/,6'yvth\Zÿÿÿ!ù—,ü/ H° Áƒ LJ¤h`¥9$B€°àƒÕé HD2p Ô@FŽ˜0eTxˆÃˆFŽ â…¤‡’|ıŒ2{ö¬üP¦(²táÒe ;Y¤ü¸B„B}U<¡Ã#VTìÐbK8Ï‹ô¸8«ÿ±x8dEñëÀ?Cú¼hìέ£Øï[ÇkôÄ~sÜïêV ì÷9§Ópiú;œ\aßÔ–þoç~gøEYøy-yhg¨¡í€?×>×þûkÛÒ]´{ÚòÇ4ùmiôÿ’©døŒá3 †ä41”„eû¯åú¶Ÿ2IýLø{Æ%ðgÉ32òNãfO¦ÖèÒ+;7s²ËâÖ³Ú¥uùÔ†9×1ìyÚÁ>à7må»·wÑAºýÛ+Š^$º¦€CÙþ gòUŒSöš}++¯h,{5i_ðÌZÉoøMö)a?rRÇ“⫌ï$cÄF|\\?¹òABFfP—Kõ‘õþìÿwüT3,µße„rÞÆúÊýFÿLz¯&Â%Á¦?º6§c[]b¶{XѾ_Êýå ½v0+õ6ɰ1£@Æmw©¿èÝçUœX§„NrÉ¡”®Rôðú½—6,rÍÈšù¢?YËþr÷ÙmmcÛmu4»khµäéôqÙEµ:Ë¿àÿsôŸ™ïj2é¸ËÃÌ ‚öI‰vÖݱûUå°u;­cm,4·ô÷XÖUPÎSŒ×ú ³nü§úž­–~—Ñô=%^¬šìsC«¹†À%ÖTXÙcZ¾_~Æû™fëŸôíô¾Ÿ±EÌsÒ€”±@e„™á<_ªà÷%ðq{qó~¿\?œöý¶Ç/æÉqÆ"Bø¼dWíþ†_ñÿÍ:ÎȇëI.¤Ÿ~‘ßæµ1ά}c¤p$xë¹çÉcßÔññÍs,U±Ìôå®;`u›FïÌÿIþ G«Óuž‘¬¶Ð5hÖx<¼´~‰¬ÿK{*Pßögž<¡Ž!¼¥ê”áœe~Œ³¾oµìòQ— ó7ýz~n9ú¡[íÖ‚a¥ÍvÓš=Îj Í«IsÄòHtŠÙr\ÓhcK$ a`i iÛîUœýŽ-v`êþb³ñ|[›¹QŽNµ(ßðà1fËËò1âÈ1qxǪGoŸ‰¾3k.‡9ÍŸq.#Ñ™T—³ôÃW7óˆUi¦«+Üíûü727·Øß¢×ÿ+ßéþ‘ŒF4°Øë74˜€îkYü…©ƒ/?1(ááââýdj>?×jå‡+@9n'†¿W-ï_ÿÐëÜDºtÔó§tŽ„ƒ¡Ž‰Ì‚ãÛq׿˜44på«8¸K«ÎèøùOæ¨Õ˜çËÇ`‚?¯ݵ۔ÔUNw¨ñ«F§qf‘ùÏaoµfó¿ ÅÌdG§%r¤qû¸ãÓ‹×êÿŸ0aD‹éëÁ//ÝP u±#Q¤¸£K¿;gÒeh†gYžòª[e8´×]…¦ªÄ±Î:¶¶ûÛÆÏ{ßcýOÒ;üâTõNvGÙkÈc­u†ºŒ×ÝîØçnÝîßú]þ¯þfÏ §äñâÇd¯C9@Ï×vqâ§õÖdã$EKmb6ôüŸÞm~ Qfcêé¹ö;×cf  ~Ç3s›¹÷û©ý¾C Ë>Nÿ$ªY†Ô~ßS&ë YfâÐÍŒôZçÔ×ìÊsëuž—èWÿÁùŒ†9pJp á–A“ü—Î}1÷8x¥ÿ±±²Å®õqqW_‘YFŠ+ô¯Òqc$0ml3c+‡~ž¯úþž®MVW‹kêÈsk²ÛM¶µŽôX¿kýÛ?™þoÔ¡ ì¿aγªeo¹Í©ûïvׇmo±¸Õ‡1­ú†ÏøŒ{-õ/Kʬ¢ìÍήŠö›…no¼ý/Ðmmö·ôŒÙé×ôÿEüåk,G.8âê #9Kúþäe?êC'ùOqµf=Ÿdô1«ªœç±s,%Á­iÙkY~Ç^ÿ³z¶}žÍ–lýèý%3±«É8­{]U-f;/ea²Ö9ÕØÖ2¿e4úŸ Ä¯è-Ì\w0 ò)mY¶7e¾ãcšÀ\k Úã·þæPÚªõ¿â”„ÃÔíê÷H¬?™ÀÚ—['fëñ=*}çk²»}?çü<–,3Êy Ï'.™±åý\!Ž9¥“–Fý/“Ýý4f¬€ƒ kIpƒÅáý&€-Ù¹³±ò[g½sA5»k,þÖ~{ëÿÀìW0ØãŽýŒc^ç7Ó}Œ/nÏßuLuNsYïô¿IúOøµ'1‡¨ºËm-  ±Î"§1®±Åϯ÷ëmíô¬cÿÓoý5*u\lÉu¢Àê.`÷æ¾·m¥õÖÿæ«Ì«×}¯³Óý%u¨9~V0ÌI˜&6D}:×ïJý\Ž?ÕÃÿQ°C—Œ&%ÝÃõG÷’ÕSj_esí¹Á­sÜGç6¸¯Ó©›k¥ŸàÑôÛýfþPœ‡6`D:ÒKuüÍ\™ŸÎ0 ljºF†V6ÎÿÿÑëÏ.0F§‘àOæ¦'i ´Ž=Ð×ßI»·ÿkjr@q—k'^;”ÁÀL:'UêZÈ´÷pO#üìšGˆüx?t$¤Ô×f&G¨÷PÇTE—Ö{XÓê;íÕ»ü+qs]K¼ߎ÷3#íw¥g–ϪÖþ‹Ô§þÙ·ùÝ•®“©Á±¦¬k§XÞöïŸúËmö~z}>›r)Êx{} Õ°‘î~ݬuõµ£ù§í¶¯ûnÅ/ÌË•çqÀË1Å“˜pÂPÇÁ‡$}?Ö9íeÿ-'ê}©B"G¤0‰ˆ>ç ÿœã—ÉÃúZ—z•6Íž˜x×A;I>Ÿ»èû›ïö©öˆN¼Ÿó¿uD€4€>¿ë x‚"éÌž<“áýVž™Ïæâù?M“qq’þŸ¥ú(ÙKkk+Ûé€ç´yû¶´¿CéRŠÃîkïØXth—îíÑž¦Ö×ýtÅÎwµßHêãÈÿ8û’õ"Ê™;žç¶+†Ï¾èú c9ßõ¿ç6%ÈdÎpŸ¼Àbœ&aCô£ú<0ËÃüÜ–8â@Æn4+Ãôx_ÿÒí{ë;9ÎíÐ7{˜÷nÑ!’ðíÀA8FÙ˜NùT9ØûܰCø7’«fnLß¼+‰óý&í¿æ©gÍàˆ»ðŒ¥öq0K˜Ç|B^1”SøÐF³ 7CçJÛùÌj‹í}€nÛ¦²xsš²¾×™þ‘ßæ7ÿI¦9eÁÆËѯòÝŽþ¾ÕúGÚû!ÿ~Ç÷Ì}§öGþý½‘ŒÌª…n.ik…Œ{6’׿îÛc_[۲DZìz›,kçd–èC¢âùÍþªÍ7Zö†ÝêZÎÂH×±ú(ÌËUéÓKšAŸ|¼j>—æ;vïÍúÇžÄeÄ}>lsú°Û‡‡Õ?ò‹‡5Ö£½ââþïËÿ=¼”Oi…FÌûޕ׳ÍÍ.?ùÛ’ó/}‡À h³j3ø†(é)øü±ÿêÿš‰sxÆ×/¥Gþs­µÞîKkµÐ¬sêIyó—&-$™Ÿ0OåQ‰vÇÿ;ÿAY÷ÑûŸó¿ôXYYvÐö—7ÙŠ‘!­—Öö$€>R±‹8švØœ°“%¤Ÿ ü©¿é)Wócüoý|þ§üïýÕõè˜õY<Ææÿz^µ1>£#™ÜÞ>õ›_¦ÑúJ ºÈ‡º¸Æ;r€ið3ã´¥þ’ŸîGí’>øt}¿ú ®×5ÿAÁÿÕ þB¦ÐCÛ#óšž,CX$K'û?ìW0¯ª’ƹ–ïsš ‚æý!­/ö·þ¶¤Åñ9Tâ ?{‹öp²cæ„HXËÿAÿÙÿí3TPhotoshop 3.08BIM%8BIMê° com.apple.print.PageFormat.PMHorizontalRes com.apple.print.ticket.creator com.apple.printingmanager com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMHorizontalRes 72 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2007-05-29T17:41:35Z com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMOrientation com.apple.print.ticket.creator com.apple.printingmanager com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMOrientation 1 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2007-05-29T17:41:35Z com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMScaling com.apple.print.ticket.creator com.apple.printingmanager com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMScaling 1 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2007-05-29T17:41:35Z com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMVerticalRes com.apple.print.ticket.creator com.apple.printingmanager com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMVerticalRes 72 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2007-05-29T17:41:35Z com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMVerticalScaling com.apple.print.ticket.creator com.apple.printingmanager com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMVerticalScaling 1 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2007-05-29T17:41:35Z com.apple.print.ticket.stateFlag 0 com.apple.print.subTicket.paper_info_ticket com.apple.print.PageFormat.PMAdjustedPageRect com.apple.print.ticket.creator com.apple.printingmanager com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMAdjustedPageRect 0.0 0.0 734 576 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2007-05-29T17:41:35Z com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMAdjustedPaperRect com.apple.print.ticket.creator com.apple.printingmanager com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMAdjustedPaperRect -18 -18 774 594 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2007-05-29T17:41:35Z com.apple.print.ticket.stateFlag 0 com.apple.print.PaperInfo.PMPaperName com.apple.print.ticket.creator com.apple.print.pm.PostScript com.apple.print.ticket.itemArray com.apple.print.PaperInfo.PMPaperName na-letter com.apple.print.ticket.client com.apple.print.pm.PostScript com.apple.print.ticket.modDate 2003-07-01T17:49:36Z com.apple.print.ticket.stateFlag 1 com.apple.print.PaperInfo.PMUnadjustedPageRect com.apple.print.ticket.creator com.apple.print.pm.PostScript com.apple.print.ticket.itemArray com.apple.print.PaperInfo.PMUnadjustedPageRect 0.0 0.0 734 576 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2007-05-29T17:41:35Z com.apple.print.ticket.stateFlag 0 com.apple.print.PaperInfo.PMUnadjustedPaperRect com.apple.print.ticket.creator com.apple.print.pm.PostScript com.apple.print.ticket.itemArray com.apple.print.PaperInfo.PMUnadjustedPaperRect -18 -18 774 594 com.apple.print.ticket.client com.apple.printingmanager com.apple.print.ticket.modDate 2007-05-29T17:41:35Z com.apple.print.ticket.stateFlag 0 com.apple.print.PaperInfo.ppd.PMPaperName com.apple.print.ticket.creator com.apple.print.pm.PostScript com.apple.print.ticket.itemArray com.apple.print.PaperInfo.ppd.PMPaperName US Letter com.apple.print.ticket.client com.apple.print.pm.PostScript com.apple.print.ticket.modDate 2003-07-01T17:49:36Z com.apple.print.ticket.stateFlag 1 com.apple.print.ticket.APIVersion 00.20 com.apple.print.ticket.privateLock com.apple.print.ticket.type com.apple.print.PaperInfoTicket com.apple.print.ticket.APIVersion 00.20 com.apple.print.ticket.privateLock com.apple.print.ticket.type com.apple.print.PageFormatTicket 8BIMéxHHÞ@ÿîÿîRg(üHHØ(dÿh 8BIMíïï8BIM&?€8BIM 8BIM8BIMó 8BIM 8BIM' 8BIMõH/fflff/ff¡™š2Z5-8BIMøpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿè8BIM@@8BIM8BIM[èÐpolitical_world_mapÐènullboundsObjcRct1Top longLeftlongBtomlongèRghtlongÐslicesVlLsObjcslicesliceIDlonggroupIDlongoriginenum ESliceOrigin autoGeneratedTypeenum ESliceTypeImg boundsObjcRct1Top longLeftlongBtomlongèRghtlongÐurlTEXTnullTEXTMsgeTEXTaltTagTEXTcellTextIsHTMLboolcellTextTEXT horzAlignenumESliceHorzAligndefault vertAlignenumESliceVertAligndefault bgColorTypeenumESliceBGColorTypeNone topOutsetlong leftOutsetlong bottomOutsetlong rightOutsetlong8BIM8BIM8BIM /€@€`ÿØÿàJFIFHHÿí Adobe_CMÿîAdobed€ÿÛ„            ÿÀ@€"ÿÝÿÄ?   3!1AQa"q2‘¡±B#$RÁb34r‚ÑC%’Sðáñcs5¢²ƒ&D“TdE£t6ÒUâeò³„ÃÓuãóF'”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷5!1AQaq"2‘¡±B#ÁRÑð3$bár‚’CScs4ñ%¢²ƒ&5ÂÒD“T£dEU6teâò³„ÃÓuãóF”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö'7GWgw‡—§·ÇÿÚ ?ëýPés_¸b=þ)»d½ÅƒA.%£ï)œÏs‹@iÜLÀìu¿KûI­«Öc«µî!Þ{{·dîú L‡÷Ç'#>8Ï‹ô¸8«ÿ±x8dEñëÀ?Cú¼hìέ£Øï[ÇkôÄ~sÜïêV ì÷9§Ópiú;œ\aßÔ–þoç~gøEYøy-yhg¨¡í€?×>×þûkÛÒ]´{ÚòÇ4ùmiôÿ’©døŒá3 †ä41”„eû¯åú¶Ÿ2IýLø{Æ%ðgÉ32òNãfO¦ÖèÒ+;7s²ËâÖ³Ú¥uùÔ†9×1ìyÚÁ>à7må»·wÑAºýÛ+Š^$º¦€CÙþ gòUŒSöš}++¯h,{5i_ðÌZÉoøMö)a?rRÇ“⫌ï$cÄF|\\?¹òABFfP—Kõ‘õþìÿwüT3,µße„rÞÆúÊýFÿLz¯&Â%Á¦?º6§c[]b¶{XѾ_Êýå ½v0+õ6ɰ1£@Æmw©¿èÝçUœX§„NrÉ¡”®Rôðú½—6,rÍÈšù¢?YËþr÷ÙmmcÛmu4»khµäéôqÙEµ:Ë¿àÿsôŸ™ïj2é¸ËÃÌ ‚öI‰vÖݱûUå°u;­cm,4·ô÷XÖUPÎSŒ×ú ³nü§úž­–~—Ñô=%^¬šìsC«¹†À%ÖTXÙcZ¾_~Æû™fëŸôíô¾Ÿ±EÌsÒ€”±@e„™á<_ªà÷%ðq{qó~¿\?œöý¶Ç/æÉqÆ"Bø¼dWíþ†_ñÿÍ:ÎȇëI.¤Ÿ~‘ßæµ1ά}c¤p$xë¹çÉcßÔññÍs,U±Ìôå®;`u›FïÌÿIþ G«Óuž‘¬¶Ð5hÖx<¼´~‰¬ÿK{*Pßögž<¡Ž!¼¥ê”áœe~Œ³¾oµìòQ— ó7ýz~n9ú¡[íÖ‚a¥ÍvÓš=Îj Í«IsÄòHtŠÙr\ÓhcK$ a`i iÛîUœýŽ-v`êþb³ñ|[›¹QŽNµ(ßðà1fËËò1âÈ1qxǪGoŸ‰¾3k.‡9ÍŸq.#Ñ™T—³ôÃW7óˆUi¦«+Üíûü727·Øß¢×ÿ+ßéþ‘ŒF4°Øë74˜€îkYü…©ƒ/?1(ááââýdj>?×jå‡+@9n'†¿W-ï_ÿÐëÜDºtÔó§tŽ„ƒ¡Ž‰Ì‚ãÛq׿˜44på«8¸K«ÎèøùOæ¨Õ˜çËÇ`‚?¯ݵ۔ÔUNw¨ñ«F§qf‘ùÏaoµfó¿ ÅÌdG§%r¤qû¸ãÓ‹×êÿŸ0aD‹éëÁ//ÝP u±#Q¤¸£K¿;gÒeh†gYžòª[e8´×]…¦ªÄ±Î:¶¶ûÛÆÏ{ßcýOÒ;üâTõNvGÙkÈc­u†ºŒ×ÝîØçnÝîßú]þ¯þfÏ §äñâÇd¯C9@Ï×vqâ§õÖdã$EKmb6ôüŸÞm~ Qfcêé¹ö;×cf  ~Ç3s›¹÷û©ý¾C Ë>Nÿ$ªY†Ô~ßS&ë YfâÐÍŒôZçÔ×ìÊsëuž—èWÿÁùŒ†9pJp á–A“ü—Î}1÷8x¥ÿ±±²Å®õqqW_‘YFŠ+ô¯Òqc$0ml3c+‡~ž¯úþž®MVW‹kêÈsk²ÛM¶µŽôX¿kýÛ?™þoÔ¡ ì¿aγªeo¹Í©ûïvׇmo±¸Õ‡1­ú†ÏøŒ{-õ/Kʬ¢ìÍήŠö›…no¼ý/Ðmmö·ôŒÙé×ôÿEüåk,G.8âê #9Kúþäe?êC'ùOqµf=Ÿdô1«ªœç±s,%Á­iÙkY~Ç^ÿ³z¶}žÍ–lýèý%3±«É8­{]U-f;/ea²Ö9ÕØÖ2¿e4úŸ Ä¯è-Ì\w0 ò)mY¶7e¾ãcšÀ\k Úã·þæPÚªõ¿â”„ÃÔíê÷H¬?™ÀÚ—['fëñ=*}çk²»}?çü<–,3Êy Ï'.™±åý\!Ž9¥“–Fý/“Ýý4f¬€ƒ kIpƒÅáý&€-Ù¹³±ò[g½sA5»k,þÖ~{ëÿÀìW0ØãŽýŒc^ç7Ó}Œ/nÏßuLuNsYïô¿IúOøµ'1‡¨ºËm-  ±Î"§1®±Åϯ÷ëmíô¬cÿÓoý5*u\lÉu¢Àê.`÷æ¾·m¥õÖÿæ«Ì«×}¯³Óý%u¨9~V0ÌI˜&6D}:×ïJý\Ž?ÕÃÿQ°C—Œ&%ÝÃõG÷’ÕSj_esí¹Á­sÜGç6¸¯Ó©›k¥ŸàÑôÛýfþPœ‡6`D:ÒKuüÍ\™ŸÎ0 ljºF†V6ÎÿÿÑëÏ.0F§‘àOæ¦'i ´Ž=Ð×ßI»·ÿkjr@q—k'^;”ÁÀL:'UêZÈ´÷pO#üìšGˆüx?t$¤Ô×f&G¨÷PÇTE—Ö{XÓê;íÕ»ü+qs]K¼ߎ÷3#íw¥g–ϪÖþ‹Ô§þÙ·ùÝ•®“©Á±¦¬k§XÞöïŸúËmö~z}>›r)Êx{} Õ°‘î~ݬuõµ£ù§í¶¯ûnÅ/ÌË•çqÀË1Å“˜pÂPÇÁ‡$}?Ö9íeÿ-'ê}©B"G¤0‰ˆ>ç ÿœã—ÉÃúZ—z•6Íž˜x×A;I>Ÿ»èû›ïö©öˆN¼Ÿó¿uD€4€>¿ë x‚"éÌž<“áýVž™Ïæâù?M“qq’þŸ¥ú(ÙKkk+Ûé€ç´yû¶´¿CéRŠÃîkïØXth—îíÑž¦Ö×ýtÅÎwµßHêãÈÿ8û’õ"Ê™;žç¶+†Ï¾èú c9ßõ¿ç6%ÈdÎpŸ¼Àbœ&aCô£ú<0ËÃüÜ–8â@Æn4+Ãôx_ÿÒí{ë;9ÎíÐ7{˜÷nÑ!’ðíÀA8FÙ˜NùT9ØûܰCø7’«fnLß¼+‰óý&í¿æ©gÍàˆ»ðŒ¥öq0K˜Ç|B^1”SøÐF³ 7CçJÛùÌj‹í}€nÛ¦²xsš²¾×™þ‘ßæ7ÿI¦9eÁÆËѯòÝŽþ¾ÕúGÚû!ÿ~Ç÷Ì}§öGþý½‘ŒÌª…n.ik…Œ{6’׿îÛc_[۲DZìz›,kçd–èC¢âùÍþªÍ7Zö†ÝêZÎÂH×±ú(ÌËUéÓKšAŸ|¼j>—æ;vïÍúÇžÄeÄ}>lsú°Û‡‡Õ?ò‹‡5Ö£½ââþïËÿ=¼”Oi…FÌûޕ׳ÍÍ.?ùÛ’ó/}‡À h³j3ø†(é)øü±ÿêÿš‰sxÆ×/¥Gþs­µÞîKkµÐ¬sêIyó—&-$™Ÿ0OåQ‰vÇÿ;ÿAY÷ÑûŸó¿ôXYYvÐö—7ÙŠ‘!­—Öö$€>R±‹8švØœ°“%¤Ÿ ü©¿é)Wócüoý|þ§üïýÕõè˜õY<Ææÿz^µ1>£#™ÜÞ>õ›_¦ÑúJ ºÈ‡º¸Æ;r€ið3ã´¥þ’ŸîGí’>øt}¿ú ®×5ÿAÁÿÕ þB¦ÐCÛ#óšž,CX$K'û?ìW0¯ª’ƹ–ïsš ‚æý!­/ö·þ¶¤Åñ9Tâ ?{‹öp²cæ„HXËÿAÿÙ8BIM!yAdobe Photoshop ElementsAdobe Photoshop Elements 2.08BIMÿáÍhttp://ns.adobe.com/xap/1.0/ Adobe Photoshop Elements for Macintosh, version 2.0 adobe:docid:photoshop:a3599c98-0f7c-11dc-b315-c2d7bf0c1666 ÿî&Adobed@ ÿÛ„       ÿÂèÐÿÄ 0@!1P"2`3pB#4$ACD5!1AQq‘" 0@a23¡±ÑBr#ÁáRbð‚’ñ¢$PC²ÒScƒÂs⓳t!1 0@PA"`Qaq‘pð¡±2ÁÑáñ#BR €r‚’!1AQaq ð‘¡±0@ÁÑñáP`pÿÚ ý[Õ×£²ˆ°DIÀ$„ ×„€ pl"<“€Dyà€Áì”ÆIO`g“ € x2Nu”òänã_¶ŠnHd€ômH^Ñ;SÈiÄàH¤N ¤{0ˆÛÂѵ «²‰¤4áå;(–@@Dy7$ 2kž¹vÔ3æmc_¶ŠÈ &O 0š@`ŽÈa4€Âi"„²C ¤P’Y†HÛPÏ™µ~Ø(K 0š@!<€Ì´N®¬#Â,,g¥§ ¹ëjÆm™z–ÖSîcÐ!„Ò¦ êº}g”õY^³êT|Šõõ!^öe&yyË)óÊ]ÙÙô®oíÎY€M 0š@¡,€Âi$–@!„ÒvÔ3æmc_¶ŠÈ &ÉÊó´c^¿ôøÕ„{6EJ®†[*yÕd¯_nµo;8kƒfÏpÄc$FžÝÞrÎmZ`ét/{}.Ÿ¯v“›SOF¹kWV}ކµ:Õyï“ÍÜÛ”¶ìÕÔÓïƒÅÄτŞ[`Õ½u¿t8Æ5k¨ÏuŽڵ4^w:}bÞÆÌ«éWƒF¯8Ä›3¶é[ÞÌC ¤lá>à<žÏ'¨G“[8a4€<ÕöSÓÝuDЦæ[¼ëRÙÎ!„Ò(I,€C ¤í¨gÌÚÆ¿l%M ÑÖÞØçžO£ÉçXö:ZYg±³=Ùñ¼Íà ٚµdß·ÖS>½~¹|à3zܶ7nß·AW_¼óš1ñÏ¥.GV¿²,²ŸiöŒLúÇ{W6ãVZ{ìloÙÕ^Û¹ce¯VýÍ­™ &»®v0xñŠ“5føßÕ5»£Ctu5gbBM*Ã~Hˆr|^…*çYèùW6ôŠz›`¥g˜òý­.MûOuå¶÷ë¼·«N®ínUî‹Ðòí-i†¼·öbC0ä|—wSÌöôôçeêø=Ÿ®áìe$–@!„ÒvÔ3æmc_¶ŠÈ &W–œ6sí¹Ïó*ø†e±nÆÞÉÄ4µãÄùÎf¿6½:fÑ^x.ÿr=›=ó9õøÎþŠÖ+éZ·aÖè[ÚÙ©rݦĻ÷OèÒ­¥§J•õ8fgѧ^¶†Yw~ßÒ[nÖ‰å<ï*‹ÎñçÑ ¾Ýòå—˜ŒF3N^P5pÃG `F™Ôîvu1uÞƒ©}wu¦ÐÂi5a¿ ÂiU×ÙËx/SŽUï]Z7Þ£‰±Ò§¿kNİCV[Û1©×;Û"Ìyo é±BÜyá)œñ±õ|;uÕóíÅ«;®Ç? èÖ¶¡Ÿ3ký°P–@Ä8_=ÌÐòž{)ÕµcË-^þ‡«w°ëZóž{û)êjæ9utucô_EÑõ”ðÞO‰Y§¿ÕvÔjr>W‡±»d½N‡qéz¼çŸåVÒѱ×èíÔ««R¿»ÖôkjØÙ•×^ÿKpÃÑÏPÑÈxß;µV¨i‘víØvº’g:[ìv={—[bI!„ÒC ¤x‡Î|O¡®¥c§½£¨ö ¶á,€Ä9ÚÛ-7c·”PRß_„õ´iéÎl2¢ðÞ£œæZµ¿^,±ÏœìîP»Q&2˜®µ[W~œ#gVÜKcÐqì·á>Ì'ô\{µ:¶i×Û]áý>Ç ä+nµõ\;‹º‡®Ñ²Þέ|gç~£¿Ï¹Ðú.UïB¶ &¶¡Ÿ²jò›K:€“ØP–@ç)jæ<—Í¡å6¾¿Ñùß¾£Æù™µêŠÅuëë]»³1›×"äs$Ï.ÿßú¯8Ç7ʧWÃåêÔÑ{é{RnÙU[_mÞèoæà¼ß/KËð6:ý(÷n‹…ÇÉYûíumno¥áòìû=.ÃÐõ Æ-¬fa4¼Ãä¾ÏMZ¾ÕzÞ¢ÓUÐèI_M§NçŒòµèÜî{·€C ¤†H6×-ã= ‡‹õ6Sð}?¦ãˆ¡,€Z²æ¼'¨‡Ãz¨ìèµú‡†Öãô4¬h£§¾ó—Ñ“f:¶kksnÛy®ð<¢Ld$#˜’'$;0ÖݧO~ˆ;¼žÃÔqúIÈЭ¶³Ð³îs4¹Öì:Téy·)þUï<èÛÚâ³_K³Ê¾­oW§C«÷>fÏ~LmC?œs¯Vã¶ûÐòe#ţŵç“cß&ϯ1Å]™ëÖ‚þ™ýEµÌ¦/éÇCVlëkÊÆÞ¼€4uÅ~¸WÓ>Ì­înÓÁ]OFí­Û{'W\p>S‹¯_O¬²ÄD¼Î|¸à›«ÐܽjÛ³Ò³¹¿c5e|8_å6uêë½ÿ­½»ºŽ¦®cËq Ù·SVùÔ1–shÑã,¦éÞry¾SãnÍ®¯BÓ¥r<1í}P0šCœÐäy5<×ÓçËÛ£D¶íÒuúsmÏ6¬×ñù¾îZú¯¬ë8iêÊÃv a4€g9¾<á<Ç__¼TµòÐéa¡·’¾Oáì´Ôš@ C©»[Ë÷yñŸ¢nrú#ÆÌ,¾·óÚÏœû'£«¿LºóÙÓ»ÎXê{_/»â=P<¢Ldæ$JÇŒ±÷êx1pº²Ð¹xI†pîÕ6 Ycãfúüíž((ZŠ¥YãµÞäÝ{Ï-/¬a€%E-óïÕ³·e ;jüÓ‘ÓõÙ¡gw@[çnf–ß~#«iå:@‡$Ø€À—3èyœç·ù¾ÕÛ6={67y™à86)Ó“FšÕ±žsõ:Ó«Á{] V6÷÷u76¥å͵~o‹‰C»~mØ‹,ñ3½«G­û¶»Ý‹+[yþ+N{¿C×£ãÐÝæsv®Y²ô«L×—÷üãÇðàçÓõ»mÿ«ïXlš­j=\nV+è—^±«¾Ç¯CÛëúÖº;Û€ÂiÌòªRi5ëÖò^lƒ$f',Û³oØè\özw¬{a4€5áÌî\i|ÒêÑëaÛü¢ÿ+îy1ßËo‰§è#àëåÒ«é}6ÿËõZ2ºô<™mhÛíso}&I¡[_l™ãg»_Ÿc§èWÓÂu)ïƒ>­úPpº²xŸR-¾±à9þJ»Ë÷=á•Ç>ø#Ê$Æ@ŽbD Yc.9ó–:›…µN׊ۥ³§9áæcϯ󾼇¢—؇<íjiÎ=YQð/KЧÅ{Oâd"L¨­‹Uë€eœ¸à4önÓÏ;m÷;½möWìÝÜt÷ôK~¤2Íréü÷ÇJü¼®lý;÷½{՜ښ\êXìu{oIÔ´Û3H/ƒÏæ¼·ÌÍŽšàYgæÆé¶gaÒ¹Õz^ÅŽüðG d"…o’èc…ruÉÙó—aµ¯kÈô—uOô>Õ½`†HaùŸÖêúƽÓ¾SÜ÷€¬êWá}O‘†×Œ÷Ž#WŸô«î¾®¹V>¿2ûÒr<óîGS~¾ýStiøâôµ4ìØóýˆvëÛ­`eŒ¸ä3Æžý>ï/<®„ünš$G”IŒ€ĉ@²Æ\r};Õùž{èZ;zµ±šÎR·ÆúMQÃì}wÌ4«g£ó.î÷.Ç”êõ«^ø^¿¸‘&Ìv®éõž0UÛ%­~ð˜ñ*ÆJn…>'Ú|ƒ # ÀA€ ‘LÉA€ ƒ2 ,öiï±Ø{EEã<Ì–7^wz?s£>JjºøÀÅû¾¨RÛçÐ÷<§Ñvoû}(ÓÑ^Ý‚S >óœÆëù®Lzç{ê:ûy°{òFL ‡Ä>K£Ù|ë¹Ó­cÒ­UϳÊ^ÕŸ«ùë>†‘€dDÀHOŸu0ù—_Eàúwüóë.{t·úê9ù.Ý[O°yºZ:9?%ÁÇ>ˆÑÓëz/3õ¬ÃÄÄ¿bù½–Üt¼÷__NÇϽ€2 G1,HÀ2€dÈ’)‰bp €`À2€dŽcÜHÀ2€ ƒ"˜’$ ƒ2 ¶åH®VšÖ²ú?©êøõû`ˆFPQÛÒ_Ô+x–\;qrlÞüï¶E’\@ K–êQãýÈüLË#™Ì2zDÛÇ l²õ ¢2H€)¨³ofý¾óÖv·2j`§âs7¯Y¬Õwwô·|ß“§£µŸKs81QÔÕÊø3ãN™qÄkÛµõ úœ‚(K ãx´©üo™`¯ÔéýW¨êÚ"„²Vrlf†ß\kXéhÏ¢¥ck BY*åÁYŽ;±¯Wt}#⓳ñ;~fñ¡×«¯ô>.§§åó|.&—•yžÞ¢ÝÝ-zþ}V¶ïê6œ^11g™/#£ë ÀE–2ã<¢Ldæ$JE–2ãñ–>vëûÖ¾qQÔÑ_¶`ÉCDZuðï[µ_0 ËZ÷ôg& n…N#Úüȓрz<ƒ$i•ÛµÚûŸOqc(a<€ÂIsqØÃGÆùË…ºúúúÏBã±ÐÚÙ<ÿŸ§N¿ŽŸC®éXÛÌ!„ÒùߌàGÇåä&o}Ï«ì:[À ÂY®Ñ²’…޳­La4€ùÆøùÿw]7¡ÕÛòvx˜ê>!èþ•òÞà©êVØûבØÎ9žE.oÅy=e•~†ëÏ}?vŸX²Æ\r31Û÷øzzw{F¶½“nÓìƒVÝšêiÛ¥¡xŽbD Yc.9#½ScÒq¼êÏWÏuýÚÑçÛùmž{ ú¶€ØÑ³êx[½¾e¡äE«? éÆ£§¢¿l)(í¹»«Ô…'?lõó×ù§w~¶Ë?5|+z•é=O7wÌô%ú'­NÛÇõ½r,ís÷ia'é|•Þ.Þ½82“*{;Ï z»;;ž@7O_'à|œØàvû7þ›·Òöo[îš:šììg³œˆa4€x‡=Í«ç^¯"Ÿž_?è~óÓïîÈÁ%æ¤+kìÑÕ—AwH?»Êûºõ;šûŽ>Èåiòç×~;膥ý:Nàyõܺýrž'̱Çrý½û¶)ùž†oôK*¶@eŒ¸äfc¸ïðõul›zÎý`mC>VÖ5Û OG“Ÿù÷cÍÞRÛy]qíø˜æþ¹æµºµ¬÷Å¿V¿ƒÙBA«* 58ÿ1ÇÅjòcŽî­>¢¤€"Ý·ªö^Žó£gc)„˜8_1ÈÐó|@õz=w®ïëjÛó“ŸÉRíó>{ÐKŠbXsD€=§ yG¿]çl} b'òÏJœxM?›ìoéÝeëüõÿb€¥ä_çþmì³BæÇÔ<cè9Èw3ä­c[¶6OR%=-–÷u€2 -sãâþ¢.¥zŸW͇Ï]ê¾—ÄÎQ,€â…XÜÃ_ãøhz“* &FSæ#°õ^†ÿ­wbÆÛ=€K\q¾K…«Äå#œµ÷ïܯXx™÷Øê}+Ù÷¡„²E dعúÛ9îeÍÏšûnÖù›Žÿ6ó§OÑ«²+¶ÆJ<ššñ‹K¯ôÿö;|Ýã”û—¯íqz.•­kŸõõÇÎÏÕ[Ið=.gÅû\éÛ,dŠq–2E1,HÌ{†RñßãÜ{ß)Ð÷y¸BY)yWh¾}ëqç»ñÏW§Ïêþ¿óÛMø”O{C.rÞ5[#fYÕòÞß¹ ÞvåGÆï&ÎǽäÉ{L²µ³‡©E d9 8Qyž–{T©ía¬I•MfçÐéèë×¥†¿šóy“Ñ«–{ý^…÷{§sÒ¹¿¶pŸ‡Ws>cÏòêü¿_=¯¼õ77K 0G d_9ʹQòcÖæÜû¿/Ñvyòd×Îý\´xÔøÎG2‹Ðôö|¯Cµù¿¯­÷·¥ÐùÏMuÒâõ”tUùÛÚ¸O׳dYcèÖ³§ZÖŠmůîѤö|m{z~ƒð_w}æ:ÓêÛYÕ£=M»•l€<¢Ldæ$Jm×§ë|åß´ó–Ö5ØÚÓc ·ljM 45gÊ|;éòkÏôÜ;O[çúOMÇþˆô_o¶¡Ÿ3ký° b¦£·nÖèÊÚæ¿rŠÈ }1¡ª+(Ö¥ò^wkN‰2  ÌÑ Þï[£õÍý³ó¯›øé±Ä³ÏNíÙ©ÔÝê_ï½k00G d,´Í¦™ç6?/ý¬v±ê¼îߪxÛ•Ÿ>õÏ)è4mÖ½ú_Ší»¼œËætë÷oø_Ñüî§K™nyM_Gø¿œöìöoìÛ©†ºôØñ>‰ßù_Oïdt“›ç8âø·øËý-ªÝXü×íÞéž}¶2y{­|·N£Ýq¯¾ußúÅ=¾å yD˜ÈÌWu¼‹<e§Þ޾ï7×lÖ¼k_§³FÞŸ¢âYz®òê}g a4€'É}ë“ГØù¾ßßù`WéŒJÇlÛPϋܯ݇7ϳ»mØÑ«®mîëŸ(š@¡,€)ÍÑËø1µ† I•ffˆyÙϦîIÔèóž3ÌìTª1ïÝ «?Aö~‚ÞÆ`Á%Õ?1õZzÊYuüÜ«üßJ—çÞº×Ôòž£Óùηè^Gœäôkëö>5ôÏ5Yè9ýÇ#d˜8Nֺ͑ô¿Ï^sv®¼ö^o³_¯M.­0Üú¦Ëbž}“»»ÊßóÏ©ñ¹¿[^ïÄv~ò?g˜ î:cÅšžëÿ#ÙÚ­»ÄÆJŽ­˜þùÇŽÝp7üŸKê>‹µ[~`H<¢LdxnÇ'»Gm‰´Û9ŠOEóM»~ }^ŠóËý•zÜï~ƒ‘¿Ð©Õ{O:0š@;çþ«Æ¼ûO¢y CC†À§Ôìùùjt"¿l%òÏ1ÌÐãóm-n“‘Ë&T!™š 6ϯP=_¹»Ð·«‚lpÎS5Û]g¡Àù.'uì{öÛRHÁ%Q§*NNzv¢Û§zýz™æÑxÿ;Óyÿ_gÏïüoôÏ’¦ôµíªåê­ß™ëO£hõüJΕ.'ï>';àzåîÙåÙÖéÖ“N{\ ÑõkéúZ|½ÒÒÝç<{/‘zÞ¿çž—ÔeœQnçÝU­.8ÓÚ±©²Ì[5}…CkNZ{ã–é[Á&1£¶Ìš­ÚóãWKnÝ7(»>?k§òŒ€ ñ~ÅgËôÀÚúŽ–Îš¯é;¿°ü÷ †Hy‡9_?8eÓ\ÕmC>fÖ5û`¡,€¡Éqhñ5E=»Þ*Ür’`ÌX©êy¿4zÏÌ·môž›µKÄækq¹€å›nÏ=>…Žì¤Ý³ªïtèjkìz{â‡å8º^ û×~³Úö®#„²0TRÓQͧEB¼ê[©ä·Wv­ðÿWêkz-‚~¯¢þ|ô—9>uzÐm¼êyÚÞ~Ï‹ûžG1ôJã4¶bcŬ;nVsá<—S -ÐßÌ}ÓþïÀ%ýóZïmÊÌ€¯ågqZq„ó 9ÎŽ !닟yðë_Ið¾¾ëŸ¶ldòŠ;ÓyìëÕgYµ§.Öxšß’›þÌ€›ãé½Äúnÿ?ÐxÏò×>­àZýoçÚ|n‚ýKEȽ߈†HT×Ùâbç~í¨gÌÚÆ¿l%?Ì»šœíýOŒêÃ’l@¡êR¤ú7Ÿµôº'»†žŒ9_ å$Ó¤ùìÖõg¢´y°úßÒ½?Vl•Ú£WV¼Ž}vœu(Tõgié»2‚8K hhÙãÒ§ó\ñ¼âË©[Ì1Œâ&»™`ä}ž›^í 4hý€öÀ1{5=<ü®÷ë¶\ß@&úëý×™ä<‡z߽˹èÕM.~¦Ý¸[îĶ¡Ÿ3ký°P–@žVýÏÆ=H‡$Ø€£õœß=z׳WÚ©Môž·J*´è¼wš— yÆÏÑ|'×*:<¾OÕü·C¥Ò¸ô]ކÆ\/?WÒ;×l³Eƒrf§^»rÜÆu²Œ’B€p–@¦¶ÏüžÖúkWv²Ë©Fß©ô=Þg¨Ô«³cr›}n[ëŸ=Û»¿éÓoùœ¾qôN¯#æ¦ÓÃî/t­·oæ«Tùö¿™úl}‚½n­‡ ßÕõ?#¿ª¡³œù?¼±âu€Å¾ëÌ_ûÏ1u©Èï‹ý°Æ%GAÍÙMÕÂPÅô?€yû>ml€kNÿ©|÷ìSëÈ6ü:ßeÈó„ëVÛ.Ì9o'ÕV@qÍöø¿¢ðú¶)ìá¯Û¶¯M}å¾Ä1ì|×môŸ¯W“vÖΛëÚX¹ú{÷÷a»· œàvÔ3æmc_¶ŠÈ»ÆtíþCéD9&Ä×Ê61š/»ù üë·êžÊªøêåF¾íÙÇKæý–ý.±§ÒØÇf—«æóßBóô|9»zÍÅû7=kÞ =³>-Ýs¹®b–Iñkf‹(ªÝp–@³Tp_8ñÓ «£ÖZóþ•·»WI¦h½‡oiÀè¼ï¡æüŸB};~{öÅwù}Ê9·þw™ä‡}¿#²ö^‹¢¿¿^%ìáÔÑÊßÎß«ñ>›Ï•ïúÇ Ýù>Ûßùj<õZ¨{Ú³“gÏëúÅ8ìÛÖûMñ_[Z¯ÑÈwÆéî>WÄÚ¯ª}X œ§ÑÓ›U‹~_¡êü÷ªØÕ˜à}o‹‹} ÞE\‘žÌ€5sß»æþáµ§|ºìoý'Åtþ·ƒêZGÁ}>®ï™—QÊÎj»|êÙmv¾Öpí¨gÌÚÆ¿l%wË»Öÿ?írMˆ¯”lc4@âÞøÖ§CG õOŸAC•£r÷a·ÓlùNGàûWz^>½æ·ºZE=mwv0ÔÅ»˜ ÂYw–ú]Rú&\^*a½ó~&å=v\ºý¯ô>ukØÆzþÏO•|ÿʾ…µÆêXý;ÃÞzn??ç»Üç–ës¿lñŽž×Èu»ï7ÐØiè3µó.‡.÷¡Ïš@9ÞUØ<ïr×wK“v®¾ø5O{ößšI/žt1ùO®Ñ˜Ýó~“ñ?9ëYÓZK{Ýûòkr~ê÷%ìö µ­xv[ð²ñº¾–”5ïׂÏéŸó`"L¨Ç“ìz'§ØÑoÎXîWÛ¹£hg¢yÞ¿•ôß?Æî`3Ù“w=žævòÏÂ)õëÓ±Þ¼Óô(¸;mæº[ÁÃä]~}ë4mK²ó{¾‰ç·tV0†HÛPÏ™µ~Ø(K ï›öíþmÝä›,z•¬zUâÕ–½m›uúÊ5jn÷ž3nÁ0‰¨ãÛÐÑžÆ2qù—Ö¼wO7,|_VóÉô€8©ëV¯úÇØs¬ì`M 0G dþ½Üö}ošúÎ%Wr ⸣—mæ7}/›–ÞAQ£“£«ì[æ|«Ñó½®ï.óè>K©Ý§Z‡O‘ò{öØË××µ¶×#Îòž:ÑÐv-Ί/V®‰lï½ítn.ï"§®×ŸÖÜßÒà6ÏÅß¿éxÿYèòä— ¾ΟϺ¼¿ xïº~Óƒ£åµ9ž>ñ¤ÕØËãßdìy»=¯Îõ}/çÞÓn‡cÖ¼Ï'«ÝzWüÞŸcçyq€D™Pxö<ßG³¢î¥®e_KÏârØÃN@Œò\î±±–[6/â×[nסÇoŸsv–LRbø½èûE9ƒttZda4€µ ù›X×í€"„²«ñ];¯‘úQI±Ï«[{¡^9Ã_fÍ­^q˜µgã÷CL:2“dTq®WVÏc¯èh®÷|ŒûÞEÕœ0d &@K Õl¡¯¾:ßÉ¿`ò·ÇÖ<ÎêkXð},>ñâ,XêšÍsÓç•ómO7Ùí~ä}¼¿õ1Äïúž þë¼—ñ·V*ÉÓ»A~å•óûºtI†xkšgÜÌXÇ„kç³2Ÿ^»ýö®º:~þÿƒýooÓ¼Öï¢ùݽ¥Yã0±Š=Û}WlýLE˜|“Ï|Ù[š0U^ôó»–U±Ýåû}kžÍž›žNx¯‰2 6lèýg ÓÕ…ŸŸaÒíö–}>ÎÍ|uú{wùà5q~h÷Õ¾¿ä÷ý ŸM mC>fÖ5û`¡,€4ôe³ù÷Ùl×ÙI±5òŒdW¾âúÎ:o¦pçÙÂi0Ò׫F=%¼æÈ&ÅYÊíéegóÏ×<ˆÌ· gݼ¦ìKpØ«¡g[NËÞ­(¡‚ª¶Ú+Ûî½§ŸÖÆ~ká¼åÕËîÛŽ®ïžù.%ÏdE¾íýE«¼…:úõÿ3ÖîÔècok„ú'ëÝj;…v­“ò=%Ž››sSp‰>Ê,wò4<§?Ëò>«Ïo㾥忞ìèèx–¹SóØîyœ€&T®tÞ¿Ðô×÷q\ªýNÅv­·ÜO_ÍãBçÑy¹r€Õ…VOÏÞú·Ø<~ÿ PÈ &¶¡Ÿ3ký°P–@J–Ý_—÷­üoVl@ |£c;ô>$CâôðÂi‚8K-,uñêmä*êÚæ¦çƾ•Àñ²<ë›îf_bò»ú=h¡£Gl¶p×åïŠÄg+3Û¥U̵GâýçVií¼ä’"·?ÀålöúÞ".šêü—õ†sÎl5ø™,ÆcYnÚµÖ佉*[û‡Å¾‘ï×ß…Ð8Ó}‹ïf4þÐY|ó¥³£=K®?cÅý¾L°ù·£ãò¿Ûø/iÁ}k‘ÕxnmóN¶¿[[·òZ"L¨`.ŸBÆíœÛ³ßwz:8ç£B×Gð¯OYô4_EâXlK›…ß=õÚ7«Oè!¿c ·Ó"ŠÆ:äÒvÔ3æmc_¶ŠÈ Ï•÷ìü'fl@ |£c?êùµ¿Oàu}M 0G dq:·ÃªoöèµÉ¡ãzSø®¬ÜKv4öÁ³µåYë´Zý:Ÿ³½Í¯‰ÛF̱ )tµ5ãï)ökh׊Ú:>•ÎS‰ÏÖµbÚåË'£ìöé’cá¾’¿Ë~…çRî~;ÛûOÃ=4˜HÕß‚ZskçO§ïÔcòÏeÀä=g,ÞøNßÓ¼—rî7Pèäñ¯ ÌúŒl9.ø€D™PéÝ>ŶíüU!R€ŠfëÖú.¿¹Ðê+åê46Øü“Ñézž}oÓ8¾!Âï‰wGÎûºù¾ö¼kw¾_wÙ8›,5M–©ÙÁËÜÇXš@Ú†|ͬköÀBY.C凫óWì9–×Ê61¯µR³é<+oOGo~ M 0G d8q¿2öž<¯~º¶³åö¼ fÄó*ϧV¾ô8Pßóö qdéje ¨-bÓÁSWUÕ½»=ë›Ý3O^Ì{uÃÎÙKÑÚÕ©”|;Ýù¾ÖsÇiò®ÇÜÿ>z  Ëz¿:þ¶ñ=9Ø#Dý¯æ~«ªäßùŸ¨âj]ÕÆzN]~½}¯ËøÝ7怉2 –ÍÙ5±ÃW¬ö¾ËÑ^t,MåïIŒìzj4Ûããþ‡U-¨Ýçåž–÷e·+> ¶€C ¤í¨gÌÚÆ¿l%«\ÖVÏåž‹×èÙÕ­Y_(ØÆ@S{>T¿UóûÖp8ÌóXO‰y†f Ì0G dkâ¦Ñ ™Rq:4_8ö;œÛ£WNï;iv¼nvÎÁSôêÝ?¡Æ±Ëåöy|ÚÙsÑ··˜y‡?Í«äèúv² &–î¹³Ñ?%§k²§èyêÞ/æÿ[š›¸\ÔÊöŽ_9µ?rüÕ뺿-xsÝ¢)¸W…}š?dx+ýSô.ÍŒŸ”úM?Tùרú« >'ë¼ß ìøøÕ;Oø‡™÷$Ê€#<ݹôÿéò RÒ*~AïªöO}ÞÈî9yìÊÏ–-Ü€!„ÒvÔ3æmc_¶ŠÈ &•'.ç=ñߢûúŽ÷â{7É› t·Î¾V: 5,êä5òŒd)~óú_ ž¼·sÆê¾CO(6¦È#„²y8?ý©Åin¤óå9{OU¥6Æûñ«úÅ^+ó,Öæ‰z£Õwmîn£¡_—ðžS/\²·ïmmm°ïò¡Óž—ì]u9Þ®hùÆ®ÐxŽÝ'£î|óÕqy_EÎóº2b¼þ—üsï,*í!Ê>úOÈsŸCæà+—Ÿé}sÉ÷ý¤p}~gÄ~—å}o‹OW¾ùW Lqe@žrÎûÚú~Ë«d>Þ×õ^>}™M mC>fÖ5û`¡,€Âiæ~ËÔðõ¸hü?©E³GeÌãŜ˃~¼€+:Õ±§-9Ótëîûcn>|õßU6[ùž…7Ù¼µŸ« M 0G d ù€×Æxˆý/b¾ÿ*\V£Ósq÷Š“b¶ìÔÜ¿^»•j»™dp>ïÈVÚðcw=½î—ÏåÁnÄ»öÇÌ #¬Ÿû§˜á~ÁÄ÷_-þeÒß2öž¤Ví?úW‘ªìÕ{ÇåôO…wâéxM•Ḋ‰Ÿqd[·_úßCÚvn+qRã=fÈ¡‹ C ¤í¨gÊZÆ‹(™#$²´ÛVœÏÌýžÿ¯óÚ?;öRжedg‰Çµårvôå­k^‡Ñ¸{Þ÷“&Q§Cn–Ìnm`2`®ù¤Î¬¬~Éæpd€2@æêã4¦–ªwqßÏ|ë×øó}nž‡&âž@¥,3,Ìb'¢òÜ÷¡ùøÛÏ;Ûëõê«Õ¨“ßí–{"9‰"húü½ì7u~ÿÏßç{Ò@ëäú´<ñyßFßFæüñ¿&ô–žzìxeã\´=˜~‰®ÛvÛf­™×‚Ÿzyað?¤ù.í ¯…ì}á¾ëj¦w^£çüþÏ¥®¦%Út»–ØWå¹77<×±£ëèѹãýoá %î^×Óõýk`n&â#s “"PÜÐÏ•µ6ÆÜIJŠÈ &ŽgÏ=l|ž‡MõË|ËÚìyÎØ¹½ÿ ¥7Ú|µŸ¨ ‚ÈQóîÅ¿ÌûÕŸ üesU팧È0š@`ŽÈ­ÖÌFÞÉä¸u9ÿ£&¾ûGñþêç‘`I±äýšæý/ÎFæ{/w٭צ£N€“öÛ ¹sÃ5Ç£xÛúïÎí¯Mï3ÑO=}>Së ûO—–îšjØtÖó±×7UòõnÞù€C ¤á,€ Ã9Ê§Ëø¿7Ïúkµ>šÖ½½•~¾ïÑþ1ÞúŸÈ=ÎŒ„9&ĉõJ—»àEŽÍ½Õþœqõpôy  8m°¡Ûº¿GnÕ^kØ='¶ú¯†ÖâzkM7€G¶Ž¿K‡Ýe^ Ù|¯ŸÉ»«è->}Û„×ݲ¢FÆÈÆXXê·*~uÛäüoéž_9<êž¾ƒæüNoè×!îì÷¶,|ͯҟ“½Ä[1ùçµùœ8@¯žÎÿè>²Ã~~ä+á¡+øk‰äÂnäC ¤PíègÎÛÆ¿l˜6óy‡©$‡’Hz‰ƒ(„–@a4€øÏóaeí¼Íµ|ÏÉ}üœ»à·Ü>e>P‚È¥ƒ™©g{.Bž= œ¹ºñÓXü›8Hõ K¨hí%(K TíUqÞg‡µ_W̽ýß}ÈYù§¥î<¯S×Ï3³óû¡É6 6kò~‹ÉSöüW¨€IÀûm…Ø‹,eÇ!æqè>Ñóm›»ÇôûQ°HÇÚ0ŸHžgo_g•èÛ¾w®‡uØìtL°òSXÇܲTòû59Û«½£ä~÷ÎEwXïø{>òª[\êÜkwȾ¥ÑúgÆ{ÿUù{–íñ¹Mó¼ì¢µëtþŸè{üõ:¼„é¹îôµ¦:Iz†%A “H &¶¡Ÿ1kÝ‘€d b&– í ¹ …þ©Ý×5û#Ü+w5f=š‰” Jk§Ò³‚CÁå3¢ÄNÜÇ¢"Sô'-Áéõ=îf¦œ—ÈýüœžAÓçkjÙ³³['ö_ù€Ì€˜•M†Ž3±¯|›ënY©µ³_G 0·Ô\­u–Y=CÀÓÎ0BJdÈ "H`ˆ˜Èâ¼ß*«Ëðkz¶x/´÷aêg€}[çÞ‹ê~c¹á^c |Ë »À2kîÃå_bóüÒüåç£Ür= ½-œ÷+ÇgM@2IÀûvýÈ‹(›g¯SÞüÓ¾öúµtY·ãz¯IÞh³šÑ3ØaÏÅè™hc£‹äx; ÃæÿRµ³Ø™*åö+ºÞû#Éóû¸üǹ®·½­ È  3 ï™Sè|'7‘úgf÷‡ÝûÏÁ}DÛðC“êòy/Mòø6Ù±æö7)õSÝŸ$kÈ<圽;Û7löÞŸ³§;ÉpºßMضµ·"CÀ2±DH ‰f@2€z щv¼üùÛx×퀆æNvž®ªîÍ­’P–PÃg²CÈ"%<$#2K(¡è’Y!„Ò-]µœ>ž¿Ë=ïÕ}‡“¢çÞÜÝ«æ¾s¿Ö}·æ{öu9?Ô‹ÓÞîrìû|ØpÛÛi×Mœò4úÕú~¯òw.P©Õ=|z{yZL¥˜œgC ¤á,€ \_ãðeÃ]gnÍÒo€>‹áûŸcñÞˆ ÚØxùÜÙùÝÀßr¾I÷¯5¡è+ôïßúדïëF¥+Ïò¼­UEl÷6µH9ÏW¼f,ãÖÎ]§{­ÜzŽÄþØí3 ÷¾Z~Ÿ:[ÆÎÿWKâ¯M'C hç«ÙÖ:^~R¢ÌVX€så÷w¾jd©ª««gâþŸ¼óÖí:õ%Û…'çνO™Úõ{-ü¡±àm›V\Ð|<>‘¥ÉçÜõïÛuoô=KCpÕÂ<&Ë`C ¤í¨gÌÚÆ¿lÖÅÊskõ= û{$E d†H¾ÿÌûÛZ3Ö߇ÏþÈæ>…GìüCÔ¨üÿÆÛÛåhñö/3z¾}æ¹Qõ½ra$—³™Âzsú2è7ãu_)5™$„),â1Oµ ù›X×퀳F;û²’B(K 0šCÌ5âv²Š9[îÄ0æ|¿j£ç^Çf <åŽÏ¼òv^‹‘«Ê¿[ä=\nŸ;ì|ÿÓ¾ãmö"È4u·¶9î~‰r^^ßÃðïQy~Û/—íuot]þ¬5kÍjÅ­¶¹6ãÜ€Á%»­–%±‚†Î1ä6êáòe IŠæ¦PåÄìr.þ„ùϯ3NVÅß'Ï3åüoëž_6±Z§Ü4·@ëü¯Sï>Õ ËñŸi‘;¶öÞïÔÛ[Û]Ìì\Ðî+°­I[‹E_‹ò´m×êéÁôO%»êüM™%>?Þ×óÿS¤`0}GÇïú‡/0!„ÒÿõVÞr÷Îþ‹Èøïé?)ˆ}Ãä¾»s&Ç’±ï>M×¼äðþk•ƒ¹ô½PŠ-ˆ‹Tý-My^å!,†¶Q@AŠ|€í¨gÌÚÆ¿lÑÖÝØÈP–@a4€Žç•îTøÏG'©ï €ž›‘ií9_8ú/‘ýä·±rûq²Ú\µqŽ7-W–¶XjŸ0çÛm›-3«‹HÚÜâë»}­Ì'GtAœ ÂYF–[ò±Û>äÈzø|÷Ðh_$°äYý1òßjD á»ψ}GÊôNßÇÏ»šÇyâû?lñ^”8NWˆ¨åxña³og{¥AZŸ7Z ›6ËfÏ×}צ¦×ÿ#¯§Æóö—;»Ûnq^æ{XëñÃ}µïØt~™æwuº™”P–@àoãñßq\<ë}³ÃYíªÈM!_ò¿Akàû—µå|Wô/™‡­¦z»"±¯µò=Ÿ£|»··é¼í‡n°§Ó¹ÎÞ`"‡Ã}F˜3}ûÊî´Ñ2C^øñ âº2ê¶D¸ó-Lâ,€µ ù›X×í€2"„²C ¤)ù–ê|Ÿg±ÎòSx]Ñ{vï[GŸ†—G¤Ù²÷¯~á»W8 ªÍqi¹)‰ ÂY l\µLz»yOb9ðžk ¯‡íº|?Ð:SèÙgWwŒãû¿ÎýVôlÌcÏKUþuêxÜ'³ã·ãZýó?a¿¯gG<êØ³\±]…^‹óÈiq@‰2 §³uÄaßûOYgoÎø¼Ê_'ç„YîŸ ]Ýþ.Ù°õÝ«{[BYŸ'ç Vm€/ø»>×äw[H!„ÒžVû~>ªçñ_g^[C¨çåæ'æ}¬C|§ÙCá>¿ƒ¿éhóÚ÷©_‹óº¯õÏ'¿è”ç{Tú=CGdGka>1nmˆa4€µ ù›X×í€"„²C ¤o¢f©ºj·¸¯;{‡åx-R¼Óîf­ß¼ú‡Ø±¸MŒëglá;ZÜf¬ní̲p–B·TO”íæâÒÑá¼½Ü_ö§]×õÝõ¯[­ŒrZÝG»òÏÒŸ5‡ÑTÆ ybê×ÎØûWË=_Êé€Ïù^·•䀦D%¡ë®ù}˜¶èÔ÷Ü‹NÛðþXE³fÇJ÷QéûWwÏ"„²ù7g_Î=v>Ÿä7ýG˜a4¨0›¼¢IQpm|›¯<¯Ó¹B÷æÝýîEÎÛÏto<ŽÞ†ÿ&×ê~~š[ >×ÃÅL¾‹Rz³l ZÙÛoÆL¢M ©ÓÛ¤mC>fÖ5û`¡,€Âilá7u¦XUhʯ“gæ~C·ß×êØöù7þãÍsâMðc1ã5Úâó$¹§Å‰zÁÊDxGjÏ[àp–@,ôÍ–™ù=Lþm<{¯(Úœ>¹ÞúNžažB’›KÊî¹Ó:øåSéù úך¸òÝ¥|7ÞPú^NZ¦j•ÿˆíêtêÛQ¯ßû<7²Þ“å¼–ú©ÊF™OWé^ê³k‘SŸÁ}_ÄÛé£{Ôè^u®øß,Z:â·V=-¼ÀŠÈ–ß÷õ³!ôO)»ëœ=€!„ÒùUˆïêͶNv¶\‡ëÇõ~=ÔþüÕõ¨õøÞbu©j–ž»?³ùxe[ºCå·#_Ÿ—=j>‹ïX±„ê×ÊËv>J½‘4€µ ù›X×í€"„²C ¤‚ʲ…žçžÇo=]ÿÓ|VIJ2Ñ­¶‹Îõë9 ïGǶ‹¾:\o+ìËO ú‰Š\ñ°Í,€Á%mk›N^Ä|Ã_BÓãþߨՎÇOŸS^ßaïhÈŸœ|ÿ£cK®M|µùÕ˯èx]ì=ŒÞgÙøÇ ªêù~«Ýrº›=Àι¾[Kà@¦D=¯úÅ÷/¤}í|Û8½¯Wm^y‹Xò y>ž4òú§=œ˜ŠȤÅÊkŸ¡í‡úm\—¢Ô>Óàìöõ$0Œá6>‡­ê\Åœ¯‚ìvÿNáÚëpß?õrøŸO܎㇪¯êÞvÛÛòÀVÙÓ5•–ì€ =š°ÝÈ9ýY|‡§dçÅÖM|gù÷rÓÇ_ÏÕ|öÏr¦æ`40ndÉó¼Ý¾.gùÜ{:™}Cœ%űŒëå°ÝÌC ¤!„pC^Êâ's[_6†Øµ ù›X×í€"„²C ¤‚ËNRÄiñ»Wõ¬Öoæli½_À·ÏýX°öü^§¿¢v^‘ÓO6zMÕQje*½ÜŽ£±Ïp–@Ì/«eGZÝý×É5c©óßKWÙùOOg6Ysš+}#ÔýeŸ;óªÜ__À^ùo´"zîçw~Žk“Õ©¥r›¿ó¿]žõ]ï¢uVý \yO ÕÚæÜõŽT}ÿŸIÐù䑬F™â,uþWèý'¸0œ KÖPâþ‡Êá~—ÈÓì鵡—è¾IP–@ÒÅ»’8|kÐëÔx??ÒúNÇyfܰÝÕ<•ü0š\F¹íö@Có?´Ñôn&Ï©qó|›6cmÒÓnëŸÔÛ„—]‹vZGÈïcOÔç­—ÖøÙ€&Å»_-]ñ­œ»˜YìAŠiUkZíj`´NÖ2jlÛPÏ™µ~Ø(K 0š@!<€úq£­¿«½«ŸåÞ§øïÑ=á˜Çb§v¥ßvµŽéÀäy‘«àû?Dú§Ïî¯Ö£±nèá,€˜má1J¢•ï‚}•¯Ä»ôŽøËÜÓLÃo·ôÞϽ·š\7ÏwüÛ§ánü¿Ù½ã7¸ºÖ(ùëü¦Y«?¥ßÕõ7sÖÓ·cv¨uìØÛª££æj®üÖ 4Æ™å;¼ïOô/ô?x€’ý‹Ï|Ëî~{îÏcV@E db–îžîÏ1QÖ2vÔ3æmc_¶ŠÈ &O ^OÃ>Ÿdz˜ó«§tV´köh}3ÚÔÀϼîÍo'ÖŸ\éýã{S¦n¥þ§£cvÎÏpñ/pd‚8K 0ZÕËæ½½1æò~›ó®e÷O©U¶ÿasÝTYæø³Ë²ÙS§”ð>®?ê5·×¯ôÿ»Û66êåœ/î¶[ônïÑUr½+c,"£Ñò¹¸ó{š¼ i‘_yߣv^sÒ€ãóo©ñ>QúÍuÞoÜüÎÐ(K |ŸÍôþ¥è¹³eSRÓ»cfæÜ€M!©‚‹™NÖý«-ÙpÞÓò‹?Jô<£¯Ïñ/põ =‘ÓSËÔMvr_;ö<¿7¡·ôŸ±ÚçØf²—Ë:xýC•–Î@45·öŠÈi`ÝÍ0M!­‹Ì6ó LZ:¢ç|Ôê[m;jó6±¯Û ÝlBÏh"„²C ¤‚È?øÇѺÉ×cj´:ó丽‘ç¹Ïú¼çDŽ­Ìe5öN—á¾ÖÙÂê{Ÿ<º±k•¯R^•¾÷Ô÷÷ ñx–ŽÈÐÛ#„² #’Ó££Ã;‹;0hëËg}äxš|N_gï=J¯R‹ãÿEwøÿNú…òWkZlšý¸Á’›—s€ùï[kO3×BïiôN=½yž¸çëcÖÝÏÞ×>á“É‚XE,‘Ê|^$gœš[ 1G ¼À;jó6±¯ÛE d†H'ÍyÞµWË=Üš¶µºÝÏÒø»Ùeñaæ¸_qÆÌ¾•ùƒéW|7¬ø_¦Ó?O/ô¾—S¿£ö^ÀÁ%Âi{…Ž©Ì!É$9î]Oy=.«n«{{n÷Öy_aÈù/Ñ|ØãÛ?3zߤxûñuëÃô­Þ§g„‰û5Ã{.=í,º^.Ï­yí’HBYäÞw£ôïAÎì*åæUûb³tÂiÎUÇŸàòõ|ï¢öžš«ÄûÉ8^Ûì¿:Цê,*5½ZŽGÂúm_èëwðýv>?»ê=aèz{™² =+m|cÙ·²z¹ná;X*öǸlc:¹À‡%Íy‡$°ÔɉŽRî *µE†ÉÚ†þ©'MW.fÖ5ûcWÒ’@QVÆöÎ@¡,€Âi$§c‘K˜ù׳¸àúðàþ•乯©ù¯¦y®µµk#÷¾sC«\Yø^Äú6R{.6oa‰dÅ9ý#àìØH‚³ZÓ`a4½ÂËLàñ)!WÈ¿QãûÖ¾Å'Ñ+Zúl/rŸÏß@ó|W¨ç\-ÿsüÝë{žFغú#úˆòÙw4´#æÿ£y~kÑóûO5·ìžwnÀ%9nuž§£Xa4€O?^¯«Áäx¿vKþæÇ±_é÷êŽS’óž§×ÕŽÿ›>T].‡mì½]-Zöl¯¸ìZùWŸ§ô>Õ«-Ù0š@6˜õ3}šÇLÅ“£­—3ký±/ÚÍ&/drŸ×Ê<È Ù€E d†H•ñ´õöõ–¤5p|ÿç¾Ow uñí8ÞËk™ìå×°|ŸôGÍ«}Ï$Œ^òx»>…ÉÏ¿ãgm-‰ ÂY & ‰zž—»nÞ®/§»¢÷Óc51ý7Ës}ºø'Ñþè~½ñŸA¡c÷]¯©Ù“m¦ž~‰åfÊŸÜ<ÖßRBY缋AëTÌ€0š@ l\ý-<ï‹óY­_^íγØzîšþÁù>.ŸãCì½-çb÷+ÍѧžÛ=Uú^•™³Ÿ—pëw{”õZçmÏov{Y̲ 0š@i`Ý̠иڟ í¨gÉÚÆ»d&‰»¯0dŠcCj€ ˆ”@G.¨ê,Hr.}w—áõö-ûË-ۦÝÚõq_Sò|Úí<ÏìøýO7?¦p6êìŠ+˜ÞÕË8¾iÞÕô”úŽ÷ÎgâŽJ—RßÔUè:X’8N¿²µÉÈI•ŽH“*–©Œ|út~?ÍùÏ+_[è¬îoèïï §«ðÞ^]:€Íë}ï¹ôÜß*ãÇ~í—s«g›zÆÊzÚú‹™ðÜ­y¾½*ݽõµð´£C¡ìô-,g¥Œø=Úóõž1c6ûpô Yês¶cW8’¬³ØºÚîhgÈZÇWlO .¥ÖÐE d†Hy+1™ ËÌ0XÌzByž¹ù÷„òû\n[æ}åç3¥‚²Þ;ñø·ß8ŸJà€ñ‹Þ@tϭв“AÏÊò¶Wô2ú;,È 8K !„Ò¥Ìß©ñïK±õ ¶ÙíåŸå¯®øL\ÂÚ¤ÝTËÄ·tÏÖ׫µSôƒÕú'œúFÖ‹ÿ/¥©«n=7ºöÔ+÷ÐØìùɲ€P–@a4€8s•âƒÇùÈù”y´m='o¸ôý™ù.,µmìžó×÷·sr|𑨱SBµÝͺ<÷ów~³­ÄåyçÒñ9nvýGcßïâß>ÃvœC´M&•:Ý­cs8=CÔfÖ5û`®Ö±Ø(K ßÕ;Úçɉ--!„ÒA ä~+§Ñ|Ò€"Ê?0þÆð>:Æ/y8>¡äwýK“˜Á%Âiå|+´ÖÎ.Ýðô}W8ÇÀ=×eóßJ:§6öýqÏçT/õ?bùÍÖàXN΃c[8Äñ®õÝjwx,´Î¶mŒ*·Å~Øa4€ íqµ”ñ~k‘çvë=ÓÖuzºæYŸX"ÙDëå~«57ìl¯Â:ÛR¸9úºõ¨Uìû—¸Î>£ÈùË>Ÿ«ú_Ýn´ùO9º—/o›.·ãe§C—¹·_ ʯØôìld§ÓYI ƒ0¯ÛÛPÏ™µ~Ø(K Ì4ô¬v°y—‰a4€By­ùO ¸ðÛú.;t¾ëk|§ôšñ‹Þ@“ ÈÚù½¿kó›Fá,€5uå³³Œ$bb|'[8 Âi“~ãßEÌO1íx?:ýà:ïzÌeŸÙ¼–r%à[ûæ¨É†`{öŸš\ôj€ \[92E diëÊèÖÊ*÷À &¾SB£Ïòu¶íõ_CN¬zN÷sÙ¹ïfVÛCÄ62y1f¦[³’[y¹Š:ºk»!׎þüùî}~KÂykn—©ú?_ÜÜÍ&z±r“³åm,Wš@j뎙§vÎޖâœöµÏ‰Œ’DÒØÇÌ€Ú†|ͬköÀK¾+] 8C ¬€¡,€àèa¹1Øß؆H' }9cá^·“g «êWüÝúßÃbÖ9ÌxÅï OÓ^Ï+Ðǘ½Î;Ú€ˆ}?Èoú—#1‚8K ±™r‹-3³„Å”`ÒØ×ΆHz²ã¾#ôÙônKËQ”;5ügõ¿É=ngæ-}‹óϨŸ“Ѧ³Ð“U¯xäÏØ~u{Ùç«€¬äBYz„ØÌyF!„ÒæX-æbʸx•=mUôôA« -»­ú¨ù•=ìÏR¥{[Ö|ç— ÂŽ—}·½Ò·Ý5zbÃlÏs™[9N… Ý^>®¾÷ϧƭæv7*tô»kZ#sˆ¿S„÷œ¿™}ëÎmÒÛõ èx?AËæ½W4½äSÏÊ9jlS[Ä™Û÷=´ ÂY &0p¿ ú¼N¨#ý-óUÏÈ:?Ñûïæoa63Ì~Eô/›õCìàj{¿)ôåFÆ{V|Ê–Ìkgᯋs4P–@a4€s_)±E’\U›£Od©¯…E÷ý;|Ÿ&¥Þüº›Ö®´LØÎžÈ¡³ˆBYC£;íø¯¡ÔªÑº‹uÞím¼ñ÷ 4´ãG_tö7³z"çtûÛPÏ™µ~Ø Œ&|Z¹ÅN-ÜÒHúÛy¥M ž@®¹÷ðOa»ÍÞyÅ/ ¯eôéÞÇg̽äGóYÈŸ•¿°îÖùŸrûé|9$0ž^!ÊÖÝÕYÓêC[%\/íç,¼ÃžÑùõ ÜÓ>ru•2æmc_¶† üÕÚâã­°š½Ñ­œ&:¹x”°òE’¯tkg &O Vy»·?õ` ûº÷¾Å$sÛë|óÔq¸Oʯèè÷-ù9ý»Éo»Ö`ŽÈa4€ÏyÞµ?Êýß½{-®ÓéºÜº.uúoFlø×0½¡—ôÔ¹ŸIOéß5ìòý¾—ºùö‡Õ= UØù—¢ÕãLþ›ùåŒbÔÁeµÌs´is(öž“ª(K 0š@ÐUËĶpy9«˜äŠK 0A«-o‘z;_!ÓÈ9Ÿ¡ñ:§p„f¯çïcÖü埦¢0š@TëŸh³Ø XmK¶¡Ÿ3k3Ikr!Uª5±_ïŸR(Sha¥t6'wdÖëYl†H'4©ì›á~·w¼ Žö‹¯¨ç´Õ·:ucn›w9f¯ç¯x¿yóŸèO!¼0G d0š@ã>gìüyD:*ÞzæŽÍþ}þ•z®=º,Ÿ<ú§Ž÷|Žb¶¾Gì=açèϜڶÚqù÷Ïü®ïBßÑ}·¡%M ²Ó8Ì+w@ŠK'ˆQTÙ»ñ¿M«ëy¾/êé¾Eé@¬êW«û—’èmà4°ßΞÛKÐÑé\;GHC ¤vµ6§c¾okO‚†Ö>%µ®uóŽÒŽ|Uœ}ìˆòh`ßÌš–šbçtëâÔÅóŽv=Üz 9ÚmLVÛ@ &O ZÙëüß·aá{r¦ô•ï>—²f p³ÖlÕcðO¡yJí­`ßåçúKÆoÀÁ%Âiظ¿ˆ}:zÛÀ%ý?yC9ìk—~Œ¸n•îì>fƒè<iÓ¼ñt¹Ÿªô‡MçöýëÌmÕµó\>m7ŸäË£E§¬ô]· è€ŠÈ &Xjœ0hm€$—ˆVòlyù7¢¾ó×£Ê1)p1*eÊ›ê<«šµtgµùïÙìjʧÚò¬>¹ç0š@V×Ãå¼èû'cfÆ25ò„¶ôÍNÈú ,økXðòîö@®-¬€)¥U­Î莦Ìïæ¥ÒÞÍÆU[g{L§w6@!„ÒA ä‡;t¿õ4^ÏO_ém€ˆøß°óœ¹â;O5·ížwh`ŽÈa4€ LgæŸ/­6«~õÙÎ;1æ§ès…ªî¡­ÅÙÓÛ°æ\ ³ØÎ¥ÆyoÀå?@x ~~V:rï¼öÛÝ d8+Æ­ó¼P:?mê:~µ°aèŠ'ÜÆ%˜ K0ÀC ¤P’U¾3§š÷.¹v@5-k¬öœ©ñ›ØmÇGïþ7~î¸a4€âë|Ë ;„üû|}f+2Ž»tf»¦°X7sƒ&Ž0°Ø¡,€ Z:±ÐŒ¬v6a·±“'“ &O †©üãí´þ•ù‡´°×´W45Ô îañŸ©y`Üüž¶¼€á,€†Háêp¹Âý3·Eô[Êë5ÓúÇæï¥XoÓÙú0Ï垦‡ö_+ô1»éül÷¡Q¾ä<ÿ6“ÉùáÍŸDú/¯Û̲Ó6ºf×ïÆ³dYk›=3äöhlнðÂi"Õ—ˆú«>ÀPýgÍÙûnf&& |dleã3erôðê.fÐ…63õªYs¶ñå2]çbÑÂ-vÈ~¨Ò‰½Ü¼­”YFÆ[²#Í­œb˜·3E¡{½¿ªm4ζOF¶M°M ž@ |_™~…[ôÊ}UNàÍn'#ç¾gå<Ñ»ïÓzZr³ý ã7Ï ÒÁ‰ì a4€§®>yó¿#.½`ê½ÊO}t O Øú‡çï¡ç ¥Ç.êžGûý7àlÍ ä8ÝO-ÃÛÏfÏ[£ÛúžÕvè»N:z±¶³³Ô¥•v¨°Û9!„Òvœ½ÕÎ>EŸ>#«·ç/oó÷€ OYÍÚû™ÛÈO§(jïm×e·6»}Ó;jó6±¯ÛPc¸åmlàŽ ¥73la>H!œžÑã ¦ L#c)ç«Çar@0Á“Ì#=È'üÉî«ýÊö¾™á=®¹š'‘£ázÇ ofÞw½gâŸFÝ­ÜÙœ€Î©ûWˆ±ÚÔ#„²M 7Ïs)ü§Ÿîot¬÷oáó¯ò?ªt^ŠÇÕ<ÖÙ1žvþ¿”í÷ÿ öV´üßôOÏ¡ê×úô>v@ l_<ùç“»‰²Ûº÷Ô÷®úi¬b&ÅESUݽ¾âtò‰d†HÒÂuüORÏåþƒb¾qd“@9o¤p¯>ÅŸ(å¼ÿOWßéÓé/×Ì5MÉÚ†|ͬköÀê¶Pg› ,´ÌØÎOp¯Û{#WLÛÌA‹É³³„Üט2OŠ9x+7ƾpž@k7ÇýŤy‹5ÿ÷ö¼Ÿ¸knäè÷þS6\™"<Ÿ7û'¢Ðõ›ó˜ˆ[òóú7žÛ³­ÝÒÈÁ%ÂiS×ÇŽù×ÄÎ K{ˆûwmpÁ»Ïßöß›z®žÞ7­ÍøwÔü˜ú7”Ýõ®À0ùÇÏ<Ÿºú"Ï?¢ýØmfs\Øê˜¥qS²%M ·ÍÝ·øŸ«ÌY$ÅKzþ‰6áƒNŽàFν¡qmþŲÓ:&Խ»tí¨gÌÚÆ¿l>3˜dÁ‚ à¡,¢„²K˜oì`¯Ö±ØÂi „òr6#áþú·eñ?_Û|³×¢@ñ–=o#ã¥áö6p"Ë/—þ€ôØèÈÜܾ¿ä÷õ5gj@#„²M |_7ùÇ› b›»Òà~ç×f®ß»üÓÖu<뱈ü³õÿ ÐóòúçšÛÒèùïáêðù;î¿Ñ=wlBY!„Ò(¾uÚèþgÞ,’bÈlÚ×Óú¾_œg3ÉÉxÞ¿r£ÑGõŸ9µëù¶ñ/+e¯œKŒû…%Œq í¨gÌZÆ·d`2`À2dÁ€dÉ)€tõ2ÖÉã$s£ ÉäÀH‰L £OÞLÐ0`Àô`*uE¾ÙɃâocñs_é?›>‹Ðy>ÈÈ9EgkÂKÑù¦–]NcÞûžsê^lf_ >sjþ@0 ™0`ˆ˜È“&  É䈘È Ÿ2ð>bJTÄ÷»¿2û7[µátûÎTtÔn`‰W;ÈSûŠßc§ºLfŽÆ9 M Qg¶p`À#%“&  @0 ‘˜@0 €`2q~Vÿoñ¿Q˜œ‘LK€dÈ 2 d‡(—ñ1Wú#ÅYnÄ` @0@í(gÎÛÆ¿l œžaêS#V'bb4àÈQ!¯“rbº'bbbyVê{•†a‰0š@4u·3zO È»ºøk[¾©ù¯é[”¬€Yc[ÕòùÙÌ¢ô×9O®ùý?GDcèOžZ½ÄŠÈ¡,€ç¹”~[‡&:öow¾‰ÛúDÓ8É M£•·Ò©yÑæz+wF(K P–@_Õ×½pd“@!Ê&Æ|e÷Þü}÷S@mC>fÖ5[#Ä'Ɉ`õ,žOG˜y=È<Ã2ôA ¥ƒÐ!„ÒA äOΞ¿GQñ_sÖxF,±—¸Ÿ§y>+î39€é¼þß½y  ÂY &ÁÎñº°q©ØYêt[úà -8Câ°¾÷5Ûq íyýͺÀP–@a4€Ôø·¨´óI1d¢ldS}§ÊÙúŠ 87³µ ù›Xó3Û(K 0š@!<€ñÍ~Ú¿Ò?6}.ç‡ÒXùßJ‡èþ?çgàâÖ"N&ÏÑžSu„#„²M 4´îÞáúÉÙn4íΨ£9'r¦ø8¸Xzmö<êÆ "„²C ¤ ï Ö·ùO¢d“@!Ê&Æ|Ê«ôW‰±ÝˆmC>:Î<äÅÆÀŒDJ!&š=#ì™~¥ï€E–68rûŠ”éû•~'÷ß3Mدö_3·¨Ñ #$ˆ”hs;W{[mR±ð™XÖ¬iΪþ¿š³³Td@"PUóÜüûìç×”s@‘„Vt«ùêWzš šžvìþvîÿ"ØŠbXš¾õ-߸yL€kÄìL#;šòV±ç³\åH(K 0š@!<€¿Z|áûd Ø~pú-_¥þlú7Aå;;»ôoYÑêcõ1ÎP¿}_‹Ûî§=_9õtuþ¡ÃëkH ÂY &»“ݹ§×£&¦z"±MÒâO³TÒE d†HüíÛßö²‹$Yc$N—f¤?[ó–}ªÀœ¡ø·¨Ûâ[ªìÕ½ó÷@rˆ®jÔû“°éia4† üg¼£•%¼köÀBY!„ÒA ä2{Žû ?[ü³õ-þm¾©Ìèz\øñÊ8ÊÞåO”y/Q¤ó]^šrÒÛçè¼KELÁ%Âi4ô튗Mbœ–*á9D²Âi"„²C ¤ Z{!ùW¡Å°ý#‡¹é¨ùÆe˜ØÈUùÛš¿†êö>'®C”VýGÏÙ{î@ ]-juº>•;jó6±¯ÛE d†H'Ä?<û yî§Õ¿;ýe¢óìß7½éTÁ%Âi!„ÒE d†H¡$²ç9Ûlüfçç½ —ôÍ÷ÿ>pp~[7k«Û_ÌvÔ3æmc_¶ŠÈ &O ÀÝÇã¾æ¿eñ?cÜ|¯×eŽf:°üîç­Dp–@ ±`Á$O™‰1lâi!„ÒE d~µ†À!„Ò(I,€ .U‰þ#ê·(îUú Z–…ïÓ8`Ô2`d"‡Ê|'œí}?c_^?dµÐæmc_¶ŠÈ &O ÍæøÑ*÷ÿž=ïWá}jô)Xún/¾¥Nÿ&ã£T`ŽÈ½®l4ήq—Fßmé:½µ ù›X×í€"„²C ¤‚È|†ö4:,ýó÷Ó°çÑñm¾™âolë—(†H ÂY &M P–@a4€ŠK )~yغù§~¯»KîVY§©«Šðž_ÇJÿUèúý'C©vÔ3æmc_¶4µÄXE–ü€%M ž@ãæ¶ÜOG?¬þmú˜ŸƒÖ}CÄíçM 0G d—Õ|¼K-Œq! &M P–@a4€ŠK 5jlÕù£ƒÜrm}w:i5±süš\ï”àë'è~÷ÔXîÈÚ†|ͬköÀÑÁ½˜BY!„ÒA äÏ럎ô°¢ôþ‘ùçè눻œžƒè¾>ï³@a4€Á%Kˆ` Âi!„ÒE d†H¡$² &uµñ~/Îy¡Oc³Ôî= ÙÊ2ÛPÏ™µ~Ø(K 0š@!<€>cb>Sê´ú·fü™õ™µì‡v®¯íŸ3±±¬ &#„²M 0š@q ²x#…†ÀE d†H¡$² &§7ÂæÍ¯_aë{×7wmC>fÖ5û`ßósÙÂf‡'svÖ }—³‘»†Æ3ÓÔËç}=}…øÛø`ŠË èêåêÕ¼~•ÊÙ󾞿¨rvð÷ð¿¯<¥ÌkvÀ}™ŸÑÇ[(ê©å«š‚Î=­ ítÏÍzºúj™vTsù?gPÁ`ÈA äÅæù?¥Õ?šì}?à_B·¯Ìî>½óû ¸ &#„²M 0š@0É™ŠÈ &BIdM 4ðjáÛ¤éù¹Ô]Æ¿l>ÍÏ’¹Žæy^w0˜Ê­øÜèË—·Æ‰éêeÊ\ÆÓTñw°íhçSœiî‹}¿®y{˜ú‡3oÆŽuÛc«§•fسÕ<'C"þFælÐÙÙG'wÚ†râãïcÏÙǶ¡Æ‰ùçOÊŽVZ§‹½EVlµO Ðÿçg«›‹½…Fø'Æö´¶îê~#ô.MáJeöOmlÄ œ'ÞrÀ2iì‚8K !„ÒC ¤ŠÈ4uƼM¶ÐC ¤P’Y†HaÙóó¢¹~Ø}›œ‘:™Ç;g¦¦_>éaôþNÈ2sñÞֺїÎzzþ‘ËÙY¶9ëXÒmŽêŽ{8/ëåÃt0õfÞ6Ú'·¡Ÿ1oúÙ ø×ìkç l¬ôÏ%s–¬èìVmœZ¹¹û8öÔ3ÒÎ-5L2¸Ó<­¼o«åi¦x‹ø]hžvÖ7•òâïàO ™k|·×kúĽÏQâ=7©ô_cùÕÕ` ­s&§„rÄ´¶G‰Á%Âi!„ÒE d†H¡$² &z¢Ól€¶¡Ÿ3ký°ï9Ùò7qÑÙ‡Ñù›#•^Ø²ÔæíFî ÝsâV§‘»‡eG:±16+q7ðÉÎZÇ0ûoÉûûšÓîÆ’Äw|ìüËŠ¿‡aG?u5÷ÜÜù‹xöts÷Bî46qìèçE¿ÿŸÏzXvô3ç¬ãy_-ýn*þ=Ï?=‘Å_«t‚ÈçÙ>u¾4wîè<‡z뉻èþÏÌÙïÀÌr.óžW¹kÕ£ÚúÎ Á%Âi!„ÒE d †H¡$² &kbÙȶ¡Ÿ3ký°Áƒ& €`0DL`2`È2`É‚O!ƒ& ˜2`ºç£«<}ìrByy‡ÎëÏÏú˜÷²ùo·¯õ?“ú¤ú.<“¡,€VV×.yonÈ ÂY &ÁM P–@ª¯†ÎÜ÷3a4€ŠK Âi§È¶¡Ÿ3ký°ØÂv°$„Y6pdØÆb–žqgªF%êÛaŠ·|lá;°òfú'_8ƒ$×É6/ñ)!µ„Ã1­›£|!<€çrpÝ<8ì'ªùÿ¦¹æm¼ô¼k^•K‹Z„0š@4ðQÑÑÔtw‚8K !„ÒúÖÂi"„²C ¤P’Y†HN˜¶Ý ÛPÏ™µ~ØZi¬'[[%®©¢±šgÔ*·FŽÈè+e¡¶&Åä×Í Ùâ¹Ñ—‰B·tmá;Úæ¿dlb‡'¸IP¬Ý§%^ØÙÆb6ñdæmâ „ò9éYC~@a4€Á+õ§É½˜M 0š@¡,€Âi$–@!„ÒvÔ3æmc_¶f—¸x—¸y1 (K 0š@!<€Âi‚8K.~¼t$M 0š@¡,€Âi$–@!„ÒvÔ3æmc_¶ŠÈ &O 0š@`Ž˜!…¶Ð0š@a4€BY!„Ò(I,€C ¤í¨gÌÚÆ¿l %–@!„ÒA ä†HUje–Ùb8Îþp‚8K !„ÒC ¤†H &BIdM mC>.Î5#z^€‰@D pf¦:±èmìßÛ2#%ˆ”DJ"%€F{2(0kÝ ù XÔlZÌI ¡,€Âi „òÕÁ£« =8ès©ÔêKÒ½Ü_Ïs00G d0š@a4€BY!„Ò(I,€C ¤0hc=õ¨mã_¶ŠÈ &O Zšñá¸õ58ÜÞ‡¹ÑónÍõ­›(ßÝá,€†H &âDìåP–@a4€S鋳¡„Ûl€Á£®7öHBIdM mC>fÖ5ÛcÈ  H¡,† € 20dÂi`ÈÁ€d@ „ÒÀ2€dó_›Z-Û¹ï3Ÿ¹¹³>“·ÑÞßœ²È  @0E ¤ƒ @0š@ 20d`ŠH  O%U]z±<דàjÑ«:½Þ·ÐúŽåçFÖÎs<Æ  @0 A-Lº±Ñ§^»›Ofå:5I³8°ÃGN2e1Ó­]ΧçóŽ;vìmYÝ»~ÖõûUœÚ”~{‘.½[ýά™åqØèx™kÃV¦{2^:tëÙu/mܱÍùŽ'x"0K»nÕÒíÏví™·lõ³-Ë[÷ó{”0šX20d«Öž[y3,€ íhgÍÚÆ·l!¡4Ç„úF”HFyLˆÁO‚iŒ'É*=’É2%ˆ–L@ ‰bfPÂP É fH`2Cׄà̆d0 3- Q_[Wã|í§w«Ñvú7[ò׆e0‚%“,ˆy#%Ɉ™`@ ÈbdK„˜ d13!ˆ™ @DK,¿\oì‘™`©Ñg2žºõ´Öóª{Ûœ”) €:»wmoÛ&̤ٜö6¡&Üî;‡¥h%“Ì9î}}u:Îe-e)p×$F ‘&T`À2€dÉ}  € À2"œ·;-þ•Û.•ÌãÿBƶê[9Î d+4Å–Ù’ÌíhgÍÛÆ¿lá“Áèðz=‘Ì0`ð`öy%M ž@†pè­gÃótÔy;uÞêg<ê¼ßÕ}vlºõ}û>¥ë›M 0G dâ#&VëmdØÉ‚8ŸHÔÅ1¿²e–’8Wjƺ¦5õWP«SçxûÝ®ÏbþÕ«ÇäÃÃäi‘»ßìv¾«µ>sQ[+êÓ«¢£‘Ï×âòÀe@žÌ€F™­»vb'±º{;½ìÏr厗¿Ô±ßž;jó6±¯ÛE d†H0kâÙÈBy¢¾ÿoé~¯¯4€Á%P¤¡¢‹ÍÐó¼\LùݶM@B)™P2 d@$Ê€#=™2 ¿»4€Á%äùç;Åç[s¹Óã€#LˆF™2 e@žÌ€F™<Ù±kÛéîݱavÍÕíà;jó6±¯ÛE d Å]§tj‡F­ZÚQüª:œêyˆØ»kGV6óÆ1¼=g”ZuùÆ1FÆ­~£ –1Y±ŒqóŒbÆìaŽÝ»nÙµk)Ë«cCŸ.8€†fh€N^2ËNqxãî1ô`Éá>,ØÚé]èýZîîÿRš@`ŽË– š`Ѧ8TqùÕÜÊ^µá.8d2 d@4È€I•É€dF{2d@"³cצô¯ZÍÆü€;jó6±¯Û+µ£ÃÞÝ”²ä¹úo­íçt(¼ßg‡ÆL€$Ê€33Döm¸ôk…¼Yßœ6öå6ܽlÙægk'˜:ðñ ½û7³!„Òâ¶•}*ºj¸Üíu94é’ ¦D#LˆF™‰2 Óú}‹]»é4V—,­vîÐתãuŠ=°|ÙÇF{2d@!±c½÷Þ¦Ïv@¶¡Ÿ3ký°Žàó¹¾7V¶ý:1”ú·guI[FQ$Ê€33D Zµ½zÇ›×5sÛ¥N¿UwdZuÉ¿oOÞélç5š1Ч_~í›]Ò†H *)i¯çÔÓ£W«V{EÒ¨¯…=MSg”Èèúø_/È®äóüR«$cd@¦D#LˆF™‰2 Ø»í¬¶pô¹½…»øG=^­æû4š+tvmücÀÈg³ ¦D mÛm}¤÷n×uq™µ ù›X×í€"‡%ÏÓÙtw ššµ4jÓ§£Z®MÚõéÓ¯î•@e@™¢nç[ÆYE·tö6ýÐô0à¼ç3v0è»ÝKMùdÀ4°CŒY푃—ãQ漯 JÕÀñ3¡ºÇ[ë»ý^ß“Ä5±of§Ó4j‹V¾Z¿ËðfŒ@2 d@4È€I•-vîØË=,5Ï9fRÎQÆ2NQF5t`3ÙÓ"iÝ»Öúîý•­»͆ÉÛPÏ™µ~Ø 9n~ZÚvôè—n~¬nØÏ,å•-|^lœŽX"L¨C34@!Ù³k¡rán]¹úÏ)vg³k~Í­õxåâ¾ÆŸ.‡Y躶[²Úqç8´D™å.Ý’lËÞÌ÷.X¸µ³S\Qr©A_V½}5<ŠVÐÛ³39ÇZ4È€#LˆF™2 e@žÌ€F™³÷Öév³»{b@í¨gÌÚÆ¿lžçè¦åQ©óœ_Zð‘“*ÌÍ!ÍžìïÚµ»fÎémX¹ë^†¾ª¾e-ZuáÕ®=xů8b†‡¼p›^°F™i‘Ó"¦D"L¨3ÙÓ"á;Ž•ŸBÝNæõË{dí¨gÁ´VmM·?0ÄD™e³œÏ”{0r\J÷›ãk×Õ³«W´f œH&T!™š e@4È€#LˆF™2 e@žÌ€F™òGnÖÍ»v·Ïcve6ý»66ý _1òÞf†µ\™Šrñžz·n[N3oÙ6í²íÏÞyzÊ}e8†"1 ·gåO̧UË¥„Œ€ý:'ÇI•ffˆD™P2 Ó"¦D#LˆD™Pg³ ¦D>¯Õíð´ùÔUª€2d@$Ê€33D"L¨F™i‘Ó"¦D"L¨3ÙÓ"WêöøZ|ê*Õ@#IJMry’  œBD&D‘À¯3’hŒ€§Ì¦ˆ4ùš C3ƒÜ$@×™É,G 3 À”Ѥx–I¢>¯Õíð´ùÔUª€DS$lÄDœ¢)Ë(؈‚fÏ2´åäž"D iËÄcè1Kf Ó Ï¤lD yŸ š"T YËñŒ€a1Ë ÙŒ@#IÎ^‘±õ~¯ožÕZŸEp#O´d$ÈŒ€B™2 "2D$i‘Ó"B9™g³ §Ú2ô®[ÿÚ³ja—Ìy“¯2uæN¼É×™:ó'^dëÌy“¯2uæN¼É×™:ó'^dëÌy“¯2tw'ÁïÌ+ä&Çä%ÇÌy“¯2uæN¼É×™:ó'^dëÌy“¯2uæN¼É×™:ó'^dëÌÉן6?!*Âb^dëÌy“¯2uæN¼É×™:ó'^dëÌy“¯2uæN¼É×™:ó'^dëÍŸ7ÈʆüļÉ×™:ó'^dëÌy“¯2uæN¼É×™:ó'^dëÌy“¯2uæN¼É×™:ó¦eò!½1/2uæN¼É×™:ó'^dëÌy“¯2uæN¼É×™:ó'^dëÌy“¯2uæNšôìß#*ófuæN¼É×™:ó'^dëÌy“¯2uæN¼É×™:ó'^dëÌy“¯2uæNŠäø>á+/2uæN¼É×™:ó'^dëÌy“¯2uæN¼É×™:ó'^dëÌy“¯2uæN¼ÉÓÜŸ™á1/2uæN¼É×™:ó'^dëÌy“¯2uæN¼É×™:ó'^dëÌy“¯2uæN¼é™|„¨oLKÌy“¯2uæN¼É×™:ó'^dëÌy“¯2uæN¼É×™:ó'^dëÌy“¦½37ÈJšìÄÞdëÌy“¯2uæN¼É×™:ó'^dëÌy“¯2uæN¼É×™:ó'^dè®Oƒß˜Qn ù W™:ó'^dëÌy“¯2uæN¼É×™:ó'^dëÌy“¯2uæN¼É×™:+“¯>l[p˜îó'^dëÌy“¯2uæN¼É×™:ó'^dëÌy“¯2uæN¼É×™:ó'[TÇ+ÛþîÌÿȣ̴߲%‡Ý‘ÝÄÇýô‹ãÙ8çm'd—³Êä:n([+v'ìñ¿hÿ–›»ˆ?fãi!;<¹…¢q@9[±?ć3‘;Æý‘,>í$ ý–Ì­ÿwfh]¯ûöƒÚ‡ãÙŸ·hÿ—hÞý {vgøö…ÚìÊß÷vgøö…Úÿ¿+» ;àÍïmÌ3–äN›q‘O¸›€Ý°*Å›R§yɳ¬‚ëÌž1$"²³ð’•5h™iÓóeM#Ö‹å2¯˜"^|îŠå‚A¹3î2²šÑ‘‰É†Ý€Gp¥i>.,éâåÏë™Ð‘eÏ*-Ç+ÂC(å'zTštW«ƒµ¨IK¸dÛ˜¯“eòq¨,Çe¹ÏÛœb#Z´ÇÇ‘<ËÇ‘D Êÿ—c™”—`…þb'U¥×i§ íò°(eiñnÌÿл]›Þß÷vgøö…Ö+1œl9¼­2  Þ åâ³Y¯aÊä´ÅÞ)f‘.²§Ä¹NBÑ›lÄ“˜ )¦™„]—«¡NØ¡ŒG³glS3 9¥C\Žs‰yDHf%)äfÛMÐmðÃûsÇ1FÞQ¯(±òK¾øž÷<¿´C ¶õ^ÛYÝ+Õ÷Ú€¢‘¦ù+v‚”`LcÌþŒöí²=ÒL`+™Ž` Á"‡q Z‚Ì…d|Töä/% ¯änM =9ˆ¼+ªÇ,mбÊáìR€)ì“I.d„6ås B^±þ=¡v»2·ýÝ™þ=¡rœÑÆžývPÙŽ~K´‹äãÀw8‘µ}ÀE‚œ1Û†Uf+½˜æ¨XÌåwÇïV–Ã@c#¼Î(ä‚dÚ>Rf„RøéìŒÄôkFñ:‹äk¯“R[ŽDóºÃˆö£í˧ä*vœ°Ý+Ó?ng|ô–§msPn2ÖŠ®ñfªœõäñw›n4áœÏõë žÀâjqÏ%Û¾ÓÜZéôÈøMZ; *À\0dBÆžœBØ?JÌ%8ÅZÈ@=§¡Åñ»ØÛ˜dûÔ„ W*’ÔQ)î”­ç–X¯ tñí µÙ½íÿwfi#°¢Ü ù(R Â>xÉñ•³>Ým²6>Þz²¶Û]Z¢ÑŒT¬‘Ý+HìÄǶ*ÒŒžm‡G¶ÄJÕO`¥^tL!\˜Z›©åŽc‘†K ùp¨ÀìídLÞËpŽ(eýJwæF†q.QíGÛ’F'f¹;!¿Šfd韷?²š¼vu ÔH왌³É2¥µKl«SЍM³Ea÷}À¨ ]Ëq¡y0lSþ]£{ösØ ÂÜe” u—|ŒŽ*Ñ;åuöF¬JÓ'b—œÿл]™[þîÌÿÊåÉ!?&ÜëÅšÁKZG§^ÓÀ# 3¶æ*9FaUïc!ØŠ5ŒvÀÛÕmÃF3±9¦µ2ó,DÓCæ›aD¡£aœç· ë¤Õj3e¶eáXe$ozÑe(r)Åëª0M,Õ!ìm²³43ºzóŠÑŸÂz–Hdšúȶ˜Yß*Û|Bu¡}¾#7Úâ`nŠ9n³r·&O»‘ÀÒÅ#tÏÛ¢BÆÖ¶Jö=„ ..îª×´Òp˳{p3yð3ùæ¼òE¹’{ÓJ¼r5B¹Cѱ~¥é …ÄâP“3kF¤»/edÆÑI`5¬ß°ä'•£Âú ¢ Y]}éìYg¯uæ—‰þ="&ŠQœNø ŽáƒÈ„FükÍ®‚P“„Ѽ Î3í6eoû»3ü{±Käk²ùë3*Ñj¶Ò¾6Ur mwQ¡¡2mºlm˜UJMYOFI¬MRÉöêîÁµIm°É°1 |PFòC«…YLëh\Åxç!G „¿ËU‰Æ®V%&Þq¡<Üåzbgܬ:ÂppÜ' ³…Ÿpן:+ó“Ï;Ø~#íÈÎ"ï+ÚìC„QI½3öêM+@¬…¸ùŸòìm¼Â bk#¹(â’i[oÔ`d1t¬Oãƒz«Õ¼ÈFHÑΙ_ÈrÏ,Ò“¨ LÌ܇HÑÅ‘ùŠaò„ž¼o€L:?Ç¥dHÀEƒÃ–£a5i±x,2jÖ AE£+T^ÁK"‰®*¡e‹³Ù•¿îìÏñì=•âŠCá—+˜ƒ!°Àõ· AëXkæ@ÎÏ›…銥Ü,8G¹Y7‹>I¬Giã!;Jržg§en»'Ln-y,'&á)¢³: 3B_#3¯.Ê+òäÓ/”‘RÜÉtű´»dyBL{!öM!9GJpEYDå`¤g,b,Söíòë‘04¯4ä 1T–g†€[ß”‰…Ÿsˆ¥òàA ÊÒî°BïnäjÚ`±z*Ïnb‚?"Âz,J JÂãÿH=A·Iìb³Ø%¦d‚f*€kXê?‘a ³ùV_f:ºóë©78“I:ù…5Ù•[£k’HÆQ¯H*’•ÌAõBAò0©$çÈ]®Ì¯[„,5Ø ú¾É‰‹ÿÂì6'sŒëðwaUé4á3,Œ°dä/¹9–îMêÏ^f°Ö ¶•$r@³â[~v—r`TdßŇvb’e¸ÎpMp6'f#çIƒG‚ŽÔ‘c²¼èºÁÔ~ï'ÜÎÅØ°[gOn¹3Y–Ql¬©è´Ýcöíòë]šX—‘ä³>*”Ã÷ä’ÌP©æò‰Ù°¨ÿøZ[2Ì!¹xbç‘óJdRý¬Ïe£~@öìËÛšñÁKYÝTŒrü†NØ ìƒ"µ‹n¯"<–7hÃg‚7Z¢‰4–L³Ì¥·< ‘L™ñìöesnmŽb@:cÐ"`o%‰jN²Ø4ÕÑÒ6ªâ¥†Ã¬ˆä—.¤«ÈÁy‘/2æB›×ž[1Bƒr‚CžøFõîüã´ ¶À‚œ1¢V`k!(=RÎëŸäÜ‘K%wkÓ³¶âÕ¤9#Â5¸Ç©Mè¶ãÍ µU¬»Á8­)‰Þ\íª ¥d$Ƥ=1!0e†(‰@a³_3(÷‹œxÝÎÎ7'‘™1·_×/Ø›C !‰æÈª™Ø>v¯X*·!ûu,NcùÊ™Ÿ|¬Â±›¯ÿHH·3™ZÝç¶£½bìè]ñ´‚ýWlWÆ€›P”©Lí^ Q?Ÿ¯³„ÑÉÉ`<Xš´^LMˆ“€’*ÑŠ!…“ㆠü¡íÙŸ·1  “F˜}VAgZc9'ˆ Åž%­a>rZL²`†Ì•Ô&`óù1^|,MrZѺÌÏÇfVï“YŠÜS??äµ%$‘@ÿ>ÉýUÚñŬ̂ìñ¦ÜÓnP¢Ü¢RëÛ²Œ,Í‚vÌŠYŒc3ÍÊU‘™yŠóŠšÄvU%)£|S» Õ!i‰ÉE!H°5Ému^EæÎQX²S¦™‰¨T‚xÈ$glEÚin !^ðÌêȱϗäXSï€íЂƒjÒ–ÅS¦‰Þ7bã+f’FTü¬ÜƒÉjžcŽ„ì½c볘Š΢£,­Ý¦¹ûte”` »ÄVå¸Ö Á²÷Rí·¬¢€Åi­X§&e0±¨ö‘gŠœQ4”ã‘õì&·8¡ÜŒÀË Ç~ Y®@N¦ VM›V"wáðEv>º³g]ZžA¦&Ú¡Ïk³ «!Oµ[†ÞX4Ã5eÿÖH',ÝöìÏÛ´.wf%!=FÊä´Eeu÷ g…êèÖ&&3ä B5¢ ˜¥úäÓHV¿ô±¡æM b£š9yÌýº!šq*µjHÛŽßUmûa^dt¨×3RVŠdíø±(LˆÐÈ'Èðƒ¡)cU­<„ŒZ1+J Õ£{“JÇšdÀ#Á«€³6[ß‘Û1p#aPE Éj`œ”ñHçËÐnÌý»Bí[ü®–°8ˆGq‘£C Âõ®É`ŽÀç˜ÓÃ3³¹B׎HÛ…FÆXãf–&Å¦í·†ƒÿÐÌéÇqÿÍÖ.Œa“  ÁÆIš$6ã"gÌ«×{O0ŒµÎ¢‡nfLØrÍjÁ€bÁöÆ$*6ÎrmÄ,5¬»ä^Æôj•XÇ“rãwÅFÐãÎF µÐf• ;«Ö ÍÀ?ck<î/!&¾-$Ò!±1Œ¬²X%šÐ?nƒ§ÒRý!°°·¹½Ù§bbâ¤jZ±Ì˜dR”^µZS©ãQÊÑ!)j¹L@&´#è·¿)6-¡;*Ï-gqÕ~˜{vgíÚkúßå%mGS¦«YÔõXóƨK#)‰Kr<2@ó5ÑR[“&vNLËA"òÈÓÞ)‰fdî̱Lø¦&u™“»2Å3☙ÖfNLËÏŠbgY™93,S¾)‰fdä̱X☙ÖfNLË8¦&u™““2ÇŽ)‰fe!Í‹†hdšBýl^ß÷rÉ›(ß(ßÎøçšE$3ÈÜçøòc‚¼o¡Ý9ºÏê_–|©ÉÙÞGdåö;>gűdÏê8â‹díŠz‘‚«É(Q„TTá…ìÓ+/% @ƒln´U:²Wu¸ÁnËÕ†Ü$‚Q•®QiGÕ¤ã ê&â<Û€Í&8!|åRƒÆ\áøò5@e @¿ú”††¬Bý#öä±v ¯szÁM¸·šp6ÚvÊÓW†…zåÂIØ,ƈ«ÒylÉ93eT£ L¨Eú‘CÑê(ä!~ئ 3]½ûAöìËÛ´.ÍÉ…ço×f× Ýбo!B !ÑžÌ1ˆrn?ù– †+7 aÃe† ÙdìF«ÑŽÃþü„,l›4—÷&'…›*kvGš'qqNÐ\‚3bÛЕŠÑV†šø1 MÓ~.Ù›oÂSèãÖµKÐzÄý¸ß“pÕ±—?tû†/‚°¶[ap¹.œQIJܣûxâèÜ²Ææn0?fÞým`ÇáÀÃñžŒñmñ“÷„Ž4ƒ+íàÃz³T“®~Ý¡r»úèXtñÎϹ¶gtPÊ:±CÊR!†iSP7AJ O·À¿^ƒÇkÝÉbI¢6|[„·¼PCk†SÏË=(NÜžzÂÌVQËSC=’¶âþlJÌñYŽ@’%Ã/T»,­Z³VnïÏf“Ì£,ãÆM(ÖÖï!Í$Á*½0Â1J3çqbSmÌd;`­2„õ3*Ðøñôñë³3q·? Häøî;'™-ší\Ñoó“¾û97ëìÒËÂhµYÎH™¤‰nÏ„Œ+P] Sð,a< Œ›´o~Ãäãs•Ä~J5%Ö9¡¿¼WÂ2{àãòå½e­\ý»Bäˆ%°Õê B€ÂCàfØ´ÂHNB?u$2 „ìµz­_—fV͵óŠÎ+8¬â³ŠÎ+8¬âÁÖqLàÈ-ˆÎ×™>ã?#žqE( Kn0O VÍáœS82Î+;-FG§"Ž8bWhŒ®S)š—"i–fY™fe™–fY™fe™““,̳2ÌË3,̳2ÌË3,̳2ÌË3,̳2ÌË3#|[åâuˆæâ³¶|ⳊÎ+8¬â³ŠÎ*íis²ÌË3,̳²ŠÁ@õïG8–ë\T¶žË…¯ £˜$â„ÅgœVqYÅgœVqYÅg¸Î+8¬â³ŠÎ+8¬â³ŠÎ( rçœVqYÅgœVqYÅgœVqYÅgœVqMn3Ü`ŒåÝ}]ÀÄÌpÎ+8¬â·úç2ÌË3(vYå Ëjá‹,aœVqR\Œ)NðZ†fÎ*å« ÎÐR¬oàTj¬0 ›cœVqYÅgœVqYÅgœVqYÅgœVqYÅgœVqYÅgÄØçœVqYÅgœVqYÅgœVqYÅgœVqYÅgœVqYÅ 6ÅgœVqYÅgœVqYÅgœVqYÅgœVqYÅgœQa–qYÅgœVqYÅgœVqYÅgœVqO ²‰žÊð׆ËÃ^#¦§±P!ŠÃ-9•8ŠÎ+8©ï€;¾w)DU¬gœVqYÅgœVqYÅgœVÊLïzL“êb›×–½Á²üÇb(ÕatÖv–"Ï0Å+GÄÇøô s‰xõ ã¯n?XŸß ]5–™:ÊíÐ¥da“äk¨ìE)sËZ)ÕˆRå»3Æ.òadCƒÓº-2Ù îº8ϤËÛ–Ía²,Ù[ƒ¾ ÈsGa¤–7&¨¤—AY«ä»6ñè /Œÿæ|ìLÞ NïÌ] µäe`yp ãÿÏχ#†Y+!ÜÅCf)ù,…ózã$qÝœ'.Œ‘´­BmHǵÇ­/ã=8áŽø=Û ©]Á=™¼™’”¼b”D-Ü®¸Þ ­gÅ­¼¶¥‘¡YɪPjÇ‚®ÌRotZį´F›dim—i ƒxƽ °§ÔbÍÚ7¿hÝ™ûtȘZÇaF ó%‰4bØ9˜ˆItt™lƒÛþîÜŒQÜ)X¦ˆ|V7*@CmrŸãÓ}·-™vجs6nbèG)ÂþD¬šÌ¬ï4…Òÿn i‡LäÖW-KHG?SÕœ%8Š-Æi%ìÃñê]¶4c›~„†•š…ªôÊ9ùˆÄëÊMŠ ¯Áé“&¨N†¡K’ÜbÏßw1x¬Hê@U¬ÍV å¹¯>tû‰9kÈòK=[ àH,ˆ£õwgÍež{6÷ÌÈìE½èïí+¼ÓÉaØÈT;”Y5C5È PM®=öìÏÛ¤lN8H*ŒÓÊEz@c#O3W óyĹ-G«vNø!i%PÔ‘¤àÐI ¨ŽÈKϳ+ÝÊri÷(q­¢ÑQŠ4ÂÃÒ?ǧp'•¶à(}¬YC›/)v¿íÉP²X’ÛÅ<àÒGæ™ââ0‹¦‚1hdš»A.´}ˆ~=*ÆÑ–¬xïq…ú„ÌζCxíîÉ9%Ÿ…ŸëmÁðkÏ8ùn ÷k&;é·¯.^5¿µ…‡„ÊÉ|t¹§ªuømðC3¡ôw°^DxÞ²0¡ ²o}ÈC[œ~Ç’ôÒŠ¯¸I] œl8{veìS¹>i]c*Ö‘”r´ !Åxïhåyv÷Ó.%ÊÍŒ²´•lH= V¨V²‡ŽÌ¬Œ¾ajê1îõ‚Ö¥ššÅáË*zñ“õOñë]¥E8·!v¿íÈíŠ ÀÅZ·„‘¾~¨­QZÌåD£x{üz›ÅIíGÑpÕ×U-®%j+žà²d^Îq4BõêŹ؊C}+¹n|ªÃäMįÿ>ÊÜ ZU^Vš5Ï ù2ÆÍ²´³0ï>ú; ?Ù%gØ«&Þé“ÝÞ'ÊÔÆûm“i1Ç¥bÐWÝÃÝ¢Â9Â@Y™Ó;´Rƒõ+X*ǯO~`öä"`héÍ ½k³åê³á1q/ù¿D›Q/T©>Yx—-¯ý}tcŽN4ñGúåÖ²öÿ»³?ǯj žYcO„eÀ»_öæÓf.€ Êþ…ðH7H £enK÷^ªµwV8»3XAÅG!BþdÏ$Ö$°ýLÎìI9G¥¸âˆ˜YŸ “·Ç:}¸TryY‘H2(säV!×ëØ5ìz–;¸?Þ¥ àڬ͋+24­Ë]ðèÓþûíÿC'3ãĸ»à¯cjö÷%í×Ù½íÿwf\ÀM¤ÚÜX *ätŽ)>:t[tëã§_:øé×ÇN¾:uñÓ¯Ž|tëã§_:øé×ÇN¾:uñÓ¯Ž|tëâå$ã$hãáþÁŒ¦Ûca4[¤ò¹ ôQØ–ùbV+ ¦ °ât*Ü­<ÉÅɶ¸þîÀ?ÂÍÆ®ï¹6Vb· 8¸³©«j©€c.ÊP6¤qmÂÆL–N¶Ù¬Rlµ0Ÿm«n®-/lW²ÿû÷,B;üÂö&ò$ã·ÄÒËÉo nj;c\ˆv0!’'=‡ƒØ³9*ã4pÅR¬Ã·L…Ÿn>Üé¶èTP„ ¯O­&ˆ:/Ç”‰i¨æÅÑ NM»Ôn¾W§¬‚h8—’It+•h» ™[þîÌÿÄa’ZùdEÙ¿ª–­H™èæcXFFk$Ó?FRÊ5ê99‰ÆŒF‰š)F`3<Ùa"€-T{Kãde$ °Èϧ%cÛÛ ýpü{ ë–—oM²’“ ‹…z²ÛCú”Ž÷ö9(ƒ‚­HqC8  ­¹”³3m1‹Ú1¦ÖßÎ'ÛáttBš‰}–†]¶¤ m¡ÕÇk¯šæÝ\+¡7Š–Q«N89mSJÝ"­Ã!`ìíÆ+“@?'k‘²ê{2Ùë°3³ŠÛqhÖ\­Â®âLmÈ-™üC^!¯“U2~qÚEœvÈY Ì,øóⱕÖ@ n0×{KÙn-¨u-4Ü…Â[ßõ©WÆnÇfVÿ»³?DZ™˜¤à]§ˆBzsÇÄÓC jHÝc*zµò8szªðøñîŸÒ¥“íƒr¦}ÊEp'ž¬…0§¬;FD¦«„̓hšfÅ`œ ºøöVjG:gÌ ÇE¾LS¶-_q–³ûFNv-&¢q©ã0´a$;dw”<ò¬F'û]¢Cp÷U„â}¾»Iz°C¶†³‡“¹›í0è[ÛkÑEï·E·<ËF†!å"ʦ¥-ѯ´Õ¬¥ž8w¹ Ô䥵ÍuIB]jC’~O\±†rGèG(´Á† Òn~…Êr jÕöÀñ•A€¶VçÅý„q=¾J´í›A@b›˜ÆÖ/ˆ…FŸ(B¿e³+ÝÙŸãØÉýÜ µÿuTu³<Ïp}7VÀ¸j‚iÞ´|\KââPÔŠºšV„uÍÓÒŽ¸j Tf$;n*8"ªÕ¬Gc„e¼ŒÍlc´À-m™žà¢´Äžv~ ~=•Âɶ Èÿó$ÕA/Ž~xáXÇɱ´Åbhæ 0hgM\YY¡Y¤ŠG„«\yÝ;àµE Ê­/‘ijlH¿_¬ ñ±˜K gbnJæ#2¿†©-ËÂc+Ƙ]Gr¬mtä–n¥`i%ñ!Â8Æ!äfÌÌä)¦˜T[‘E(Ì=+’Ë®Öì dÅø·<ØäjÑ;GDØZo*fae=xì…Òµ[öÁ÷j¸ÇhõŽƒM+ÆÝ¦Ì­ÿwfcýeà]¯û ¯&Vfš-V±9Nñέ ‚#Fй<œf™Âà4aà튆 ‰»@üz»ÅûßˉïYt½°`ÞnË»Ù6á7.lÊ-¿Pn1Uh;œ…V¼cx,(\™ˆX•4ÊXU89"¿*[¥!—o[)×™âÚªJã²Õbñxç™ÿ.iöö7ŽŒ†åµZ’±Æ8»<þ±‹±2ݶ™-É$G *ßܳ²o ®¿ÿž¯úð‚Üv‰6ááe+ŽÙl“m¶ÉI²YŒ~>Âøë J“DÐ ó·¶üWù*é¯W$÷늊Äsò9‹'&egqDG+ò·kœêJ²D/·ÌÎôfg—þ*ÔM34¬J ¤oÀͬï²j{(¢9ʦÓz)#ÛÛ2.×fVÿ»³?DZ¶ÇèÅ 0ñÄaeã1Â&Í]žf¨ÙåJãÕÝo<€M ©v‡³löÚ‘ Qžµšp ™ü\ëÚhäK܈•û¸D`Q‹›íð=9x¯—͆U=Жò¢±‹Œx0ûsá빡C¸ÎÊ[3L´h‡9{,qUmxÊÅíxÛt"9eŸ©' V–Í­™„”Ó”T¶Èh—íveoû»3ü{})xF)˜î &°´Ã‹Û^@;„âËÈÂG¸9|‘^TjSÔ/÷èÉdc3È’=¾¼OÐÐLMƒñêE\ &¥¯wNœ<*ЪQî´a«\c)‹mÛÚ„v®ÃQ›tn”"OþžWŽu$GY'mU{eŠWŽC6O•6Ó.7á!Í:Ó'ADÞÈ@hšqqᤠH…w*5«GºmU´Õ+ÐCÙkV6–&[¤­ªÕD¸5b%âšj¤¦¢ó3Ä|ðQS“BC ™¡ƒIÞc‘ÕÆÍÑnÎBÊ5öüêæŽm´¢Å€JÄ1A°³Ø‰ç>i‹$~ë`¹©!v»2·ýÝ™þ=ˆÞXᕦ]¯ûô-Üû޾môG³vÅW®BiŠ[q)Â<Î=&2ƒŽMîWz|1áE§Õ²3˜ONMCpµ]ì5jZ$ˆÄH24÷+°Ó€æu,!8„šO&ó¬¾r[!nL*WÉ?º§0Àm¹B¬Kªü´²µ“ÛdGeŠºÝ«Lw`€+F§¯®«?o3¯,“[uå´»l€îY_ÿíÖ†Jµ Â}¶g­-vé·eéš´0ǶÜFñÄ5Òm1õk"$Ò,Óp¿pk×fÅlt »rk³+ÝÙŸãÙp7¬J]¯ûô%}9èA¦Ý!íCñSÀÖ€G +ìû¾½Èœ]bÊ…Ü ¡3ÛÌ…y1aå²’ØåˆìéÅl&ybqRWÒUaŠÄ‚, ÂÎ×!˜d{ô2AJk­†Œo ³¿¾ âØSÈ,µ<‚Ë7«Ã3#ܲ¼[ ƒ‰1²–6”2Øã3ÑÆ«™ÊMJG^3ZS LÄÎÅÂy´v?bpÜrˆo5)µ­ÒÅÑývm4[‹)mX1ûsaÕgÒ9mK3ü”⣠LƒI$ÔeÈX%\æiœ¸î¼8%Ýã:ÓÌVoc{* Ô!¤ÎãK2vÁ˵ٕ¿îìÏñìã-.×ýúmE€ô‡µǯ¼TŠqÚëV„'qù 9¼\èjž1rRÿrœ&aaVõ²D[’Í­)­Ô#Œê”×,0*44Å3˜Ñ˹ë(´!;ZÔ†•q§Cû$’‹ðÛ÷©*€»»p’åAqòàÄãj½IïîaD7 âKãɱCZsš3ckõJ™޲=ÍU¹¯Î>ÜÕÈAêû”y!éà5èkŒqŒ,VÇ´¥¶ ñØîÆîœ˜ Òuÿè*­ÒãÛ›‡ëÙµÔ¤¶.Ãv6"õríveoû»3ü{3”[sqAf3~Ïýùå´1õd¶¡„`ö¡øõˆš69<ƒqbZ ©ÆÎ~ÜŽø(™æ-7È{eƒ”ÅÀ”V#Ê`ËÉ%x¥EµV%C¨É†O1•ˆŠuØ`w*I§VBZ±’t–úÒÈ\6JQÛ—£<íX&•ç“m“oŽ(®UÍÚÕ)ßö;.ÓÏ%“öTj¸s·7™3 \‘ÌrtÍU¹®üOW]à9WŠ7Ü÷ÛÆÕÛKÜt™ã¯^K$?®>-¶×©%JQQRíveoû»3ü{K“©kÚò¼gùTSÍØ¿3ÎS ¯tNA‘@bíºŠ5$oöaøóÊzAVÄÖO’Í­5¢R­Œœ3i»òÄï\Âw®^:Ñ–ìæã¹‚Šxæâï‚-Î4Ûœ|¯ù(ÅyRbûï—<¶b€-oÖZ3Þf9m]+Kf8@îø¾4Ss§¼W·ËL–\¸Jî"¶Í?$Ý™{ÕXªVé—k³+ÝÙŸãÚÇ:ÖÆ±ƒOÈCŠ­;X ®GÃd,r¸ÁüØSÊÄÓÅ.6WÿBòJ7òÙ¸CöX{Püzœ‚\e1…œAÉ…„žE Ffad~Ê„Ã@3Ø£~HÚ^¡=ìòBcy$’~/èÕÛÓ¶+LLêì:ñmðví̆Y>ŽÙñ±^J†¡€ç}š[1¼‘s·å¾\–YqM꣧<ì̓q»liEvÙÝ—bdÑ—•¹GÛŽfÇ®^ÇR˜BNKvZ¤SÔšësCÆü¥ÚìÊÙÞ{"Öå̳,Ë2̳,Ë2̳,Ë2̳,Ë2ÌŒ¾ÜË2̳,Ë2̳,Ë2̳,Ë2̳,Ë2̳"%,/$Ù•« ÇNJà3Â2:̳,Ë2övôDî ²Ó†e%‰;Ë: #lÊHÚGŽ´qže™fY•gÈVK"̳,Ë2̳,Ë2̳,Ë2̳!%™fY–e™fY–e™fY–e™fY–e™fY–döæY–e™fY–dwŠ þHYw¢Š˜ÈD{„ ¤”ì;“»ÅR(Öe™fY‘—¦ej:γ,Ët»2Ì ˜Ú„Φ®pe™fY–eT¾ÌË2rõ̧ÍùŨU&gÊÙ–e™fV ŽÐžÁT“m8Òq‰Äê¶£, >"³,Ë2bõ˜_4×&”ë/UðêÅ+»”×ߊÅb©Ùjǰ°Æ ;|+ÄV™ª˜ÈƵ‡ÓÁo"¹‘{3½#ˆ¬U–¾&³,Ë2̳,Ë2̳,Ë2̳,È‹Ó2Ú›™fY–e{vŽŠ¯e¬G™fY–e™fY–e™fY–dD³,Ë2̳,Ë2̳,Ë2̳,Ë2̳,Ë2̶WÅïc­É¯dh\\X‘ÀUÈàùa¼8=np- -˜ù?ß‘ÏÅ9¤§ÿ‹îµÙ£en˜ö¡øó…Xã'© Ž˜áºJí #© 몗áºÜ‡íͺcÆ ž¹¶êxÏt§Zß‚"Z2c”QHÎÍTºÂÒ¦3Œšhò•Ý6;Am¦½*­Ð´é¿$dðL„Ü’kÈ7mÏq’ì’Ês¿7²‹sž&‹ö)ŒŠÌwÔƒ¶ÞpÅ0ï4°·gorØ+F6oOmQÛ'ºÛlŘZ¼½Rö‚²lÌ-ÆiFÛj<¥+I1e¿È]£™ÙØî‹ÎülFrŠŠIÓPÿйn×¥ñ䳉C‹0g‰Pr8ù?ßÁ¤%ÄjúÄG­zûy{ªÄÂz°§’,%õ>aíCñçÃç!Y2+–Îù)T·d˜Dœ—ì*)Bqà~ÜÛ”d2p«—WÈ«”l×’Ä%%oÁT˜"G}°áŸDÈXš¹»toŽ"ËqÍ·W6¾âj´|¶MlqšÔr¡*p&»óÐH+}Ûå³ÓÁ4² C{M é-Â¥r·*9&šo"N¬…”jAãÆÄÅÆÍhí…Ê>k©œØ"ÎáË!鈰MÊ]£²ÙE…[þîx` â±n\¯ƒ6+L°x‰¸ãÚ,±ê…ˆd„)Ú–C³Y§l]‹–RÊG¤ŸïÉ0Hkâ5 nµ|†(âÒ¾4Mä¶gÐ<4 xæš¹ºÐ6g®MÄ{PüyëHÑ÷kÀ6®A1yu÷Ùk›qØ+Ã<òº?nk5€—oš6gÌÝ ß‡÷àí‹Q“6X(·Ê"’-ÆŽ”[mpŠˆ¼,è; !¨w7È*¼Ÿ²ÄÌeœº[E(,Ç^„„t pÝÚ*µãˆ¥ש“ÝqÈù…ú”¤™£­o3Äpí°¶¤pAªÏf³zq“3ˆfËË çÇ òk³+ž³3`˜Y–VtíŠÁ3`˜Y–VNئ«ÈÍ‚„dbÊÈ9ÚÚ!ó4‚ÀRÆJ\$)ìÃÁ±s ,Ìíè ‰³?©2Ì)ˆY1ƒ"ôEï›Õ¿Åbë5†)ÛÂ̲²qgNج0YYŠgub¸YgfÁ>Õ(—€dǶJ*Fl¸`²²qgNج•“‹:vÅj9‹:‹y%(Ì»ƒØÙÄ6æ–X'ŽÊyŒ“ÌnÞD‰¦1O1’y‰Ö ›Ìî,éÛ‚fÁefYYÓ¶+Í‚afYYÓ¶+Í‚afYYÓ¶+Í‚ÊÌ„[lV ›Â̲²vÅ`™°WŽLªäº1™”‹‚Á~µêØ'vÒ:ÉŒ&gV ›Â̲²qÅM$p…ÝùÉÒF¢Ü0[d#¹ Ûa ü]›•;b°U[þlØ,¬‰±,1X"q‰«Xx¥ šÈà‡1¿’g,á›++ušÌoNÂòmƒ[»eŠ;»„¬Öwe¥º•àkÉ›•“‹:vÅnÛTFÓÖë‚Ã`°X,1X,0Xp¢necìŠÜxIúø3ÛÉÞu Xn•òFÂ2^4ä«Ñ)Ñíòƒl©Û‚ôd4¥‚ŒmÕ !cþSQ®ðG•“Ž+Í‚afYY8â°LØ&e•“Ž+Í‚afYY8â°NØ&e•“Ž+ V&e•“‹:Æ …™edâαX`˜Y–VN,éÛ‚ÙE™íÿwQªOv­î2U¦ ¹ñYi²ðZlœqL )Ã𳬌´Ù4l‰³-<;b„2¶_µ½8FøÊñÙ|ñ©,Äî& <`æ9Æ´ºÑòàÎ~dê­“æ„g¬¢q¿^{GI ÆÔ’%´­zàV©b`®iÓ%¤kL¹µÇžcÒŒ7ù0V® äýh°›ˆDŸ%íö*êÅ©m—Öì`JXBv›m0Xàü" ["Èœ=^u¦žëE•øÏ0jGš±ô·Ùõ­m{œTA÷¨ç[}6£-ªÁr=Ãn=½ãŒ¦yëSá[ižØü=Œ¶jIS—iÚFëI´BgÛÞª<°m[PÒOLü¶OpU‡Ôô^9iW”&ŠQâœ'cN·ý%‚HH]É ÉC·´eÄÿ«GaV€{"íveoûºÓB„ÂÛqC0Øøö…ÌDÃÉyˆdåŠ_¸Þ<³`Dø58Z BػÀœ$ LûtP pÈùÞ¬C¹0n±›V܆A+Y‡ËtVsrju¦KHÓ¶±[Ý(ê µ‰ËõñðQfo×…×ëå…¾'í,C3ab% 0I(BÛ®ñäómÌEkŒ°„í6Ú`ˆÉŠ!qïÉYòØYŠV™¹ÌÆ6Ÿ«"su³mÃ7¿1 aÞdy-ðØmÃ5ˆt·é¡øÓÚg¸ªÕ q ö1cfk¡“XÒŒoöa¥ºJV œ,€Õf“Vi%7!Œâk6†³4‡Âù›ƒ¶fŠ!€ñöU£¬vzöÉë¯ÊP aÌUG*07Aä.†Ì­ÿw^Å@²â, Àÿи¿¢†M`Vë½–â튖“Æš`~GlÌf‰¾B4W¤$ù¤'v–óœÔB},…Æ:ÑDJÍÿâÜ#‘Ô››¸„ÏGöœÑ•]ţݘ”»£0C)ArCaiŠö~Ð?N-e³y@ñ.²7ÙÀ ã/“´ïòÉÚu´ž[¼+ÈäM™·¨|j¾üF Š´ÀÜ6!Ís—qÿÑÅýùZØÙàÏ¥?$ö£¬uS]¶ã,ÒX~v/"0Œ[ß¡ûÛŸiÙÝŸˆ{p©öŠý†rxmö £RþôJ?ë 2»Õ«’rŸã½^h›l½~¼¯”)Èò‘¯GC:ÑtM•ú¹t6eoû¹¬Ø¡Ô?Ç´.¡È¤¤@ˆž4ÄÅËö´»8 ’³Ïd,Q5Èg„š~Í´¯^fn€8W•¡{ ãN Ró‰)4f{PÁm½ C¯y|mVF./Ù‡ãÁ·L…%ÈÂv&ãû ¬ÒsT=9øŸ·ØòqÛ÷XkW±¹@Qpýv2{<»þŽEŠo¸C\†eŒP–a¯!+âðÂvÊ8<ë,¢Ý]µ¢ Þº2"Œ]®ï(ì× RÚaŠn ïÐý†6(9]ð[fÒ›=¸E÷MŽ Ç䣳\ªÊ0I+sHþ<Œß¯Nc=˜š5… 6ôF ÂÈq?Çz,Öö™J+Hj‘Ó"OL™e~‘> ¶¶ZÑÎñ1\%äºò‘–gìveoû»3ü{BëÍL%w«8¨èæRÒȳæ_.Ö¡i‚©½&8¤Üd†…¨F“ bxºÖ)y1QûcíCñá,€ jÄ”C8;à‡ÿ¦FÛ$%gb7U«Ãމ[á®áð÷ÃÝUbч„„̬YŠ«>õI–óºÞzTŽô•«HÐDX§ñ£šR±'÷丠“D©îÍ:’ÆqÜ™µ80¦|·(Õ»cdiA¡#{ô/ÖòàäÛ¨á$4kÀÜ¡íÃJHßAäRúI»ÎÖ-m—‚šùzάïæ†9 Û¬½ÊÚ©ûT¨qHÌÍÉl5 Ìä¶]¸AÎBžÁ:ò‰Ø‹;ô¦0®àñ¢íveoû»3ü{Bì«eE9Êĵì/1±Æ‚ÏŠqîsHŽñ ¶dè fsãäaX±!cº=¨~<'© —Žáâ D¬Ñ;Q»rmÜ“nf-µÁOË,¡Éj[%xÜd៛õØÄkòÝ›^^G÷ä¯Jc‹âr´›K»AfJ‡,‘psM¦ƒm…”qŒMÊÞýþÆü[6çeuÚD8p¼en”Úá_o’ÀüD˜V”ëÃVS–w¬rŠhà(Ûý·JÑW:w†Š‹~¬AÃb>¬ó;˜FÈºÕ ’æÙ•¿îìÏñí ²…³ÆõÁ>ÛTäÜ)f†8°*pG$^fš8¥o+IÊf1´R„Íø\BH$waÅ´—ü“É CNW´£ªCd#…Ø‚&nÈ?h&—˜ ö Î^lxV›0y-¹ˆ µ‡ÂüŽT¢3ÇŸoÞÆÇÅ4%íµõ¤Vh’›jÆ-¡ñm¾»(ö¸£9%:ÏZv±Ç _‘ýø´g:Œr UËCšG ¤h6Cv-Tx´bV‡J-Ì%>-ïÑÞ`{xþ»2s²¨y·*‘ÀÌ·Ytk~Ý´Ñ PMµÙ¯SÔ„,ÀÏåBé­Ã!§¬ mÅKeåCüwꃅyBä%ª[½V GÞ@~;á è»]™[þîÌÿл_÷V«ë‰_Ž"ŠÀXxã™`ëØ ìۉȟ^á@-$gIÜkÄ!%ºaÊ@²Tæˆ# £*Ï”GµÇ–Ô WŠ9·-’;ÊÎÅ UkøÁ^«XÙ µÒ PP3µƒ¼©”Ögv=ÂÄO X’®×-w&Êü²È䶆Ë&áߔ٘Yn`9%½ÿÏ·Ôð 0iÆøö"ajéXBlÖ_šäíZ ¶ˆ˜îöÊ•šòÖ¸2›Ûž9aéÇ+ÄžÙº×|<’EÑ'—QY‚YJC!x5rÎrªŒ²Fˆ#ÂVÀÖÌ­ÿwfh]¯û¨DrnûPȼzÑKN÷ÐI¦G¹@ÎWà°ÊRÕ›ÐX%:ØFjH#™5HYx¤ *ÑÄÖkÉŽÙÖn//Õ­`¥—W²Ç”IÁkÕ@7ñÍ8<óµqç.™˜ÆÒL<3ãpDá¯#MQj¦¦î¦ƒKŽÌ­ÿwfh]¯ûñݶ÷Ü„c^¶äeh#c°óI+›3 ;15´ãˆ"dσùâÓ 4Æ$*ä~µ÷wíñèøƒÊ`.5˜–ý0íö+MäGb:•†Â±T!ükbðŸ±}Ð1ñ¨ðé3ÂÊæLêGy Ù‰mÖHß+ã ,͇Œ¡q&>/îS©$Íge­d¬~»()+˪u^ìÂ, ÊÞý•ÆD`Ò ›‘*{<¯žM¾lÁZ11AÜ«ÇÄ=•ë¶òM @­ÉÆÀJNõŸ/GZ&ümB{¬ãAšÄUÚ¶ãZ¬÷7¶_®K„œ€znÛ€‘K&©t,‰T„«‡FHÆVQFÕÍhÿÖ9^%åH¤˜¥ã³+ÝÙŸãÚkþü–¢yâƒlÒÙn#ö°q(OÏ•V°ÖG—r˜ «I%xañ#ÆÈ?bGÈ‘ÑHFÒÙ·X-Yžá´òÆ~ÊpcŽÉ°S7– ð…[Ë”–fä’|®ö°a¼"RŽƒJÅ·È)êÇÈŠ™ˆüt˜ËµÌÏ iºtìΫÙz‰‰¹÷݈ÍKŇ™½úxšß$Í’jÍŽPöW +Uy8mGFéÐ:ÄwK™®`Þ[p·;ÂÊòDÿ†¨±]½¸O³ÃF¸¶gÙ©M¾I„(U(ù³µèÐC’Dg–«×á4Z̧µ#MÍf¨ÙjuZœV¼•Qç!äÙ•¿îìÏñí µÿ~yp`‡ðd&ñåtðH<ßÇi’÷ÿ A+J2á^åŒ-Ï‹u"*ók‚ÃÑ3fNØtÃñêH0ÈÇr«n2"´5âùxÝZÜ™ÈäØìŒõ5H6h0’„@ÓCã˜ôÀ#EP¥Ú¥b=®Sšj+:Cò'‹oó5óGxÌOp7Rþëѧ÷ãBæ^m@[Îí‡ ž——7;{ô7hš½˜ÏPxؤ3”0vån›Ç[¨îC);b¬ÖΤAêÙ΢€¦Z%‡ŽIë“96W‹RËZ¹ä#Áñù/Gª£«Q«Œ‘Œ£kõÒezcÆYô‹¦\^l%FÎãfI¤IÔÔ±^I¤U©…^]™[þî Vä©cÊ”ÿл_÷èBÙF½ ˆpŽ5%øÈxgÑ5#“~Ãÿ±C\ܧ¼ÛTÏ$sÿe£8†ÛªÕv¬‡qŠ”4÷j÷;+É€ržF“¦VzñÙØ‚#ŠŒ0ñ ÑÆçì‚ÉF¤•äSÀÓ‹Q‘Óí’³Yê¼7J-ÒB&Üäe™ˆ¤Wìá ÖaVo„NæüìÂ1ßdFFgÌ ©šB¥Ë+Qá:ö|¨¥Ü…nm‡É@†Å‹s+ÓÚ¨æðõK°Ü3‚ž# g¦Mp™§%³+ܤA„0B9ßL–WO vÁøœ®Ìh]¥©åv†ü<³L5 #OVUñòº®‘gn!x¦æn`&b~ÑPnKJõñìÏÛš§ÞÁvðýfl²d7ºÞmÂ~×"ÆGôj³=ˆÁ³OÃ\ÑÞ¿öí[cÞ&fé7¿CyÜٰ釷$„ëÿúaƒ‹©ÁE…΢Ÿ—lÁY=8¶ÚPÉ_þC,uªXx`ÝÄNÔ6­rª'vØH{Œbƒp€Ô–¢‰ŒÐ.¤ò¼-É$#±È3 ™[ϯ÷¯½}ëï@F÷‰§4vE!—Þ¾õ÷¯½}ëï_zû×Þ¾ôy²ýëï_zû×Þ¾õ÷¯½}ëï_zû×Þ¾õ÷¯½}ëï_zûÑf_zû×Þ±%÷¯½}ëï_zû×Þ¾õ÷¯½}ëï_zû×Þ¬BS‹P=½}ëï_zûÖæeš¬Øî1‚}Ò0T`6{uG;åbw[¹>«Õz¯Uê½Sb½Wªõ^«Õ;,Æ›28Þ·ì2âîB¾õ÷¯½}ëï_zû×Þ¾ô°û×Þ¾õ÷¯½}ëï_zû×Þ¾õ÷¯½}ëï_zû×Þ¾ôy°û×Þ¾õ÷¯½}ê®`až`i7)!;nîzÞ··'ªÚDJM¸G\-F4¾e%zËtl³m{t¡Ä×Çâ Ë$EVh·u÷¯½}êöÞ׬ý̾õ÷¯½}ëï_zû×Þ¾õ÷¯½6l~õ÷¯½}ëï_zû×Þœ\—Þ¾õ÷¯½}ëï_zû×Þ¾õ÷ Í‡Þ¾õ÷¯½}êsš­Áœ—Üœ•K~:©|ìŸÞ¤«5”x}Ëî_ra’B†wgwayczÕJÚ¡BÝ[ º5ïùE£a¬ØÏ¦ñü² ’¹HNá-YÙG1mÄÛ”îðÜ×Þ§yØ'€¬Ç^¯‹ž;Ák]YŠS&r%÷¯½}ëï_zû×Þ¾õ÷¯½U‡U¼QS ýëï_z·LªAf÷­—oáäGŽ^I5â.©þ=¡qaŇ3§"M™bx7§SýùfÓfϪðÂVN¾(Ÿ€Ò€ Ô5ápwÕæn|8ã•‘¦Ýíˆ?ìSáK~ W¿0~=™ûsKe,Ã4ã‚XÆ´@îì-ÂO0ƒgy6œÒÄ@¦‹FN4¶£‡o•…¨Êí4o{ –J­´ƒdœœcn£{öíÊ`Ò0WÑ)"*äœXÂvžÆ•W£*±}ã$Œp¦v%@XæVÏþïì•cŠÄsð+c^[{«Aî“JÕ7$®Õ’8Æ´bô¬øŒfSš!bTÁÞoûD†È¹,VÆxÆQzÃ<ÐI …$p˜óâì³:÷ävÌÍHcQûìÊß÷rM©“ÿ¥úuàjÁÀÿл_÷å(^ÃMZJ·ÄñEÆþí%k|šÈ‘9¿3uö½ßÄhíÁ*bbá xàtf~ÜòËÙÞÚ?ìo©ºnÇl¸W²u\/ÍǹÏ6óiHo)p (=é]5ù™¼Á¤*ŠÌ](ÞÉÛŽ7•x¤š±f8ôùÈÄ83;ôÃÛžX†q–¬ëmoú(ÃP†¾hÞ®Vñè[8g‡h޼º_òÝpñèÈó³Ä¶9NhÔµbü82³0©k¨bÿ˜mQã.ß )àãGÿLcœžˆ›6ÚQ± q),J2K3i˜ª:.$ÄÏÄžMN®Ì­ÿw_Dµ¸ŸãËZ6”ÞŸ¯„é«e3+t‹µÿ~S'Š~C,‚Fò?A» ë¶9ñìÏÛ¡¹Ðy§øƒ_jÍw¬} ¤¾Þ´ð6ƒM_-ŒX*@áÔŠ]'k.)­}ÒI©Ï)áCüy_ÕY–@’…ëߨ]¯ûóIJÕäsj غ-ÈèD<‰êü‹4׫?üz&#(¬bÒ‹ò8W38Ó{öíÐkL-梴Ä^j’LíÍn°Y •%†`•ÎIJ]ÀéÕx”?”™GIÆÎLœs‹?ò8±0Œ°+¡®V¤0}…ĺ’Øx¥«n+ƒË4!`VÌ­ÿqTÕôn,Iªè‡…#ÄÞÓ?ÆÕï¡Ý+ʃÉ&¯¨Ñ©ëë?X»_÷èN E;KÉoïZ«Åõ–-.¿þ6ëm…–ß0~=™ût%|gæÛÿó¨¡ÕOM™K›Ú°5FoÙYÜì[A¸›¾ã aÁÕ)+ñžÈWhÛíˆô¥æo~Ð=¹|R^1³xÅ—@æ&+ydcŸ„RYŒäãqòà NG4Q–q8 Ì Ä¸R{QÖP¸Õž1Î^áâ &ÁÓBNÎÙ_fWì„vó? i«”£•É™¢Ü ™Ïñœ@*½9ë 7J±ÙœÎ X¤†À—T»_÷èÉʵ$G(JÊßÙÀ$p^A£”¤ákvØ›/&¬®˜_0á†={vgø©<Ã]ã³Ñ’™hxŽ"S #iF±¹‚.­ñ‰¡Äy-Ò+RS»ÏúœÅb½ñ˜·RÔŽß Ä’p36>[:¹Ã¼§øò{£Œd²„4§×9ªåZ¹E6+ P‹šÃœ»_÷êY…§ŒL„«ZŽä|…v¸!Ü«­òðx½M°§Ìf~Ý 0xÇ˸»êð«aëHÏ™Wô‘β¬ËöSŨÞ=¾JgvËA9Ř\›`| ò´hGÉVQˆü¨ÓZ ö$iÝ…W‘£w–&xYžh]X º¡íÙŸã,­Ã^Ç?Œ@m#<¤à˜,㛈Z?7’ÔLtëÉ^MX¿î°-iÔ‘´ 4¢èكɎ½R¬iêbŽ®Azm™¡gc ¬ŸÕA€q‹q‚WPH ¼¸ÑX u…ŽCbnbíß©)éÙLNŸÎbfÊ…nAôéÓ;²GÂ<³HqªæF™ût'…¬>fäÜ?»kgYã=AuºÙõQ›J<»Œ™ŠÇ;>ß3*ûl§/Š|‘[Šg¼Ö¢Í‘½ÕšÁl=» öì'²Ð9Ø}r‘sqŽ´q¿e4%gi£j¾v­|'k4¦†q°6+âüÛ2·ýÜ$”kÃãÇâÈŽ"{ÂÐɘBÁjšÕ$VtÑ™erræƒW,:Ú–+… ¨3J="gíß©è ß®ÙÉ-›ƒ]|œL¥¾1‹TµjÃ,o·ÄËvÛâht¤é{­Ž´•¡çdz?nè4‰W ï\w™"ªÖ&ò¨4“ÛX¶ŠÙw¾ã%qiqk7‚’B”—ë²æ‡’i4cõuVãÕo•%Jö¨ùœDÔ{p„¹]2·ýÜs-FåŽ Az¸;Ót5EÝÁ²£„$"„ §wá'"ž9úgq…ƒ’ÈzÐ’ybZ°#’'G0)_ìÿß©»ÈqÔÛái¤¾*ÒQs8‰˜˜G"–,ëW.ë½&æ§NK§ÿç!¦Æ5fèãÙŸ·GpÿÍÇrþ®bê òÛ*;D5Aÿ/Ø"Ô«Ãõ¿ìäQl[–Q‚-øÃ6’{¸£µùÓ{öí×±êüOñè½yx’2Ð6M¿Bx‚dù`›£âHŽŽ»5c^,œû2·ýÝ6…µOcZ¦žBvâŠê4âò˙𫭧À»_÷êo„CQÅmÕšQä!bmǃ°²wÌükÖ’ÙÔÙ ®£†8zaøögíÑ–6˜#|ÃÂôe,|b‰æ=»j ¿ƒþF,b ÚíX[fÞÔ#å±ב݅WìIÐo~Ð=ºðNMÄÿ„-ŒŽLcøž#€;FäØ?1"6Íá*³yóùψYʼ¼šéù¶eqð™ŸÄγ3'veŠgÅ13¬ÌÙ–)ŸÄγ2rfX¦|S:ÌÉÉ™Hÿc>)‰fdUC€#…†IVfNLËÏŠbgY™93,pX☙ÖfNL˾)‰fdäÌðX☙ÖfNLÉ߮ڌLë3'&dï‚Å13¬Ìœ™“¾ ÄÎy™93'|)‰fe^ĆðX¦vu™““2ý†\•–ÔLPfdäÌðX¦|P)9»à§ÀÃq„")‰fdî̱Lø¦&u™““2Å3☙ÖfNLËÏŠbgFLœ™–)ŸÄγ2rfXà™ñLLë3cýOŠÅW|gº`ö6}ºF–ÞÓVÓÔÚ«T&&u™‘1Zºg&ǵ1Íã&§Š'aNø,S:ÌÈòº•á|qLLë3'&dï‚Å13¬Ìœ™–8>)fdäÌðX¦vu™93'|)fgNLÉߊgÅfgNLÉßö³â³3¬ÌÉߊgÅ13¬ÌÉÝ™b™ñLLë32wfX¨BQ&u™““27ûñLLë3'&eŠgÅ13¬Ìœ™–,™ÙÓ:ÌÉÉ™b™ñLLë3'&eŽ ßÄΜ…U|°c‚ÇÄγ2rfXà±Å13¬Ìœ™“¾ S:ÌÉÉ™;à±[)3½¿îᆨëUÖ§®°Íð“ÑfÊšW$Ò9&‘jºc'YþÃ|–,ì±Å bñ¾,‰çÖ­?’á×.{0ù1·£u?ß©û¸Ë /9ÃWnQMUÅhʦ¶^yÎÁqÛ¢«Ôdz?n–á‘90ðƒûôÇ™ÿ)öð˜ ?¼£^\‹~] {uí}Äÿиfm»U¢ìveoûŒ² FòÁelxaÃY[† 9YaÀÙ²¦l;2íß©zSšÆÓÌ<7 ÎÏɶí¥xú¡øögíÓ ‘>Öj®Þп$ÓŒ,å™E_Q§‹H!‹M»&÷íÛ®ìÄÕÉljþ=¡qƒï.‹<º¼û2·ýÆÒ½ŽÈÿл_÷é“åiç+'B‚.aEÎû†ÄÕavvã G9Õ®5"ê‡ãÙŸ·NprhäiEEªj¸ ®äÞ*š-E±·^°ÆråìÛß´nÁþÙøãÚôU;™^! †(kžU•eYVU•eYVU•eYVU•eYVU•ý¹VU•eYVU•eYVU•eYVU•eYVU•eD+*ʲ¬«*ʲ¬«*ʲ¬«*ʲ¬«*ʲ¬«/ß•eYVU•eYVU•eYVT/¤m÷6U•eYVU• *qjL5ÌšæÊ7–ã³XÛUZR^“mÚ†ƒeYVU•eYVU•eYVU•eYPÛ•eYVU•eYVU•eYVU•eYVU•eYQ¦U•eYVU•eYVU•eYTc’|ª3(–¹¦”Ù¼‰U, IÏ*ʲ¬«*ʲ¬«*ʲ¬«*ʲ¬«*ʲ¬©‡×*ʲ¬«*ʲ¬«*ʲ¬«*ʲ¬«*ʲ¬¨Ó*ʲ¬«*ʲ¬«*ʲ¬«*ʲ¬ªÄ/$q¾¨eF?nU•eYW— ò3,,ºÒjJ&c,«*ʲ¬«*ʲ¬«*!YU¶|¹0YVU•D/•eYVU•eYVU•eYVTØ:ØÝ÷'™¢’6Œrbh]¯ûòÏdk©¤Ò Óù!Æä l‘ä…AX& nß&/@Ù‹n”HVÜÞ‘ØÈ>[/Ù&–h6Š]pü{3öêBÚRð)4&&ŽÌ`úÇ"Š?åÚ7¿hÝ„ÒåQ˜#üu£C ’šlŠÄ½¹d¥h%,d”bj²’s’šM!Š Õ VâàD¶Fw;ÝÙŸãÚkþý?ØeaV«Æ¶ÙBi£ÊÒÅ Ë4x¨5ÑDeUÆÉ3yJ0".À?Ìýº’Eñ²ËAÝxÁh4mÁÿ.ѽû@öì%‹Q5‚÷%(èFž´$‚ ¡>v6=1ŠB KûäëY‚(7 >M™[þîÌÿл_÷é{-Â×™=Z…iùÅ8Q”{ ü{3öíòíß´nÌÿ™  µ€£ÿŒ¼Öäx£Ž6ˆ:öªøÏVËÆÜ6eoû»3ü{BíߥûJ-œýyÅ0586ìƒñê0;¶BY_ˆ…ôË„™;`Û´Ë´o~œ³gÐnÌÿ¬ ä¢œfå{MŒäs·Ku•Ö°~& (éfaÜÇÜ%)6o{ÝÙŸãÚkþý-Þ6’¢Û+0,Žl¼k :2ŽŒ@ý˜~=H¦Òo5y?óòðEk:+ hË1·hÿ—hÞý9«êŸ@=»3üzÒB¯¹£oJm æéט@Ày©l ·3ÊÊ3cÙ•¿îìÏñí µÿ~—ì6r€r`n%#cQÿÖd=¨~=K1É(,ܧíÚ?åÚ7¿hÝ™þ=…šþH½¦{Q3Y|òacrÑ?.ÎA–w˜–Ì­ÿwfh]¯ûô·YÂÝ»•D.ò1èÈ>×Ùjf~Ý£þ]£{öíÙŸãØ¼­VvŒEï›g×»;À ƒ\›j…“6 ¶eoû•‰¼xêØò£ìñí µÿ~‰NƒoŠKrÛ9gäÊÁCÂ<µǨ ‰=LlÜ›2·ýÝ™þ=¡v¿ïÑݯÌkŽQR3Öš9‡{Pü{3öíòäÆ!ŽA”yä'œ¥f÷íÛ³?Ç´.¥º’œ¿~q•êUñ¹¶eoû¹6†û$Ü¢Œ¢··Ü*5bã´·üjÈ0ϸNjoø¿½+ÃüxíߺÿrÛ ¤‚AÈTc`ƒr$ûDm³Ô›—hoùËù­§ûw?ý g[”M,+k‡R[x<_÷èî[Çǹaœ¶ø†Y¸Ì⪉ •}Ÿû7?ýq×ÞUïü•ï•pÙÿ¯mÿѺèÚ?»sÿжe$º7wº²nÒei?ñíµFÁI=8J¤°ÊŸÿnóíCÿFñïÍþýàÎ)0*›Y‘ª´"ª<‘Êñ&ºL¼·‘y†žË’{HŸ3õÃñìÏÛ´Ë´o~Ð=»3ü{BêCgZN†Ì­ÿw&ÒL!kû—²ŽÔ7cº´o¸Þ[j1¨>wbb“j°6(À¶˜µzV|i%†µÅ>ÝǶÛãk(†:ñínÍ6èìó&|LÓž¬›d¢ðîÒ±[4’kí5¨«–|£ºbõVÒL1Ç?3­ ˜UçÆz–H®K­4†>Ùh`y(ט«CUïvÝhì(¡­I\±äÉÍþýꌦi®Ï É]þ`Õkpi™œ\–'³˜0T—?ff~Ý£þ]£{öíÙŸãÚY¥>m™[þîÌÿкu݆]Òxæn‡ûô]±[…6‚+qÅCb•x}vyZ´@M#r^{aYá°?Ìý»Gü»F÷è[¨6Æ´¤Ý™þ=¡uš1bæÙ•¿îäŽ"•üI3=Iޤ‘¯ñðäz¤G'ÌËRÄ:'pÊž´p·Š30Å^C޾b¿EÁ£"ññÜ+¦„šÔ±M„£¦ú’U8ÑT'7« ¨+9¸Ô2`ªr!§!´4œÛÅrikMs•¼9 I ÎŒ Ý—ûôîípÞGúÙãv§ˆ[tøÁ<ê1¸ïVbâ=y«Ç;6ÜñIÈf~Ü+V Ø¿åÚ7¿hÝ™þ=¡sÖ×Ç«³+ÝÉVAÅ €ÀIÎ8FH°ilbo…ÆœÆ ÀBÎLóJ;EY Ì/^9<ÜGpœ&(ò×0Ó)¡Æ9áwâ3ÀÁ)FR4Ñ<þLlùâ„$š)×’ ØŒg‚`Ê6bp–QxŠx¤–Æ£0ÑÆ)#-c–ÈH>Dz—ûõ‹j¨OZ°TAíCñ²òÆ °±?nÑÿ.ѽû@öìÏñí µÙ•¿îæöàäïÁ‰Ù{ôñí µÿ~Ð{PüPÊÑÚìOÛ´Ë´o~Ð=»3ü{Bíveoû»3ü{Bíß´Ô?%( 5ž#ìOÛ´Ë´o~Ð=»3ü{Bíveoû»3ü{Bíß´0Úie¯hló@2L?Ìý»Gü»F÷íÛ³?Ç´.×fWÈw–6L"ë ¬‚² È+ ¬‚² È+ ¬‚² È+ ¬‚² È+ £ËVAYdVAYdVAYdVAYdVAYd@+ ¬‚² È+ ¬‚² È+ ¬‚² È+ ¬‚² È+ ¬‚² È+ çÈ+ ¬‚² È+ ¬‚² È+ ¬‚² È+ ¬‚² È+ ¬‚§¿2yfUk”@L² È+ ¬‚² È+ ¬‚² È+ ¬‚² È+ ¬‚² È(räVAYdVAYdVAYdVAYdVAF†AYdVAYdVAYdVAYdVAYdS€ãVAYdVAYdVAYdVAYdVAYdÀ8äVAYdVAYdVAYdVAYdVAYdVAYdVAYdVAYdVAYdQ€áVAYdVAYdVAYdVAYdVAYd@+ ¬‚² È+ ¬‚² È+ ¬‚² È+ ¬‚² È+ ¬‚² ÄÆãŽã¥–®`À9±?Ç´.×ýûfŽ{д–ç®Ò>äNŽÍ¹W.áqÇØ‡ãÙŸ·hÿ—hÞý {vgøö…Ùû¦l¢ù­ÿwfh]¯ûõäbÖFÓ?ŽL:¯So  õ:Ì`­fìƒñìÏÛ´Ë®äµãeïŽû@öæžô5ÔGª1M)+ú!µ ü{Bíveoû–+LV<˜¬y1Xð?Åb±äÅcÉŠÇ“ŠÇ,V+8¬V´Ëɰ‚üÀ ²z»2·ýÝ™þ=¡uŸÑ ÑÉÍþý sM I~WP_—܉ õŸ3p‘œ†ˆÊRjNIB&›qx÷(ÉZ¶Õß‘¤ù¯dà»Z0{µÝsäIíØuäNˆä‘a‚ÈÉ­Xe&3>- Y[¤ ‡*Ô•jJœs- LØ'÷íß”\£v½a•«^T^ÝAöìËÛ´.ƒ‹:ÒM$¢ÚÓ/"ÂŽü á¸@hLO›fVÿ»³?Ç´.”’„-¤–á¸àÈd`f°€ïDÑ»˜´ E‚+q´³Ø ï^ìv ‰ò ‰«᪢”g éEaÂJööèô²Hš‰C–‚o~ص6x¬jFCh‡äàVl½¥ì±Y}yÛ´~ѽûAöìËÛ´.±> …W¹veoû»3üz$L$ 8È#&|ypàJ[À¤ÝTV‚@—p*ö‚šäpSíÂzáe¡ˆ` hY"zå DœEÓF"³-1Zl˜S‰ -6L9]¦™^œ׎aÒ¡÷ Û_!:Ž«ÆO<ä³ÈëÕÓ -O[#Ú‡ãÎî̬œìµT@½»G÷íß´n̽»Béú¹;3Ò½!ù¶eoû»3üx{õÙ>åù0G¹¦Ü²Lúb²6.r:’•jJœäuêÌ–E¦ËHSÂ@E8±-1^Ë՟׾¤:ˆAõfMjÀ¯>u¡‰ mÍþÜÙÅj‚Öj‚Î+G&dÆÄ¼Yæh¶Ð‚$=¨~)ýY„îP/“{—¡XœÖ­0^Ý"öíß´o~Ð}ºxpؽ»Bèâ†9&jµÂ6èìÊß÷u¼¸sè|…dG+ ‰©÷GA3ZŠvxQË/b]¯ûp"ʆ„ä‡l5zµDmÕd×ë2ù Ë̪餧"ñk’ð«££¡ÛëŠjÐ(örÙŠòP¦ÜþÒžiH]0³v%íÚ?¿hÞý ûsm Ï·íژݘ)[›j´dPY½áÂ6d–í[cF¬ßÈ^Ý¡tMWž¥³+ÝÖ›o•Îg:ů"xÄ–'rpcvfÌ»_öá†)… ó–ë’+UÉšLÏñòaàX_d „kƒ¾¯W{¤B«[mÈ<¦c>å=Èy¶›a|¤‘²·lj7ÍB›x®ëå n\–R,dL<Û³/nÑýûF÷íÛ›oÒÚ>Ðÿý;c‰âÜkãÿô©\«ÆÍÈ^Ý¡t¥Ë€nÄ=šöŸ¡³+ÝÓ0i©Ùªü%¥Åñ±"ÛI“Д•,3xó£w‹³.×ý¹aœª¯.xÛåM”{„ó? 3ezº†„†áJùd±Jµ€±É6ãŠqÌü]˜™êÀËo’\Ë@$†¬ éêU‘|l¾25~¦ƒFØf^Ý£ûöïÚ·7›.`ܬµ©|éµüΟqákóŒnSÖ>v—ß½»Bç–A¯V­‘ð+¨ªÅOfVÿ»­!8 ß²É÷YþH×ɢܣËòP¯‘®èÞŠ6~À»_öè8 ¡’XÓ_—ÈʾJDÛšmÊ$׫’}¶¤Ëá£O³øé¢Uah…¨<˜þ>eñ󯎕|a/‹_((B¦œ+‹îlŸr‘=éÝj­ ZB´Ùdd>Ý™{vïÚ7¿h>Ý™{v…ÎQ‰¨_Ç’¾ã ž¦Ì­ÿwa>Ü2œÕ»»±ûv…Úÿ·`ñ¦©¦˜S\°Ê=ÆQM¸Àè·0EzrObwZ’¯¹ÖLV­ Z@´A4b=öìËÛ´~ѽûAöìËÛ´.‹³:†S¬¾FTÛ”X5ȹöezô1Øm»¯6ºó«¯>ºk:i£uŽ=;µ¥9ò"”0ÌÏÚkþÝ ö£íÙ—·hþý£{öƒíÙ—·h]\0eLr kv6á;/™6äi·1_¯ÚºëåÓ¢ ³>V95‰Ù5Ë ¼ù×ÈL¾FUòR/‘•|Œ«äeM¹’—pÌ2»Ì¼V^*ñWŽK@׎h'T»_öíµn̽»G÷íß´n̽»BíVü·Oý}™ûv…Úÿ·h=¨ûveíÚ?¿hÞý ûveíÚkú·åºëìÏÙËŸ×?dK7®tÇeþÚŒ˜±ìñʳ¦|Ý›> ¨Ëß²/g<;G÷ΘñìñÁj&,{6|:gDz/g,©Ï*ÏÙÍêÇŠcDzý[òÝ?õögìíŠ Ådì‰aë‘Ó ö_í¢™ŸË.e¦…°ìðÄr;&l±/gÑýòbì/Ù»fZh[Ï.fhò¡l­Ø—³¶(…ÝòvD°õÓLÙ~­ùnu™,ü]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛEµÛÃâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»iö»‹âí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâîfø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úm®âø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Ú®Þm|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñvÑmv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñvÓívñø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úm®Þ?m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛCµÜÃâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Úø»kâí¯‹¶¾.Ú-®Þm|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛOµÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_m|]µñv×ÅÛ_®Õš±ÿÚl22ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ²2ÈË#,Œ¥l{ž'Aíþx·ð\SÈ̵…3âžQeŠiEÖfXõðO`°¯,:Å=àO¸2}Á—È:mÁ|ƒ&Ü|€¦¼dgoðlè=¾¸V„S\E|Y6àÉï ÁÑ_ÅîIÍÖÞÈj3/6XÖ¸§¼,žð#¼ˆÜ»è뛨1 ™»J‡&†'EEœ¤¨.õ]¼‘H½ÌŠko'#õŽ64réŽ=93t§ª3(iŒJí5ñr§§,jƒÍ”‚*Öè ¤Ý£ZðOõIÐ{}$§^@&|SgÅáO\ aàRew¬ްàÕ1QSÓtt=Z»‚ia4TÀPW ÂóñCJÌLîÁ™Ê¹ Ò%W$ÔY¸Øˆ]Ê»2(™hº([¾fQ\cí°P“Èí@PÖÔ×Sú¡ûTgyGé¤L<®øq=­¤'§Hʨ*ö¢Æ_ú(#v¨ÎƒÛèÒÊâµ%4ðÉ+ø¡56d7X Zð&|xؾ+ÝI©Ž¦A+ÄëÍ4ÉæEMÙE³Í4‘¡Ü=¬ÉöôÛ{/Žuà³'¤ JHª¨AÁ¹‘Ñáz&¼#ÇãÝ>ÞHª"n‰+I–šвxÙi2hÑôa‘…y1¦ˆ$fl;xæ(Ô¶JNõÍ™ÛÄkæ#_-Š+“ñlš¥D=•4ìïÇ xÏd!MºÄžÀJòXy7fG¼)$i8¤ Ë‘C¹iÈʾNUò ²3vY”×£‰EºÆo$ÃEz9V ñ&Å xv³ öú+– æZÃÃ*:lKãÙ4X¯½ ixëÀwPÂÑ' bZBȨâ‡on:iÛT‰5SE ¬‡Y–„¦†™’zæš¹ l­5^HÜ9óºÔu¨ëQÓ QÖwYÝcÑŠL VÊÌÍõ91ÂÆêl¢Û䙪íx¸í± zdYxâË5Ð…íî˜)w)$O1:Ìü¢YQ<íº‡o)]¸Ó>=SÌ̬mNû1¦Ùät[)!ÙIU¤0)ëê©`›òT 'i:o¢Þqâ§Fæ(m³(Ï;fn3žA{¬šÄ„™0¬Töß0Èr¨É\›#j’kF¡ {)76ä5­¦Ÿp^y ½™³óC6§ZJìM$Y³³¦¢Š˜;@$,Õ&܃ĬªÛK2`nq™‰fdÏŠ+/œÙ bŽf±.¾ôKÏM¼(­…‡²ÌÇÔÒYA–fdR&ÙP»¤ß4I·—_6ê¾à¶¨¯$÷&mâL`ÝšEó-Œ3´­ÅÛ0pu+Hë-¦UÈÉ»‰ÓHÌša¢L$JZ¤a¨8Iˆ#ÇÒ“2Ü[É5—$mƒÅ#ÆàY˜ãc^ )j …ÍRn&&© ²ËÂìÎÜ)?ß5CwàÈo:Á–¹cZщ±-Qǧ`ñp‰Ë²ñÝÖ™2+¤†Û:®ñãõ+&bÖÄ̶ËaÎF·;Ù¸EÊðFÔ£—ye[qû<ßY·Sr ÜÃZV-¿Ó»bvZ®‚dtJ=Ò3u,­C¹Ç+¹³!½,ÌŠØabb”šRe䚎Ԉo/–•VÝHœ$cí'E·Ç"®!éžå/œ‹·õct–eã…õ(´[ébÛÌ)÷¸Sop Ý!$Ö£tò‹!œ  D†V%-‘E;IÃÝxÀ˜¸æRUa}ZQ­NØrÁkI6à‚Èšá(æbá\óŠ–&‘Љ²† <’­7EŠvÁ b¤¦CÄMÅS1žîW†ö="O3³É&*V¸aŽhP衱]A±t@2öo39¦ KÀ$õ ¬šeEA8¿Y arÙ™×Ã`¡®Ù–<—qÔà%•Vmh¯@Л ¦&FyøÛ”4{ö|[¹ŠžÁLë߃²‡q’%ó&s”—*mÂVUwgÄY×ÝÍä;?,é÷ â°2ô%Ý"Œ¤Þâ¸ng¢ÜÔíÈÄ×Màé¯ ¦¶­),3§²NžR~YWšXCeâGh‰k’kćpGf9œn‹oâѧ¬â„—‹ŠñMGLA2y å–¼Œ†•¦ƒ/4×Â0Øi8\‘ØüƒMlÔôa€E 8<µpR@àˆxÆø8“º‹?FÅV•Cšµ ³uðC\GAaìÇ„PŽ;doØ#‰àÝã6ù(V幊i4Ô¿±LêmÂY^Eù¢A½(w8äå „Ö£?'X©l„J)µYæO`XÝ•™c!Ì⼓Qnr é”~=‡³µeã¶M©ê>¿òdQ·ÖYðT®›îù’/‘•|œª Äãxo·"‘fu¨H-È(7iA{ÉWÿ±‹*q”ÆgLøòI·E"ŸbGXÁaÐnjŸŸb/•þEy̼ÆdÛƒ¡œ Jåguæš î‹pCûƒ-lÕ—$ä|]ñLø'³Š"ÍÂ)4Þ5‹¶r(³+d~-d™yÆ¢ºÅÏlðlS·Rµ)³3R )›÷ SFN·-ãÇ-Ãr;¤6äA)Œ³"ÉÅÛ,(·iSºÖ8nø4•¶½FŒ\Zmç6ç$‰ßd˜tNr>Äò>ßD«­ÚaÈ£6f<¿Á¶µfškÔüwà[Œ„fPð={*–ÀD¥ß#{éºêW·6•ä&.4ÛŠ,SÇöÏø—ÞF?¿Ÿnrgè;ãҦü+TÔij³1@ìÐÔ`æ³`Á<Žë\r»ªÑ£ É¨’<…yåå»nA›0‘ ¬F‡ot°sßÝÛáŠ–ÈÆ2¾ñ¿IŒ›ÿ¬34£Ù [—ìï8»ãȤ6­€»aÆx±d!( 6ÎDzºøymmù«°îò3Þ”gBN)æ'ë²ÕWrÕÒ™ÿ„íj˜S‹:ÈÊíÕ(­®«ÅÆHFEoh“3m:}¢fQle‡'VIÓÛ7E)+.éíÖ|8ÕþÏ¡³à˜ÉÐâ¸!´b‚ó&|xLîÚDO&]) –;ø('iQÀÄ\÷!wGI˜y©3&nœ‘;‰¹š­† “LΫí2Õ¯ =ƒS5Qý–ä‘FªJÀ¼ÆÉ±-”¤SXhÔóêñ’=Fã‚íaë•íÞŠaþ:, ,* C:‚ŒƒÛ™¥~6¯…u>ø‹}Ì<íÍY¾ÿ ê;¨aÕq‰…†6qæRPõ b*zÀ ‡O„¹ÝÀI¸b¦…¤i"xÖa'èÛvå nê Ú}_'y¶˜¤_ š(ÐB!Ø3b£®fž‹ŠŠ£ã½n³ëMrI¸Ô¤v^Š1LÁOqÝȳ lV›#dÏ‚š ‹n²wÇù6*97ÔÔöèZ²ñ)IȺ!vÖ¯çôby#`èÿ´ìî-`Ù5ò^a(¯3¯CNØ¢ .ŸoZ#³aÒ³[O–i;‰É©âãÎ 1/4W˜+ö:©Âœ/)°åár<ÃÈÊS`RNçôW¨Øè ñ™<×gAY‰4 ‡ŒÊÄZoô˜+¼ÎÛ1¢ÙäÆx´ÊG+Aׄæ#åŽ"‘C³‘!ÙÁ”UÆ4u#4ñ4LÜ’™$–4ܨDO6Üç6ã·ø¼Õj½‡Ÿ ¹ŽÜ“½  A­TŽt[º³µI¶éGDá0šqþÅ≣n”‘gNØr3»ªyӻ⦑£a,Ý9j´Ž4•˜Hœ!#Q†Fîå<“7gÓPI­Á©2jb¿h,¼kXx –ðÓ¹> Àæ„4Dèi¯ŽVj”b@ÿFò›˜yLŠ|Hl3 °ÂžÃ/%•‰uémj à p º1{;«F¤³5–ޤ†ÿY¾•¡ÅTÚXƽa¹'AíÓÝkÉd‹g•†-¶CG@ãl+m£âµšžB¯L na=|è«»'wXb°î£,¯ægÇ¥vgæ‚ÓÄ£“;=efΪ«c'Ò3#°þsr×!gdD‹pQÚÐEêø,jG:©³„iŸI¦Ûœ“‚¯jGQÎxÜ|Íü7Oks_ø˜—ÄDŸgW ©hFêM¬6ÌJœ pµt`Vnœê(^G¡_AºS öæŽf“É™n¶Zy*ì„jJÏ]@Ö&6l:x°¢•¥ ¯ÙaÑ¢~šâ˜±è±+dådò“òW´ìÿDu*ˆ|unLòAiÀkX)u cʼnÅI*–ë þѵT«— ¢-(ìâ-‚ss¢ìÍIÍïl•ò·ëÏÈ9_øe}¸æUh ~Ùô@·Y];ã½¢…FÎÍÒ™·4‘çnI·H¢{w$”~CPTYß$yúÍ¥¬Èãqgn¼i›„žÝT7ÝBL\„Æ…^‘º,ø(%Ôo¢:µe›.5¤ )ͬ«*f˲³ö¯Íj´M¹M­q¼Žc-Vw"¦‡öYÙìsº~wEºVÃp¹ çƒ'?„³bªmMƒ62|­,Å+ñsz¢B){t%·kS3MLäUöèá®",ØvZ^¥]‰È0ë³à³,αê‰8­rTæ#ubgIh©¯ dÊ'ÐY³'®jXˆ+tR»GE¡™â*›ˆÏÃ:γ¦|T±±°ìŒ Zzj½f #¾Ì¾EyÌêYFEbqŒ²'žû:?WÁÜݦ 3MFWQ~³!Œ«6˜«4›t&¬m‰ä~©IaK›ý™Âüq*Öv+)Ÿg‘¢‘¤nsÌC•øE´ÈJžÝã¿ 0vÇ¡:n\SÙ,ê8;sgu`Š£c&÷ô rÁßÕ?PQØuÐ]!Lø÷¦Ø¬kÃ#›+àØNd-gtyEBøî±3°¬ˆC‰ˆ›Ã6—Vš!Ã…Ó à>Žö$P†r"í?^2(À9ìÔÂ×¢ˆØpW¿] žÅr®]äu±oWŽ)ê©#pè5“a[tä'¸]*ï´Íœ:œy%†-R‹h!.–VÍÉ: x3ÉŒ-&2E™Ú"u—½š&wïIJ¼wñG&9ç.«F·|I¯¶tŽjÌRš:$ øU4þ‹ÌQN2­¾®E!äb¸ì¼åfbÍ4O&|ª#Í„®»~  Ô}õ›(ö[¶ïÖãÉÉì;|qJ­R°GúÔÌåúôìRBQ’Ã…0ŒÊ”Ѹô÷m±®ÂYºlØ¢”YgY:°ƒ‡!†vèÒ®óëUÙöi1†õŽ;UGÍÒËűe!fAíô;X äbV `ïó¾k¼«ãÝeàð GC,ýH¡gSגʯ´ˆ·ÅÂ)¶è™;Å “ïRÑYN¹m÷@!̼q^0-×u:*mòĬö$‘Ì(I‹ŒQ<¯¶mçYù ز{bÈK;n»/˜ãú©)v;Ž7ù6¯Íz;nKp–˜HG³Âg%(äV¶ø¬¯ÿ?ZTtú¡PIö¸fQmÄÛ¶Èð&lx–µÙy ‰iºqG†*3Èù…Öae¨²ãÈ™ÅÖŸ ñaÍd0~†Ëí¼þ'Ò“œþæh» Ð{}JBéóni7ž+ÏçŠóÅyâ¼ñ^x¯ Èu®ËÉ$Fåœ9[7C+)`ÉÁ›@ãɶoXá×Pl9¤'fŒr·a:o¢ïur*ÖÁ¡íc’YžÞl¤š8¸H9ØÁÁú¥°Â‚A4Ï‚­3“ãÀ}±ç_ËÂawªY¤w¬¢ü~€q±£¢Î¥#ðÈë#ðŽê=À€Ky!O¿’¹»ìØ,Y\²Pß’Q(äuã¾j¢õîS2—u˜Ô¯ª¾êï·Ý9‚Äî%äŠrrG+Üw™în2Ù壸7ÚwƳÃÉ3‰q(˜—Ž) °uä½d7#%»œ©ýTõXÓ¶¢Ø¯—†KÄtÕIߟäÓ% ΃¶+ÇÊÄŒ\hí˜pݤpjÖqåÕõò·Ñw“ËmOuzí6úî&no^Üê´RƾÙYêÆ‹ß,šGóPFè-2{@ËæÜbˆ¸cÇþH˜Óði0Z¼Û 䨖àNï:€$uNž¤sB¯UòÜ6ÉEúÓ»ISÆ$"Øi²1ÁG@í”t`¤îø¦¶d…Ï H±Ôevf¹c‰ä}ºŒHfçÂ&Ì\ðM`滕╤nµÉu$å"Ìü‘þG0“´â±aAÏß•…ÝW©Š`Xô¤<†ÜåÆjÚ†5ÄK‰Méd͇g:o¢ïŸÕÜѬ1íyå-¥ËVçãÁ£wD.*[ËÎ5ç’r‘CÊ_.0Á¤Ú)ÑwCDY°¡&.²­4à²-5‘eïÍðnYG8ÃHås¥$ä­œ½1| ”ÄBãd™mv]ã»I¬Ý¿Æã¦ê&'x ÒâD§"y¨„ªm Á;aË´dnتQà°á¹ÈñÄr‘ªîÂM(­¶`:›¹œCäž&noÊñ‹§®¤£Š0p~•X…Áê¦ôé²yË‘Í3b©íl•cÏ”ÈS¡7Äë±»6¬è=¾‹¾ÙÅû™7¿±U°õÊÌÎAR45WË@·-Ìf;u/(«ÔƒB,üY°úxšGðÁx`ž€§ *M±¦Úå=Y°CDp: ¢£‚üPÊÆ®ÕxIŸµÚÕUŠzжÖG-Z¯X\e[€àø,¼"¡50‘M¶ =±= µJ6ƒ ü+ØÒQJÒpÝÿ¥i¿d wܧtWçu¶î3ð"Ê¥ÜâÞôycý™“þÉ]úH;ì¾é04]+5µ†ië›&¬nŽ'L«7$°tqN8-7Y`Š<ˆv’12’£¶èò|Ê"giehÚÅ‘•´ñíçAíô]þ>¤P1‰ÒSB,Ѝ²ñGàoµ›ezŒž¨²”2 W­f9TÛÄ@­nç7#6)¶ù]mUгG&¥¸/“‘Q˜¤K ÎÆà.ÜdÍHr¢%oiqEÀá»È¢œdNH¤'+{ÔäUÎL^ae‘Nìð[M(º9„›‚óÉG\g°)¥gX©)¼ob&m®„ÂW31{–Úx!‹„ã‹0¸­E¨£´Q¼„rö†µ½j3¹×ÁI;¦ulS6„•ÓÐaŠÍÒʈñ@îKI lÈaçþÕìŽG>ætßEÞ«¼Ó†v;¬I¬ŠÅ=Æ^@»…†eäàetp{L¼±SwéGX¤ž˜HÒÜ‘Ÿ¦¨ÛRLx̦֙‘£i$ÔvlS¿Í¿ç³~\$ˆdWv̪ŽÝ¨Ð‘”£ƒÊYž(•jù^PÅ?Ú³'|xáÇ3¬ÏÀ@ζٴCªó‡Ýƒ§Å™ÍßšXòòM|„<¡g{"¦›Qdð ¬{ÑÊ l*ì‚ÍfM_ƒ6(!çu³ ¨´Ë¹·Ñd í,o÷ûe5·—b,-ÞˆåFX3K™›§‚”Uwûù/áËf q¯L`ä’QÛk?lT;»Š s¢ö·È‚ÔdO5ÌröV÷y&m‚äq×àC™àüÇ(£qNØ«#‚I${0OY5’Cd¾® o C¬ÎìØ)Ñ4ì ŸvlÉÝ£k3j¿s:o£K\%[Ëeßl–TQåú6*1ËÓ•±j¢Â@liß•‹zhÍÍײò£M#;|ŒHm­tÆÎ¬Wk-5b[LlãÆZþ•ÅØ¥…™¢rekÐ2¿ ¯Á›Âƒ’ñÝA²œŠÖÉ&ØpŒÞ7‹Q›;à· Á¡r¶d£˜£A¼†ÈÍÅ›Yº—d‘6ÇfÊÛ¶ª¿í‘3˜ÔwPm¹œ™Û¾«#MyÐ\"LKu, M¦ëg´Bï Žg޹‚‰®¿ØðYÖ~ÞtßGÞª}´AžJ¥õ¹â´vü¦tþŠMò6Vwi&G`φgá³þJàüÄ‘H)°ÄË :h©‚l”…Ô0 j_al8푉— ãõæ7~!!)¹¶¼Ç¼¯WmÓ~M×6<$ Í㯲{o*¯ÕÊFôéצ™°YEjF„"°ÂÛqêËÁ¡%㚥 ˆ˜UãbàL˜NÞtßGÃ{S9MNH{ʔʩµh(ã`o§îw5]Ÿ¨êiHÁ¹¨SÐd ‚gǃŠÃ•ÇãŠfì‰ãC€«Öšá'!LØ­¶6?³ß8£ Z¥F¯¥# ¹aå%VˆÎ-´(+ )ÏAn›‘ÖÎë;¬zpÉ‘à°Òòn^ÓÔÚB»°³!Ê Š5åfqš]KsdgºnÆnIßîtßH$Qæ}Ãkx_Ç$@ãÜQÚeCt1ÆeŠÌ™ñïfå¹u«´ÖäiºÓ~ ê°Ãš…ÍvYò¡¼(.&,yØ `\îË#+U‡uìä4„Z8²+lîÐçÍ~˜Ú Û<Õ9kåËmÅÛ„m‹Ä c¥}ø¾|#ünÛ×~}‰ŠL2¹vEûÊ甹@½B1f˜[dïŠwǾ·Ò®A¬}OiÁ§¯¦ï]xî† @RDáØìÔX›¤BÅÃMi¬‰›¡]å8&`o$V¸© £^IsÇ#Æõ¦Õ•ËI´Ý¬ƒðµøpk&ËåZ5ï ˜MMh"O¼†>tDÕݬæNË.tÐ3(ïH -Ð]Œ|e!™‹¡µïÑXä—pEº»·P´¦°2Þ)îí‡xÊ¡ænZÖ˜BÌÿ@·Ò÷J®%NøÈ;Ì~¼‚ø)cÈá ’8Ü9c£$‚õde,%§Xgwý}—Àvc‰Ó(‚ÙæÏÓ‘ß3³“6)ÇSeNøðnBÄ>8ãf&4fÂ>جŒE;Ʀ´òrJø—& …ŸMÎÆ¤¢$üNð½{C3+ÛˆÕWöñ¼V¶WˆyÿZ§¶(¦SÝ›’0Îôêè7â(ôü¦MëÝ ´ ü¯ô9Ð{;à±úCª3f[•`‰êe± ŠÏòbñBŽ<Ž¢€¥z›#«È!åÞ¢“h±¥'Òóàú¾¥+ )'ÕíÅæBȦwècÉ·ñfÅiºqÚÏåÉ´gÏ•¹„œPß‘•Ÿû¼ƒ`ës§¥&“§Ztr´­ÊÊ­‡—zÛ丬Ô:Ïeqdà°YSGƒz+ŒnÖŒˆ«M êcL‡•Ÿ¡LƒÛé—¶ÂÆ¯¾Ùšíù¿6[¤h$vV¡Õ‹Æ5bœ’°þ³dÞŽÍ EºïpÀ¯Êu¥y­R¾¤ÌƒÛ‹·úEʃ+m6²¹{H./ÌîΨHºWj½s«œiÍ£àKX:o£[›Mñ[vßå¯ÿ>Ê‘¢)¡›rý[$oðnz&Øp™–fNMËgòP› {œ6¹ôÍe臻ÜäœXÕÙY–ãIí í32ªbTé;HÌœpLühÙh¤àÎk€Îj·ÛÕQ¸nöÆ´(EÉAõDs„$ÅÕ­]Í YU™r5ôú<è=º9x7 qD8­5•eMÞÇONE$m#\ªõÉx¤´=ReKÖ^‰Í‚ÌNµ]“L˜™ø(%rq&5•eYeYV_ ß—…*þAÖ¦ù7VûøZÛZE$eóGÈûoêø£Úa%gc&y)h9ÀΈylþ\¢Ù–Þz Ÿ æè»b¼gY mqG†1GDÏU—–Þ%¢>¦ z‰”~ûL þÊxVÅh’Ò%PˆI0»¯$öØP]gOÈÍŠfJ*/‹6 Ûp]ÎÈ?G™·S/«Uªâ„˜¾….8nXé_FUúÕv’~ã!AX¦Vöá5´Úaáv'©UÅù‚•<%`ê2ÌÎë2̳t°è΃ۘŸ§ÚÚÆU.Ý 'l9h82õ¼x"dË^.œ1ClS ËÃ"ÓZ|0Áz/DÏÛë'6f䥆>\|×›ù¨g«9@Xðݹ¬ñ|¬¤l9”Re|èÏÒoµy(&bFyX‰É`°[-¶{âŒó¿N©`|¤X)fyzñâhâ=´¶ë­r3vPÂÂâËu‰™[ñ¥xm7,><»{z_µgYÖ§V?gVE•eMÙNƒÛêö*èöU[l•­°eCT³µ8Ù3à§Ü· “eŒÉŸ±z öîÑ“nK¨zKmÜž%bÓD/¸ÆËåC”|¤iù\—Œkn¦@ü×-´ DäüŽø)dÏÑtÏ‚õ…nÜøj2`yT!z¬ùPfãjΚ^½¤lM°+­èq‡õªÏ !yŒJ:¤Å4-+Je¸ãí—¼‘B8rº¯M;Èë*ʲu±@ìíÛNƒÛëFœ1N+w†2 &|—2(홸 ·EŸGnÌÏÜ1&l8â¤f&’7ÕZª Ç•ßcpZÅŽÝ$r‚wÁ>ã 8›-Ùu–Áô¤¶"_ ƒqÁT½™[ÁÀEÉ 3t„6…)p~7›ïè?È3ÅúöåªÜ/þÄ0ÿªõµ¹3«Öu‡d—!gLXóU•ÜŽ :΢ã{ÛëÀ®Z{' Y‘áøòp¹.b‰Éxó/E 9(ãÊ®‰È°æ† T^Ü“`]…‰ñ¬ÌçKþÄ`²(árATÉGA]‰…Ù°êºnfwã>9úy›„íé^ޤ„YntH‰›xÆ.îKlÚôz*5ý¨(u±YU—¨LïÁ“ð5‹¬Sp·×ÝW²ñ¬úŒþŽMŠD7‰³º€pi[âÒ»pÊ«U‘dY¶ÎU‚ÊŠ6&½OD¢¡•Ož'wÅ4îµÝI_2оRXb |¦%‚šß©>/ÈC*0ËÉrÖ“3à™ñnk‡”&R8­ƒqjr!*˜y&“;õ°á™ …›-©Ðn&9›Ãj±ðu¸míUiy [mއ&<}¸e̦¸“rºÌ™ñNøv˜¦|Q{ âðYЖ ,M~3Ó›DÙñNë:á&óƒØ°ó½PáJoF|y ý°DYP¾áxi†å»ÉyGg#V”ævl96¿ëVmö–±r`+ÑeuújZ‘ØP>šÈ²+reEfwAUx¬„r²®d(3Me£Mt’%y Jn&áÞÈØ°§lVE‘NƒÛƒqdžn\~ Fø´ŒÜÎø) 9eu¦¬€Š&ÃŽÃde‹©)‰¨–ý#u±«(VÌÞ Úf~|Y#çýºU(d}¤~ÎM«úíZíl;öØ£ fš-7UÉØ™Ÿ? àÌü´QD$9ͰðÄd&¨¤Î®FÇ„àæ!X=ÇãÝMX£úª£`|r¯/?,ºií`žÎ ×q@Y™æiäv)k¾oÑŽWUï¸ðŒqvl>’±_KŒY¤RæÀ1ÂZNdU]œê§lÞ-Âîgx£ R;;><%´Ñ9KöëÈ¢±L$ÌR¹*öÛ êºgÁV˜Î„ÙùdÁ3ºdÝ|¼'AíÈëצ-‡Ô÷K$2Åe¥VäÎ\ví¢K¯[õ@ëVãÚØ©ÈÇ|a™¹í¦ãKoÃ‰ÆÆžtUÙ׈([;¹éËäòIfãRÛŠŒrôÛ]ðYÖdÏxC™OQãá··¢E™fYÖƒ9°¾>¶ ˆœ%bÉÁen+DT´•H8Öâ2eLxðÅ`›L”è=ºøzôIðZ‹QgLÿFÞ`)G‘›V†>áÛr,…ô oxÿê—ÿªuµn^hô?d ¸A‚&.L*;4‚=GlVE‘3s“ŒÄÍ‚&Å`ë+¬`èzÄ8¬”=DÚªkâ±ÌÄÞ¬Ë+á‚Áaë,zM bMVåt ¦4åsán³;*ÜŒh~äàI¢wB9xJo³ãÁÃrÁfY–uŸ£:nëX,>‘½SÓu_ \A~«ÖáÖ"ËÊÅfë^­®6;þ4¯82Þ÷i#–/Ù‰7.ãI­ÆC—š£ãIúyY(Ÿ¨³¡,{'lT´™Øê˜ ip94¤ÈgÌ€³0·>¯«oDÏŠswlPFÁÂyÚ$ê·,nÏÊC™ˆ4:ΈqN "È´ú3 öäuº-Ô"vx¥súIƒ]¯ãɲªpMª.øuq|®YÞ¯åѹ+õÜqZk"È™º¦YZBÌüƒîÜw]Á©G.õbUäfY1XrP“<|]ðBY»L;!,Ë8ð’4ô=Â*¹/*ˆH]ã•ÜFDðŠ;¨žRY88bÌØrKU¤pªž¾›¨ƒ„±à…ðBy–8¢,zð(Ø”Q8)ÔÏ+ûõ'AíÓ/få~t2 ¤ïU5 Äé ÚKÖs)¬ŒJ]ÄÍöÉ\ú>(§¬.†è[²ÎßBÜ Ñ“—e˜¦ƒì¯ÿÍÀ_+—ÜÜvy‹‹¬ë:nfúE¦²-4ÍÏ$lj¼â§™VƒI‰2|Pô?þñ(SÄìaÊa‚Š2n/÷ôgAíÊï‚Ι;t™¾¦C™¬Õ*ïÅŸ·ÛÓ'|xmöšgDz¹.AúðXÉÃ/°bÑß’·~Ðâò~Ï/Ø!{ä|{Ç™ÿùaCú«áÅ9ÄÀñÃU`°ú>=³§cÜg’%_yŽE¨Ê·©q³{J×>Gäu#¶åŠe:n ÁËí™i¦l:Þ«Õz¯^ ôÍÖ’.Jáœø„¤ ¶æì£¹¿ZḠ—Ð÷ ¾;fu °§°.¦‘‰”† òK™¸m{‘R=óuŽàª!‰V“LÊF¯1#ön"8p±+ЕßèX¬Ë3¬Ý‘›n÷rã´íÏ›£c=¦w–.»ºÍг—.\ON©É•ÂKÕ8'~›‡¼–'ÍcóP螎 hôÞ^b¯ú‘:«úì+;.ìÖˆƒò™°© 3Ò<§ôë:̳uÎ\ŠMÎR}·uÅnvHxÒ9,"lMÌ«߸5¦ë9`™ðwYÖ~9¸NƒÛ†ƒHÏÁÖFYV¦Då‹:l~›,#+\ÙQÆáÃl›!ó6è )ZVê\|èN¥‘Íù¬·Ü†Wäšy\ …âý ë]ŽÊÝŽ-––gárÇ%hØ÷)çuJ1qúE‘dYzÖ­„-zãOÃ%—–¦òq(, 쥓LL³ºØŸþ½i;z \S)Ð{cÒÇ™Û,è±vج¾ ¨K3?Ó­Vj‘×}ª›Œ|û}Ì:·äo¡ºÑE’ñ…x¬ž«/—ŽH)°my•½ªJèkºrÇëÏ…‰ehØgRn(îrnëÆœyGê;Ü„”ù£èÅ1D¶ýß*Ý7F™¸¸=yuC­&rº52GÇ¢ë7QÙ300)ÇÞŸM1ÌÚ ñ¸¿.vTbÔ>¤ß—ÐîÁ—›c„HY°QÂ1­çoò£ÁEïˆ&p[#O2–Vá°IëÓ³cM¹7zC™ˆr¿&ÁøqÝ6Q²Æ9‡ë›~w™±kò`Ì8­7Tãû³ò4Œêlɸæú!U5 …XÝ¢‰÷+¼¶Ž^ÎŽèpôÈT6FDR`àliÇžtÜE°YÓ?;Ž)ÓpÁ`½ì›C™/ôêÿ†í’f u-‘I3HÂ,ëM–ß À' gÇ¢g‘§“9}ìð0|uõËk¤UšGÁšÁ:kFªNF®þºQU=¶KN˜ÕlrŸ#¾ I3¸žUª«Û“Ž)¡uщðW·–‰ä‘ätÝ®ÇeÓ>=+Tµ;¯ˆÅ“¡:nL‰‡g,u9¬x`°N™3ãÐtÜ®°uƒ¬3:Á7ÐE±y‹+HZͺ;k3ऑäxfÈ™órm2á><Ö,´kË5-‚“èòþ3YÔ“ŸJvÅ`ü6Ù9YñNø&|U‰´†Å’Ÿ’2ûNð²’G‘ùnBìê¥wÍôÝùÛ‹t`lIɉ¿rdÀäØ?;> kܵZ3ÎÜúiÅdZ|ó öíó¦$ÅèÏÜcÙ ãÍ]±5½Ž9«G‡,‹õ«E'#ý2Àå.ýèžîFŠÉÄ¥²ròK.Töõ#ε9ؾŸ~ÈD<[©‚ç·c2nß×|{rl{ºë¹^j‘Ï`ç~f´¼¦T¶©o-»mMÅߦ^ƒÍ þüå„VE‘¾­¸Wi£âÝ»*skc:dßÂi 0~ÅcRn†É³4é›Kv0út±å^; ‡”;Pˆ›Õ0â„rýgt§ãŸî6ˆž8»Ð{ýþ´A¸Ùò%猲-¯öB9"˜eâS†ËO+u‹Ñ£´'É:oá4¢Ê;Æê5[ Ê´á#޳Þ© mÍZ-Sfð¹[úp·ðŠâÄKöˆp>€ŽeN¯Ž=þ+¬Vo¬;ý.åV²×(’²YZŽÕ$Rv&Y®¨­êè=¿„ eu¾n<œµ¡Ö/…%Î ¢¨}Ǧ²zäYTßWqÇé† ièÄèvx£s£µ}˜b6ꦕ0ñ³cI:szµ´Ôè=¿„T9^™á‰ßHá)¶ i…Üþ†lîßÊ¥QÀ)T¶BìÓçepD^l8NƒÛø@1Ï>H96Ë ¸Œ‰›ð†÷O2ÍŠÚÿ»¯4šm$ŽoV'2ñC öDø(Ï;'Ák¾Y+ •ù6ú離ºÔY–oå3B34›<¬ô6Ïú®ø).ˆ©&yLÎà,-Ætܰþ ê_¸k±8n˜4œ"Û¤‘Q¯ FÁ`°þq,­OaåáNï‡,è=¿ƒÆhk–A'ÅÐ>#wÁ3ãÑ|ˆY¬ò¢¦H*<0é7,è=¹&MºpqQž<“{“bÑŽ ot`îüÒûCì¥÷e#úÄþ“:Á¹fMÂoh½”Ê'ÁÔ¯èý»* /ØìQñª2bßâ ½ P{rL…ý$HS“’ÒvQÉŠ›ÞOhýX‡1»)}̲¦4Ñ:0Åi:gpy½¡öS2ý±Pº/R"Ê™œÓ³‚̰ÅÙÜow|™Í`¢öS§oA|Q}Äß‘Že¤éñï‹æN.( 7cŸí{Þ,6®ÉeøT‡XÄr·|u™fY–o⤙ÿ†Jø öä™48­† ü¦÷“Ú7º—Þe²”ÖBDØ)½¡öR6,%é zÚQz¼Ê/i= QþE*e/´^Ó{Eì§LØŒG‚…“~Rž „;/õIíb#‰0¨÷-›ÂZRŽ&ävÅdYVE•eÿ ±cÑ·$È}¸8¸­Wu`¦÷“Ú(¡‘Ô¾æ9“ŠiJ UÓú©}¢öá‚ÁJÞ°²1̙ܻšʃòS'YÁ9(½”Ê?cfû¥S²'r_ê‚wr@9{Ø(¦xšÜ qõ|û3Äñã‡$òH/n;©T’?ðö<ó öú™{D8vŸ‚€\‘“’®,ãæ rö’JÑ 6?á’G aü yÐ{r;೬é™gYÓšsBø§we›Ÿ‹§$ Ve™fX¬ÈM9¦T×W7ÇpÙÌ¥‚½˜Ô 61‹dãŠì݈áü°qëNƒÛ™>.‰—ºgL+ñBج¨S6 Õe̽]d@8,ˆ™`éÅÖWuê™`øeX;¦ge•eô&Y]3zå|qNÞ¾¬½Y0¬¿^$Ïø>tßÊqõÿNƒÛùAb˜pÿÎƒÛø«(KfÿN‡Ûø‰Ë•9“ goñ,È}¿‡»àµ[eG}™E9ÊŠ=FoñÈ=¿†»à€ó©gÔ¶uU IÁðÜ~€F™3óæo§NƒÛøg”ÎE‚+$OF¡élÃ$•£M3=ÃE#’ÅE`£Q[cLLý–)åRZho'¼ äyâŠû/?Ì*KÎH& Zĵ ³×ÉàÉ­‚–òyÓ>o“3n†Ø:{`ËÍ[ƒ/¬è=¾¯^BvZ² °Ÿp) pÿ 8²×èXলѠ´&¦™¢Q\Î^@)Ü Âë;#¶žø§Ü yÆŠÙ¿7×ÉK`¤Yj—7dÖ —˜kÌ4ó“¬ïÞÇ`£M¸:;‚CõÑ'ªI®óMyÆ¢¾†a.yÐ{}IßljZèÜÔuMÐj7&dD£•¤çrÁ3âŽ&4Ôƒ®Øx†š‘ºˆ»NLEÆ™>?@{@ÊÅÌÜNG?çb8ªÑi·,è=¾€æÍÑ"ʵ™Ó:–ÐÆ£•¤G+ÍÄÁ3`¥€‰äOÀe!O`Ýfu¬Hf&NN韬H& MtÐn [탲òàëäW”žñ¯,×’h‰Ë–ƒ}ÝõFLdÉÿžGȇo^¨«´|ó öí±Z¢žÈ2óAödÛƒ)n9Hä„ÜSÛ4ö טiíšòM=³u®KX–©-GNøðÇ„r¼jID&âŠÁ ˆ_Ì4×É6⥗2wǯ•ÖGY_¡'QÒ%q‡rå‚{ËÍW…‘^'O#ºÇùäG‘ôXÐ7JtÝlÌŠFª)Ÿ>T%™Ñ\wúPg½ dà3Zy šqZ‚±gYYddP‰!„EaÜ0¯(ÞGt<ŽÿÆ#o¶?Ç;å&û Ø+fÄ„²°·Ùô(°Ä ‹§:n´Ôó¼µôÖFX¦'dÖÄ_MiÉÞ6Mw2yÄ“B¾=“íÈvöBØpy“ÛByºo`ñeò¾AÐ_Ç„’d^P¯$SZá=×g+&Kã‘à¢_êHH8¦ü²æaú bšÒrbè΃۩¦Bü5â*éöõàðMr§W·¦šñ/0Ôv$.I*Èj*caårÁ 1rMqI+Ÿ.£ª®|™x83§®¼@VaÙÿgu¨ë2Îë;­GÃ;¦‘Ùg À͈Ä´Ù0³tçAí×yI§Ûƒ/àŽÁÌëT–±'”‹üÏ‚­>W º“ öì%®Ò#¢Ì‰°úã²kÉ®›&Ü àšÀ:+€(·÷y†¼£ZäµIj’Õ%¨éÉßü Çò ­cgèNšadÓ ¬ìµj2ÎË¥ºï"*æË#·ù{\“[6M¸mÅ|ŠmÀS]S4ŠoË—ÑNLšÙ¦¼i·_"¾E|ƒ/eò ¾A—È&Üy⥹™j-EgYÖtïþ]ª¦ü¿ÏTß—ù⪛òÿH§¶Éí²òÓ[^[/)—”+ÈÏø9¾¼ò3&œ]‘eå²òE5µå²)s'7X¯^ NÈŸ2Ç€›ŠyIÖgYdCÓ¾ Èó¯-=´33³ZeªÎŠË²kn¼¦FàMʼn۔#gZ`²Ê «â¼v_k(YcÀå`^@¯$V¸ýEÝ4IÛ#™xÄŸøƒ}ry\_X[Nq’xãOαÅ,Ë !xÅk‹),gn1Ãx¤š¢vÓNŠr®Hårâåo˜E<®èAh&x‰ª²aíñã\cZQ²,8¸`¡0MeŸX°¢,Td µ£dnÏÑn•8UâD∕˜@>²ßPÌÉŸI%`ZšauïÃÝ:–€"aâÒ,ÓÀÉ„˜ð/“DËDV +;'°,¼‘Gm9wÁ ¨ÙűǵÅfX§Y¸à™»!'FåË™ú‚ø'&éG3ƤÍW°Â¼à^Lf­i“0»¨i¡¢n¦®Q}Q¾–æÌµ‡ªÊž6tѳqwO:x…h3¨àÉÀ«&‹*gEñÅ4mļ´À h™ÝÞ¹2Ñ%|SWfã$lH fO -E‹wÑ»3ùj; ]»¬ËÃÅþœÍ@n¸7‘)¡iÉM˜Ú¤6©7Ò–ctàf´—ŒÉ¬¦œqò‹/À³,pg¶ëÉ$6¸<€ ”’&¶¼†$õŠËÃ^#/QÕ^92Á¹±O\WŠéêºñ]x‰ê:zÄÈ£vè˜â´Yi2™bŠ&Z,š&g“£Œ+\Ÿs‡{‚h‰×Çšð x) ŒV´"¬HÆýVONháyƒ"Ò `ˆ‰ A·³ ñIG;øQ¯íΤ‰ãì°QÖ#GDÅ„žJÆ +ñdåÚ·Ò32ÌÜJ%â²x„Všû“DkÅÅx¸  ‹"­™5Våz®¼rD&ÕZrjÄK@“BiºòVÌŽ<œú޵]jºÕt'‚ÕuªëUÑnŒg€ähÙ™¾¦8(h‹£¶¼÷“Û7O#¿M›–:å#W¢î£¤š!Y[”‡2ÃÍ™Iu…Op®Ï‡w ­òB¾@SnLŸre5‡•G.DF¿â¤qíèö]°à2¸¬äh3²×fBøòeo V¡?$¶œ¤L§“#j’ò FÆü Ó2Ì–|‹Ëeåº ,H§!!Ðn±FÎÒE“´³/=aQ Õc¤H¡S_wDYº.Ë ºÁ“ú&ugeá¯e%r…Wwqêj¬Æ²»¦<,êÝlÏñ¬¾5|b–©ÈëH”p9¿Ç –‹‚øçÀãÉÊE›Œn ±ÔŒ-ܲʜ¾ˆLãÆ8˜±xâšWn ;³y¢Ÿ26Áã<ŽÏŠqÅx쎳Šœ•vqSFÄÐq³&*þR×w~ šÚklµË&sv|Vn¥‚ÅnIÛÇÆwZDȬº,ê,ŸSˆEÔ-p¹‰s³b©Uá$HO`ƒnu5O»ÆôŽ€³ÞÉä˜ÜüqÅi2(“ÕÔb¤bÈÍILÁ3bž¹²Á4¡ž1u (ë‚z¬kãÁOE…ˆröŒ‚Ñ+¦ý6„x¤†š â(ábOT]EãñIx¤že‘Ö Åۢϊ9!ä:ì´…i‚Ñí‡,SäMmŒ\I±GÂ2ÌÈÃ2z®¢ˆÅ9È´ÝNÉÛ-‹g"n*±3)leQÙÍÓsõ"Rˆõà sF›Mk §GWè»ö¹–eŠÅb³vx¦Ü]—Èâ¤&u‘ð嫸p!ÅMÿ3©+È.8§ˆ]eàê¼e©ß»bnPÂÑpÆFÇ¡‘””€×ÆŠ ËÄôãSÐn†“á‡+!¨FLJ@`"MTC_'^Äm—Ê$6ÓY¬)åG3'²Nžb~ X/)Ôs¸"°Nµ‰5§Mm<ÀK$nž§u¤KÇÁ0/x䂳¡™;©HÝj›,‚jHJâ¼¢O#3Œ|,HìúäšÁ&û™™3#‘ÂâˆxÄø8¾(qèË Ž,Šsan»6) 'AU L=î)‘V5fe$™V*œ1†Ú,‚  –°È¾5‘mªJf®Ë/vn!0ȘÖ™(©¨@™ò±-RQE¶º=XT7ñãv<‡Z×§ý õœ1V«Q¦ Þ(/ 5à‚–“IREH\Eee¦È«"ÛÁÕŠÚ*—àíŠ*@J]¿+r´ÄÈmºiúö?Ä_+ùl¼¶Oi™5´Ò‰) óºò‰ ´öÙ5µå²Öõž\¨ærâïŠgÁk¶XðŽLŽ™¹±VbËÈÓ“/(”s±sÊX6)Û¤Qçq…™´do C[:Ši·%rˆø¸>Ý<Òpbgä(D‘íàêÅw‡†ßøÍ{+“âñíØ¨è€¦l8x¡ÒDxŒ ݦ•Pó)ñ ßÁ·^VŽ:¶u¸!b“*”b•KDÂ䆫ºjŒ¼RQWÊ…°ã7±¡ /P÷oFgôãcðíÎLý,xˆbž'á Lp`´½#…ƒšiHSÈîµÇ)>*Ä‘Veâ:8ò¼qsOn› ÃŠhIÐÀD†¢fÞ(3/bãUÝxÄž£&¦ˆröxbñSÕå[‹<‰Û;U÷“ÂÄ¥òÈw%ò"žû’ñ$•>Ü*  ¤'M‹0HâšÁ2-Ľ±‘>|ºfKA´£“:pg~{¹'¬ØsVn£;([Õ›"|Thì³#,ÏØb±UÔ£ŠÓu`­?¢9²çãµ\h_‰‰(ò§£‹AZHßøé9,Ò©-JÅn†B'45]׉ëÕŸðú«ºõ6 ›íŠ:¬è`RB$€2p|ScÄÁ‰n 0‹»ôggvå®ê8²uuI ‚eæ:+$éÉß±ÅfXªð ˆÄÃÆIXZwG"–Ê"Ì£lSÄÊ@fhÏ#ÐÜü‡âôÁ3aüß`ÆÄ9¢™ aÒrniÿ Ewfä~Í9&¶ëÊtY׿¬.ž¢an”ÐeägÁæîðS{ô¸eYUI[–Vá8fnFTèù/^AôV´økòkb/;²;Éæuä:‚]FúL³4Lû“&ÜEE&v’ÐÆóHÓH°rœŒ MÅ™ànŽG43¢<ïÊÍÈÊiIÏ€Ã6§4’dCÿdш)l; ™Á5´Ö¸£v6øÓda—¾ÅÄrôÈs"lˆ¹:ƒ7,½C‰5qe /ÐÁ€”è«»¦×Žê´Ûéî ÊI^GàVÉÚhaŽ; +Îl>IÔ[†*{îÏ,¯'+uk›5¡Ä§M3? Tòç@yQÈåÏŠ’<Éë§wXb°î£|¯¬=;2;sE>D/™ge<ÙÕypúKDä¼wfå~9V\Sq9X–\¸‹b au$lˆSl¯ü7böUçšóÍyæ›p5-’‘“d˜WÉ2±+Hü!®ò¨k JIÌú¥ÒnƒŽ á•ŽÖ :,‚ÝG5 `ýŽ gôÎÝ+1eågÁ<¤üÎøýQ–ª®BX±)£`çwÁ†£‘“ÌR;ñ̳`‚Á2{˜¨Ë3 šèƧ´òôëǨM@6&®Ò"é·AŸQ€‰G ùLȤràχ[*±™8³·^$-ÂVôè³à£³•5~_^I°è‰e@yÛè°3Å™OcK‰íØbwIÝù…±Ný‹}©â¤óv¥2Ÿ!!~9‘†Nˆ²Š¹ÎÓR8™Ÿ®\„د“UfO]óh¦ú Ž(«3©#ðÈëMøCUåMY”㦆G%4ŽÉ°Lì¨ÕfDø,âŠV·Uk’{ªÜxNBÐØ!3,·ñƒ¨+ |¶+4ÊÕ'‹† LV=†<,{¨ç(Õ=Åñåu¬ËU–ªy[ ûh;…ÁYÛ„Ñ^zö-ðÝ¥"Ûê=aájî<(gš-7åÉèý“}³}Ý´•󡨙°G£vtBžEïÍ O)FpѨ#M*yE—¹r;côB$þކl· VußÅM]™˜ÅO¦êhƒkçB'´­Ù#à Øi62 kœaŽK/ 8àªÄàܲHÀÖ­ŒƒÏ—°™ñ.HÃ93aÊ^Â.Ë#¯WN.é¹±W.´M-Ñ4„ÈŸŽÙWXø[¹‡› ¼ŽíŃ԰íèõ.æSÌõ¢ÁYÃ,ß 7A\‰àÚÙ—ÇÆ¾>4#,­å²<Îæš®(k &l>Ÿ´ÙhŠKb ¬„­ã­Á°vô@Ù˜=S‚œp"Èë`Ü4 FψG“Ž ’à.™ñåÜ3q.&ø3Èî | 2Í›«#àÚ¤ˆ³rã‚k²2mÊFPî¢èK3t· 2FCº›)f):qa™«X¡™ßbëŸ#¬x;z ®=³} wMSÕHÚïÚ‚»’ޝ¯ŽJ_Œò飑ˎVú;¬Ë2̳"õO]ÙhÊš°²*¬£¬ÃÁ‹VÆ£;b®Á¦ìø(í)n¶I$Ôu[G²`ZÊrÅùãœGq5ÄÖ…¬Joǃ·? X(ÝÖrNF…ñäÖGO·ºøùÇÈŽ™Š7fn–ß{*ó#M(ºyÅ›5›ƒ X¶Sts' “­7Y}Gn<ϸ0»ÛV®jrbŸƒ2Ç·o£Ó~¡Ó;­GNnšGZŽˆÝ–§Û«ëªµ] âÜŽØ¢«”†±:޳.¸«gNØ}*6Åü&S ò7+¶*(^&gÁA}1ŒÈ¶áGüB»a¬ ‹py)o“ØÌ™ñDL(­¯-ÐÚM#?œEã³»eÖG7r@"ãWi«’¡Q3`·(=2ªµ—éŸqÍ­-Tp8’ÌÌ®nƒµuìuã‘ãPn„(wu6æf³ºÔ~TRfPã+½ÍA‰Îw.ní¾Tò¿LÃhpZnœi-7OÓôÑ|tÖ“ l:e#3Ù‘Ùñúdm몟† Ù75?Ïröà&â«]ÅZ¹•äq&¬Xµ«ne”0äDÉð™fá‚ËÇ3¬ïÂ1ÅLJ6Dznà†n& U–¢"bV«è—ñëâ |cR§ŽÖ÷†öl¤•ä~ðG2Ž» °m„І°º`ÊÎyQÎïÐtÝÓ}Ÿ%›èʦlŒï|Í‚uŽ=Zî*ql¼ƒÍšo,ï' ‹’¹6HeÓRXsäÁ`™–^õ¾—Š‚|ü1î%³•;ãô¶å¯]åxë„K;,íÂvʽ‘*Àd²(få)…Ì%фٜä`c½™Ü¸2t‘ã°ÇË&8ÅN›–´98;b¬Ä\H3'ôï , –@o¦A&™fôk¸ Fqv^©…Ýh?ej^›>G«Aš'7ñÝhº)]úا,­5²—y2;HÏÂ_LJJ &bt ‘ŠŽ4Ûqaâ›#ÁNHcyTœª‹£ªL°âÈ=º×pä—…ÉhÜ[„±çNØwŒ¢,[•ÛèMôÚ3³µŠ®¶Ÿ)†dÜå32Ôd&Ä¥7æ/1ÐZgMd]Õ‘À¾£; ¾ çdDåÊgƒê:Œ±ã¸ÎQñÅi²vQÌरçÉ0&*ÌZƒ9àPÕ’D0ñ–‘n ØUkx v³?=¹ø3b†"~Y#K.£ñhu“lM„ÕŽèa؆ #úC}5™R˜§Æ)!¥nC‰åRdÏÀ…Ik"n)ËZ¤îÖC3}3ï‡ÉV§¦¥<­ïÔݿح'DsmïŒ\’3ƒ”Í•ës±'¨*}¹4/(%ų&|yI“3,0FyVªixà¥äå©;BñN2©E­Ói”•Ê4ã‚Ê´YÝ”teâ eÓm¼òöŽ ôvúu[­…¨5›Ôµ¦&æœxX|K£Zl©ÛM‚Çé³M¦¾Reò³(·)£•©ºÿ_,«]™¹¶ïéNX,íÆÌze^L¯Ð8ØÔ°äLé¥P¶æ`QLÒr?£§lV›-6SÌään\ìø ÜMš½±Q0:8ÙÔxcä £uæ )&sP×"Q ³np4oÖ¯o¤·Óàã} S(ɹžSþ](¤ÎÆøºwÃésI‘ñQDļvC gÁEq3ãÒÝ"'nï˜VqNM‡&ÝýJ`rM¯ Ñf¹Û¡a±lŸjÇbLä^H§°)äbX‹&˜]<âÉŸ¡sê4äÊYMœ_3p„3ºwÁn5‹ª#™Ärý%¾¡Ö‘K›Ç#ƒÁa¥nY +upú[¾ iØ–qQ;;q¨Ìã‘1te¤i6ž–Ýý\ÓG¦QGbãŠÌË™¸Ûfòj(âèV—%–÷âý&REŠÒÁ48¡%N,8^0š¹CÕ‚É…™O&F¬ÞŸGo¨•ŒÀ„²ªÓê²òEk2k,ê×áÑŽ Ë(2ÐE]Ù¸ðÁåLøýÑú(ƒ3³aÉKÛ‡²gÇß-ÄBÄŠº§D]§ÛÔ°”\»õr“åVo„Ä¢“#ð1Åi:xÖ“¦á$yÛâÍ=I™epX¬yÝ–YÛ‹Õâö¨ßs¬ì³2»È"#M¶Hªmª]«Ô›FlPÖ'A[ád]È+}» pú`©âÒ!Ó‹»¶.ˆs1ŽW䎙šmµÕšú<ܸc‚knÈI¥c®íô¢ÉügQE“–—#²bå–Ó’W>JGÀ…‰Oµ³©a(øµé#_%2ù9—ÉÌ‹p”—Ȩ̀¾s6uZLx ãÓ¶X“:Ç ì°OLJ32!Ìš!e—2‚ ˆÇÑZ.€pV6æ"¥X¡q|)Ûf€2z‹Kîjˆ!`úsv&y>?B l%Ëz¶<‘rN¤‘ä~VMa…ÀéýWŠkXqgú)reYVUMþîGlW«&|S¾ Ŭy ü¸±)ö¶tpGõRmËÅ4y:‚£D½”÷²¿1ÈÀ†F.•gÓg‘À ¾+]™fúòb±XªÏ÷p]Ü6>ÑÇž£}Ü»¿¿@ +±pª|²L1£ÜÁ—ʹ—:eÐfç®y…\,„DÀ2ÎÀ™ÕˆøX‰ÉA >=“öíõx.¼h.&|yl3©Ðz ,ܘ}bç…ð.kumëË»ûð|Œ¥9”r`ëÙ=‡hÛv%â2+v4FIGÊéÛ]ñl½WçáM½L¤ މóp¦~¤ÊH²tIbý»}f)Ê4;’šé‚áFµÙÇ3º&ŬL1;YÌšBBXõðúŘ´]DJ8ó»@N¼R^)¯ж ĉ…y¬ÏŸž(µG/'Q¾<&—LnZ×è¿¢ÁFy™Züx¹pÖe—Q„r·dÍҎÂ’w5YþëE˜™Ö+%•DYÛ#:ž,Îé›±~ݾº'•4« F:€ÞžÚDžŒq·Ó]±GGv1meµ¾-Ìâ´…L.ÃD¹ªŽ˹ØÅú!¶j í€Iö¶dù¢y‡à"䆳¡‡±.AêBø˜°ã‘¾-Ô0;' ¯m±è?Ò[ëíèžrÃo©©¤v)ärƒ¶ ý>‹¨ìÂÆ+ãä6¬ÒDø¬xáŠ/ù?’èlfVÛUM Äû|‰OLfyvávlõjq¨è>âOÇ&£r\bèUªS&ãb»Là ñG6=õ?ܰPa(Éiõb°ÞœïØãÜ7ð¤Óq¾¬ÊÇ(ƒ’މ - v)ÞJùÔ‘äâí‡ÐpUªk(!Òi¤Ê„ÛìázH³Fž@enàB×.=µµK€3ãÍwrÜøÇɹEœyðÅT Äý¬»aÊ̛±.-ÔRàÜ)Úfg|‡™ý”óæè»ý!¿;pËÀAÉaÃnöR¿ÛT±dâβ²*ÌèA…Mu ñ}‹VΣ™Ôb&™°V.R5ÓeSyf˜ 8:ÜbÎYU:NÂ-ƒrK+FØy²Ô¬PògN8«±4Rsm!‰pÅлâ­E±íŸŽfn£'<Üe̤—"9œºmôvþ W®(çQ–ª¯L]Ù°NØ©+³aÇMÖD ü&:’¹ wð¾ò¯3IA. î²ç2 Á2d^£¶¼&bÌìJp[ÍÄÏ‚Ûí<¬½ÐÀÜw ­É8òÅ¡S©¡Îu„ÑÒO·nÏ/ %‹Å&véº1žG3rêbœý¿‚?ªŒÜ8W| ›2Ç™h—;b¤ö”0~ü&Áâ‚ÉÊND.騖ß'"õBÿn×ýØòZ¼Ðn±Í3ÌKi£ö“¦²Îµ›%“NÎþK+õšÊ2?%=ÁãfÏì2úrZoZžýFXô¡‹Qäã~Vnané¹[ø@{9³-FYÛFĦä š$ÆÅ‚;8ÚO8²ÍßÎÞ€KÕV‘ÀðGi™Î\iZru)ä$y£Åhº½GØâ̼fM‘x̆»3´ ÈŽ0{õ#vå£wS›fÆ»}¹6$͇H'bXð ùš=´EX®ñ?;6*ŒYÕÚU<\˜}¿„V,Á$NNõÐ×vãa½¯)‘ÛaRXsT%Î;æl±†d12˜r½sïÎ6$"ÃÇP7-ÄI1H§™†L³­eµXÕŒ…\ŸÅæL¬_yHåÇDQ@Ø5ctÕ$uR†E‘—«,윑I•IqÉ– /e§ëÏgÊàyÕi2>*ÿáÍZ¯Üñ“ᄀ»j"|y_è­üe"Êܲ¶,Ü-Eë—õæVj”ã’(ÝÔ!•™þ•8â-:z²2xÝ”S.Å™\?N…Sgg<HÈK7%¹1uŠnÎǰÅYÏ‚ŽV&©m°³ ò†ÝІ‰·KZ «I‹})» >­$ì o܇–áÈV •i›´Ì¥“;ð­qáRnIËFúUvÅýÓ¯d`.µZ,Kž8ó¼04h£ÅX!8j¡,Ê׿™»HÝÛ±Çh0áÙ{ÎÊO»Œ–4ݤ®ÍkyM¾ú\¶öI@DÉŸ$Œ §¨ÈO3¢,¾†ß^Çלß;åuæÍ†ß¸¬%™¸]‰Àú¹¾“\2‹É‚ÖD±Z*WÅùè,WÿÞl™°V¿6nÖ2ôìL3´±äuàO†^Çš·â˜YÖU¦$™°áèNß‘13ý¿‚Ùöü_U“J¨×Ò\VeŠÝ«ŠÁ:Åcôj爼x§d|-M›ƒs:‚,¬ŠÌ†ÌLãÉk¤Ø Ì™ðPf¸1#¯Æó»6 Ãà1Ï‘G’fÁcÃ×í™KD)Óz/)±ÍÊÃnßÁ§,J•=u @‹ŽU‚Üîìb³,ÉŸè@y; 3¬¼ ƒ, /$÷ ”V>Ýv@Y™)ŸbL­ô©¡ÉÆ7sGŠe%|ÎUŸ¬L°UY¸YÅF–©?™b³’Å(¥rQØgSKŸƒ> oÿ«WD<¹±OÙ·ðXcÄlíÄ œZAÆ[ +®èÍÍûV¡Fy¯6%sÖ{¹1X¬y(ÐWŽ(G/bRÉþ—-|¼*·§.›c—„žÕ‡nq‰™VtuÜxí?‘> UI?#(™ñì›ø-ca~GDø¿rßA¯OYþ)|R³_Eúa}¼'Å3’‹66YÜ;fĶ:¸¦v…y#Ð!Ì€x7;z($ÏÂx³pÚ'X ÊÈ«# ‚y„QnY{þ V\xH±uuÝ›º³6=i´‹…ø5F ˜žÙ‡4é><Ó¶Ú⟷’$UÉ4ŒŠRM)2is&|zò3ãÁ‡JÀm/÷bž'Aî튑¹]±X0¦Løõ›¼Å ãô–|G¸HÙÛÁúMôlTc•¸aÀ½¹!QÚ°²pC;:Ç’Ðe?¨1-^8áy}ÈÓ‰âØ¬«!2ã‡)ÄÆ†&eL3+ã¼ÊPÎÄ+ 6•VL9%…¤RÀÑŠrAM™Y ¯ÐÅb±è³}œºƒËh_σ¶*´šSqÜ"o«æQÇ“„ æ¢#u㳕<âjODÍ+¶(GÏÔoáÈÇÊB…™¸Z‡7fßDÛ›íá„]ìB.¥¦š¡*űàè[ù2Oº"¸d±ÇŽ?\ôÂbGYÙ`"Ìù¹[ªß , ý¸”"JZˆ¢që·Ñ*ÖÖq¼&$ÕÉ”@ìé¦'Nîüf‡Q«BñºÜ%ÈÉ…Óô$,­Tñú»6*´Yy,MÉ`Ö)w Û:bÇê"ø8HÇÈ|²­T%›„ÿX~‰·7ÛÌ< ò­t™eEyK;Èž»*çÐwÁHYž©`ÿSfÅ qe=|q•ðyIÍŸpÝÎ8}@K¤ÅàãŠÓdÇlWŠH‡/U¾‰073pqÅi²öO_[a28œûAn$YQZe!çu\[©nJ(²ñvÇ–J¬è‡/lS6-ûu‰±è·ð°<¬iäg.{0aÕ¢ï*ƒz ožtÛò ñÇrˆ“Ü”×cuòŒêõºØxî_† •aÉmù ê5[`Àº$ JjÊ ùx»bŒr¿lÝç·ÔX°p<ܯ(²ó#Veû~¡f.jþÜ*ϤMê¥öÀÓ±«y˜žÐâÜs?ÔY±QBÁØØ‰ñëÿýæoáÕßã~wˆ8W/N›7Ñ]±F9_’¿ãƵ·‰ fn•„ÜöKÅi:­¯&(±úFAÉr$Õ l=œµØºx£‰Å0ú8áÐoᮩ÷O‹&gu»âÎÚL£f™ºmôkcÀY°vñæxÃ+, ÙC¸lÖÔÖÆ6–W‘ÓsHyÜ$ʵÔ^ÜŒ Ò"­™åíí‡NàôsÍÐoᬅòªmö;b€2«´šËOFHy"üy™– £¿%~@ÍE Cˆði+¡l_éµÛêê2Öe¨ËQº8«cÚ7ðê5›‘Ûcg ¶Hxb½¹¾˜Mƒðª8—Ï #¬¼öc|Ux]Ÿéµ:§í† ÿæ ýStgƒ*vòoáƒÿÏ›y·—…8µ$Üb`ú„ãpÛ¿²oÊ´Îp  €C’íç^ŽO„»Çì›øa(!Õ(âhùæÚBWmˆqÏ5=‡™þŸf,É߆Ûý˜sÏL&S‹Ö!<¯ÝÈ9_±oá„¶Øð…ë™yY¾Y™ÛâEV¦0ò»à“JŠ|o¬×—;wV ì[øcª±i=‡ôš¦ í‡>ž•ÙñàE•j§“ª–‘3"ÍõªëÝ?dßÃ#<„Þ½ _rfR0š–,‰¾£Qøb´Ùde¤Ü²©eÏõ¶|zóêŠ+"Éî:òÉ ¶A+VsÊ=“ £&pFy»/!“XdÖYÙ¹G‹è«âßQª\qàR°£´Ìˆ³}pÍ7§V*ir1ÊåÌÏ‚‚\è‹*†Lý+ç~É¿…ºfÅA«"äÙeraM`ÊhŒ-²ò…M6§Ô€ÜW–žrNø¬~¾%• ¦tö”–úpG"y_S+ô-K`±r7ð²Uj¼½RÆ@ÿàf<Y]ùä,­ØX… ãÅ¿…? ¬ú$ø+j¿ø3Èàl\¥`EKa‰»<ŽÓ‹ Ÿ;·ðÊò·,òi·É2-ÀÄà¦|©'²NšWdv\›²–\‰ß™CDßÂD9‰›C•3â· /ÐÛù[> £wl Ø<¬Î¬³2¯^ ü.Ì\—¢yÇü#VL Çã×2ÊÆyÞ¸;¾“cÁ¹ñþ ‚ŒÜK’Ý­?ð–T6EK>~¹ØRJæ£]›Fþê—¨ð’à³.©ˆ²©fÏ´xó7ð¬Sz¡lbÄ.?â)¡ÔOUÐÖ'@•¹™cÌÜ[¢Éø7MÎÜY?OÁ»§[|leÆÃ‚ñSs7áån8ðǃ'àÜ_ ÜÁ¹±íã‹P¡®1q±.˜»ãþ+nfX¬x?àé¸2n-Å“ò?LŸƒñdÉÓ'àÜ?ä~Ù3äz×u‰O&˜‘fÿ73rà7MÁ¹aÁ“ò²~vä~ ÊÝÆ ^'mÑÐî L\±ˆº®p²³8ø•¾¬ý«ðtõ܇´"aLøÿ vÇøS Á:‚ûË,&¥ÓDìý›FÃþoóÓž›üôßç¦þ&î±tßâVþ%Š#aEe™E+š!ÇüHßÄñRLÀŽÆd02ËÁ›ñ#Õl]<äN5}«&l?†eLX§²H¤rX¨çqA3iŠ9˜PZO`W”ËÉö™5¦^S#´†g¬KT“Y&^[¦´ËXQÚO#ºgÁ5´ÖÓL.žae®)í2òE5¦OmG;#aOi×’KÉ$öI §e妶ɬ i…Ö£w-ü5ÐÆÂ¦X½1þŠ+ )í¯-ÓLLžÁ?>+QÓNKÉ%åòÝyn¼µ™Û”¥^[),æYÝcô¬SZt6™Ö£-QíøNnºy‰Cc2’ÆE$™ø eQXÏõ÷|°§°(­§|Tvp^@­FSÍi^x¹3-QGa™=§ú¦£­GA3‚ò‰yn†ÚÍÕoá³iÚ) pûMëô9%`C+3`QØÌúŒ¤ÊéŸL"žÐ¯-×”éì“ðq^[£ÉfuªK nË\—’KÉ$ó’ÎýèNâšÚ{‡×DœV©/(—”KÊt±LLüíõlqE “: æ‡7+¾K7EÇãŠÈËÆ$ÕI´M13¿äïô”YKc‰HåüìGdnVúf8ð9X“rfävǃ»›8¢uŠiI“ÎK;­bM12rwLø-bC+Šò‰ ´v™1»/%ÐÛ^Zò“Ú%ä’ò 9rÔ÷ï¥Î³"÷þwNiª/PFÁÎÝÎ)åä +LÉ­2;.éäwBn+É%äòIy$µÉ=’Zĵ‰j’ÔtåŠÅcÀ$pG#šqO9:—”I­ºòÔ“fNøõ°YiºÈëf„gCvò3-qOdY=§O#ºÍüò3ÊúlK :MØ»à±âG‚dêÎ?JÌíQ5qeƒ ת+;,yX“ 7rþ‹TSÙV<Žëâñ7Ù}º”Ûþfþ…&F͉äoùý <1gǦ݄•ó¹Á•ddÄì˜Ý“X$R“ý5¦&MiÐÙÅ<¬é¡^#/5Vn.ø-aê<ŒÈ¬‹/-yh,ãÁߨË;&•Ÿ„¶2»Ø'Nøÿ‹ÿSÁúJ¦üògfú bµzMÖÁø1-OT]x‹Äuâº(~ Œ©­;­bC!¿!ÄD‚ªfÃ¥$ìåråÕ%nlÆËDT  ÏükQÖ«¬îµj:Õ|5 ®+QñúÎìë£v.Nž\”Ëɬ+TV£" R˜}=Ù ’eå¯-—”ɬŠi…3âi§\S6]±^#/xŒ¼Q^0¯S7|’+ÊdöÑNN³ºÕ%¬Iäwÿ1` ›ÏQ»#=VDØ}q‰Ù4äšÑ/-5¦ZŒŠqdö×”ëÉ%äÖ%ªKT–©-BNnÿàøìå^[-aè²sdÄÏÃZx³§€™dvÿ/k’k$šÛ¯-yl¼¡^@ 6%7åËŠÔts/$—”ëË^ZòÙyl¼¦^[/-—–ËÊå ;X­Uªµ¢ÎËQ“¾?åÚjoËüñMMùž)©¿/¯cþ:¦¥ˆœ´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$´Ih’Ñ%¢KD–‰-Z$ªƒŠÿÚ?ÿɵèÛË—ÁbÅ‹-Wöˆ&—/†q=&–-G$tV¤’„_c*}ÅËÓîÔwZ²ñISŸ ã­Ød:ªêB¢C©KäeRTï:r’\ûŸ¡zFï[oöüÆéBZ¬‡B°ù³Kñ'2íäJª ßsJûši#ä„8ÊÝ©¹_£æW9‘YH%º`•¾ פúÌFéÔCÈlªÊiVqò¬’¿2rüÿÈȃ[r’ ¶Ó¸•ÙI?ãô,YN”Žd¨Ùd‰#³ò^ž°éY,X³ôƒš–« È…‡d¯Pèµ—/ò!hûü–wv\Ê„ÈçE’åÃga«2ª“ó;ÈÉúÔƒêUìÔ#Œu>açR½'Uf*:­$î/K’¥Îš2á’0_lÛyG>ÓR&ñ.üîs99TèÈJüε}Œª‘ŸáþG\ÇIdoHRê2$÷¹rçQÓ¸: ª5JB—Á~;rôs™rÎYM9Q‹—.@äÒäÖ¥°Ø±;wÊNñ9NᓊtŒõö!b7Ì­Nï"ûwtAÝöì´|«ê] R• º¡Ó˜ÿ‰×Æük»y’£¦ ‰¤Ó¤¸× ¹rV$kð-I”¶Ý·VC¨…[Š´üLÙ¾6:ÿ“aoÐuùJÑ…ÌYÒ…oÍ𶳿ìGÓ÷?z/©bÅŽå.]¤¡h0ÅzUäu£olü0HÙš5 šsÚŽƒ’^2ަšé«á‘ÆRÚ4în•êC§‰ô#ŒÑáM<Óa&ŒŸiC©}ü”ÝTÔ¹s–Ï$+“3ï‘Ná³# Ô|Ã[Ìrã¨>båÔû‡\Ïàr3}Ý]v“˜ió:Ôky1Ô3™÷r!Sa$R+j±*}ÇÜ}ؘ±ZDbzÁ#e6Îèuå:vóVRM”nnÄS‘§æu)Óò!F]»¡r3JæœJõtû%Í9iqÖ¨ŸßÏ€92>l ²bÈ\û”¸Ùö_œÇJì4ª‘#$'×nˆôš^Žÿ¢`«zJBµ±0JX˜všŽBGR)èê@ç5œœêÙ\‹®ãc«~EÍù øþdÁ÷ —*Í51óT‚ÇR o<0µîOš×©GK¥Ðé‘—39Ôï:~bævJù ù3æ9ŽœjVrê\¸ë#óñ-nB©ÞZÔ‚ãfLN¨t‹·MÉË:RŒ£N¢±aÐM$áÔÿÖŽƒàò$tÁzN6Û0Ë4¶ÿcúòeGKªš³|ªÝÔŽ(Rf°jÔIÒ‡w•YöL«FÊ:©§Hì2Œ˜4/ð,§ÚÇR±Ó$"'ÌþD|¾$#Uÿõõa¹Õ?s'½çõßþFO3:x?ë83¨ùÍ?¦ÂFjIloµ‘“d•:òþ†²$¹(‡J“sÖ¼ÓøŸ/~LªiT_~ì©îÇSåò± ¤)È˘oÆ”Léä¤Ü>ߘÚOÅ)«¾± E\ÕêG+| ±aó«a÷óÃðoL Á¦äSª°sÇ4Õ°|» B6}} 9³eýI9|IouOÈêFCNäË&„Ë ÿ*IïÞ!;$Lª>OòiüˆÆžÏK—S§7©}‡R~©^£¢EFÛ'b)ÓG–'à”b˜júP³yRḡܟ*š³(¹‘;†Ï™V½>§TÇI8]0_´N‚fÙB*šû(LIà ½ êÁ 6Î0êÞ •}‡SØ?»*t¯?Þ¨ŒäSËðf>|Q ';'Ìça{—ä{ø~ãp¦Ê])¤|¢½äœ='TçJ†”Åøyór‹—ñ¤|„æø›)§,æ^j2¯À|÷ú ©èBúâ>•5fEb>‹¿°É¸Í]vKQ·Ç:¯‡íÀˆ™ïÿQ]YM9¢ŒIj\tTx7¿}=ü?aüø'ÈôùýÌ{ø~Ãð—Í%uf“Oãþ‘ >«È¸ù$|ãeÜ‘2¥½ _!bÃçŠêÍ¡vòýÆËüã‚7çÚiLL8Ôeá 2©§$âê u!U·ÐzÇ©×+Wg5"ôø~¥Ô¸ý|ðs§3™Þ;ˆŸè6ezÏ¡>ƒ!§sTîÇ#å=¾gú2§™§ò[Ÿ/¡6¦òKìdtÙO^î÷úЧJ 능"F^hiÍ_þÒ6Z2'RG?"2·ŠªÃv5ìƒìÞ“N’w6\,ª?à·1ófpc¨|Û„m¦‘†Fâ/Ÿ‘Óó:Õ~œœ±ÈÈ;¹kP•"½<þG#—ì»'§ì<‘4þ¥Ó±P?äÚ8ù°2 Ÿu•:N¬Ë䆛ùšrÇ–èç–ÿÉ¥¶°w–à,…Žª:¯ÀéGïSR eÄÊB¹¤f!)©Ôt\“¨µY9nЊ}«è:«/q՛Є'*“.Ud¯B Â$èÊ:´u5&Á†¬Àîû¢x’|÷xr8 mâ8ލÔs¥ÒÍDÀøÝ*ôUCÇÓtÓ›,rQ“¡ªþ A«$/ÈÓŸ}u­ö:QbŒä ¼¶*iAÕv/°hó:Ûà?pÙ•þ\yÐêGÛõAÄA$fd%¼êˆ´g:TÖ÷–cJâÕdõsîS£7©dõ>Õ>wv4³ùÜLÙ|-³Ó”deñ4·Ç‘¥o¿*²wš?»mÓí4Ù*Ù`†AµPéSî.ÞGPè¾…çüˆ\e÷ŠIþ¦DïQ³«á|†ŸÉ¾´¹ Y¬môæY!P|ªô“¦ø˜õùþTo?–ÂÄ Ÿ×`ÕŠëÏéDó”ì‹2}„eMM_—?Ãù"Pr1º]]ì~g‚N' é£d^YB2åsRܸù¿ ¹VøQ2üMY/æ:ŒŒJ ÙéÓlM”×Rh‰‘Pd6긟 ïù—ÿ—ꢳ”AßšüÛ˜›×^k}j™—‘©0:O‘ÕÆþ;Ò1£’(‰•|ðº5“J¼Î®¥•$‚8s ƒªQ|Í#˜ûˆGæ]ð2`aÉO‰Ó8¼jÿ檹IRiÓñÚêʪjuq×±Ò2ìÞŒ›Y£ç•ÀÊwÑ•>#æŸ\wúû§zl¨ËïjjAŽ¥þ¦Œ˜é«ðvZó£9oAô­$‚i‚Ñ–éFQ2)£#:¯ö¨w´uð:Qpt«q÷sÖFU5"¹×•PçèsôýFÍÎÛ7K–,¥‰ÅÕFÖMK~á˜Õšø%[ö u£¯Lß £ûÙÏ~ö9óùŸ¾ ˆsçòýE_O© ù¯³â*asû}y#$&>Õ2_…ºQóa•b'é…$ÇèLÄ-5s"æÓúj«çƒª’tÒKÕªê&oÃ?S¯àœêÈjEŸq5zº•XÖ¶4åBNcVFÜ;ˆ]å§¾“Ø IËh©ïÜ{÷¹ÏŸÌÌ«Ïõ=ü?b^ëó3xÿ)ú‹˜Tóù¸·ŸÙPAök,†¬ÉÌŽ‰Ea\uFL-z…Í’ÝÆµ_CNiNúÉ#"m›•RSöj™3[’sÁÕ¿ÁÔi¤ÖvÇŸ`XÒ¼3æGEù*ò¬j7è¤m“ -ñi#©®Q;¼+¡h: S—îiË÷~«³Š0Æœ®·Ñj_~e"š—ö O`ú‘Äɑҫð{øm0éVAÖ·AϸéZßÐéXÓÚIÙ½†½u!Öžƒ¥$¸è¤¡ÓV&F2£wxù ž\Ê©sŸÁ t'~u:KV©ýY¾[°ÿÙË鿺£¢ —¹x†³WºVNï!•VEòÙ²©f¿Ù’9æþ0B“‰Í(j͆m|ÐxwíýüDÚ:àuÏæEl¥KI» ©“7ÁŽ¤ß û–FN!§-’—%qÍ×iln:Å|J¦L¼î¿¦É2÷‰øû±IáGrä ¶4¥“ç½¾K"z»¿¿pÙþHiSÆ“Øh&Þü‡Ëcù'xÕžæ£'ñ'ceºQ÷̪þDKšs#Ié†Nš£ˆˆAÏÝNd9'•[%=Í òàrAõ:a)=ŠaŽ…õ4æFò!C—©Õõ9î_᳞ٛ>B×Äþ¡«—Ãq³\˜!N¥,\Ó•\Ë”š\êÒª‹a[bŸŽÊØm”d¢.RwÛâe±ÚÒĬ‰›k©ûTêF4»)÷S§¨ÕòçFîâJÔ˜:}p¸ÄU±@Û$ÏîÃúat"’oã[Ýù‘_aý®ù—åIQQð²85gаé½HÙ{&ôeætúWÈeÂÔ‘¨ÙGü“áʽhä&žw¿†M†rTÓ–ß]²îÖ¿ØÓ›¡sVkN›Rp²ØtÆüð¢d_€ÙÐq³b’=Íùº~ž]¨Õ–~¤Û™Þ‚ª[zQ¿e¯'Äq3pæ,XМàoÈŸ¶Ñw^h>Ó¤k0è‘ßÈžx4aÔ²ä&6ÌŽ>UT5~>¯¨Ù ™tIe:]ί¨è“Þ^dÖ«íä„íÝ{1âhZuc”6z}ËáÃ`‘e˜û„ÍªÆœèæ¯Áéû š6Zyýv²5[¿hÔ“Hšy,7äLS³“V”;¾ƒæTzª¯—ÅhÈ6Ù†ìΤ”ùŽŸàeq"mxk­s`S¨t”ع1¸¦m›­ ꃑDEÙÇqßI„Z²XOÇ‘]/Iî#jõ~ÌëʱÝFQ©ïæ9ïïÈM¤à‚ÍæGÓDÈtàøUòÂü†Í²É«óÿüþåõÿ”£Wl䌃.8˵þÔò¦…åóÿ#sôb 4÷ÖçI8ú°7r²8IÕî´÷ò=~bÏÔsRab¶ ¬×¸ž ðr5f‹³–ÂëÓ—ÞÃdO<«,Y ¥ˆ£mW1¥äß!×)"¯ø¬Ò<°éCJÓZÙO³’{ùžþüÇA³^(Hä£xò.iBT~À:p(¶/ë\ šºSc$Å8+-_ae:QþÏÍðOßá‚vq²„.Ñê­áôQ}ù ¯çƒ§ø'J°©M9yŸíéO©þ»ÕžPu„SRoÂg‚tÂ’˜z¹Ó'…?±{ýÔœRE4²š²¢øA$Ž“Á­Kl¥DÆÞ;w"°|•éÙ®Á’ß\kã4w·.úª/ý¿SúsBÑÐuÇî/Øç˱Mé§áŒM½Hø]FtĸӠеDÜ#d¹s|cíì)—òä÷ðò Ê2 †£ûr]>d|v*?hzˆReN˜SB–­Ž[ƒozqéÏ)ô5^¼ñAe5fÇâ:ï06ÙÇL ›-N®=Ö“ÎH=ùŽ2Š‚gÉäÝ(ø£ÏrŽË1 92Â' `lmw>V«jB0®&Ù2’…½ \¹éÜéÏ|¿4®Œ¨ýçÛó5$¸‡‚ìV·~àÝ—‘ØÒ¿ä‘cÜ}ùÓø93*>'5>#÷š­æRsúQS'1óux øÑ¹M þFÅ;\:y-$Œ›t\ ±Døþ˜2fä±ÝáM"åîRVŸÜL¾;ÍΚsÛ¼vÛ?ft©æœØŸ1¥‰Œ¿SNQªÕýÈCZÑÝp*r-1qGžXõniªÆœƒ- ý΢75WØ¢w`L©ÿ ξ#­?±'¾My¯ôØ©ï#/?—jמê"p†R-ȸςEªþ4¤r ØY/GÇç^^î+SªËCŒ›«ÖvŒ+Ép>UóñMËOxû4wGò6^Ô¸¹¨ôd¬ìtóàQ qrw‹ÐV¬žýäSV —«.6Lm•c¹Oöåø é™MÒP”l4 “i«åÌ|Ãö¥ªºnMdæ:bÓ™Dà]'öæþ)Q|…ضRôox£Ñ—ŽÇû-¹'ê´}ÃNäè&UTLÝÄìµ–°$Ž'~'[®àÛ:»`•ÃÑ&œ×BRiôùÒÅ«2Zšòѱ5?³LwÑ©¯.X:²ªbÖ˜ÿ·ò}¼“¿ø¦Å ‘²î"™s¡¦ÇŽÂD:K(õeír¦/:¾Y.ïê.Ë”eß“R؆•%HAÓ"gH4©Ó™E_ÉÔ6TJ¿Èÿ]þ£- *tÀʇpÙTÕùW€Ì{ûò£®ãOMÁÒª‰DÆãwÒeœ(ݬTæ:âušè®gF‘Ȳš³]GïáìjîØ®|É °9«ºIâ>åç¸êJH¹ÒPLØ™ušvÄß‹1«d΋©ÈÊ:$ a«©ÙN©ú É—…u(¨‹M(•øì5æÿ*u[¸cJaøÑ²îˆ™·1Ò¯”гãá](iùTd§Hõ¹£û©;ôö —á°z;2¥ÒûVË)| ræžk_žÃ.O?N]Õ=ßr‘©÷7ί°rÔŠI1$oØ–&¬žê2L˵|ƒ7ê1#ðeñ‘\ÉÈJjÌžTŽóŽg3ßÄ÷ñ¦¬ÜªØUËÂ<¬¨Ôe •u¥©áGHjº¤v…Ô|7.fÍÞ¿M³Õµ/Àô¨ÊŽß< åTFSJw‹%êÙ§†7#º²ßêt’9¥*ôð2V tÓ« âŽÌéÍö÷wâˆNþCæÌ«ò4åÝŸ/Ñ”uô5g¬àjæÊžï‹O t¶ÃRŠ£ ETŠÎ.žÒt¥¹óÄ™S’oOßÀuÝÏ·æ}¿1VͱvºzÖNB³û¾–¶ï•¤‰†JF($W¦¤Ú?.u~Êbs¿:QUn–O×{M´îméY²Á*†ŸÅšLº~x´¯ÃÌlIå»9;¼^æ•.}Äl–“GåH¤ãŒ,¢%#…2' e-S>^Cí½û–LÉåûÿö]”rà.>ËW§™9½ ëõ"p§š6Ì£¹+N„;‰£au£¦¬`šw›…br¿– Y ‰žÉÊ’D ë³tØéN‹›ü*è6 ãåÅäð£&òËÙV6&ËYç¹·øbdU5e^bÿ|ùrøÓßà"þ)ex¬‚õyšA¿‘t£E#ŽêÍ™²÷/yý™Ó¸DXRâæUE[G,Ê˰¾Í‚;"¯Êp¢xà³úŒ‹·‚x]GZM"Œß1’¯ËšŸ’Ñû‡/?ãö'c§‹º–Ƀ^†T'”pÜ\ž¹{ÆÍƒÊq'Ÿo k[ŒhËu?Ù™¼Žj.g±£’ì$~*ëaÑFüž¢èWÀˆ½ã¿Ã”ïêu<=³øçÀe£wãæ:mW‚:ì`¹#±Õ”èW2{¡ý‹Ëë])WS¤z?|ÜÄL¨ÍXÂÙ¥>f¬´Ô£Óá·cËHãðý9ޝSû{ö ø×k§‚[†[}ù!<Ϋ`OˆêB…‹G;‰‘y,ylºU†ü¾¦Œ–çWA3nÞs·¸ë)§,@˺/ŸÔ˜•U9ÒµySá)Ó?LOVâN§‡vã¡aSpnÊ0©rí퓨éÁµ& 9ELÔ¿»R{…ΊßJi±£&Áêøup9%~CæÝ×"ìõå…COåFú(‰’Ý”e£fM’Ž´Œ¸Ûx:àL2:Ñ*˜šÃ—måOO_•ö0.U¿ž ”_Å–>¸ºˆªeðÃ<-ªŠ¸5{ˆ‘ת:ðä^[T}ìÄ÷åýÉçú"½ëóTÙiϨý˜œILØõSWq›.o> ¸ÔÓ”éS©p2xÆŒé}ñ³Ê¦¯O1ó«ã±a×§(Ù~ÃUØÓùY—ä>Uzêñ÷n-¥N©ãˆ½Ý¹ÓÞ›?Ðqò‘–ÞÃvÑr¯1±ýªY¼ÉS™Ñ#æMª'dzO……Dóõ£%Þ~C{ÅÈbÇû>cöÑó…¹s!12ž†ÊeËÏžÅ2¡¥;<‚æ^Bç^tuÞÂ7ˆ¿þ¾ƒûÞ‰“?J§3¢P±ãÛ]9Ž™˜{®Í²êgüÙ»ãcý‹ÎÞ[xìƒ÷š.ª›.`ýRƼ¸Ó/~ã©ìk-2çølYö¤lØu" ™w->4dNŽ'$\ZKRt§°¦SíCR¥ K=Û« ëØ¿#6dä˜zi«—ŽÕûò4*Èê"~<ÈÿTÜXe)©{­ Ù³ùzá“¿Øö%pqÔqñ8ý†Ð*,óîœ:–ÞÄ´æ!Ôü¶ðIÔGc¡,O3J"#Ö®ƒ/²Zºö?©H·w!V²>_dqFìzø ÈDËÎø:áìÖ8#ïzEÍéæ>uªe;=v‰·§Á;²å_#R«Í4ìáöOÄätô÷±£ò«yyþ¼ÎNœ®ƒ]ûŽ¥ø{oò¢@ê"±Ÿ.oûn²G²‰bü> D9'È\Γ#!ÖÄ«îo—ØDîÏDJüE¥‡£nÎ?ÿÛÛÌ{gonÌBí݉#Û³¡Þw²3E#Û–”ltTdìdŽ”ÞäTµ šD\½f8'Ri0>+”¹}´öaˆ±°”}•Ëà¶ÂÃpé.^“°ŽÊ³0Ì:ÑÐeIzEis§tu¬©r8¼VHì¬Aó*:Q¹¦à²IrT„l/[ïÐ[Á|DqÖE.§:LàaÔŒŽBVGN q’³Û¸âRDÖ "¬´²[—¥ð\Šu5’Å©räðH±~ÞÁ+Hà/H¤A|¥Ë—ÅHõ°èû͉#{½#·ò>òKÖËK“˜•£ÒåðÉmæKÒ;4‚Óà7€œ¨íÜœ'†Þ–')Η%FÛ܂ŋcd/ÙÔ¢ŠžÀø ä Þ .ñ ëY,AzÏe¥‹`¹ÔF(ÁâêÇb÷h¯Àz‡D¤oŸi9p\¹'Jq-K×™u.5X¹rø!(ëK­Ë“ì©wù.G½d½!+råË—/ìBd±}•Ëî0[ÚýëbÕðX±bÅ‹«"b·þ˜ÒÿÚ?ÿʽ«Í._ ì¬ZHZNÎT¹rô¸Î£ÍgŠòõ¤{‚äÕÔ…D.‡R—;ÈA¶(M'zŠ^œë&ÿ#š‘ƒ©dÊB‹^¤>Ò79&‘ÛWZElKL¤VØ`þx„¹[eê¬M'³óŠ0ص`çˆJ8ã%,uY.BÑ÷ùì«e"“˜ä¥»5‰¤…«Öi JÒNâõ½"“²¾Óß·”,?µePêÌXéÙ>8¤å! .5$±x-H'qÆB{ 4³–Q²£._ ös¹:¼Xî#ŠHêã÷C®–Û°Û‹-,J„–rrœÎžÀJú’¸^Ø ¾¤u`bÛvÝnI/xø’ˆt“°œV.\¸ÿŒGÚÄžÿ¹'íDô.\½l6ÒdætñøÃ҄ѨÔlØ')#%Z¯‰ÆÛ6ç”:xŸR±Z>ÇVj:I"¡'IÞ6dmýÒãÑ’¶¤¢X±b¼iNëiÁŒ‘ŽåË—ÄÛ£.Î eÙÜêC§o5b~+bPˆU”éib*ùø ö¶,>]ƒãÔ„ìqU¤Òû+ÖH\2J‘I"–¬!u"‘‰é8#1;+îrFÿl:Pê-K»Hib`„±%©fuÁ£*‰ãP…‹¤Ep[¦9ÜrzX„$j='¡Ÿåñ:m¹Ú·u£ÒFJºi¥Œ ÄwÖÛ'JIÑОà ë΢è]ÈØúQO~áDÀ¼ÔégôسRH£Ž ÙÎÂHQ°¶é8ºœ3V&’”lˆ>jiRKnûƒaFÁ5²ðx¤Öxæ°…¶’YpIï̼ùàuC¨|ŠãözGRm ºð9FÁlRNÙF)àÎF:IÀæ•Lí(Û8܆F ÂÛ7L/Á´í;éàßà=>"·zþ?°ü)Ôµe<Ã'M$¸ë´uØM©Y[û ›U_i4þ6Œ>+Hãp–ñ>+ó=O~÷=ü~­YÍKŽXê(ë¹Mb­Âhɉ‡£ ËÂ`|Ñ·ŒÙ&ˊ亖«u{’¥#lÜÓe<ÄD'døc‡ìË`묄ܤ|Û„S×õÚNÁ¶oÁºN£¥ͰdØM.t­&{ѵŒ§NïmÙÏ-þ);x¥¸+1>ƒ(øšŒ˜¤…>âÁ;´©rÄ!%Äu¬Â êQ”жÅë7r8|‘ž #Õë;VîÂÄ°q¨Ë#¦û¥$„,„¡«.ÃRÑÉٸ͹tÒ8ó“¸õAQð+Q×l”šÀî2®ÍÖ®˜: ÚêÛ[m£/®Ùèb¦”â’„›ûm¤sVné'LRçRŽƒfÅzXé·ê:ímHì«.A“µb•4æÁ¹ÕŠHÇj¶UœSËb›8âòw.\¹råË—.\¹råË—.IbS ¦ÒHBØZÀç8¢Ê6JJ ¤é¬loÀt|÷M3ã|¥ÏÆŽÀ0Š›´–"³‹}GZNÁ“‚69"FDQ™«®º”鮡ñõ?ÀtÃ#¥·ÈQ³îN´|2ãÔ…•k§-lOÙ)£©Òƒ-#”V¦¢bŸN8´¢RiÖÄZX°Ël*¼Æ\S‰Ôdß[r‹’¥ÇغÙ+§-U;ÆÀËvYè¸޹,Xèèƒ!+Ä[5‰[£•²Œ˜ç†Ê7[Ó¨tÙŨ볓K@ÙhÙm°Ž:ûÔц¤Ñ× ÕøŒ_E].:ѲÉHÙMU±-©œP©Wbvzs.\Œ^$í‘’ÅÆKv¶éöO°ŽËHÙÍ9ßI£s•.„QI"“‚Frä œéÀÊ* ºÒŽˆ;b“îCõd•'oÒuI(Dm™_SWfR¨6݆ã,%`Ó˜Ò:B÷ tÁ;¬âer¸ÎiËrMK¾ÅDÚÆ‡6ÑÉÁŸ`”#l¨>\SŠGÈHè9'Mo¿I~È@ëØçQ“o)"6m”Rã¦Ç5z8È5`úN’Øu'c² ƒ.øÆœL»6lOF¤íþ¿ˆŸžÇêZÆ7ܧa;'Ñ©4míûau¨ýÛŒŽ£eÂøçdõ~Ú¸çR•wfN&›Y¬ãE¤‘K0Ê»‹ìc€Oe´©à*mZ=dn);VNxÛ"bj>ÀÛØ>%älɽHÙ{->£²’+^[Òw|¸u§Ät±M e¤îޤ £å‚I¤¥,3ròí>œÄQ–øõmx„.u)¾\L6ó8àa³Xê.r"ŽHéÏnëÙIHØ/š½:¶n–Ùå¤WîÚ=[b£8ëŽ9m˜n̲Àæ7㑆 ز+²Ë©«®Á—rÖŒ‡VÕû9¥}hé_*®ÉÔ¹Ò´žÛŸI$²>C«\N¤lÚ¬»»‰×•"ùQ±NãÀäŠú|„xl._ðÇÜ`œ I:ª²)råËŸpËFà“_(ÖÃW¦šIíâÖ˜*ë°µ n,ÙvRtQÅ ’)‘ûK2F’Ä×Jc’(îƒfQÒ’OM‹‘FÃÔDÒx= T×´ì³Ã¦ê›(ª¯4,L:ð-*6íl„A2jBôd’Ç.%#ld¸É°dëdÔ|3X}lp8ݱT,tŸìø†¦ØÍkm¦„Ùjzt©§>'êJ¹q»fƬ·úÄ šÒK."ŸÈe5wQÔ部°æ¼£¦ͱŒ ¦ŒÜjŠBvÕÒŽ˜ r%Gá Faêã­,;±8ôß®íŠpÏ;WJÒôdí«Ò+ªÉàIbÔT{ÏöZº»©<ÆÂù…[ «……ʘ߀6ã=¸eéIÚ?ÔO,J"-b“Oê¤â×’Teçc%ÉíÔà„ ÀfšPu"“ʪ&~F¥¦¼Üþ…¨ôj:\eÂÙ­±dÜŸs}› ¼{%8¤t$Rh鿸ŋR0iZ*ޏô=Œæ¼«?\ZVøÙ76Ã;eʤìä¿l“Öp'wƒ¡Pé‚ytÒH‚pAbÃçÁ‘ºÏ¿ó¸:Uö,k̶ú-çÛ'ä6(«Õ4ðû£åm‹cnõÒ£b•çFí–›btX%FR«qIM“oѹ=njªÜÿ\–“RÒ0Å©ï=¬gy؇â ÷ í<Ön@ôD㌛G'‰ÈÛ]¤vŽlF^îÂé UÓˆ:’(ã%^®£VH-H¤àTÌA8g³1qÒFÄëÆY>$ ›/ì\Z“†ºl ¤lcÕÓØò©rãlZ°s$VÝß¿w—bÄl–EÁ”:IìËR8óÕû¨ù‡|OwxÞ`e¥öñpÎ&ì»Õ¸ëm#ñÅ%É£åYîØÍŒ”e¤öq°M`’8ê¢l/ƒWš¶å#vŠ(éÇ]†:i8¬_° 2öϤž(ÔŒ3‚k;6ã“´ÎFļ}ÈhÝ€‚xäñ«ñ–MœR |R@ô~'ì žÔ=a«ˆ±Õ'Ú}¤¡rèGЄZ7`ße'Hë»H㦗<øŽ­§=«vûSn$Ûë£`Õ|QÂb±¹¾Ú{,ÄR Â8lö‘”f«Ø Ò)5éÝp_‹_ ‘Ùk©HÆüi5•ØÇj`¾ ã÷S'lœŒÁ™+=»Ž'’ Åd¥°ß‘Y¥°O‚ý½‚i)î\¾($ŒPûÔ‘ìuÛ±¬Mn_ï±Ù•ÌZ|ðÈõà“¾?±(§=ÖŶ žA_1SÀoà'‰æ7‡‚˽Éj_ˆ²ÒŰ\Ë«aaÛ³ÏFaø ïö-‚åÉb8Õ÷;—ö +¿Í#°q‚åË—/ìB}¿_ ´¶ÞÞ×”_o*/·•ÛÊÒÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bÅ‹,X±bOÿÚ?œc2ÎW™%æIy’^d—™%æIy’^d—™%æIy’^d—™%æIy’^d—™%æIy’GîIjJÕ‡“c–‡^d—™%æIy’^d—™%æIy’^d—™%æIy’^d—™%æIy’^dû’½`æI׎I±Éy’^d—™%æIy’^d—™%æIy’^d—™%æIy’^d—™%æIy’^d™•³Î›y’^d—™%æIy’^d—™%æIy’^d—™%æIy’^d—™%æIy’^d‘&¤¯^9§$¼É/2KÌ’ó$¼É/2KÌ’ó$¼É/2KÌ’ó$¼É/2KÌ’ó$¼É/2HqÌØ¬œŽt⤗™%æIy’^d—™%æIy’^d—™%æIy’^d—™%æIy’^d—™%æIxåbó$¼É/2KÌ’ó$¼É/2KÌ’ó$¼É/2KÌ’ó$¼É/2KÌ’ó$¼É/2H}É/“c’ó$¼É/2KÌ’ó$¼É/2KÌ’ó$¼É/2KÌ’ó$¼É/2KÌ’ó$¼É)RL³šñÉy’^d—™%æIy’^d—™%æIy’^d—™%æIy’^d—™%æIy’^d•µ%y^9>„⤗™%æIy’^d—™%æIy’^d—™%æIy’^d—™%æIy’^d—™$~äz’µ[9&Ç-kÌ’ó$¼É/2KÌ’ó$¼É/2KÌ’ó$¼É/2KÌ’ó$¼É/2KÌ’rW¬É:²r^9/2KÌ’ó$¼É/2KÌ’ó$¼É/2KÌ’ó$¼É/2KÌ’ó$¼É/2J|É̪}G²µ­Þ›åwìCZÅܸؠe›±j·C,R¾îÆGîNßb¶óoc3g\'3,=‹r-t¯ìƒjy!‹7c”u'°jFR¼ö=©ãznÄu-Ë6 ¾R_± hKBÌÈbùnìU6*ŸQ쇲}—ge:û(ÕÙ7vQ·²—d:»(×ÙjlU>£Ùeû.Ζ)X#v”âqma[Pl·Ü¼ÁíOJBZ‘§9±ØV1R8tºaPm±y°ÿ °Ó©t xÞ­Êuõ#1¹ÊñÞ"SãÙk«DÀÓ…pÔŽôgˆöÃJ[H †œF²¸iÄk“¯l?la-ã⼸ÿ‘ø!Š˜:¥ù/+ýËŠœýŸÍÄ"¸¬µbpÚ_°nì£oe;;.þÈuvQ¯²Ôت}G²Ê5öFƒËS~+ŠhrûTÿÌü8Rü‘ c<ÆÂ¾Yë î@Ò§Üä¾ðÇÿÈ}Å4 ÛlNÃrÄÙ@&Šräã ð‡^¹b–t°•Àg®MÁÖ/[0e¤ÁHŠS!ûŸráÔK¯û{Åx£Qø¯º>ðùGÍ«ú±}Ê`ý'âZU*CöfÂ<§8|ŒKꂵH}_’ˆT„ÜXïËhVÄnè˜ÜÙØŸr¿ý‡âƒáüׇúÞ›Ù'\ã*p‘Í'tâœÌòðpYíXMAïL*Cü‚åĵøY—9ÇñVR—±qFcbÇBX‡Q»¨x…rÂÖ¬-jf¹9 ™<ºCobgµÔ˜^¾Üe%Ì11|ÅbªYì æBµ? ì[û!ÕÙF¾ËSb©õÈ{(×× “LÚB¨Ç!ÄÖK3+m+ HF¬{ïüÓŠui†ùHX£V¤»€¶Ä Τ¥á¹–*”¤ëŠœ†Ö÷#N»CËÞBðÿ[ÓÕ‰'3KüU±Ž¹/bˆ"FÞ·C(â 9µaåIö6õŠF|—Í-¸>%> Qúž~ôaÍœlÌ0ؼRŤH§œ¥&¹Ë£­Zž ÆÃfO¹,ÑŠÃxï^_Ô”Zjq£o¹ pÅô»î\uúcñNcˆþëS£wPÑWˆ3¬L1rÞÅ*S«/ ÇŒg” Ûaö¨Ðªm:.Ó jU‹´ƒÛ”mÉϬøn± ÆâtÜZSJ“Dû–Lƒô½Ý׌ 3ÈÕ$#¬® ¨¡J„e!žZb ,Ñ _òa)[âÄJ²Ž^ÃÃf•Ê¥ê!’ÿÁ ÔåÀÜ"±Vœª÷JäM#^hŽö'ýõàˆõÅ£»oS‚ŒðIŽFõÇ(eaŒŒ"˜d8»×߬vɽÉèÔÁô”!F|Í8îmv~(ò¤$Ö뎮Ê5öZ›O¨öCÙF¾ÜœF²¼Àu[î_jNFlý]ŽCÅmƒó^âý-øÜ¸Äã¬?¹aq=W„Äð@^Wâ¿ãU†ŒûÓ–+qmåXúÖ¸â0ëMÜ™qÌÎ1ý¯îø¢*b¦ÉqߪÕsý/ø…÷"L¾›SÄÏSäl<:oöX¹f¤¢MÏoµYR-ß'*²Ôµ}ÉFhʈÜëÄú\äGy<½0'¿ û…ø€éó›ÉÊuöQÒá¦jl³zŸ§¬(†2·ÂOÅGøôÞ‰q)h=fåÑœÍfaî,¼rÿ"¥éèÙˆ¹–u~8hŸÅKÔá‘!GžãâÚïX9u)~çÅì+=D{±ÄÅbæBRÚ¹ž´ƒòŒúòTôð|tïBf›–áXE9Ãù‡YÆÖ2l]—2”ZW>WM0µÌ”‘µ;º¼0™wp|P†¿©Þ¹¾Ÿ¨!)È€.m*bq9ƒX¼©îM^$ŠæzG3å5ÕdÐŒ}Ÿ¼rÎ&·`­öåßqÛÕ]”kìµ6*ŸQ쇲ƒ+¬.íúA*òú0—B¥;bV‚Q{î³ÞLžò¸bcô’£TJ\%툼~ÁfšáÄÝÒ(MNS–vŸÅq¤—mŽ£TÊ%Üë ®+ 3G÷Þ°B“á6áþŠiúIÚž›Óúnܹ˜ªN/n6w®e9â<á}°*Æ6=L繬X% f¡<ªHGôIún8b#›¹cª9ƒþ+c„ï÷/Q_y7Å0«þßÍ[~LR3Å;nÖXc~¢KwðY÷Ï«¢uöQÑhíÔ¬Àh±±aõ,çª1¤"=6Y»¨±u¢(J”>ÉÎI¿½ vFÑ‹tÌ€¹Êi‰S¦Ïˆ…Pˆ³;ZV*Ó¨{œ|='§—Ü#Äo`¾Ýͳ³~(P©œFå‹>A·²“bÁÙoès*œ1Ó…Ãb¶®Ü6•†­YhÝÑ_oš~¢#ø:Q1 Ô>ãbæ‘V6~—-a¬qÞbÒîwê®Ê5öZ›O¨öCØùT¡šò ÜÉ©¿öÁ·™'œdd÷Õ»wÁ?ª™Än±s}\…žGbåÆÂÓl¸­ö,Sné [J¶œÛgÅs)*R®@ã85 4符áœw£È~]˜XŽ¨Â¼á/SòÀ~,¸ê0¦ÄѬÿ⌄±÷H|EªJ‡áΞ8$4øB¤HƒÔd°U“9³„°«pÏXor‘jr¶PÏÁ SÁ Ö Bû•"5E7ù‚ÁRµËEªßUïÂÈ PoÂÁ½ª&µ6?µaªG/4/ÃÑÇV/+Ÿ:GRÝdÜ©>Åm#°‚°rŽ/fõò §à¼ê—Å}Ês܈%‹ç³©ß.1áÃÊ-Þ¤Ò6hCÓñ`ÆùŸÂ*œG9ö|T„î5moÛQ­"x§;±| Ï}©–¿Ä9<)G=?q# :8Ïý.G¹[é†É•Ïô‘˜0Ÿ¥åò êq×$Ñ-ÿë‰%?&r:eù•9Ê813 ]N Ä»=ÈSnK : R"MmªÓ¥xã¼&Ñ*Úsö|WYSk%ß èW¥XÊ,ÆœC¿à =8Àäßi+†Q¼jÃaó˜§Hο2¥ÊaŠÌZ«•³Ÿù•eJŸä ÄÄñf2 T¤0E¸^ó§ uufr° JiøJ1€”ÈÑrûÐà ¾õŒK݆×_uéýWoMÍŽõöä%¨ä0„Œ£Ruç67»-MЧÔ{!ìMRq¼¯3s¯0{Te|ËùRq˜ž é mHÿ挡—þ«ðƒñL&¨>9b¼›u®aþö÷,-MµŸ‚hŠcúÔŒÉÅRW–eü˜ÔÀÀ6•Ž´¸£_þ« .[i´/¹Q¾‘ñB¸X°È89–/I)ÑúM›Š4jÚâõÇJV^C&ælý+ éL ÌçÝrÃK›á‰9£9“i”‡Å5 E»øV%£ uåÊ_ØT)WÀëŽ0 ¹×üv”4Ä"3Ž˜‰•Á®Õðñx›0̱ {2»¸"!y8ŸÙø¦2ÎMÃ?ú¦Çý;¬fVê K0n€è‘(Bno.îp‹Èâq«Jå‚MyÛ;ú*“”ñS›fî¶U¦íö.}'Ât†é½‰ý>Üe¡qÈÆ7X×Ù¤Kp¸eƒŽù‹.)Líorð®ßzàˆ‡UÌÃ)÷E:—§‰Xc(Ùeê¥âÊT§N\9Ã,Te8Rk¬¯¿3?g¹xFåá•>ÄddI:zxm~àëXÞpþ~ÄqTÄ G€g×ðÉ(Ó.caÈuufŽivB¼ Á– C3ZÇjòÿÜ(Òˆ'> ¼ê—Åxcrø!Z¡y‹šÀ¹‚{ ÃRñÊ ý³#ÞP™ìX½D¸?I´û;%MЧÔ{!ì.P—§cSæ9²ƒ‰”²ÅVÓ¦VûÖLds€=çÌ-ü2 ‹H ¬¬_Zp\dÅOÄH‹èuHiÄà[í\²â§CœØÚÖOZB.¹‚qÃ¥ÔªB¤Ä ák,Be)Ý"Êšu9³2fJž‰¶M£ó\^Òš5$#õ/²f#úÌ¥ìÒ±Lð“ܾÌDî´«jÉÎ`Á}ê†DÚ`NÓpVB#i+ÌâŒfpœÕ"=á9«¬Hâ¥ým\™·3öÜzíNÐl)½4D&cjÃ+$æÎÄ2 |³i`\2”½DÄ) ^VŸbÅGÔÆGD˜*‘¤iÊ di’$ú™DÎ2,ýÅ4»­ÝÙFÞÀg+½‰Ï–n·à¹|±=R%µ© ÏN6h·JÁMÙÞÛQÙÒÅ"À(P¤E®dMŒ6¯6ä*dHw)BXž7Ø£Z´(›Dm-½ Œb÷ X°MÌ´D:ÇMÀ·½xãþ*ÿbåQ¿Â¹Þ¢S †q›br^ÿ~Mý_ÚŒ§«ó^Ø>+îG¹|„h…‹ˆËz«qðÛ§1^8ÿ挣Ës毇øŸŠ1õ<2Ò¼Èï^?a\¿NqÔömX…[uCù a9ãðXŒbߦã½ÄEú7ާPÓù+UÖ’ÁµüuàõíF˜Œ`%e¥ÊäJÜ&ó&a™sëN$î8ÿ%ü™–©›`Œ%â…Q#óFz1RÃO=¢Í™8ï7D^WüˆàoW0Î8u«qG\Jçc4º‘ xóp•Ì|Xír¼#rð…Š_ØPåc¦´ÎOì^l¿Ûð@ƒÌ¶ã‚à @<†éÅÝŽ¦ÅVB¤Á5$sg+ J“#gÁ …–õ8¦X/· ËT~+¥Ã£Åðö«Li÷"ž¹5>«·!R< àà±Ýa§RQ†‹´¯±Uåû€VÑ–øüQj'l‚¶‰ÿ ¸éÔÚþçV–Ö ó#°¦ÆÚì÷§?¹ ;³îB” ÄiX)ýÉgc`Ú°xj~“ød8Hœ Ä«¤¸§…8¾¤5®T‰—$˜\öbü_ÿ+ÏÿÖ~*ñºcðNCwtL©7à§8HÐÍíXmSdD…šÔ%^8'œ"`^öRÄXG‹r|"'¹Z„ ð&;®öd²Ê‘ \Tö±X¨Â þÕ‚u'Þ Š¼'6 ÄçÔž%Ö$'0 ¢Q.2Z­ö!:À 1MéHÀ>fw_ò8¡ú®#¦uåš«ŠÇ9Á3Ø{—2¬#‹õD˜ÄïÀS_³¯{•±ªúqGÝrûœçÔ?üSL<2ôÏ‹¾ïz˜s 8ÐýI¸Ø°Rv:OGwZkÖð„!ˆ±ùšÀ¹œV¾†õö鬺òCýKŸ^¬Äá¥O„m)Œ°GôÂÅöêÌmø¡ÍŒgì\è,AVH½kÌôò4Ü@Ô°ÆPaÜS\†Ô1T‰§úm'z>ž\$XåHžåöç¨ô;ܾü¸er<÷Äpxnõ:dâÂY×[îX!vNÕ:q|VÊx´ÿWtwöCÓÃ+Šûu~þ/Íc‘Å3|ŽL@Ó“quZÄbRû0în^gûBã©3µ½Ëæÿ"¸e1ýÅb‘Ç{ß½J±©Š”í„H¹ÔDùrÄC†Î¥NdÇ kEŠÊÞ¬œw«ËSb­Mâ*HXm±`¥¢VuNœ¥!¾+‚—ù=ι¾¡œx@¸wëêN®…‰Šj#À¾6{••gìVµAßaö.:rØAV‰í_oޝ‹'¯íüõ­rîÌqßj`­\©T&ý;ÓÐ8^ðÎ*Ò$ LùÀ+ˆB^ÏŠz´§š'y7|àQ›ÆM˜éÅÉåb°¾«PÅÐéÎ(è`ûÝ–8´jòçþ%ÆÊS _Ì©"CðjŠ\X𵥇xMê¬åÂF÷X)Ö-˜cVJxÁâ7[î\ï» OózÅ J'¸¯6^Ï‚„ªqÖ0¸kö.\Æ š4êÉVGõ6à¬2ÜWI7yzçzˆÆSÅõ'ùœ»jte2'K ·Ú…V‚øxOµÖ †ÞôÑËe¥Â²¶û¥!ïp¿ä[M³w7DëèséÃõâ»ZæÔõÀœ%Ÿ]«þ+JS2"æ¼."ûbRÔ Å3ËÐÊÅ7™ý×nMÚºýÝQ­VÈÆô==ÎÜé½!ŒI¾RÌJ|Ã(N÷ù•‹ù¦%ÚÆkµ&l_M©ðËr¾ÛÚU€!LÄHÊËWÞ‘7ÝbÁíBw6íɱÇü5n ÿ·â¾üp 7…Š™ÙLí-m‰ã03ñYïXEH¾¼œ¿*Î ¥2â©´&Œ±µ4(Ë– ’P:ñîuU71µêE3ñÙ&›t âxBû31î¼{Wc-E“O9t¤@·CX‹ŒQ~öœ² ~RãQ\B23”™‰Ö¿íÇyBYîêwöCÙF¾›—6‹Þ8^Ãn…Ь¤ežÒ¯¼¯ÿȦ&ßRæP-,ïkë\Ú”æk[Çâþ·*’‹á2{CfÉá—‚;“Äaúl÷. ²Ûj©¸…¡Ç±zÿtÓH8_jrŽßŠÅ<3ˆØSæ_jQ–£ÐûRœ€lö¬BB ˆ·ÙðL&¾ÄÀŽ ôªjëÆ¼¸W†Ôò¡DìX¨Ó§Lÿ—¾äDf÷2õ±•ƒ6WûŽ(Øm+,<Ù¶ÄÒj‘ýÖ&§CÛðG• |EÍãâ˜ÓŽ˜ËòLÀK1Щòd`9nDt•Š©3?»+D5‰ŠŒñÈT ,÷èÊ'q‰pèÊQ‹8¸ô0ÛNŸ;÷!)בk-Ûc.WÊx£ðèa„§ H«*Ëk#ü¡vx‹Ð© AÈuô#ém•¯!bq„ñ©„w|X)SñÊäjW•¬Ã‹Šs;Yi_n‡cÝÔˆzzœ¯Ô@µ/Ôz‰cw¶yõ9Ñyq ÚQõUjÎ4ê;DJÖïR8ñ~¤+Êœyƒ>O¹}êtŸÏ•Š';m?Õ¹$:Ö÷/·Pê—\š¡¦Î¸ä¨"8Aw³º1‰¦3a•û–S p¾ä ˜¿õ÷æeÝpܸ@0ÚÚ¦OEÉâ2[y¸!^«B o6¨FI"ë² ”šÍ+ï¤õû!ì£_e«ª+ÔF!äkM†•ɬؙÜgËÉ”‰¤.Šz­µc ygU‡ZÂ)ƒóÅ˾¦!Êে¾gð Í[s€=î„}E95¾F®)Q.pˆá¸é±5zœÂ÷³eÚÕ× ûÙ‘dc-áQï³}ª¡×ÿ‰Q²ÅZ?µÿ…M]x×Ô›lÍÜ:ƒ‡9|¼B[*üÚÆÐ¤D°Æ6\ö©N3æÓ“²í 5pˆ‰Úßëúd'ê)ì<)‡DÒ¨yGôÁbµô¹\¼#¿4Þ¸å‹bÁZ\¸æ ;üÑ™‘Ñ&XymÞMŠB­ðq$#N™†+ÉÍßbÁRxåŸFÄuô?•L86K»AXªLDw|WÛÂeíê8‹'ƒÈ ð‡_f2ž«·«„5—öÐñg‘¼å²ã¨Db3”Þœ´óü<š°¨ÚGÁDÓiT‘Ú‰…0ï,W“(Ï5͵8ªòiñF@½9²BÉ ©ÝÔÚ°Ó.ò`N{V?\c(‹¢?†!‡C+F€Jçff íO°¥| ÝùdÅ!Åú……12—}‡à…ZuAö~jèËQø¯(ïsš›ÿt·ÿ™Ä~z—ÿ[“Æ©~ûB–8±~ç.0 ðÝIé0-Þ¬­¾(Èá©)g£Ò<¶Ç™îBŸ¬¦`M€‹²à¡r+nü×Û†¦¹R” eybPut\ª˜žÐ…Ö£¡me†à¬¶Â­Ð™bG¹?{+S=zÂù¾ÄÆäñ”¡¨¡FS(›Íì¹r€•–™^V*pZWY  š‡ê7/¿##œ ‰Œ8ȵÏðR2˜0–`3éÈÆ7> mMZbTý¹œ„‡qF~œÖÌtëX& g ¦>¼¼¸‡™¹[~S¯¥Ž¡ÇX=ç¢å`ŽM€L±;}Eb1€lOP5tpƒ6ÑŽKìÔ”{ö¯?ÄüWÞ©fˆpþkt›}ý^˜‰*¤(L˜ý³h=î¡üÞ8Çôœ?’åÐ¥G|·¨úбÇ9;¾eÌ¡N1–œ¼¸qTý?Ç1ûâ~ åã–¹H¢D¥ y£vöV"*Ú,÷…éîø¬TâÞ"ä!N£[secrÃL ŠhÝמ˿²Ê5ö>"É·)ËÅqÖO¨õ&€á?®WlÒ€£àÍÕeHd!8ZF§Ò›#«r²l’Åbd5ö.]õ ÊÐ%<ò"ÕbÙÑÃ0ã¿!¿:{£(–ŒmÒ¥R+Ƹ¬T¥ÿMƒroQîÁF¤€”~WX'ڗۨü:°Â[Âÿ•)b”¾LýÌ£JÃMùN¾‘„­Â…:¤a/†Z´§p›Š5gLóbÔÌOůêF®¼Âœ°*u $™\Kµ Ý 2§òñD{Ñ•wœ¿Pâ÷d02ƒ‚Ùô:¾~’ߊ?ý}iýÁ2#£fR"~䬆’Va†Aêñ°ê=‚üÈSpgk â–“ÙaÀòÍÿˆ…ˆÊVS;sê^#æÆú¯ã—#•—Ø„0ñŒŸ¾H@ãðFO¯à©ÎdñÂr³öÚ‰röøÿæ\¨—ÿ^À{(×ш2‘¸å²Xyn{ˆdä5¨Šq”šü!U¯ZDÆ1Ä-äôÃtxº p 3ø.:¶~Øü]]ˆœòµpƒ¤ªÀ'/j©õŽ*c6¹³§»+U IƒKÞš&`h+ó*âÏaÎ*퉱 ::QŽ K™\´axÍo´£W°x¤±Ô&4óFë;×ñý!Sçå‡XBS¥oyÃìµHz±‚ÞûUå´á,¥ééN&r‰e÷a…îyEpߣ­û ’kæ|G.ΠÔiaˆ°`„ôކ*‘¿¹Ö:<‘~‡Ú©Fœ^œ¼GFH“RO )Ür}> ë)rßÄ_r¤¤4\¤'MŽˆ a§Å#s!NóyÖz‘«°Y–U´,UäL¤Ac› }E9ˆ“x\¡8ÏN˜°Åöèd"Ѱ¹T«*`‘hŸyÍ•ˆ._}¥OõÍ­B4)ãÄw'{¤,ý]Ë ^ h•‰£n«V(R‘}žôüœ }[a7B2s»:Œ!eö3vSØLÈ—õ'@ÉãÛÆ¤ØK_Þî> ž}¬Y¨qÅ»â„ø¼0‰JÁ†è˜°ÃïFÂî?üò¡R!š-Øeúè€Ý)|3ÅW<²¿«©Ã!rýE:C;[¹|ÌüE5>9hªT0ýÀY¾+Å ÅbŸtÇ6ÅÌŒ°ÓÍcº2s)ç£Sb©oÌUêõz½^¯W«Õ¬¯V2«)9º1±c”H§š_•è \cæÃò¨Ò¦%)æaµ^)Hó}IjcÀ?‡­§(H,—úß­•2¦“øzpÎoW«Õéªa#½=1W;Ó*gÏùûÖ ƒ‹3Ùýl\R‰–röºs,qÒ3'2½^¯W«ÕêõzçW«Õêõz½^¯W«Õêõz½^¯\$:ðÏØ…H ½_™^¯W«Õêõz•x!òé:Uêõz½^Œ¨Ê6Þ Å""\†'B¼‘¤ gPá;Þ¹À“NÓ(ƒÜ…HÄ=êômίW«Õêõz½^¯W¦ƒܯW«Õêõz½^¯BÕz½^¯W«Õêõz½^¯W«Õárc8™³²4¥+Gs¡M O`Åc•†£!_¡^êõ ôx„A¯ËÌ&Ð%z…ONƤ@„€ù»Ô`0 冗Чéóå†Ì—¼¢(H48u+ÂF3…®åDú’Â¥°—«?x³ñXÌpè‘ø£Z,')·3!j½^¯W«Õêõz½^¯W«Õêõz½^¯W£j½^¯W«Õêõz½^¯W«Õêõz½^¯W«Õêõz½^¯W«Õêõz½^¯W«Õêõz6«Õêõz½^¯W«Õêõz½^œ¸~¯‚óŽè¯8ÿµyþÀ¬­¾!?¨"¡}éIŘb±m / ÍHÌÄDü ½ºUêõË¢Ò©ì×2´±KØ5Þn ¥)á­»@W…z½^¯ õz½^¯U¹Lw•Â3:~‰ˆnnŸ¢ŒêuªG3cÞÌŸÓÅ¢~y|©(±n ˦2m7kÅv½Ëz“q £B¤9•!H‘)5 (Ö²œ§q¸¿â¤t&½8꾡 R¸éVDîNGwQ:SlV›æ¼_í)éÎ&Í=GÝ€=ùÖl$?·£„çXZ쬱Óð|û!×ÙF®¸Š2Ã=$:‡§¦ÍTà•Ž¥F´1]vŒßèÉÍä“jaR8ˆž]¶íGñC ø@·EëŘ“¿±ï—Zc‘g¹sðn“±7¦å1r%%8¾6á°Ø‹†˜ñGF\9›ÇP†sµ™g‹ÄFòVL¤m‘:W6VÈÙ¥^1åg½ž”Ô˜G@½Ð1­/4¸‡„ŸÁr¢˜\æv}Î(vSÿI=sP‹÷Ü«\‰µÁ¬¤5ôD)–œ³:â«?`÷,1»':21 5ˆJ¡Å)q>¾©ûTŒƒNBÂåXã2nŽ ÙkØ„Fl®nOBœtص^£8F#S$ hPŒc™Ë^õЏæO9*Œ ÄpÙ™œ¢zl‡ªâ™OÓÔiHe?¨'‹Z× Kí<]õnF¼hé}EcéüÌùJYK”ÛñPkâjË¿ŒKà« üÞg}øz‡Éı}†Ãú Û4/» GWMF@ú`á·Œm)½L±O9PN,.ä]Õašå%Mø£¯²]~,¬‚'*’”l¥Ñæu,ÏXüBþ9ÅŽOl€·r°÷+ÓÎÜ o棊VÞh\¸IåÂÒÉ„ùجŒE‚-®åJ5DEþÜ{‘©;‚á¦"Ns'÷)Ö'åœæO;•0øDc'³÷+%/bûr—zô……ö‹lïX#NbÒ.ØpR5°ÌÈ»=ÞÄ"iφünVvCÙwöCÖb•c™•8|ºO}¨S…ÂΘ×ÐQ|A)þlç9è9Xj†Â3Õ1´\ªH—6*ŸQêH"/Â? ‡ð!Ì'æ6ë\Ú¿t½Ìeìp=‹™ê9fÌÚ” òâ ¹/r£tzG¬Æ 'Fo‹bªOPbÎhã²S6lé}F*R17XœNNïzÄ''Ö¸¤KÙ~Þ«fW–eaÕ"¡OŒX’ |‚0ˆ?1Ó¡}ùbî¸në1@˜›œ,B¡pn”œ!LrÈÎÅ›¿òìƒW[Ϩ ˆÇÓ“i”Oà£*˜ELâ¡·Úž8A~6Û¨^€á«ÅË´nXkË„‹,±Õ–h=èSõœ†”nü²ZBgÓìNá1.¥!ã€3ŽÅWˆ”‹ ‘£Rc?Ü",3[¥•¤kJaˆˆ°7yB¿§igw%”°ÕhŸÊíKí@aýÅ]LoB2‰‡ê”x·'ªkM®±½Ì¢hóv+lFWƒs.Tf“–ïWwDµå:•lŒÍ’Íwd)žÕ†¤â´üÈ®<4ºázz¥û³.µG.<ë úòqÊÝõÌßÔï쇫"¥¥b¤4|Ó8·±7¨˜xÛ(àü]67Õo¹c¦AÈÕÃB³ßÐú¦/"Ä&3är¾Õ2ÚeÂjÖ1h½ƒN^u+iUó‚3®W¨ˆ•2í8æ×ÔTت}G¥÷$#­/g½Lz‰928m¸..3¦lSD0îêX)úiƒâ–vîSôü¬?7êFr©†”\»Z‡Hkì»:0'æ; £éêG‚vF}úàmx”wXp^Ž1žïêôÑŒFÄ#Jd¶iÚ£T|ÁûÕÕ¼Ó“bLDðËrhœCHÉøœJ3 -ùŽá ¤`whŒ’üÅ &ïbH±X{–2Ö:Çpǃˆ;gs¡Ô™ŒÇ…‹9ت®(h "q|@D×u§¯•oN0N!øSÊfoÑû.Ηü€%1¿r¥*uZ‰â8CÿC2柟‰…ÁúÓ­Xw.Xq¤á( †.8¯ìCWZ?‡"'ÎÎûXuŸÕÏd>(Ô¤æFÎ%)U”y:1a ¹abe¢,U·­ÊD „Ÿ©B2”#Å­lÁB7P!ÉÄ_Ãý^ˆ”$*á—&C› :…f"›á¼™¨XbÍú@?ÊT"qaÏHmטg±8€C‰6w¦+øâ8æ ý¬?à§í_zœHý¨°žåŸH*t @¥ŠÆ ‹™~åËœøæ*Ψ™tgG ÷'“è\Ø–“ÆÔe郛.0A:zÎdnÎ4¡[Às”㥿£ŠW.a7FýëÃj—Å5A(žñÖ'L|Zz°Í~®¨Pó³â°Ìâº]ýêpý`Kw@kèœ_ 7⇂Éü:¦*^¢ŽWœ³5~äÞF hÌZf&ª}G²Á(@ˆ ãšÅ÷"ãHB–,V[¤ã(×Ùvtù‚þ§â"üÀ/ ËòBU°±-d¾+Ëÿp^_û‚<â#· ¥L[t"°Ö™Å.)œû*s2„ø¢÷ܸKµ‡°]‚t):Є|V*ÑŒ¸‘5Î ä„kú†{¬ ÙLÝó)BŒLê1-c¬¨Ô¦0TGÕ[Ö/ܰO>!„ø¬S¯Hý¸?ˆ4¬îB­;$.X…KuO4Ûz2©;séN^ZÓЖy·â"Ç8‘оäL{óeÇ4%á:zFQ1i6}*Ì>Wëø#Fv¥„8’á¦7®'‰Ô±Ó.:"À2:P©Bx$öÇ:|¦ Øo “ˆ–{Ö*G¤*™qFäõM×u¢ð‹‚jr!bœ‰(Qõæ’Å"Á8»'Ø?¨Ýù®*§`pT˜Ü}è¾)ÓÍ&râq®%`‹™hÕ{sÖÈi;>ulDþ“ñ^¨ü˜'ìXkDÀ÷üz¸ SÐ)S³¸¹\Â%ƒõ1dù—.™xüÍñé?¦í]L¾²ÕHýHVð/³:qvQ¯+›¥Îeļ”%몤ZÙ“¾¦ÅSê=ö˜tÞ–V34íQ©Ò1‚ú)©˜òfxM¶ ÅjP¿½x¡í^({WŠÕ⇵x¡í^({WŠÕ⇵x¡í^({WŠÕ⇵x¡í^({WŠÕ⇵qÎ ~×MV¸?¹}ØÊPl›&³÷k\U&ý̆6”M‚Cñê°È±ZîVJR?´4wËà°RˆÚV*†2ŽÅnÐÝëíVÂ0üÛ!ÖžÂÜù)—¤.޵9ÆL˜?ØF®ÀèC ”©Å.ªÕΈŸ&fXXtb,˜eÅŸH½8©1¦×u‚™Ä|¼ÊeŠzôÈ=ÈÆ,3ІÌ•ÎÍbÿ›lDM›V'4¾™±R§9Ûx•¾ô(Ò‘”cm·‡Í•…ªÔiÆáÓò§Á Ö6t-‘Ž+3ôg…ïùº|ìD˜`?L]s1ÈFÜÚŠ¹“Äd`ÿ‚¥kó!‹°°r„Í´jB¤#Š'I'­Á0àæ+‚Sp?ÃVMÞãyŸÜJÃJ"#'#ä¦mï+ÂÕÒµY)>²¹u,—°äy à_¡9 ŒïØ2ðà³ñB­iHZÌ3#é«qB é6eòýˆc‘±rª7êì56*ŸQ쇱r,åÔytç_fø!˜m|ƒ_cb2_ÕŸ’!ɃiiŸÑ\Œ/‡ô‚HÚ?O(6-¨òüMc¨[Ps þ‚Â'„lk a8ÿæ¡J¼À 匫 ?5:f|L“_í ›è}ý€jì TÍq¨O'wÅ™›âLNÌ¢Q&kFtâ6kòãpÞå™b«T6|!Ô©2 +jÇe«˜eˆÄÜXÓvˆµæ>Í ~¤Mà*K ^ ®bu•d ã¹b«ìBUâ0ÆáñWµq<“ÐíF5#uà¨Ntøx®Ã"s÷*•+ÇŽ59ÌYIéYÂ×þ£gàªU§$ÏtI»&Oræz±8è ›\›¢1XFpœ<©éÉ Ã¥•¡²òèÌÆ.éù†ÝK ¨Õ^FMw_Š1$'dA nLQx1,¾é2¤]ÞÒ(ÚGΘµänY­-ídK‹¤ÝAû’Á 3ïFU¤d39fÜOF\~‡¿QOÓ"м@jŠ3kE®I·Y—ì£í—ä˜(Ñ'‡;n\¹°ª/ßÐòKéâg?˜æ X¥Ž¤Î)KòìU6*ŸQ쇱Q‰»;†Q¯²Ô§ÄX}Ë IžKÿ%‚˜`I“Ñ"-Ô3ö©›œ¥#!¡¿¢¸~ôcòËÌŽ£~õ*Ñ©<1¾Û·„1ßÓ…²6£DfBY„Á:²BÐs˜)Ô#îTgÀq ;¯\8¥ª?о"à7ø£R¤ 77òD¹ã­uèsƒ²Â.NÖÁ2c¯¬»9Ù žœ"ß´²À#ò»âÖš¥I<ìÛRá¦Ù¬e†¨yèûHW­:ln{nÚ¿ãUœ¢ügBÃè®ÏgÛ™ GOŠ)éF wÚ…z²r3hÉ Ê.p—Þ°5Ž3ëü”^ÆSD~*>ž'¿@úZ||…åÉõÄnŒE«†îô+UÊéstO§4㩱Ž|#8ú^Ë$ÄÓ‹¾"Þäñ†)i•©ëH@w•! Æx˜/Ðtg~¢*¡¤/|{íÞ­èpßpÖTi”щ:BHgö¯ñþ%HDú@FD‡K"×?Iê5®W¥“Ë<…Á}éë»r'L:†B€þíYZ>Vyiî ÅŠ¬„A-j…Hœ^Û-ö¨Ö£Ã‚0÷åÅP€;Ô'@™I…‡22õw’àhR•8€eiìu6*ŸQ쇱RþÿvQ¯²ìÈ=Lí©'´æÈz¹ŸÕ€5À•Fʸ„c-©È|ç ÙhËzÃ27/RpþØüUóÿ%|ÿÉ=8ñ~¬ëØ4•„Ñ“ë ½T­R”eœD9e÷ˆå߆!¢„£B'MãbûµöØŒ tŸÍK“,LmÈ$Q©ìd"ßÓ£¾N¬¸ƒ¹jÜÝ`ÕØêKö”Úº"¶kŠjg¹FU¢bÇ?Æå‡ ±Š©9–”€a¥*N%øé\˜Æ1˜¼îäjÕž®^8®î\>¢Cû¾*O\{0T…è‰E£§#œƒ„L›#µc‘yÖ&‘áx!Lœß€DUy“žæXh<ºÒ}‹îÁâ>h[ù§Ý^ áo‹{ôäÇéÜÎÄAa-kxàœF°„®%pÀ(ó( !܈„£–ïb•Zá¥+vfëc ÜV – v‹ÑœÇ÷eYû Ãê"eû£ðB­? êå rˆ „ ,Ò˜LqµcŸô›òž á°Þ¼=ïŸzhçX¥pXýE‘ÍÁa€dåW §/IÀ#+HB>²$Õ‚Œ!°„a½™qð›bö#Ü„êÎBD? Á=)âîüBõ–Sý16ïF@`ˆUià”¡ŒÖÈä!TɬŜ&p§‘²ø?´'6H]!x@ÕW´qü«—V&±Ù–:qàâ) $N¹ž 4EÑøå3€ZTù /NFˆ,(R¢1LæBµ6§(ç'à¹þ®\ÚŽã» ×ÙjlU>£Ùb#õÁö‹2}I™ÌþäÎ[ódbéãL>ä”)ˆÆ >Séñ3l_öÚªHø˜îXj‡üyÓÒ£X\¹ „2à¨N° Œ'&ªÖƒe«þDqΧé%¶ò7ÛÝj–9ˆÙi±ÂæzJ¢$Þ#t‘Då"Ïz¶¤»Ã\øÕ1ᙑSŒã Y±¸÷Ùï^,GDm_m©Çyø/3ý¡a#™k¹•«‰áõX±Òëd tºÿeÎs ZVDÀšW#RŸù.^3ÁðHØú½á}ˆ™Ñ>-}ÈTÄ8É/Qê‹Ñnö©Q¯¦|53gâ…)BG w6”EQ,|mBœDXÈT¥ øˆ¹ ‚ÜÖ¬8Ck)Ì"çóø¯ãÖüN4®\…¡ÂÒPõ>£ÂÒFþ Ë–qÙ‚æ•Ç3>”hÄáödc!½a Ç¿2æI±wëñ]!œXUüÁ¢_ÇËUŒ¸å†:#ñ^¹xFî™Éb6;·±F‘ƒ;¬"8êIØbïu uiËœ^Q.ÔýlÉ!Ö'õR¿äjÓÒ…ZÏÀöfµIåÊ5Ù¿jjH=¢Õ*”œ™Ynn€×ÙjlU>£ÙbixÁªßÇ(×ÔÊóvåqþ¤èÿ7µTœy;mD5‡òø eŠÉHïU,ñ;)UÅŠPÚþ×Sgâø2ÄAÄ eŒw-Tiy*”ç)rÆÚ–8ÀboRuöGw!«­'‰rÞæÜÀ£ê*œ:òÒ>¢ž0-sm…Õ:”¢ÓņV÷!J˜yH°Lm¨m‘Oê$Úr¡R„':s-ˆ ïËN´­/‡z¦Ú%”¢Ñu¬¸­¤öIíڥ²ÛmÔ‡§·–t>î?‚)Êþ0_1¿r5¤<Æhü{ÐÃ8Zð÷!?P#Ëfø/áz*m1â“ÛSðàÍ÷«pŠŒz±pu/ ÜšA²>¹9ˆÉ1S0³z2Œ~i5çI¹KÔF-.Yks³ä¥fÁƾeÒ¨Ôã€g6Æõâ¾Ë—=çÈcˆÃ5ס)ÊUã³ fµ=‹ŒT©ÎØ]£Ùb”#âÍ­ ‘ÿCkì»:œÁ…P§JCû·,ꎾÆË•JåÌ„q›,Gø”ɨÁ@Ô 6´uv(ÖÅŠrá–fÌ\o'nŒjz8âœwmX},£ šHtÞ¢"E™ó¡JhÆÁ–8K—î\ÙÌÎvê·#Ì­c:B0”£,VaÉB«áÀþZvdåÕ#?M€çbŒ0ÄŒà’jþW¨7HçmhÕ¬MÀ“Hû”ùNF™d1NïÍgÝÞê=Ñé?š$ kþ<‡é–eÍõ RµýÃRãT D‹šä(RðÇ$^Da.§ œXdϱòa¤c&ÐS0ÍìXX™‚ÃZ"C½?¦â‡é‘´mX*pËD¬BQ¼[s¬rÄl“z•j°ˆˆ¼•Êôx„‡Í}¤¦Žñ"è‰@˜Çæý=º1œ°D›e¡HÆb¥f¿F¡Ôõ€F&Þ\JÃJ"#¹?<®­)Èc¡s(ÏîÆæµû© HÖªZ½J°#çu„ZQõU¬”Ã诲Ôت}G²ÇŽÑ-1${•IDË Z6È›o(kì»:žeHµ&¶W‡Í©´©U›<É!­hèêξÊ2R$¡T¡O˜âüBÍ].H 9Âà¹>–œÙøÞņAˆÌrá§dGŠHÒ‰Äòrrb¨Xw¯°¬xÃà©ÝÂmþ»Öb ‘ô1Sy5¤Áøƒ‹­B`áxÚ"}E!ß0*bqˆˆm@0e^›“X±±F4ä!k?õ™F´Ú5†Ø~•ü:$1ºË³ìTiW–*ø¼]ÚÖmAa{r<¬ ÉLáÓÛ-Ҝҗ³âŒcï{¬AŒáµô¡(ÜmÈiJâ¸Ljká>Ï‚á¿<@üraÖ€2¾H¾‘kìµ6*ŸQ쇲Sºrů2û.Φ4@3{eÞb¥F6F>¢ñøug_e»fAçxbGâ¸ðóÇ‹áÜ i ,fÅøw£"ÑŠá¦Þ)~?©8΋£¹bŒ"ú²s F!Ÿ>J?ÝîÉ(Õ¶r¶2ýº;™p†_ñ0™¸²W¡xDI´ˆéèË“¾Åü_PÏó *Hã# :¥LÄT…YaµL×beÂÙ˜iB4§<ì,(zsMéœ,XðR6 `¨œ|úß,`.ïÿTÔ…J’Ä(ѨÓÄ\¸³rò¢2—&6•:`‘JQ.4œ¼Š‘æ  c£/܈:Âà„F¡Ñ•¢Å?O9Æ°È hT P’Ì$¹8D)»é=QU…±{™SâŒ~Q›¼wÿA ÆãjŒé68=ýéýLL=ÞÅöbãIXedúêTçäòAvΤ?kîœÊôß¶}€þ=Y'B}O†ñŠÃHŽåŠ?ÕË=颸ªøûÿj¦dÇU“¬OïNx¤eh>ÅóÿŠ–=àË)¼p±–Œ†S½¬G‚Ì5X„´Y²ÄJû-MЧÔ{!솜î*,<Î,./; Àí?Ó+dÙÔ Ú¦àì«!Ív°SêξÊ5uøæZ#9\ßYŠâ¼#râw¡nè¹¹S+"Ü^r¹¹—6u„d±Š07Œ‘Œ£óø©Eïº?r;Iþâ…*a£²DÊënLt.o¥ýÈ»pÚÈÕ‘ÅRâ_mË…‡mÑk¬S¶Û%s(-í_r@p™nCÔ͹X8 Wå‘­h€vïêªz‰b•i_2ON°½1Ÿ0îZ-‹ê{T½4DD!dFÙ“À6®usŠI—>yŃ®ÄÎÜœLÿ_êSL½¯·«Äµ¬n\¹Ç Ùïwèb¥Frl½䘘” :pLÖ ÀJ¤‘X+ô‚ÆÕsÆ&6·‹ý 03Óþ¨c«ÃªÖFŸú‡Œx6£N/½úC_e©±Túd=”M†ò)Ê”oÏý\Ë•êÿ¶@_»:³þÒ±R/Øvtðz[³ÔͳO¹ ‚ø¥ôõ8ªß“ >¥q±I´¬2ìƒWPj1“f®f 4­½ú<š>g°.dñT#9̼'r|'vHVÍn¦BQ¸·J>”Ûø†Ì°€¹ôc X…ýåb«Hâý¤~KîÆPï¼{Ù“hÊæåöħÞ.ö¯º%¤‹=‰únC‡uàU}¨(K8w³OqXäCflú”¥DFFÌåPDqgθƖ´ç6©S…¬lMø©Š™ŒŒî ¨–˜X_ÿL¿‰gC–×›²Ô”)Ø2B^¥¹aÉÅuˈÓÃß-¼9&@ ÚñGžhø®h擟b«ÊläÍs/åV6Î6G»¿!§;ˆd}O¨ã€ Ž:;òã§âBZ{YB¬oâœ]”S…YRÓ‡:rX3›Õ_â BÏ œáb•¤éU¡éÝœZö÷…ÖbŒÍÑ—ðˆxgï:W.€m:NCü\<ÏÝwLkìµ6*ŸQ쇲y9‘ñÓ´~+˜- ê5F’ý$mL$íaAnñù¯½ GW_?øú[ÞÓ(”ïÝ…¿ÂÉ‹âoêöt#åCÅÞt&u@V˜¾K³ºð©–´È•ŠîÈ5u¶.|üS´©Ânò¹gÏ™et½«¤9nE—Øt«Aÿ)|Vpn–U‘ã•'Ûc¬uåŒû6 ±®Ïx:Š´‘®%c¦D£¤'ùDƒ9¯·9ÇTŠ´csÂe,ÝëîÓ:à_à›D¬÷¡Ç-µ£ÙeòšRðHðmÌ…QfšÆN.©!ÞßÐè¼l˜¸¡P_ó `“™hˆuöÍ¢ðoèá´µäZó#½}¢ì†ÀÀ´\Ƚ½Ëþßµ>*z˜û×ü˜áâ[ÓÎ2?ÖrT¢tã¥×}”jê`)CIi¡)Õ²-jˆ–…ŠVöbf4…Õ=¿#ûÖØMÙ >/É‘¥ 9jQºœðŸ«.¢^ä·½J †_Õèsäíp†W -¡÷äb€[x½ãZçÃÅ'½c¡F’À!Feì–Þ¢é.MpÒ÷äjy´©zNP0‡Í‰¯µêp1DÝÓ;ôÒi@ï:rYjûTå { }DödkÔþÑ t"ÙÚS™ÐÖ,2á–ƒÔá{{C—µF:d¾)H¹³£/Q+[6’¸êa¥,ÀÖÄŽœÍI™â“Žîî×ÙjlU,>"±òç;Y¢ôÁù€bfÌ®*â®*â®*â®*â®*â®*â®*â®*â®(ØUÅ\UÅ\UÅ\UÅ\UÅ\UÅ\UÅ\UÅ\UÅ\UÅ\P°Þ©z€ã–öiuqQ¡ê"H¨Y™0yÄgÑÜ¥éÈ"p‘#¹îX'* ¸«Š¸«ŠÇ Q–‰8Œ¤”'MÄãr Á\SÍí¸g+ˆpÐ<_’„XqXøÄ™ž%–,$Ê÷•¥\UÅ\UÅT£yq!»Ÿ2¤ÁÒoWqWqWqWqWqWqWqFÃz¸«Š¸«Š¸«Š¸«Š¸«Š¸«Š¸«Š¸«Š¸«Š¸«ŠqWqWqWqDzcéÛ„¶}jT€8 8§˜!ê-1—‡½KÖVÅÆ8c w¦w:#jÅTá‡Ç½F¾Rb12Ÿê•êâ®*â®*ã™\UÅgWqWH±ù•Å\P§OÄP[>•ÌÄ·±ÕÅ\UÅ\UÅ3åïWqB®+ùT#ç ©ß.N…;;‡rø«Š¸«ŠåúŠx‚á#¨üWüC"tK?Á`«„<`ZÍ¢,Ï™RŒŽ ÓêÅà–åÅÅ\UÅ æBœ4¢s£ÐkâbS†AÌÊÓìFz«Š•y _zÈ‹¢.èš’•ŒÓ}Y×*Oj”i›îð¹s:MÍœÁ¢ƒ.3õ˜c9­a·šºT©è9òÆ…;Ï»&:…¢3”kfº:ºâPñ›e­p—Ëʯl]Õ1R_jÌ4ä‘ ¨Ü éQ5¬›ZÚzF£ÁØ^¡êpÚÜ$‹CôF¾É`U0÷*ŸQê0Q '~FÏÑÅ™0XšÄ\]ø¦G²}R6b Œj6lbí¨P”ÆÒ3hö' *f’4ªY1›áÒ2²!ò€:;:1äÏ6;¨Öõ•eRc`Ér©7Ì@°[jÁV SãpSiéðã§!£Bæ(¾i)–&NC'«–"Y›Û”ëì£WQŽW.mM^mX©Œ$„á¶õm®\“õÐ QÞæáèK›N2äw'ôÕ/Ú=árýHÃ,Çå97tùR³RÇ ¶fb± SýÒ÷åXܰËÅ£Z˜y@ݤ×´^3Œ¼!\S€¼'-:”ãÁ„~åe9¶ÏŠ& ";1) UÁw_z§éÌÚ@qœŒK e˜ê\Z¶µ±\!²œVR½ÂY÷¬û’χ2zTäe Ø¥0KurŸ¨†¤\»pçQ„i‹#WÞìP8A,ûó®e*b5^"Þð°Q‰‘ÐýUPCpÅý¹!ƒ.1`V ‘1,öõ¼ #xRŸ “-Ü¿FC(“§ôªÆÈdžËz—,S•L\7‡¿½ºmP›éØÉ²žU“kJÏZÚzF˜’ÅádÁœçè}–¦ÅSê*ÌŽ­L¬V'V©Wˆãcj±OšÌü £½:ÇL‰ ¬/…˶ߊcÄAÜŒ»Ñ˃ڱË>…gL›JÓG2û—¬=ì»•ŽŸ9@Hw^°®D Çz2ojtÈõbujdÙ#:mˉ½íÃúU«öá5*~Øç ËŠ±IÓÚ°sûS±ÒŒ£:•<ù~eÅOt‚åà›0¹T¥[‡ ±· ¬t±m mGÕa"b8O ñ_ȉÖ4®­V®}9F”®â²%pÕ„å¢z Ñn‡#å'ú¨<@Øv§á+TK±–ÕeêØ”ØJçÛÆí«½<ŸÚ›”w„õÄ¡OC±%&&<‹oBa>FN¾Õ9užô#R&Î_ܰS ‚«´m°ªœÌöY“OÄI‘:µ2±XZ™X¬N­M’ÄêÔÈõbujdÊÄêÔÉ•‰Õ©“+«S&U¹Tú[üšÓ|/€ Jäz{/bmµ7¯w¼yRk@äBÚì­*ÇOXžã“k¦Í£.')í±2`V¦È5õ8ý9"p8¬Î¨Õ˜jÇ6»òA§›{ˆB”qF5ú´*òæDâyߤH#FgÅtµŽÙ•—ÜÐÊ4j³‰ÂåÎä yv,Ú2Ì¢gºð€©0c+Œ£ïeʪÜÈ݇Ù(¬5âOïˆbÇ – qT8bÎêä^1 úycž•á*â¼%\z_eº‰ÕÆ$ 0ËØ¼2ö,ZýÓqècðÌ3H^… þ" \[£Ëôßr§ûBæz‰b>ÁЩéOÍÆ6dÁV"A?§8‡é•ûÖ pËAËâ—ŠYû׊ä¼sÞ‡÷§‘‘k­WËüŠÒd×+åþE‚H¨à¾‘N˜ù¢BAœuf"êcÅ`«2lÐC)ѧNfuDâ5Îßzä‹ì2=ý#B­Ç؆3ŠºKe-UƒNÔU¢ÌA7è"?ŠN“›ô[þä#Pj—GùÏÛvÂ3¨U†(G yⓉ±Sàw©PÈkm¹ •qþ‹«Šb8Y˜¨ÓôöS¨[N*B´xpÈæ:½=Yø#™–*%À,9ÛaB¥«ßd³7xP¥d§RàÞ€æK¸#íB¥Yc"ët¥hðÜ¿‹ â0Òm·±}–¦ÅSê=y¥T£Ó5ª; iì£_XÕ#½?¥l? þ5hÊ;?ž6ŽŽ/Wm,±íïUeqR9ŸËKˆþ BÖçR4gˆZ=¨×ÇÀbØ;òá:4#Åìk•±Ä]ÞVœ†D[bÀ|=ë—NÝ$ß“¦ÛP•ZQ2ÊêÓల݋ t=8‰•BqãðFÃó¿ãá”t©ë‹?d²àõ4†›~ ùcŠŸíX£qèGÒ“ˆë)ót©T7 ÇßÐÝ—ûãЇ§«‹dNõV'㈈q¢DߔՆ1 }/þ1ï9aÜ¢e· *Ÿ)®.Ý Ŭ¹´ËÕ›.NÞÕ)Ô§Ì€hÄ'Á€Êü2>ð›N¸þi½Tpþál$îÕ¥=¦G>r¼2ܸÁÐäÄ“!pÒŸ–ÚÈQ¨§ Uù‰µ0Èvu1«ž3÷ôÅzÜU¿OG~Z²Ì0ÇñüS›—ñýXlcþ¯Rôó¾(ÊœdGpXç9lÛ–,amËøïöå[¼#+¨ÏÄß,³Kâ¹5¼lî.Ò2“Hs'xÆð…j%âzR›aìTðÅÂu‚`Þ¯ ÒŽŽ¬“r…¯a ®Ù{ûS.dýЦÅSê=öQ¯¯æEá=#>µf mdþ¨¿í~k¤ÿlÙ¡a§ã' + êLé»à§@œ14Ùô#CÑà”_Ì ÚTcª©Ê‘ãŒn})‡B¤æÄ ´¹GÖÏÍ"íçÝ’Îì˜K229ú¹F·­Qäx3dû-MЧÔ{!ì£_b8›5ë›O©¦ËTMsØ5>µÈôf&80ÊWçö£Í„†ËY½ ƒÅ˜&Á†;ÞY9ݶÅÿ3—`ö¿Q…ZÖô8¢ë†š© p”*â#å.޾Ê5e¼qa¹T[ùxC®]£:ÈŽ=Ç;,b6I­b×½èC`T¨#ÆÎ¾–:¥‚±éÓÑŸòXp‹Q§P¼£§+ˆ–ÕÒ5"x¥.-& n?»¢:<ÚXm$€u )Ô"Ë\=ªÉ‰Ý‚åzœXt[¼i >¢ðÄh6ebmКäç9 Ýz‡÷| @F=Ý#³ª¶˜ËHwéèV¥žÉt÷ä‹>e’LDt¬1ÍfJu!MP?¹UGÉ­hî:2óF9Ÿ:ñÅÕ?Ké¬ÂfQÎ=Ÿ ûô³£ÙeûAj%¸ƒ+³«iÆëřУJf„ÏïMXžd~_Åb¨-ÅìdÆ"3}7±QiG‰™T4]ªV—éc¨Z!b¥! ¹µ^ŇJ”iÔsÆÅa±Zë:Æe ª~²Ü2- BÐz—ªá6ìC­G ­ìcWLâ¸!Ç:äÀ½Xß9~dܲ5ð„„šW:”šcÛܸˆ “ <²á’™Â?byNM†#B|G7⢱¨#±Brœgk3®UK×4øaïÈ*FkØá—}«ïˇö¯Æ^C0(T¡,8¬“ÝÜ…Q´h=ÑÃUŒYÉ„£ŸR2ÅŸ½cõcˆÝ+“ÓŒb;²š“²"×F½Ì"…,6]Ð;:©añGwB§¨Í‡O~JÞ¢¤ÀÅ~Žä}G¤ðÖ8ßNII¬6àUZQâ4¦'¦ÃoÅWœ 'Rè>¤kVƒD_hPŸrã˜;;—ˆE4¤ ¯-Ü4LC¼£ïdX_ñB]Ñ:—ói™¦:§?þÆU9“¤/–NÕ8Qc/”D^Tý7†LÖE¢ýilÊuؼ½ˆIï oWüØP×ÙjlU>£Ùeû.Ìœ6T±*T«pÎ…HÓ7Î:Ö C t‹ÔÏp•ŽŒ§™Ë?±-# hîÖ§O÷yž)uËÂ\ q°‘žË”¾Ø…HÂ!ÁwŽeŽ¥Y@\ÂLOD £0-"ÍÅã–Ÿ»+S@Â^Ô!è„$Änëì£WIŽ…y¹Df–/b†æYorÄc⿹rítq=ìAߘh_júwjk¨3‡Èk^ddÄèuÃ"æÒ¿îÿ‰XÚ¡cìÏeèQŽ(›ø‘•†XTã02±ŽÏzÂo)R€|Ä•(èŸà:1©FX¾C¿1»¢2¼‹g›ËBF›¿uêœý0“ކh‘žõͤÁÌ„*ƒ Édâ?²ÖCøñ54¿ .\>Ü{¯ÞªúyÛLÃÂn½`¤GwDìê*V£ãˆS¯ýb1JVZÙœmKÁQËhÊÐBG¾žôG§"53OÕÕã–)›ÁÊÃNtç <8sIô©Õù½@¥Íý?5òÖ9Û®]¦ˆºZ5¬R,§P’0¸‘­p´`j`âŒsžóÓ©^VˆÅÔ½g©"¤ëø‡ËÜ£:ÓM¥,gSõ^®"£1.qfÔ£JÛåw^P«¥oXpçMb`ÁZÌïµ }LDå[ˆçÉЪiµã1Q† M¥î_òpã—BzÇ';d—1®Î¸p“ù#…ŸòRNJ›O¨öCÙF¾Ë³ $FïÅJ½@ÈΑ¾ÃÅïB …8ˆÌJOa:jƒ ±ƒòÉ´,]ÈÀb‘áÎJÉŇQÇÆe˜=ƒÝÓÞŒMÅ >°BË2Ô2{- §2¢køiûƒ•*t8„@8³[’TÁl@‡PœÚ£‘ ¼?J<£!š÷Oꉑý?.ïŠþWýÃ;:”Á€¦Kâî΄£qµ9¹TDz*vYó•É *^²µ3T°÷ (ΜIÀA-ì_ÿÛ¨Ô¥‘‡ ºFdY„Kõ6~ŽÞ™»Ö^±Èh¿¨ú¼UëTcP±™hë\Ê'nS”ZázHƒEÅY“¾Ÿb½òÔØª}G²Ê5ö] l%yCÓz¸Ï†DžÑÚ¹´«O–ج/fÕŠˆ#ôÂÛs“ýmP§ê…¼¼BWN(új“ <>½ £rÃ+B—¦ù$1~5(ˆŽì޶~i±{ÓDÚï’¨J•7¶w1F5¸F+'òŸ‡eº¦sj£^vˆÎ"ßßgÁ_¡)Îì(Ufu:“fܸÉbÄ$_½Щ™XpGÜ·/QL ŸÅF2¸ÊÜ£™‡#ziߨ¯´Í„]Ý“ËíLQôõK$ÞˬYÅ,À¾ëÊYí)…ÙÜgâˆ7ëU(fÅ»£ˆ#Æq¼:ÇwSÁ˜Ë‰ô.Lˆ ]STBûrKO6OØ7dþF)\Ø^ÅÑ¥–¦ÅSê=öQ¯²ìèÊ”K˜K B“ZæL¬`6µœ&ôÑ ”V¥i¸ƒœ/.?åù,BÉ %.H5`Ò ,t!1Š&V„(Ä39)›ÛÜš[ïìCWTî°È¸R>œsH¸ˆ\ßTLª]w±1$DÚ‚2ôîÖ;,3Œˆ «rýßÐG]|ª¼hl6 îoj¦Gê Ÿ¡‚‘-<ÃJ¶ÓyÉÍr²,[ZæY´œÈ¹ɱÚGr¾+›cYíX:æÓ1â°‚°;d­4³ÇF¯‚ÅP}'¤6|Òá“™?6¥§¸f3³©Ž žûz5cû±oTÚñˆìn–ü†4‹TÄ÷¨SeVâ×—©áÁn1™ô†(øK®Q'˜LE³bðähy’².¥H# ùã+µ«od(“Ç àjTý%R0Â[èUkMêË ™™‹ÍŠ\Æ›&ÇMÝJ˜y1aÞ©õtïéDå›búrN¶)qµ™¬Ñ’02—²-¿$£:† &ÌY²ŠQb ÉKBKb‘¹º¦cÃ1x\˜“,ä(I³‰,^¬1Ì:56*ŸQ쇲}—gQ#0ñÂ\(½ì8ĬVÄéÉŠã¤Q¦#k5½V«åÌ»÷èø.m;‰_tð÷¡†nÿ¬:s.`Î%ñR” M2ÆÏµsp˜ÚÖäÅ›#oV5u®í;-µF‡«$N0Îov®L|ÒªFV3ø'»½B¼a.]âÖ׬u_bYÅ;Ås}L±HYg@Æ÷ Ò‚ˆ‰xJçÌrJc0M½c‰ìÔ¹dÅö÷üˆ1r=ë—¢Ë ÷£&¦<ÖehóF›-ìNXôø.\›X¸Z¹Ôå"#Nœƒ¡E=Q?é6 úÑô~”ÛóÈ{²cŸ•NÓÞs ìêyðŽ;d…FgÝc˜ÊëA´é=-ùgRÆ$…ÿ$aýÂØ®]9ƒ$År#§8]© T™-¢ÅÉ­æ÷À„³Näÿ·ņËÀÞŒNd1av Á9’†¦Å ”Ùð›–8HÆW8R…H¹$˜Ëù1ªõ س„1žeAf9^Ú¥Pe€ØoA_r L~+îb–¹ ÒÅ ¶ð°B1×Üs,dÓˆÆÆö)‰_‹ÔSH±G“+Ik2qa’ˆ°¬S.{òàÅ·:«Å 7Ò+‚2:¢PþlZ$pÇIïø(šC—(xL@Os;…[!¢V¹~™§+Ͷ*µàŽxËâ¹P°o Ú±FQu:q¶8¿ðÿæö(Ê›K›UŽèP…ºN“ÔLŒ€Œiœ=å”1Ko•qWĨߕŠÿÙ7›¸«õ…:…«g{€|ÄËnT¾°‹‡t#+ïF¬`dà AùoîÌgÉ˧æÊîîõήG7’ÉøGu«“Ãá$ŒÅr¤_Vn‡2µ‘ºçTeéÉåÊAå†' د̸šç½a²âw,o`oÒ…"ÎV¾} Å™ï\¢ÎθšöVæ$n³\÷¡u¡Ðˆg×¢Äeš ·¬0r3„)9“g=:›O¨öCÙF¾Ë³¨•:´8ó,#¥ÝŸ3j3¥dZÁ’~¨aÁ&>+_VPÅïF¸©D“Šœ&þõn0¾å@÷ߥpx"ªS­œÄ޼j웲éBN-ea W„$HûdÈì s .!!®%x‚pm¹õ|ÊŒvfQôbóÅ-—uI÷bä«eÀÂ@ƒbÕÈ6(‰6¼–…ÂKÿn›YÞÙ¤#i{B¨œ!¬nô(ïn¤ìêX‹09—wõìQ„Y€\£l šÕeïÔoèrêÒ—.Ƶ}©"qGD¿4Å`õR"c9ÎcHÝ1vÝN(‘$*Cà êÙS4£Rˆî•ŸØªŸQ ißæà± }e̼Íå ù¼Z²HL>'“™¾  ¢ý Ha7+­B0Ñ*¥»³”+D0–œ˜FåzIÂs÷„&Oí³¼…„1Ἠ`±ýå_dq òF¹^ 0/­fñKÞ¬ùûJ”±6÷(Cÿ•BØáâ~ûnüQƒÛ(û‡_ÉÀ öfB0e†7¬ÉËiU6*ŸQÈkU²"ô' bmgò¡)´=ÙF¾È9„,léš~ž.AlFå‡Ô_~,ÅúFP›BÆ´{—Ü™+øÞ¦xH%žæOé¾äÎk›Z—©©|²‘bMÅ6,#¹9¿ ú*Ôe(¹qôødñ»YÙ7dÅ¡a,Ë•ÁF8œ,#2§†œÃMÜŽâ¥ÞA 2­ËÀ0Œ-²ÿÉa4ãîLb8óÆ-«¨ >"±Ž*¿¨þ &œÒP3gf³!®C;º«¬{²s«8£ÿ‰r¨Œ1‡Tvu1£êdD¿nƒgY¿¢EZ@S¨á_/ñ(rxavfýÝýÙ%21³ŠÑ…¿¢¢kx¿ Ýpר'üF-Ê:äs0¬a\0U6*ŸQÉ(ÎÔa9b9!eé“KC¦=R'ñ †¾™ì£_d4}U3#9p|mÌ£ŠRaã]#ZwEag™rÞÔÆÞå‡ FÐX8ÅzÅ–Xƒ GZ ãŠ:”)Ó§öcb\ïB­R)[†RËíHKQëdÝÓ> ßRÝ™–'ÌJlJ5êŒTÛÒI½9œ€×£Z!¢,Dëèw§B¬¢bt*’?,c¶Ü·®%Wû}Ë›WÈîîXc`YÙÔÓ&Ú’a‡H}9“¯DF¨v.5®g–a9uyîzžó¥Oø‡ºÁë¨Óò«2̳(H3ÇÍÐÚ5þ €‹,Ë2̳,Ë2̳,Ë2ÌË2̳,Ë2̳,Éä"Zå™fY–e™fY–e™fY–e™fY–e™Pm¡;%{G½CÕà FƒwµfLYTþDì F"}‹ #cÆ×Y—Þ0Œ£#€ÇGzâgÏ­fY–e0`eréÞõN™kÉmˆÎŒ„LƒëJ"Œb##z3–Q³Ä›“+  •‚œ%e„˜°)Ô•Nœå®îRÍgÉâØŒ¨Œ3ˆ&GÄ5Œë“TF'3\T‡ °¡ê=<)Ìʘù–*” Ló ð®fòÿN}é¢b%úM…fDúq>ôhÔ±ô"õ†Le}ÿ°ÔÌMáB¤&!„ìO$,Ë2̳,Ë2̳,Ë2–;Ã+½¨Æ,Ë2̳,.jÒ™a¦/ŸR&½nk€³*˜›2ž? ”1_Ñ5)ýÜF BáœõDz}Ûô¢Ïc"Î΢KçV;··­ÙÒæÖˆÛs£\ß2ë—ÍlŠÆ^rå—i‡Fr¦;°Ø]L¹Œ@ëŸ(ÌPÇ"Zç(RK³çMpM«—êø%ú¾_É8é ]“wLT§eHܦ$E9\D³*Ó§* l  †uŠWfC[ L•JB\TäcáÐ¥FU0˜Ë ±¾ÇQzƒ‹Ês©R¿ #-3êj=„èB£Z~e›V ZSôKÀ|†íš©ˆuOA6É¿JÁí;;.þ‘„퉽F"þš 'éøºåTþÙiüò4­ Qô ’ƒ¦) cùð³ÜõMK"C*^œ‡¨3FÔx½ÙDZÇ»%HR¥k¼­¼²‡¨¦ÄkT¿0Ð24ƒ¡NœA‰6׿GLl;– @߯#çXšÓrꉼ.o£¨D´KÂŒ'»RnÒ67íïBsŽ~’±VŽ hwê,W”ç¡„ÜU>T¤ @‹Ùµ :8•MЧÔz'øíÌÌ÷*'„êþ]_,+I·¿)ì£_eÙÒ«oÞÆ:#£ýPæ1†˜½‰ær8áìèauHüW/#ëËžÍüQm,ÄfON¤NÔñ/’R¤sj웺^›scwxЬ…€±þðzhŽU–È[Þ¥JoOs~¬¦to# ”¢|d“f•*Âó8t؆lÚ £R~)9aŒðlÔ¯•ãrÅ;òšÔn¾QÌ5)LŽqgé‡W!S†ŒEš$š+27fAóôÞd y,ê÷õºÂj¶Ç4þ:2T–SÒYÏsûfê0\¨ËåÄ«U¯7Ž;"Zt¡*r¶§Ëš/jçþæEïpÚÕXTŽ(‘*r‰°ØùƒÙjœëH“yÉŠ¬!F–†Äh\6:ÆÍQˆÆ/¹5>$œèó$f£¤m_dã%a_~8{ïòÇé—à„4¦™qÞš@G<gîп‘‹‡¿!5aƒÓÂù“íO^cƒÈŠ®':rk€u:…~d…K1þîä@7_–1€»qœýÝuMЧÔ{ò1œ8[né`=êÃe¬¯B.?6Dêöõc_eÙÒæÌ5<-ˆ~:º&g0tjKÅ"çojŸ£oÞ:WdÝÔá§&ùË+Æ7/ܹr¶Ç~¤Áí–Ë![ÝEëÔùx¬m蘲þEbõªˆ‹¶u˜™ÕÚ‘³GõìBÆn˜ôÒ„°ÅíêË5¡JŸ†7#‰ba¹Hf-›BmÈE´f_o7KPib07‚*¯f=èácNüR,ÚÔá(‰“„œ2¹[H·q Ÿ¨‰àpUHÖ–{ö*FØÄÙÜ„æE‘b×^«Ÿœw…H‚ø]×'=û]3á1´nSôîd 8ÉöŒê#. =1ûô˾iiºR ŢR^;fÝÿÕêq¨\˜‰o“ùl‹ñG5¹ÆAôKð\7¡}Ë}Ñzd¾ø¹s”¢@p@Ü„}M ö´ÌØëþ%>YfÅ?À#+Lò%ÎIW‡‚«8ÒEûV:EÁÈ=Iq0×Ùå¡‘°8Ä©ÒR.vèíØzWUޱØÅÎËÂw+ý×twu<èxáíÓ‹²sc⇻!©X=¬-F|ÀÁÍÝ(Õ…á ÆãoH @oêÌiK¿S>S³¨•xDstþª"3{‚“ÞŠåˆXw¬]íÔoê*–ħô¤’þ×/» G½¬ö"iãc~ MìVH˜ƒsØêË5Xš­sôŸÀ#T&`a3f/¥‘¨¨*Gå-n܇R‡8kòâ2Â(̇gÃÔNXš ŸzWS92\èž Ýs(áœI­Ð)Ý$yw›ÉÉü|8¥(ÛkX€•ê?L¿$nOkË„âä½áJ›³†tc‡`8‡Ã¹ }Jww(¼¦*<ÏÓwõbž(sy4 ë,ׂ®tâ(ů ¼6«cíOÔTت}G¢9Àq±Ü,VâÁ·¨&!Ë\£R¤pȇ1ÑÑ=&+øž„%Gb}éÌaËsˆŒÚ:ѯ²ìéá–á}ÏI‰Ù”Щ°è)ºèø[þäx^Ô")Ó«)ÝhllTQÿü0ê¥êäa@jê°ÔŽü˜x}«(°ðÚ€›¡»©•P¹hÊÃ$žæ=Bž³©ƒÔWüBÃô¶hM\Ýñ¹XzŸè‘C›T´BæÒ/¤ìê¬XÅyVž£MÎeΦìt H½:e­N±ÉĴIJáœÁØSÆ¡æwݹJ^¦Ãò˜Hü:•¥ˆR†ùë¶Òf›%*Þ²qŒÌŠvËò\Ûe/Õ–lX'S MæÂ}ÈΫ†¾N¾Ü?gyü§ég3cØ#îQ5þåXüòXeh+ø‘xù tìˆèJ·0Œ]À¯ºó=çà£*‡ì´€‘ÍÜSs!þAÆÚ1´è‘Í­4ƒ…‡þÌ¿Ú~ A¹/÷.\£(H4¸•9zyðºÍ-!ˆ΄*ÂãsžÀFõÌ„£€ËˆiV Mc‹™™GÔ¼ŒÚ¨á­Òˆ‰bñë*lU>£Òr–åÉÃ,ǧ-Q¬kÔMa†mhèÊ4¥†dXt 'lšÒ¥_Ã…°féóŠçΪúî \ßÁ}ØrððÜ:Á¯²ìê9Ÿ%F‰î9¾*Xcq u ò™1sÈ)щ”‹þY2Ÿí°É£aÎ_R*c´~Ñ¥ýáHbL b°0\š¤¸>˜7¸u1­0Ô"]ôôF®É»©b¥@üžìÝ)Ó‡„…-g܆ܒ§SÂE¨UˆxÎñèc™à›3ëXÜ‹ºh†ØŒnèœ6œÊ3­Î2—RÀ{UÇz#JðûV×éñø…Ò…ŠVA³|Ûé`ùÇR…3ʃ±•Žt£R°õ‚·A÷#ƒ ß‚âÂíø.wÍÐÀHÄst% ˜ø?,¸eh+,2§˜mµaõ˜þ¸4ˆßj… pÃÁ81î΄ª;,BBãoC—=v)ßR-Ëaj¤ :³¾ýº2Ôôô§uŽ3·¨©±TúKƒútì°ÊõކpðÊA vË;u5'9½96èG²}—gQ*zBcdÇŠ'7CÃÄæ(Wâp^øçX}(¶ÌƒÖPD”{´ŽÇ-òÔ˜\:#WdÝÕTºÁ¸d±\w+rs%dt…É”@ ±°Þ— ·H‘Rx6ö›õ,^§„1¹Šã dÎÙ'ô•Š_¥ÃgµpÀ6µ†S,Sœ’þ9j‚ÐÊž5$Þ Êò¯(Ô¨{ÉBp¶&ÑÓ;;.þƒ”+Rðž¼êé™S¥˜\õ8©¼ÝÌóh`˜U/"ð"W› ûÐp/œ~òªÃXç:5jÈs3i:®öt'êéø„Ì£Þµ Æãhè y0›ŠÁJ¹1wõýjLmË(Ò8fE…Õ@¸ùó,l1iÈÆåž‚7YÊv޵mý'õZ¾Ú©±TúŽY‰ÃbZ=ýù!Í~3…Ñ©M¦ùÁM…c»¬9 ¥§J‹XSjÂ7©—Ž®¸kì»:˜W!!’=Ê7µšÐ§ °eÃ*@}6Íôo8þŒëjrˆÒG_ _ žíHòy^M½!«²nêFÂÎe£RÂ2HÔÏ;Ö7?ÐB¿–ü‘­Ìl?šæÎx¬k›/ ÃÃ/Ác®¸÷ä>iŸ`½êúÔ$go«ÿ2ý9›EÛNXý#!ÁöFR%ø‹ä4iHJr²Ïjä“h#¦vv]ý'“\rŽoÏܱ?·½bõgWOÔ½DÁ,AÑù§ËË£m(ž9j¸e29‚„I{7w)Л6ô¾²ˆà”dqj\ÏN\ :\ºÃol•6*ŸQMbÿ0ؘÚrñΧGÁ)Û¤:å^|ÀqcÕɽaB&œä|Bål°šJu©Ö…LÐïõQ£ÌÏ’”0Ÿ—?^5ö]KCÄà…Ö¦/‰¿ =8¾¥š²ä·~JßO_Dþî˜ÕÙ7u5Œ1ö~}:z²âX{‚çÔ|1ЛÓRÛ3ø9²f.0†QŒ¢/•Ï+BœêŒ2Â,èqÉ¥˜^w'—ˆÚu•N¡¹Øíé—IÆÄe¡b« ³VueÅ+E¥ SÀ0Ä#"ž³ÊyË•ñJBÀ_ ~ 2ãæfïèI¬¸?q6®e*‡.c§,â/1*3 æSžØ÷.E{Ï(×ÖDV,&YómF%›ÔË0Žìè@çOnekÚˆÈ$3¬'2©±^:’–Šü­Š@Ã06}t«Ô©ö[Âs,y¯X\Ķ.0Ö#©a£>\´³£N!Åì„¥1XM¸eš_Õ‹¯3z‘qú}‹ŸÿלEÅÖ©FŒšw ¨Æ¡§‚ÓŸ\5ö]U¾!q…÷¸áú¢-Ú> éHKVHz«ùyµèïÉß'IÑ?4HGí{B4ê°Ž¶8Þf:cWdÝÔJbñWy´ôãñxù%^ŒÉzÂ4”iʬ¸äàÆGBÃPžõTð‚¹ºÂNPHû1 Ð"Ë:1•I=ÛÊoïQõôíZÓ2%cg,¹Õ%ˆÊ×ÚV:q<¸‚ò&Ë{´¨ÎP-"Ø©ç±ååóã’ñŇ,!< â“ÝÒ?Ångî¹OÓ1Åu½#Ñb)xHeŸ-ÒmUeNTËJ8ƒÜ©'ˆ t®^#Sr¬L/MSôƾ˳­•3{î(Uù ôO ötZU#½#}øJåÑ—;F[ r÷ôƮɻ©aåËÃÝÝÒlÌ2ó×IÅÈw¨ø‹BÃ7°(F‹r³‘®ÅÏ¢,Ö¨Î4áLFéÉýÈS–@k¸t![KÀûÆWæÖ£D|££Šw2þ´£i@ĺyX2Î0÷)B-‹R·Ü€¥¤õÛû!Ô±K`Òt.m^*ºtwÃ!VÈJX„³jXà\5ôå8ŒD ´¨Îc !ÛFNSñ³·wB~–~qÑ4q?ÍÔ€§NNql¤§\ª}EER3a,b뜆”®k97!†'ª4œÇ¼.ôålßÄe§&(Ø;Ö7ïLö)‰f±m#vFB&MžWô!N™¶O†Í<êí9”LEÊSfž˜×Ùvu²©úA9)àùl–¾„hÇþã¹î £b:Ôf3޳•Nï˜èR¤"îåÖ=ÃJyǹ»&î¤Ò9î֜ߟ_GûG@1ûyâ…Aq†OâŽâr /”( ÜgðR•;£õ±fÏŸ@upàsnpmú:)IÊÒÿs3ûTy¾6µ´£³'*«áw±0ì[û A2™`ÀRÑé½< ï—ø¢gVNß+±Äqi6žÇ:Þ”´¼Xo¨ÎƒÕJµøXI.œÇ:‡§ˆ¾Ùš+;—òhF'Ô ±Ý:›O¨å4åtƒ!Ḛç9W]ÓåV¶.èòCÈ BUã†z2xŽõ„›Ð›b°[z•·«z_òpãý·)óãà!rjÜ©ñJŸ$á#ÅÔ6Q¯²ìëx®G—árÚ”½)º q¬,Tý!q <7#HFR1½›ñNÆ"€=ùÔL‡[þ_QÃ9Ο‚2s"籿à–î©…ê\øá2“Û«¨»&î«ù1ðËǯNHàÆÒé{°àfîQc‡Zæ3XÊ9øLâô£‰äsŸÔ;Ð8@‘Œ¾câXý8o¹§å"ÏÅ v*5% Æ$g‰N-ž`J–ÈäòÉ÷ôeVü ”e;e+JœD_ðEѪi€ -²ÄÂ9NËV@Ì3"4#³²ïì´î èœ4ÿ^s©`§vC«²ÓƱbÚt§§!-]Q—§n+ãuºGz4½p±¸‹1Ú?¡üIý§ãèX£òÇS<´å©±TúG°álý'+u!oXTâ÷?±8¿9!R^([¨TœA”n:¥äbŒç2#áTŸ cÔSckDV/6´Žˆ™Ìú1¼nVû”pæ#2¾øáVwvMmIR¾ã¨Þ°JÆ„ˆˆÎÁF¯§w¾Û™skœír°ÊⳜîV8Y0šoýgG^Y§¦)Ò»æ–`¼É¾ÅQÌŇ1HÕÙ7uU5t?¸e¼äåP¤‡2"u/2#܆Ռ_N@ìËT~ÑïèÊœ®da/N³!Q£úCtzðûPq`EïfGgeߨ)ÓÓ0w[Ð:º¢ðÞ³oF˛ژ >Π ¡íAøa( ß©³¯¹È =Ƀu6*ŸQë0Ä–X±^"¯½1=à=¥ºÃ野#Pœ&b"m«%‹þ[s-»(×Ùvu²Áœ€u'…sêJFÖgèá•¡IÏt­MV-ßxÉÌ 8αi·¡Ê ûÅWîTï»r<˜ˆ½¥º±«²nê¥HÝ È~\ÃËÝчŠE‚32œƒmF¸†9Ú:ebcmYxJX¼“Äæ·2yX¡„X.KYgRvv]ýƒ™ ‰ŒO|M½«©ˆ:B”Á¼?±—ŠûQâc¸Šª^Ò1­`˜ps&¨üŸ–Z;à…bú&¹xm·Ú¼>ÕáéÔØª}EX¬L­N¬V&V§V++S«‰•¨žåb±2¾ÎägÞɾRåKõk&V§N‰•©Ó«+S {Õ‰•©Ó«+S”)1´bk‰•ªÔêÄÊÕjuf„ÊÕjube(ú˜`”N­N¬L­B®cÙjtÌJl–§V";×+å§¿Bµrÿ]Š<Ñ#ÝÐ¥oÊûÕ‰‚ejub±2µ:±X€V§V++S«‰•©ÕŠÄÊÔêÅbnð­N¬V&V§N¬L#òÈ„éÕ&ýUyv fÅWê#†˜´=åsª%¦6:çÑs-2*ÄÈ?z1¥#G‡YΣ\L8øH¹œ#Åh^/é“å:±2Á;ŽeŽ0§V&V«S«+Q~äêÄÊÕjubejµ:±2µZX™1V§Ö¬L™ZX˜&V§V++SæRôòÎ*sv³6ë²2µܬV&V§V++S«‰•©Ó…bejtÒ¬Xd©âý!:ubejtêÄÊÔéÕ‰•©ÊuQ»•O¨å6›”ƒ‡O`±ÖÜú–;ьքÁí\и;­Ž³fOe¯bÖ‡Xó²a.&NuiG žÂè±,Óü|6éuÍQî•ý€kéš8Œ_8MÖìëiÑ;ô)C:ÃH7Hë\36é6!­­MOîVù¤V: P«âÃÖ]“wWü‘à•’î:S”¾¯À§ÂWHm\Ñ)BFöΣé}3ÂkK«[±—`¢>({ã uvQ¯!Ž•Êõ%ç‡ÙتlU>¢Œ€ÄÂà…IDÄ‘qÍ•ú,Ÿ¦ùFŒkì»:Ú’­âÄFäjþ£îéy?@ë=•”GˆþpÕÙ7u•=#4+EÆÌÛ/VU ßÍ µŽ*£pÕѶÙ s•ÇTÆW´.ˆËÂâîöFPâ“XyÊÙ'±—`Ã+AGÓÎÓ Ž˜æÊuvQ¯-J£Ã"Ãûl~¨‚%¬9ߨ©±TúЧ…ù@VØOc=”kì»:Ã- ^¢¥ò.„do·Sôδ=4NoÅ¡s½9”Œ|@èM+~QJ²‘Qôðù}ýpÕÙ7u‚tüp8£ðÚ…H\r^ÉžÕ‰ÿ§MŠÛP”lœ|%c©öÚÆ‰wþ³ #žÁ/(ó‹ÎWüd;;.þà ¥·å:»(בÕ?¤v*›ñÍ¸Š•lg[m*ò¯*ò¯*ò¯*ò¯*ò¯*ò¯*ò¯*ò¯*ò¯(ÚUå^Uå^Uå^Uå^Uå^Uå^Uå^Uå^Uå^Uå^P´Þ¯*ò¯*ò¯*ò¯*ò¯*ò¯*ò¯*ò¯*ò¯*ò¯*ò¯7+ʼ«Ê¼«Ê¼«Ê¼«Ê¼«Ê¼¡?Ò}Ë%м«Ê¼«Ê¼«Ê6›Ñ›–¦=¥b‹ûÞÂYÂÇZÚd°\š:É9“âÅTÞ¯*ò¯*ò¯*ò¯*ò¯*ò¯*ò¯*ò…¥^Uå^Uå^Uå^Uå^Uå^Uå^Uå^Uå^Uå^Uç2¼«Ê¼«Ê¼«Ê¼«Ê¼«Ê¼«Ê”#"ÒŽ3®åy\'z½a{êò¸¤\Üå Õ¬?HW•yW•yW•yW•yW•yW•yW•yW•yW•yW•yFÓ™^Uå^Uå^Uå^Uå^Uå^Uå^Uå^Uå^Uå^Uå^s«Ê¼«Ê¼«Ê¼«Ê¼«Ê¼«Ê¼«Ê¼«Ê¼¢"N+ã¬\£PÄ^Q´Ü¯*ò¯+ÄWš¾Æ9ûò¼QŽÇø/;tj±‘ïƒ{–Ž2͈3«Ê¼«Ê¼«Ê¼«Ê¼«Ê¼¡i½^P¢$^¡Ã³?±0%•å^UåJF¤Ž#`²ÅyW•yW•yW•yW•yW•yW•d•F/ržo6ÄÚ›“ÀGRбÊ5ö]( ¿°†RªÏ„;ÍŽãЩV6À‘‘¥òœ99tïbw(ÍâҪǵ T’›H öXž§›;û»»ÕÙ7u³§úøÁ÷Œ¼rY\K¦YÄA-¹}ˆ·îžËýËâ¨o‘Coe;;.þÃÊ€z’‚= ‡RñGzá ê(R¢Æ¬®îï)ë“S]Û“Ž¢äVó#Ÿõ +BÁU5.áa£ó굈7ÞQ«Q¥Tß/ÀuØ©‘!ÝÐÑc*“!¬Túd=”kì»:ÈÑ~)ËØˆRuä5*÷ åB–(Ø oýг\ \ö÷)ág¬Ûb ˜Å¼7é’#½—•ãhî_r8ÁºTíyu?Åsëø®ŒOçØF®É»­1˜¸…‡€þëG³ó\Uj­îVD>“i\PŽå†‡vQ·²—a¤-‰Ðš¼$úb/´%9hÂQ>¤¿ìÍx#¸'•8î íÄGPê)Ö„q¾&½Š«õkNÀ?KæîP§L†ŽÈ qJ^+{¯êµ‹þÝ;»åùuòŸ¤Ã8Í—é@V²tÝ¿¡Sb©õÈ{(×ÙvunT« ÑÔ¸lˆ¼õZ8o_Ç„LLDZ]“we{)ÙÙwöC«¬iéù“0ï½ ý¹@9ˆéãŠá¬Ü…!ò†ì&V7é?éààS"P“߈e©±Túd=”kì»:¸šR"˜Í»òN–£ÔhÎWÌ«æNþᣱ]n!¥•Åc̰›ÖµÌÞÔÅnì£oe;:ÊpŒ „Ë>^§d:ºáRŸŽ>tÂÉ âo!M³ÄX£N4å8—“f=]Šåw@Ó˜x› ¾ÛGqÌëîFxµ:Œ9a¤Yžß‚©±Túd=”kì»:ºòŒ[²R|r÷t£Ë¹k{׆åù#̨¿(ø¬eå!ú‹öA«­eáö®SiVGÚƒ‹ŸÚ¤âÉ6}ËI[»(ÛÙNβ\F<¼Ã?S¿²]ÜF|ë„Ìj™Oêk5;¬ Nß‚àŤH£þß‚ùüHŽ <:×Õ˜J÷Q7-ð—üQŒNfËüzm)~Df@äØç×°uSb©õÈ{(×Ùvuqô‘ùø¥¨! $#… SĸP„mÃ,RîÈuöQ«­ÃB|¹i@L¼šÓÒÝÙFÞÊvv]ýêì"Ö©FQ––Ä-KcsZ£_Ó‰blí¹ }ƒ !Š1-3ðRôÞ ,Ëær”(€Y{Ú£ê ”â^=çá’¦ÅSê=öQ¯²ìêáé¹r&ÂXÚ]Ò\óžþŒ+Ú@wmb† ˺õ‚˜wd:û(ÕÙ7vQ·²—d:»„®®wIcˆFò—ë‹ 6¡¯¯û~d‹GñDȹ6’£ééŽãÐÝé–Ðö& •6*ŸQÉ*Ä>Í­#°žÊ5ö]V:TÍB÷XÔ¨^Ÿ\^ûÑõž“›¢iA„GˆžþåqùC9¿)×ÙF®´¥;·r:ß8:»(ÛÙNÎË¿²]‹D…Òоì'ŠâÑ,ýÊk -ŸBúÜUd";Ózh’Q°,u ”´•âÏk^Ê<›`xœç~…MЧÔr‘@a.{ ì£_eÙÕ5)òáiœó†Ð°Ç •XÊföª1«Ø*lXûŒ§½á·Ú¹f6÷¬³!VbÆ ™L1ÌT¾¥ÊäP†}«8±gGPÉSb­éå­J‘ÌošVüÿ& w^ìDa¹ g4Ù qwйXmÐBÀ<&ÑÓÙÕJ<Äqá&Ü+øþŽãâî9×Û›ÎQø¤Dw®];½ýb‰½[b¹,êN|-íF'7`»&îÊ6öS³¤céå†zWñ%,U¡+:íýêì£_d5 Ñb•hxd §Ôz3Ö<ÂË‚÷• •PPÐù8¿@GêCjÒ¿¸äÚUM6)¾”W>°} °ñX¨«a_Ú2@ìÞ©Tî>Ňô„&/°¡Hga¹KéR~åÒ¡µŠåD¾¥So½A¥C%MˆÏ3Ú©Ê?7 PôñÖ¿øÑ•O s#LÀ8ý¨ÿ6›ò*{TCoOgUS ¢T€¼Ynô%t¬!rÌ@ ù0‹äp‡ï@D+ñ7E≓0Bqb4…™@EÀ¶ôe§° ]“we{)ÙÙwöC«²}eJXÁœçêjlU>£Ñ–"Ö©ýG#…ʯd³…ŽRqÞW"‡9G$ ïQÃmˆÑ™bîªBVÞΘ‘yX„ØW7½ÅNBeÄNp¿T¶µdý¡r¡+-Μè)ÇéÈá ¸¹J¦’°L‹ Œ#pTÍ â·Ú¹†V÷¬(°&?Ib-ÄQm¥ ™[®Õ޵šÒ#@É+^^Ább9AÞÓø]ٮɻ²½”ìì»û!ÕÙF¾¸ÑŒZGN¦ÅSê=öQ¯«„¥pPåÈ~§gTÆåÉdž„äöÄœ=Ñr(Ô©2pÑDzpóáŸÇ1µ¬@ȳèNâsôN¾¼Fnò̧¤n¼g«²nì£oe;:‘ØÅœɳž¯d:»(××  fóÓ©±TúG;×,17ØTaú®¶Äå™Ú§ð½ ÈpÙqÒ§a£œ©S«x†!j…:žgta›5¨N­¹ÔªVs,1lêôîÒ–eüxãÅp—z8ì„Å!Ã'kt!;79dHfœ•ˆagkÕLvJ=ùÔ±g²ÅŽM†çÖ(¶$²pÌ$ÎêQ»îY°=‡±ìë1T²¨^¥Ê©?+¬!â$pHŽ)x‰“^¹žŽ5;Ìlâ1 |xO¹^®7Í”ëëÚ´AQ­éê×â¶ÎˆÕÙ7eäÒ|.M½ˆmì§geßÙ®Ê5ôæ}K3𶎺¦ÅSê=Â¥‚qgR§ãR-ˆ •/OLâãÄK)S§,rœ†mWõñÆâG–xpÀKb©J.Øñ"êP‘ÿ²"¨Œð›”eNXµ<=A„Y°±GÒÎlÒxÍ”)Ñ8À–)ü‘2mqµ`8î£ËCú —c3\Jmn¥:‚£½< ÅB¤§„ÆžeDFX°=šTaͺxü%Â_÷.îU*JéxIìT¸Ã"KE”á c3#5ÌUJFXAž!&TÄm§à'H*$yPFåZP f0r¹`ˆ4‰£ŠÂ…0m ¹Vy4jEeDx ßj4dYæÄ(áÁ"ů 'S -{ª gïddörpíì{:æFFâ½rhÙ'Dëì£R©[¾(5²ÔžãÄ3ƒ ö-Ý”mì§geßÙ®Ê5öZ›O¨ôìÉi9,*Þ öQ¯²ìì§_e²KÓ™D k__bÝÙFÞÊvv]ýêì£_e©±Túd=”kì»;)×ÙF¤¦ˆœž×ЧVsLj¸—±nì£oe;;.þÈuvQ¯²Ôت}G²Ê5ö]”ëËSÓ³JŸµOðK ½#Ϙž†‹ucWdÝÙFÞÊvv]ýêì£_e©±LäÈܼ$Øö ‚p¹\®W+•Êår¹\®W+•Êår¹Êår¹\®W+•Êår¹\®W+•Êår¹ 3«•Êår¹\®W+•Êår¹\®W+•Êår»2¹\®W+•Êår¹\®W+•Êår¹\®\LÊhƇ§Ãß/Í7¨–9{“Í€ïWr¹\®W+•Êår¹\®W+•Êår+•Êår¹\®W+•Êår¹\®W+•Êåv…r¹\®W+•Êår¹\®W+•Êår¹\®BÍ*år¹\®W+•Êår¹\®W+•Êår¹\šÊår¹\®W+•Êår¹\®W+•Êår¹]¥\®W+•Êår¹\®W+•Êår¹\®W#b¹\®W+•Êår¹\®W+•Êår¹\®W!fur¹\®W+•Êår¹\®W+•Êår¹\ÞÅPÆÅ*‹W&§!lp—B0nÄ{(×Ùvv,Uä";РöÉ­ÍmʬLêCˆä£/|þ`¡7•öa ¹âUk,}$(¬±L^{ÕÙ7vQ·²—d:»(רÙ7sXªNZU>£Ùeû.ÎÀjT-Œé‚-jzçÙâ©"@Ô¹Ó„\Ä2e `sBÁ·:©J­Êùfçâ°`ƲôÔ…÷œýŒjì›»(ÛØ8‹kVÎ;Âq”ìì»ú|e˳FÒ…@%ýAŠ4êÑ0Ži8='6.Tg-Ð:»(×ÙjlU>£Ùeû.λ¹úhâ³›ýEJÜÊ Ü°s®) ],Ú,r<¿Óø£#8ÆOuᵩ`‘–+K£¯²ŽÉ»²½cĺŒd Œ´2”©J" Fv,F¬Ç±b§L­WÓðŸaÖàSƒ^ïjੵ{ȰïNJ¶¤?È/6;-MéíŸx,¯Pf6:á§,÷”ãþ’¶–éÔà"tÈü¤[ùâY Qƒ¶s?É4ˆŒtCâž‘07pظ½¤«—Ú©(þõeGךpŒ¾’ˈHKC,Ç.:_‰9ÖËdÔ§&2îf_pJ'Sû‘Â' ×·ÅY û>+í@7î6û•oÔÜ€:‹{Öå‹ðXfˆÉfKrْܶd·-™-È5«2[–Ì–å³%¹lÉnFVhÉjµ2³%ªÔÊ̶®UG¾Ë/"à‹{Ñ„ÀÇSçá±>åm@}©—(ãÑy-L¬Ëjef[S+2Ú™Y+S+™-L¬Vdµ6K2[–Å´+Sd³%¹lÉn[2[–Ìûòòå(‰'VäîMÌŽÅÿß+‚ÿq^fø„9vHg «1 ôø€:Ñá ¡)DwH«*Oü— I{à¼cüñƒ®!3Ãü5:u-”ZÝymLžåöŸÒ?¬¥²ü—ßl#åÅá—´•]ŠájhÍÀÍ0ès!ÏîAžÿÐS´µáNs+]uαÓ/–d·,…ز[–Ì–å¨ÝʧÔrá̽¹,LÙ\®ZÓ«|é“å;,HöQ¯²ìêMJ–DZWØP”mÕ( F2îwÆXäòï%Ð…ki~¬ã_r1Ñx´£9—”¯95Z£ àKݼ/åÒÎ%±#¯²]‰ä[ZqrÝÒÂjÁõ¯·!-G%©œoXådFuÌŽk͇ù.®¥“J¤_ºßr Œßò•ÁNG[ÑjcyXBÃ*¤æÔ¸©ÏØUåôa.±y`X%ðVu§¤q8§Û9X)ŸÛo¹|ÿâSzhÛœÌ# “á7ˆ†í§²yŒ©HÅïdüÙû›þиÄf;¸Jû~!|Mã­©±Túd=”këœÜ¸%¨ô¶u úzxô÷lDTáÌ`,ÞW2´Ëéá´û,Þ¸)ÿ”¾ Æô^Ù ">í+cßÓðN.Êc$éTêÁÍbÄXÖquöQ«¬ÅVB"ëV ˜s±°mMPJ¾íá±”ÍÁFUä7bî^#þ% ¼Ã`fÄ…!,!šV+*ÃzûBU;ÅÞÕÁJßÝ/‚ñª+Í;‡Áa©RR?Ñ0X®:Eé¹®+sŒ÷ݹxFårÄÁòxBà2¤W›=ëÍžõÆduȯܬC²ž“Ñ‘‹Þ×/’[r'Oî´wþI¿è²}C•h}k jͶ/6^Ï‚óNà¾ÿ?hb›¢V{×WJ¦ÅSê=öQ¯ªz²ë·RÃJ¿(6ÌÇ ¾ÿzû’‹èÄãÚžœAÛš0Æ?q÷BRŽo§!x|ƒÒŽ*‡0Í­b¨s]žôa9¥fN hè4- §¯0;³¡V„é ±LqiÔëb€-!ŸS….L žaˆþ+Ê©þ+†”¶°F§ª?vwF6؈ "@ו®QD™iHç}”jêIøEéãˆê‰X 7{o-îV&+¤÷’{Pì§þ’{(××Z¥9†œóh*›O¨öCÕ<Ëôôä%¨ä4ªTˆ¼âî˜Öž¬€@ÒŒ§§3kBsjdü²!`¤9šH6.$/‰\ºŽìöX©ˆ£Ùhd)Sð…+%i>b:ü6+ý…“󣡿h áÂm‹ŽR:äS0Y÷•ÀñÒÅX3. ÃkhrÛ–*oûlVU–Ö+‰ª ÅpŽo/j¸l\ǵÝÏry1ßg¹Y{Pž+AumYl`­©?òVÎäUcê>ÄuöQ«¨µ|Þûd7Å5wŸÁ 0÷váÙOý$öQ¯«åÂ&R¾Äò1‡q´¯4âÔræŽzNnMЧÔ{!Êõ$#¬¯2;˜Ž¨•àŸ³â¾Í;?yø.*Cd–:’#öÄr÷Ú±]-"õÅRäÞäõ ‰ï+ÍžõmIï^9ÿœ—I¿ŸüŠüʹzá. ëÂ7+8¤#9)åbϤXSU&`\%jÇK†Bâ.lÝYQþ¨ÉZ w…ÅcÞ\ïX`tötï Ä7¯Þ¼Cz¼tm+ƒ‹é°Æ/óJÏÍc¨y˜ —ÚˆŽ Ž¾Ê5dµqÔˆÚ¸I—Ó¬„÷Šû4Ëþëâ¨GÒ9t›W„nL;xì§þ‚ýAì£_R3¶cq\ÊžBEVÕÌårêgµý½UMЧÔzóLÌ FÂö&5Ë}ËÌ 9Dƒ¨£ Ñ1ú‹.cá}¿Zß¶m$õ¤&Do‹؆¾Ë³/±[‚#ü—Ü©#©‚æT†,Áí$í^SgÁgØW‰µÄ«g}#¹8§ ÁyPܼ8~›=ËÀÿU¾ôñ§ý£¢uö7©!«:³è‡.™»æ ,R¨Gtl ä]ªÀývSÛ+@øªãoí Òœñ¬þÒ¨—‚¦fcz…?šÿ'UhÏ˦§!fuN>oO B˧¥zJs<&–=¦%Qœü¹Î¨˜ÔéüÒÅSüHèžÊ5õ9ÙíkÙ ~œ‹>[Žî®¦ÅSê=|§$IcÞ¹RŒq^ÀŸ‚²ÿ‚yW„#†sÜV*œGL­\6v1¯²ìÊÅp“Í—¦lÜVôOeú®6îuàrŠÓìQ•AZ•Høg„ÙÔÔØª}G¬4çáb¿âOLÏõøeæIÄŽ‚B¾ä¾ÕOòò¤#À©/(ïÅ5`aõ|{×Ùvt¥‚"bV¶wÖžSìumP5Áaôó% Gß”ò˜Ï3ܱՄg-&Kï}¸w3Ÿ‚xÀ>“o¿£÷gµO’]¥£¡‡ÒÛûÍÛ4®eN)é=•¡bÁÞ¹r4¤JY²´€:ÂÅqVO6+"F©}OòQ¥!‰í%}Ÿô‘ÙOl§Qø©…—(Î2¶ˆ³õ^¥LKÖëâã¨0ÊÌȼ¼PÍŸ(_Ä2ûl×ZÚCÔ qÓŽÙ™4¥Â_5Ï¡GÔâûAlÉú²}8ŠžâkÛ¹siÆX{ñܨ§¥ž®¦ÅSê=y”cˆŒÃ:·Ó¿à„gÄ›Ÿü´†É~JÚRÞxÌjù¿Ä¦$·Ð~ ãÿ`ø!ÈŒàø¤ïïì#_eÙÔÚÛ©!®ßz·Þ—ò?åòü—#°…Ä&?·à›™¶{Ö*' ÿۗว-Ãà¾ÜÎÐ?_n4Ϋ(i´é9yX°¯=«Åj¶¤GöþjÚ»¢­©-Á[:žÏ‚Å…ÈýEòcª5ÃJ[YpÓeù/’; OX™žõá•Ë>òŸGzô‘ÙOý$öQ¯§ÄgXC¾舜$i7YSb©õÂk‰J3;V)ÕƒwÅ<Ã#ÙF¾Ë³°Û¹pGT⬶±^(qG™Êß”²¶F:Á_j2—°{W{UµNÀ›=êÙÏüеÿÈ«•Êåá âõ#þ’;)ÿ¤žÊ5õ,Bû-„ü¦åà†ò¸± hÂë2;OQSb« ’âGå)±¶· ͇ùæÃzóc½YV䬜w…gWϦ1Œ,ÙÂû‚Q×VÈ5ö]”ëì£þ’;)ÿ¤žÊ5õÜ.5H® “_Þ¼`ëÁZ w…ᇵqRÝ%ÅN~ÏŠª"$¯ ·Öz^¹xBw—ŠWHéVTŸù+* /N¸þjè{W†ÕåÇüÁycü¿%åÇü¿%àù‚ðCyø+in’/Eõ˜Âœt\ïY¿Ä+ý‹Åýo^/ÅxýÿçM[#-}h×ÙvvS¯²úHì§þ’{(×ÙkjŠ­õžÈ{(×ÙvvS¯²úHì§þ’{(×ÙkjŠ­õžÈS,+ÙØ†µ‡²lVÙ7c'¿²…f´ýWd”“Ùv®ôã±odÖÉ[TUo¬öC“ZlÎýˆkX—·7bØ­X¥ØÈïOŸ²ÅpœÌ›±–¸ö@œ ù»¢¾ Íç±íOÓv#“¹6g~Ä5¡%܃æìUµEUœ)HÄȱeäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ÜÙžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™îCìÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&wh^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžä~Ìïм™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=È}™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™îCìÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷#ög¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹³=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3܇Ùú“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹y3ܼ™î^L÷/&{—“=ËÉžåäÏròg¹UçÀÁÙÿÚ?!&0@5ý þ…B¿¡_ЯèWô+úý þ…B¿¡_ЯèWô+úý þ…B„ÆZbAazBúðÖÉÏ×ô+úý þ…B¿¡_ЯèWô+úý þ…B¿¡_ЯèWô(L|Ç$Vš„ÆaqjB$“©_ЯèWô+úý þ…B¿¡_ЯèWô+úý þ…B¿¡_Ф/Ìçš2Ù¡:”“ ^¿¡_ЯèWô+úý þ…B¿¡_ЯèWô+úý þ…B¿¡_ШúP^­kLF´v¿¡_ЯèWô+úý þ…B¿¡_ЯèWô+úý þ…B¿¡_ЩMŒ¬Óe–0ÚXkúý þ…B¿¡_ЯèWô+úý þ…B¿¡_ЯèWô+úk2óM£¾Lý þ…B¿¡_ЯèWô+úý þ…B¿¡_ЯèWô+úý þ…B¡{šÐŒ'3 Å ’IÔ¯èWô+úý þ…B¿¡_ЯèWô+úý þ…B¿¡_ЯèWô*\5«j(z–#L‹_ЯèWô+úý þ…B¿¡_ЯèWô+úý þ…B¿¡_ЯèPÉ*<Ö8t¦ëQˆ‡šþ…B¿¡_ЯèWô+úý þ…B¿¡_ЯèWô+úý þ…Œµ‘azA,ò˜) aÑ5ý þ…B¿¡_ЯèWô+úý þ…B¿¡_ЯèWô+úý ˜×™ Ó«¼íHsru¯èWô+úý þ…B¿¡_ЯèWô+úý þ…B¿¡_ЯèUŒ”'ÏíQí€ÒBÈ^,Šu$ºùŸ²Ëņ™¨õ©Å&¶¨( `ÏÙ$ÃÂôK3!ù ',ÃìüÒ_Š.!pdRR’û8 ($¡„MÉ*`#ì¾O•1,<Ç—ÚÕè0Šeš Æüýçi);ÜZi)’ Å¾ÎØ"Dß«Q,g3‹ÖK-×Ïì½Â W¥e‰÷¤LˆÿeñéžÀž´\M…l]š0‚!ùû,¼:ýª=£ö¹x°ý¯‡ÏþVö´ù>_÷ë¾O—Ú{…iñþ×/¿jhý®^,?káóï8@2¶(À’¬GZ|e*“3jþGéQHr ¸Ð<±­‘˜`¤ }¢ÄÒwh9õ`Òl3¼*âÀåú›£Œ‰éI°q=B¬ÙžÄMõGïÚŽ—ùje€ÂX8­Ù·ìšóÈžçÕ'À)kÞôö i5£ 1¥óNO§õZÒt4¦PBˆÐÀÍÄëEî}“åÿ~»äù}§¸QöŸírðëö¨öÚåâÃör¢:œLÒE`¯‹W&­–÷ƒÚT?pšYçîyk8—"õ³Úž‚3`ü½ëCù…B)ɧ8>üRÉ3ÑFÅ0o4Ü“¼_ÖÀLÒé¬^›æ¡›†!JvïrKPš=¼pŒVŸ›eàâ‹IQg+€ºZŽƒh¹ê·‹T˜sol‚³‚JXÔÒÞ#߆ô÷ËO‡æšÆwRƒ¼jýi€Çó ãg+Úl¤ŠâRú±n•=˜êPðŸ'uimÔÐý©ÐU2ÊÁ¬ùª$ÃØ¢Mtð hõ®Aî…Om…3Ì1S„3®®‰Å›:¥:jYÚ‡ë¡+ÒV¾Pu©Ê5'ëcðÓq hùý“åô¾&2Pø÷Ó)ɼ|×I&&„Q|'â‡l[˜«šýNŸšpc'ÜR,Á²jðcÒ²ÊÞóJ¶‹r7ó£ñ‹«Ð«ÐƒÈýÔšC$‘ö_'Ëí=´øÿkàzýª=£ö¹x°ýbJ9“®;ŠíÁb-ZœÜ'æ‹‘2,Gš)ºÐ3²¾Õ{v®„G_.l‹{‘çR<ƒƒæ|Ö žJCÛR —îÐÛöRúËhЧctÀóö§9 øŒÏq&˜!ikt[þDQ:L{E\NYò=ئ}Ržàö¢3ADŽfƒ&AüÓ(ÃR„íØÒ'4¼ñسöÅ‚G%RÓwçÛçúQx¡xROL{Tа0O•Dºò?’¬Øâ—¯íV ]Ž¿ê±Iæz§â°µoµ·µAÇÐù>_AÑ—¼¥CÌ4˜Fj=1­'RIùPîS(&ÿhH$/‹¾v¨³9…íQÇ*$\y©û WV(G†Îí,°%™oJœ™8{íA€±1-XŽÑïÔ|QcBµÅ¨|¤o›ú˜¹¸ó[W͇╜Ôf‘‰]œ±~/AUºÅº“ø¨™L .7ò½8•Ls¡È«NRð˜ëäQÝáý,×|‰§F~P‚§ã[3!?E¬«m7ú'Ê¥ˆ\ÀE#Í\ìôŠÀ‡°þ*ÉÆñÏS A…˜JìtRÞq0ýop£í>?Úåá×íQíµËŇ»uè!^=^ª“ƒ!H<›÷ ªC‚oäÓZÉx/Ö‘ñ«ÖugÑ…°  ˸ÁY ì°ú7¢ö5á—«ñEo5ÌC©Å$H­˜'ô¨hu9z_怠ܼó¡"Â^ˆ[pÄ8.DzJ`'eèæ8«£å¿› 4%ñ¦Ž´™éÙhCn»åBôÿغýi6¹'ÙŠ„ˆeÁ•D ©ôÔ…QœöÆ«éN“à·â’¼%CÑüÒ<äåðÖ›‡•€=è‚ÜÜD¯UûíŽö8£ ó ÷¢óÊl±ì÷Í(2•²m^>§ÉòïÍE`X3N‘^Qì|ýa­þ j/u÷NŸ¼Õ¾Ô}2ù ‚yiÌ:Ò†6P“£–×¢Í"g™B†_à$¨hC3sæÑÄD“¾e­Æ½‹\)ԢОf:ÒeâÛêY:(öK&àÊ>¥8 ™:ˆÇbˆL1GÂF`Mé‚bJe¢*ƒé™3Œ'ÍH´\ŵþÉò¤Q—býFÏÍZTf=mJ¢Ö#†Ìçzƒo¥XaSåkùQ<Ø 1 ¦JTkRuŠš«\KòÛ­L‡™'”¯¥7 ¸—¥ø0Ô}p£í>?ÚûÏíQíµÀdØÃNçy"ú–¤IEˤ”¥hZ{”àˆná´ª/Á/KÈî/ÍXa€†ç1=¶û€f/>'­@˜Ë;ßzL„£Ý4$w¥ó2{5"±à O:tPŽ£Fà{¡è3íXbÊ!‡HQæ™í~¥5rçòIé@nÈŠGÉSÌ,"q`ŽD§…à"š„ á–’êüŽò–9r#…‰O?:0ÛQó)Á©þS‹æ/…z¤ õ‘_Ó¨þ·n‘±ò¥+˜˜HÑAæw{f í)y af¯Ô½e.æÇ’({Lù/šCPŒ¯aŸºØî‚Š\= 5o%´ 'Å ˆ²ÌNt©¡;"4úŸ'Ëè±Ò‚£ÃkúÑ0a†ceÞ•vY î¼Ðƒ¬ QÕ•ì±1šrÂÄ›´üJ­€zQŒ[‚Ñœ­h¸ _0zTF5ä: –X»1Šk/Ý:>¸IˆÂn×Éòî9<)\Ô€–nP ºêT+²‹­Õb|«{?ðE÷åì©CGT¿¨«Ò $k©ý£> 8×o¡î}§Çû\¼:ýª=£öc¤L¯ga”kB3Ï;ÔU±6жŸ£Î´•‘ïàתÅru/KÞ 䀇I󧪬F>EüšÝìÑ`¾§çj‰¹Ai•¶õžgŠB_°”8Uä°¬7€jjŸjI§¢eé¹¾ ‰$oÎ[S/!Â~*`,göUµí¤k¬zT0úéPoH-~µ}:NGÀÞ³/HPD@±ÕxÑRá )h³H.¨ËÌ¿³/0{J”䣭຃’gßʤLô/šgU@A,ž3G¢¢Ô¥´íÆšwav¨¥B[è8¶zÖb꘳„5vY(¡lŸŒ¡2yÕ>ß^¢?:–N‚ç£ôP"ˆ2¶¤`¸ŽiåbN|±­@—nÌ‘ŒKµ&¤…˜éU2Ò·"/Í£•Ri "Øßž)î7ƒ Øß50f"Ìv¡†H³›$óÜöÇuÂ1ÂËÔuzÐ_’½Ê2VTLÞ=*Ø%-ïõ>O—ÒXGÈH×›GæÃÒ(¼Ã$G[¿Çpà S1lé53¶ƒ›ëöõ*_E‹ñS0*½(ËÆ>vG²€û ß`«ú¹ô¡·W5*P„SuØÞ}¾‰‘Ö Rç:ÆC©Ü  ‡z°¢ù<Ò £Ö}H ÛÁ[ óÒÔ¨ZÑù(b°z"δD èzÔ¥ë¤ó¤|§Ý s¬¾± <ë‰ðsQ–ûÉ’HÔ7©n·Ã­[ay¿4üœö y"‰œç{ã¹î}$&ŽSÉCf1V¢ãIP¤‘z#ÑAˆqÜèÿqO°æ°§­^2xT«11éØÑ^3•×ÝÏÚåá×íQí²{„i²о —!Ô%GYä¤Ålšc°Úy¬­÷§W/­ 0¶„ÏY¢¡k À´~»ÛÚ^·ièoCÇÄ~ÊaæÕ©`žâü~Ôœ` Xɳ#‹48© 2TÄD5$ê]XˆõƒóE@ðüëñFÜ @aB‰'«8™ú…8@Â/¿ š lKËD†´Èט*Å@6“¥Ò«ÈÍá¿Ô©É _8ÑHì=åöæ”mVüP¼ó'âC¼lu¬kâÀi¶:ƒal›bˆ™pž¤¯~Î(*üêqH´Ü˜é"ÖçzÍ)¤mÅ'uJmÇ@¤P¯ *Ød 59tš¶àeÁªù5e›Ûx6£²#·Õ|Õ{žØîîêFat,Ð×€ˆ9ª`Íù¤x­bbxjqÁ¿S§O©ò|¾©ô§/‘@.¤/-ÇÜÕµ‘”@›Uê‰A¿5ìñ,’ð%´ºU¬s^6ôšðþò~™×ù«Ø*~ É¥¥ 5§ˆ‹1ft¬¢ÐŽšTéJ‘¶î•“ظ¤pÄÉD'F|çʱD`ÃÒçAåº7¨1èªÊC§rðHdT#ÅX¡fÍ:~EÈë>åÌ0òhR ˆˆÔCÏXvWÂhö{…JwdN÷¨¨…ÇNa¬ _©éQiòªbÀîDµey¨U‚7SØVÉ|Á>ï›å[@kɇ֠I"'Éë[I¿z]z4$Òà—ûL¼:ýª=£ö*À]¤ñÏ,®›54”H¹IÛX¤¬¦¬¨Ü¿3RÚ°Åà’êãåHá0D‰Òž(Ј[ƒ=˜"1_“Ƶ8&\EÇ¥JH³u=ÕøçŠ ÀëåM7H‚¬ˆAB8cœÔª‹‰ž=/F"`'e ºói‡‚¾F’t—°ÔØwåü´×gGê³rɺïnh©÷9«­yàþ ÁêТZA,¼'9 {'лO%‰åúÖ´?~ôÛ™(Ù¶é=(ä6¤y:”.É¢ËEÆàü&Ï—ÕÇÔ47iT  ÉéBØ„4,k Ï?²öÇd4Y€§Kͦ‡0pH×ú¡r–'Ì„óšê‡‡œóŸ*ü]âPb†â!·Õù>_ñi)ÀTò´›oCäÎôuªI:òV)ð³$ru§×…rRò÷Ù®•`) gÇÉuǤóA¥9•%NAR$êÑ&ÁëÂíD’Ð2bÉw ã×Mj%Ÿ‚Q6©t:~ëSaгæûL.»Õiœ7& ²ú\¦Š$Üí(ìù>_Ibíždëj3:"G›øÖJ®™è"·!"‡«wÚ³‰ß/Z.cŠ/"\ò™ß°d,¢ óí†à/xèÑþw¥yMìzÅAƒWêô&C¨[Ñõ©z/')ù¡ßÝî@õäRZ¡÷–{€ Ý0ÐHžRï=†4 ¨>t%ƒ½ñm¾²ƒjÑ0‡ ÷=£ö2f‹˜&ºÖ 8"dòoÈv 9µ\“Ac”`›œÖ5d”§+ÇUò¤Ï7®³ÎýŠBrdK©Šˆp‹RÄI;KÑ x^kH£78ëƒÊjPa”ëÉ7‡›ÓxRy¿Nµd™¶Á’ƒ<‰tÆ)V\á/tkÄ맘UãéjÀéRPß FW×É¡@á<Ѥ§¤7§F@X‹ò·ml¸ö¶Ä&ú—OÉÙœ¨T JTULûPÉ&½ŒÈú‰ŠjÀ È/³KÓ&*Ù•>;K‚£¼”fˆ¦”¶MºÖQ.â#ÃŽ±8lìE‘ïôòAmƒ5,¤¶ýÎwµL¤e°Œ±‹Ñ’ ¹öجÌÐÐù0õ‰¤¬ÍŒ¨X39hùªô¦ÏŠV§L7—˜Ýúÿ'Ëí#ì+WMŒiu¬Îú0&ð‰ë@D‘¸ÔÛm‰§;˜¡Ìk4›úfŒ$ÌHœð飛èV=h$ ® áÛ‡1.öFô”Ø'Í¢€½EMÝcÊŠ9£âC&¬qqMΠ׫åG=€G›ÔØs^.ŸgŠœ(Êð=ß“åöžÙí¶ôTÓë«ôV‰/™²D?8«Ùä.†÷oî8Ö‹+!= 7ì0ÉÞó‚€ÝÒ/´-ñÍ]ÂzFiZmÌUÕ¯MŸÞ)$±ótÌbŠ$jqK'–•pgèìƒ.\ƒòVsÑ[8šz×ðè)Ùv0y53® ¹V¶>Ã/´ÛxM…ÆÑzN¡Ù-šJ[nýÄ<«èÀ>p¦ÖJÿArǨ°{4˜ÔÁyõšŸx¡”¿¥pnbtŽ)¾‘›¸~*+Iá(íàþZà/äì„¡ÏÐ㬔†§™ñB²ÄPù‚2î´ ¹ÊWAz|´ˆÃÕˆ<êìƒÌÝÅ;cëë^ÎhˆåjºßOÅn¹õk?Rf_Îç=ÄœZ­ ¨'क़j*º~&|¨K›«»92Ø“R0Õííèt“î5‚H$ö-šO·GÜ×£ZÊÄzÐj²‰¡ •iÅ ÚáÉY°§§ø;ªcÕøÿ¥go(~gÚ€JH&ü’Ñ"’D·‘*,úÔ’ëN V´!ÅêAé@€@î:èÇ1ˆ„1W%#KhíI»:$ÞN˜Œ^”jWbǽi}`zÉèõúX>´‘j‰¢EÈÃl|ß:¶Öz›üBz}yÛ¢5B¢6ZJ/Ug”ÑhVñü^¶§JƤÂd ¸ÏÔ 4Nß–ÆäÅãiú%„ªèÓÛ¢[—Çwäù}T#˜%»×™ÔŸÕ\Á$´šCG#Z/ìæ¬F‰âÔ§$/+? ^}SÜåñjbá˜SíH4‰Ñ~Öö¤3CHBsçIÃ\ê€BFÈ×57AˆÎÝ(¤u«zÕí¬Œ? Êpáõš.úAd]¿SgJÈ –½”G´Í]5ÈÕD_µJ£âÕ–Ë3B #ÆÃ~“xê„ÕÞJ¸SH«'²óbÓ½YìWL8B[,w~O—Ú{&Žò †¬I°>V÷5w$?£‚Ý„=—ìÓü£è|Jž*ÕXæêzÒzEqjËé‘ükuðy£áßCjpÄqûzÔØÐr \µ€ÖÞ5¨{›ÎÒ˜Ô’l×áÖŒå£8y”^çf^j$§“f>jp¶éyN|§è…Ô˜ŒZÒÖG?œ(,WÉvyi¦~½wØ ££W”@®¾‚‹0¯’­O‰÷¬ö­­Ïê)^àÿF-á2:кàIošjŽX7<‰÷  ùP@Ø£³EÍÇq¤:àÍ¡uךXX$F-Oô9C þÑp‹¹7Í_Z³Åa'ÍþiDŒm*IÂTFñ?M `¥!]¿$yW(ÙAºÅOù….0XÆðJyÅÂìòÅÓÖ•¥Ê6ù-oxn(4’t&ëÁPcm݉%#Ò(0ă!y›{â »´ÎúœiVn.6NœÇ♲DM¢QÓÎ¥ ±«Ï)fr|±CÇž:3›:%Ðx(› “©¯K=Ü ŸFÞ´|?š6|o•Iï@CLZzZ¦Aoe–ÒÍH²ÊÏ”[ÑI¾ä„sŒ¥éœÉP†HŒ1IÉ)~×± ÆéO\ðL}ÊöJt–ž¿G=ê’ŒuhïA‘„i±½©62E!&ÚŒg/Õã³Ûï(ÖUÞñSÔÞšDSo™tZ‹„6õþO—Òi!J‰ø¥f‚Ì ïFÝ5“šŽ_ 6YšÃ[§ZH$t §¡ÿƒ'™Vp·H.1m+:CAiÏê†áŠ*Ve•~»ÒЖ ¤CA s¦*j„aö/­ž•"‡U'·i²`%¶ÅbÔ²ìz*yë|ö+ ûyö×ò±¼Ôæ ¹«3\Õà‚v±æÔ? Åæ—B³üRW ÀO UâpÄE‹3v¡fˆÂ€lû^úÖ ÂÈ1_È£‚·>ßáëÏѓޜK&HT²^‰¤$eÁŸïn©Öý™¬èEÁWD[Öµ6¹ú£"# 8u‘ú?'Ëí=“GÚ|Nù‰Š²4%JÀFŠoËÈ'0¾ ¯î­~=j²_9÷¯7ÄuüòP¼’\™Ä{Ê„ûL„çšFDÒ™ôT®} ºnŸÅ^Þ"}É÷ µ‰¬]?5á›ÐѶö3ié"§2#k^R{¼²gH«¯DÇil#n„Ššº& xlš5ÑÊ^Œ4Œ³°ýj÷¼4ýl¼Xí“ -Êï'÷JÍò? uº!~VJ|@“Œæ”ë)2ÞV ³~b®ÞnX×:çΠçXÂbòõ‰ —v—½B2¡… cЧ ¢NúÐö,… Îib1Këæ $Da å áS °ä[y£’³”bÙ ädIž(̺·UÕ{ &’À“Ìt£€Å^¢w‰¤ ™ÿOR£Ï®Ê&'ðTÉdÙYCU­´u:w,è„myT ýmz¹k$z dÑ:Å:0Äz÷±kÒpeg])i]ü5wú’>/…£›ý1)c€2¼åHBA$ˆwgÒ‹ÒÚ äCY AeÏI¯iT©û/“åôaK™”ŽÆÕ®µ†ù:iF-tXø¼©<ô鸗6â"”¢B ÓjR$7““N±òÍ{³2@ß’aíG;E-˜$QCáÏbNx)ÜCËdúV|ßÞŸa!g7Þà 8`Úgó­,·á¯Å nGN—»8äN—¡Š6ü,ùÍ[: Jû˜¾m4 ÒëÐ1ŠÈÒÖ Ž:mÙ=‚ëÐÖq•8 óNFÈÉDN¯O>Ë©áié?¿jM¹Å}ú'Ëí=“GÚ|O¡œý_椀ñåxË¥-Èá]›jJÍ#tfô]CCæ´^fë aiù(& Ü=Kô¥å‚d^àÇÐðî|”ÊllòËL<…§¢•ŠÙ‘Zäß—JNÌDbmnÙÊBPèSŒ$ÃÏùTDÀ\¿ûVh†ã½Ÿ•Š0!0´Cl§½„ ¬ý@¥»€% RÕ ÍÏɽYo0ðoPÂWîão´v»Í€«ºrIÒK<¸:ÔEëRèí,ø«‰)JŽÁ*Fl 3H%…&¬ TÛwÚ³¸i;åÞØ˜Az˜·By«y1Øž-¹ô~O—Ñ„2‚.ÌA½ \}Ù^iu@¨-Æ´HC`ãE$‡!äP%$ ‚ñ˜âˆ’NÖœêL x[±û3‹/™EE ¬2÷hè̘•~'ï üÔ˜ó?}]‚Ã-Óò5;¸ï"ñÅ•(Êe '°Â\]Ü#ÉÍ[:¡4çGá÷‘˜Ó žµˆþCVf‰¡SY—Fá°­Xú'Ëí=“GÚ|OµñNhÎ:éÕsåP.†¹Ö˜‰½ª#Œ'‹²Z'æ”jÚó¯µ3À0-º­i«¹[HÁíF–h|í"ÜÓ ÖéŸ1Hz€ó´ûG4ðS+…ƒgå:P±e«3ŠH›HðWF¯f/ Ìç­@ ‡Ö¾Ž¡²A¬Psxé5a"B±‚cb-oÁQ·Þ³8¤‰¶¥rß½…W.½eT€–(p« «—^²ªHK U…U˯VEI bƒ¥eUrëÕ…R±@Hb²ª¹uêÀ©\P¬ª®]z°*EØP¬ª¤n½XTR±Aˆ6 ÓÎs¥4Î,ZuònÑU-vÛÛ*u;ÜQ B¢ÁßËðµ¥þºÒ°¡££p@¨²ÛŠ´CH}¦¤ÔHZâO2›kAªéC\rô7Ò¶¤Û‹0$£æÇJ vÖ1­]c*NÜm³ÅE.¶õ•Í ›Ê–’õ•ÅZ¾’™oBÌÙ @Óòš‘Á%°LAwѡڨÆÚÔ"ôƒ&ô9¨Ù…¢R2þ])xV!éD€ÁYU«éuSEº½=ÁYœU«è&–Ô©5ÊÐQÎ_J¸‰æ$×€½ D¢op{´UK£·Z`Гmâ€pØ"u¨àH #ª±ˆ¤wlZ ›ÁÏ•$Mµ *J²©¤Ù·¬Ž)"m¨Y-VU5ûþJÌâ’&Ú…•j²©«÷Þ²8 YJ³s˜ÔÒ®$æ:Tèçõ¬î)l°1µ5AŽú*Zªì—†eî¤HO‚ˆñ´õZNÅÚTòd–Q«+YU\ºõ9’³¸~J°Øh)ÔÌV†)pío„½Ðe¢Ld,ß Ùx×J‰8òcMÕ¾*ë¶m ðžñYÔÍ׫ ¡» C‘MLÝz°ª•"Ú²©¡˜7« ¨¹mQKíYTÐÌÕ^†õQ‹íYTÐÌÒ¯Cš£k*šô„+ÐæŠŒm¡Í42 é /Cš(øçåYTЈ7¥oCš*1}¨sM) ô¢Më*ŠŒm¡ÍJB½$ɽeQQ‹íCz¤!^’dÞ²¨¡Úʌʫ ¤™7¬ª*!}¨oUaT“&õ•E$'Š•ªÂª&ëÖE“j8U…Uë¯YRBX£Ð)aSW®½dQI 6 ˆb¬*jå÷« ¤‰6 ˆb²)«×^¬*)"X ˆb²)«—^Žˆh‡°~jî};KíD&5Å·qKêâ]÷ò%[µÂN»˜¤¤%É­£?¾Ù]µ@á>ÃÖ+Ì7ɾR›Û€k£ê}zî h+°ñ%†míF˜JÖͲ¡0´—¥!6iiÈ­OFº8¶—k@­ñÅ-›1QChëâiœ›§ñ@Í(–²áóâ’rEœÍ0®k/>È r¬X[Ñ=èA#$8êâÞ!¤®@¤å|E0ºy³^GPyïçn(‹ÄÉ æ ¤úðc1çz-Zƒ–¾Ñ¡k³0<ÅÉû/b%NéòŠ3Ýo(´ZÞ©µKšq©?8=Àƒ‡ ˜X„y›&“žØØv’Жwã­HBÿGI1©1xyƒ1Ý@R4 Ëà ¾¡\Ñ›:­ßkÚ;¡Â`W€ æ÷¨§u£°ùKî(!)Ä«ÖU?KäùwE*È3*U¤ Óü%–HyÖ^TòÇÝ—ŽŠ×• b­b"ïÚ`'Àpn´<O—ÕÊ'‰½‹Ýj·Š#*ìóGf¦]Uå  Ä&u-:ÖÌYD¬/Us“W%Êä·ðñBd,f0a/§hådi„Â8Û4`P4úÿí~o—Ú{f´øŸJc?D™ÚìUÓg!‘0)¬ë‘ „›=ˆѤ'^+Ó0¸fyg¥„MÂ¥âŽýÄ›4&ZpEAÉP›/½"Á5 EAV¬"Ib±[ °û @P,Ò€ÁYx±öW¤ŽY°QRT¡JYö Xâ¼>}Ø`ÚvhL-Eì¿åpÇK|Ÿ~qMí¡í5a‡Çï SRŠPŸ*|æ³ÀÙŸ\½&²ºxxlüT¯vÃÛjA¿¢O<ä¦à­çÙ½OPYMÔX,¾·ÍX…ÛÖ¿ÑÀL$7c ¸Ž‰k$“s´Ó jèCt0ãèûG׿†°dµý) ÀH°5wžçÉòî^ÛêI¾ª>ãʃ”aÔˆƒ-1ŠA È.¼MýSƒÇ.Y·m£#Aƒ÷¦j!õ£W—W°µg¬ñìÃëÛ1ŠÒ—Ö’ ±4"t &5⦃á ÁöŸìZ’7Dßð$©‰hD j³YæE„—ɤì6 HX:Å1WN0MêÁ1űªŽÛ*BÈæU™fˆËÃjœ /P{aìš>Óâw\B1q:ÅtgÚjTi…Þeˆô¦F¸ÈƦ(O"„;PB‘6ËL_º½Ø‚ã/@½|VžËúÅhG@ú¯…Ìd7pé|yS3»ÿ‰íG„ã7èäž^RÁܹ&,) I‡'¬Í)%ÿ6/†‰ÛÐC¥êM ¨KNWæ’1â%h¾ÏÉÞCÒœ„Ã)@Ô‚—±t6½[íe¼žn<¨ESÎç›íkf†zKÒüé@ ö`Q§ä¿J?Q€ 7¶>"µDØ_TE@üÌå4¨.LCÍŸÝ+’Ïó²BοO/>Æ % éNe›»Ç¡Ûáóú ^†q¯V¥lƒÜ6†WŸÀjÊÝ 2Ð –:kS—ÉÿŽÇ`zðLÍâ)ÊŸPuO¥  †$š±sG“g×¥JéT{ËüRîg’Ç„Bü­æ€n—iÇùz¾yW{Ñö°²ÖÄvÄQ ‰Ä¬rílÃýˡ܊k Ö‘©ŗú¡†v¤ z2~]:#À·0×{ùTü‚öɆŸžÐ)Ž‘:Ò"ÙeÒé mÎ)ù K0ux‹UŠbZYÍü©NѨ¿Z$ø¿ ¡¹r èAø¬ì_b(bpu¥t§Kø}¯Ãû!!L,ˆÅü«Ì™îeŸÅ3dQذ¾‘HõêeëV¹¬†SÃsx´ ‘1|1E¤ˆn§ÝÒ— b#-|}î/Ø{&´ø«hÙœ ºzV¬“/:‡hYE¬òåõò®0Es“GVüQbí˜`‚“šL\TLÀ˜´ªÀÉ­ïͪ,õ\Ç'¥Bû(É¡®b&’8˜ÅƒB4îåáÖŠêWÖ¸µÀõ®­p=kë\ZàzÖVâ÷†¸µ+37b/Zˆ¡)â³7§ÜþGÌØmï47—CtÎtδòLc¨¬-óŠþÕ*¾¥R CàInyRbprÓ£°Õ>Å,&ì;µÄõ¤–9K×Ö¸þµÃõ¨%æ—\µ?yV¤ˆPtöjÞœ_eÐõiç*½!£ò8¬%ÆÏPfŠXf¸ÞµÆõ®7­q½kë\oZãz×Ö§Øóq\oZãz×Ö¸ÞµÆõ®7­q½kë\oZãz×Ö¸ÞµÆõ®7­q½këB¶Kê3YG%¬v»þPŽÓžp=k*~kë\Zàz×Ö¸µÀõ®­-¢í –æÚk\/Zãz×Ö¸ÞµÂõ¨8ŽE-’åGfHr%‡SãZž‚¦ùõ~¨šj4&lmZ!C€ƒ®*¬,~+ëZ Í\Zàz×Ö¸µÀõ®­p=kë\ZãúÐ[Ú WÖ¸µÀõ®­p=kë\Zàz×Ö€Á­p=kë\Zàz×Ö¸µÀõ®­p=kë\Zàz×Ö¸µ}jl1 fßµá ÚMj9¥’/4MÖ¬&BHI¬Î®kúÀõ®­*+¹YyÆ¥)\…Jšå{£Îj ™ö¦Ö‰-b¿±Q9f÷ÝÀrÓjàNöûSC$‚Öizþ…+%…´:hÙÅí5á`ù¹Õ:mE y@³‡4È2T»Meh[æ°fº×Ö¸µÀõ®­p=kë\Zàz×Ö¸µÀõ®­p=kë\Zàz×Ö¸µÀõ®­aÍ5®­p=kë\Zàz×Ö¸µÀõ®­p=kë\Zàz×Ö¸µÀõ®­p=kë\ZÓ™u渵Àõ®­p=kë\Zàz×Ö¸µÀõ®­p=kë\Zàz×Ö¸µÀõ®­cÌ:Ѳõ®­p=kë\Zàz×Ö¸µÀõ®­p=kë\ZàzÐpÁª”ŽW¯Kž¸ëQñü¿Ò¸¨ó|yRXü ­ãäZ¤·æ…kŧâJ…3äãÝ}ZG¨ü ²"ëÜå\ZàzÒ&oê¿úQ˜Ëo€ùæ /)uv(]ÿœ²¯•B¸¾µÀõ®/­B¸µÀõ®­p=këR m–V¿Z– ^C„žäQGi µ‹ÂÃÃöØÎd{2Ó[Av¶¶çz%£Än_8« ØsìÍÚ¦qÜBÃ(YbÉ ‘–†ÿžµí¢aJ3zš 4›„y¶bÕbM³hmPóÐ8šN¦×¡AÃô2ñcè%%ÄÀ›PؼBÎ7¢…P…tÏ¥$tk·ÐlZH gÇ5²ÞE>*£}ªÍÎþbôEÑ·)O™Òy€E˜jÙH€J°tã´Ùá"ÔÈ’0*¢5¾¦ž•m½ƒëkQyRWcP“&„sWˆ‰1™¬Ë†ò%BYgL¶^•7¹ïFö×H¯*Œ¶?þjÓ.RôÉ:F½³ÛY‘=lš!¹"W Ô2ä²¾Ýi@˜Îã‰ó£L¯ÈèëQQFìäbëÔ:P·Ý"èÇ-][M̬ùTt™É~j6X‘‡"·©â ZÙs¯J‹¨”$<ÎÏ9£²Œp%a<Þ˜ :|ºŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠøuó?5íš************)µ1av^ºùMOXÖó=ïÝ%ç†ÉCéçV¦À(pШ«øŽe‹[Ê¢²ä³¨'X-ôX t¡Eh7¥Á ïR±“i¨ˆv%”†èæArF¢kù£À] W•ÅY$O\UÉh ‘´¸´ZJ`bñQ8PšXÂ\6ò0”Hæ4lwx©@’E·ØïP  ‰sÙí¥¬n/O:6¸"B/¢Åô¥9'.oj±¶šû3}ÌñGaeœ/ßËÅ¡ÒDoX¬ÜA£*üÍ$Äb’¬à»QÓèBl'~Ï9jÚ“Ì¾Çøâ±¹ÏÀ¿µ#]y7¨{T8IÛ-liiêÑ”¸Nšb¯X^èD¾_J:Ó‰£„)™"ÞoŸ¶ÞÑõÈ·à”ykSÄ5Éž%#>…f6m³­.B­g·§€‡0Ë[o:ŽÇŒ b”ÑÐÖGiÜ—åOYw«’m¤ËQ ¶çRLtjåห)Ï,lb—n\´š¯•äVeØ9t®;ƒË*Ÿ±\jyëP›UvfßÒ‘).†Ö=©Xþ¯Å8 ƒü¢¦ÖQC…݉֒³‡9#ŠLâ–ñ‡âŽJ–KÁ֌̄a4O´økò|¾ÓÙ4}9­J`ȃÆu¡ºã÷']+p,·N^ç–0.«€¤15;;-œqôRlÕ©P : I„]¯ôŒ¿@ŒFo³­\r¼ñD …":GÀ‰`6æôg‘´!Ç58©A7{ÞÑú…rxÂZóÏú›Q2I¶¶a „使$D–FNþ^,}•¡‘Jä³kû•ën)¶>jp†JUÅü¯×éxüû^\.™¯‡óZ (ß)¯aö»Níô¬¤|XgÎ~¡…mÌ1µ\\ÑÉ&¤Ñ ëHË’Ï–¯´ö«„„2½i3k®S;R°R$%‰Ý5„0à§ëµ¢‘$K2Á ÄÚ£î°iÁÉËy§"åÐkIGD™&pýÇ4Õ”ÓôÒÖ´Ã&}Ò•Lã†)­uGÂZ§ZNQ›&•¸Ní.{’[Ú“½5Ò¢Q,”\‹g­Xt³IÉô-)¼©|yù*2yÁ³ƒ¿LP‡á9ÏúPƒmÀ‡î•lAP‹îŠß`È‹Ï4D¼ïûR5VëGL'@vm®}¾ÓáÕû\&¸hš ^C4hlJ å4ÛLþ•({0Ubd…šc˜Í-“¦*Êå‚Ùk/9è ìF$ß§Ñù>_iìš>–Ò*Q€n¨šMŒ=§ŠZÒ3=Pд½T”u,Êì"§*/„žçÇî&|Ù=èq€=€[Av¯ÍXü…ýªú(%n#]¯Ú¢³Aa×<Š­¡ßC/½ÔI1¯e÷0Ý»éFIJ  ˜Ù= ÒhÔfÌPbˆyòÞ‰° ú^ÑúŠÑQ°¡„@ù.X½¬b &+W‰ÛÞ€›5aHánì¼Xû_ŸtpøPž¶R9!hÒ€¸<ªWº‡Ûê,¨P+1랦¡àP¡D`±îy>T¢m:}—´}7~Jš‘?58!"1®üS2†&ÖËØf!3åB_«£„—öˆ…„Á)‹«m{2¡[A”Í¢IÕ?ž*0ªäS%ÙÍX °A“c¡»t¥NòúK1mÁzƒ—V½‘ؘˆÂU :ìiÌb¤FiË“éFå=½¸5×a0Ö,b”¦kJ C£4ÚLbÍfv×çzÀ_ñÒ_³øt2Øoô ”I%GÍ×Dž:P(bƒÁpÐt¶¦£³ô>O—Ú{f’ì{€÷¬g¤OË^1û¢ÉÍá=Ê%²LŸAi@ÅÁÅXÅ8»!aÇœ•U$à #&u:â¡='žëú÷>?wG¬!óI ‘ꂆÌQ?¤ú(FHZugY˜‹^½9îeáÖŒeLCrYšZËÜÔµ¶Þ¬©!ÑË:¼Ö;_)Lâ|ªÍ60þ½f .C‘‘˜öŠ=–õ(pË tú¾ÑúÙ³S‹A õ1üTˆ˜ îåâÇÚøüû»„DDȘJh7$å>^TÃ[ «¢÷R†v°Cø¦q«œ¶0zу\ÚbHµ¨ì{`.sŠU°?j!tÞHÇœÐĴŤûÕÅR Q;Ú<êv5i)¨zMyÍÈ= È5<†Ë@¹IÇÒ ÉÁIR:E3çj|klŸ|TóÄuÚ³r‰:¯Š2@nPô6A6)LÀÚ“ëõ2¥[iûÚ†h‰ .÷Éòî¹h ÄJB#U™écšühŸ@|Ó ÔÄ9~¸÷úžÉ¥ˆ¤/l[Ž\÷EyëôÍfçÑtšì§€ýQùŽa»ž‰=4©óD})î>½ÏÝʱ¿Ü—ÍJxú}*˜ó·Ò@A#‘¨ðœH¶ãnÖfý4µºK§‰µG´~Âo<öa(»o‡I'K$†\o3NܼXû_Ÿ|ºØm~>Š› ÜHįfñ¹ä(k–øu©ôyÔ¸ÇÄ¡´Ú=è3ÎÒÅ¿´Â¼!%†Ã|) –¥MA#‰§ØûGØiNÏ+kAïÙoËË¡K¬guŸe#Wp…Gñ[±lkÄQpdMmbN/B„¹bÔh,Øz$V¤‚J#¸21zZ‰ 6Ft6§2\¬Ð2ŒŒiK«,Ì5ŸÛFdÎËö¯%3·´UÓYÄÁþP"d!Ž>b—I‰C.§k§±ê»)»Ü~J‘d­2iÅ6åJñfÄǽ'%¦!5¨„YøZˆ/P)÷*÷Îí àsZl©¼m¾Õ–Yuí=¦14Ø%§éУRÄœÔù¯ b¯M²µó!D§hÕUW@\Ñý†Þ” _ ã¥*®zóIŒ7WyW²U<<ô×Énh]>¿œšùDü < †àZâ ø¬;¦~JŸáW˜xêÚ¸èÚžÀo 6##ëXÆÞ?h|µ…&÷ý%óZHá_¼žôi’Älô÷î2(LfêìrÔ&ìaåiò¦5²>mE9Tª2‘1äÎh±vûÛæãÓF+¤¤ORO;zVã±Kþ<韔yW;~?hà­$¤†=zšž„`¿)Æ-ŠA€úþ¯Ú£Ú?a‚åÉͱµé#/N(¨ö‘/ Þ÷ÞvJMW×GWjþ]%{vÊþ]?—OåÓùtþ]?—OåÓù5þM“_ä×ù5þM“_ä×ù5V%ÀÌ1ï[g¬¡xeQ¸©÷Åfß¡#”› ü&²Ó|ðCóOJžûÕº}"LK):ˆ{ºsJ-$õ)®6l«º©-y|ò½×â¥åê(l‹ŒÐõ„7 N ø;>uJ ˜[‡HZæ¤!V;íøÚ£“é^om̯ØûGØ@0.æ¢yX@rÔ0!“á´VͧbŽÉ{ú×1J¦€ €Àv¨B,2ô®Bîù’ÒØ(‹öœ„Û©²PŒÙò™(@6È,åÓšÁåerqÒ¬1kõ-?iÀîPë6óÍJæAlî'ËÊ‹9aFÔœÇj8U°M" #Éü•¡e!Þ q í9|„•Óžá™c»ëKTþ¶ïÎJ4~¬¨HDŒ<õSÒ¦è„hõE>”P È’!Âzý‘BX bŒ¢çi­šÌZÀ¿=ÉúÁœ5¢žIìjE–©o“ÃG,Þ~Õ,Bìk×±œXá“È^”º_J¬wz70–O2¬¹t3{,SDÙ‘46ývqbLä2ã +Þ‹3肞ëý€˜c;Õ“ÂŽÒj7ôGoÇíίlHI”ÛÃjwa‰hyùmö9xuûT{Gì¥rRųÎg×Je"#Î\¡óìÏŇìÀ ‘ÈÒVÆž‹Í$,ÄØÁ;³Â‚=lr‹p|æ…Œ’\·þ¥ÌCw-·†¥QÆ|¯¢‚§¡ÖŠŽ@ެ´ÿV—¸PEÚSHn¬Þ)àH…u‚E“»NŒÛ#(¡A4îI¤­†6 àb}(<9X3 Š N“È̽ÆyéQâ?'æ5gHNB^a©g[äf!ä}‡´}ŒÁ ”@óæ”Ä3‹Å´.Ô“ÔbU|ÇÏ`¨D26Úh<À2†ã/cæn¯â*ù#"ä®ïê§xIAmiÉs±øPÅÜAœÞ‘šîoá«&¬ ‚ÅEƒfKá§c¥€üEGý¡¼Z”$„J¸hI\›Õ~0sW#¤TfëX÷’-È^k|ÈË”±õÛƒθ"±ïR m+^䌦åÁ ùÐ&yX Ùvp@¾Ô €±ÖþT”,K®ÛÞ͈0éN鎗f¦.Öü±$zÕÅ¥¸¯,›‘b”Qœ g½8æcR©¥%¤²ýw„¶P’€7D ëÊ)'`dÓ”'Ò¯gµrj›—Ü  DÂ=Õ ˆ=j&A•>zÂù†´"×6zÕ£N¯S/ЂR»{§ÛΰOË/±>t `n‘ÕkÆ:Q{ñßkÉÉÅ6Ï2çºÓÐd»nZj*™ÁåÚQs¸h~«Å€°øÈƒMGÉ·<ج„Xm~çÇì‚B  ^cü©ã¢‹Kì>Ë/¿jhý‘§>ªéïÛŸ‹Ú;Ÿ8<+!Å:°ÖR¾/û;EèTkKdºð^€ *`Œ©ÀºCj5qR‰èL£52“ à’ tfýöH%›¥ñ/n&7wu|Ú-ƒ*l&ý;"ˆ»XI°»üU´M¿¡³«4d—ÙÀ¨†+È>«:ôè°‡˜séØ7âÓÈH‘µM¥HK:ZŒ†ÁVâ/Oí"25)ß1JÅpô}Ohû0’\»>|épѾqØö\m"ߺ# ¼Q¶mI€¤‰:”+lrÍ ¶sçY’ÈõŒmoÅ.ÞXQ¢K[R±è-n¥"ÊÚüÕ®Û^‡Î"KL Z¸;Û#ÞyGÚ†(‹O:qQÄÕç@pSÆ?.„QQËæÍ[#Œ¾}3–ëê³ÜiÊq@Îz2ÕˆiÑ –*üQ3¢ÝtâÜ&|Ý÷¤Ê÷Ntþ#ht£ËÅØŠuÖµüÌ™Ï÷‚U@ UÐ(Ô[.¥}ºÔ`Ÿú=(n")Ž atÌÛhíPÍi³})°Û šzzo„úÓB˜N7¢ruî7L«Ô ÷£À½Žê,€ùªë ó¨šX0:?åQ¹ƒ¯½æœúÒ ?õ@ ™!óïÝÍ6䥚=p¾¼ÔñŽÀþ=ª8„zP†#è`’iGL7Ž›<ñÛn¢q´3»²àë@°(ðˆU¸*pƃÛ{¯±X&9a¿ŽÞUZŠX 2 Êm>שˆ[;xž´ØSWìòðëö¨öÙaõ{;sñaû_Ÿ`h¸Ü,l#×Z–(‚YÊ zÑ·Fd¿”M°ÔV°ù‹Þý°Z>T t„%h #E¿Ÿéê×õõý}=‚*îwi ÷”Àu¨nFÏÍø¤.Œü It3‹óAYò×Î…½Ñ¿[kZªìCï-0%nÁº½KÊΩlö­JFÑø(8"ÓÅK½³~ªÖl÷hª«&}*ûB‰™›™ú‡´}xîFÙˆê‘QgH{÷P‘0mÌÏ4,5‰ˆØÝX:l›lù'ÝV0ê#ø(àf“ªõOÍxd„B0„g¦–‚H„).&€J’±rœmÃEº–¤ô‘Fú<&˜ V:™4Mª 4”îéW¨*ÀejKŠ›Â•ž¸’òڢäÀ^>Æ „ 6©…—0.?ehUÍ—DR\c|4ÅO9#a?FŒ0¬'v†ääº4Ú½ƒ`¦k0¶âI¡9%,#¡¤ëšƒ:ö_ ¢Vó†LÈomj8)‘Ó?js àÁõM!šfLW™}s@ ;¢§"¿4c÷WÉoÈ5kWn^¶_¥#2Z>œ¦Fè_•íÅuå ö'Å!®Ù¾¼lZ±c³Û?BæÐ0âñçElg&íuPÌ·Jª¯­:H Zê`bÜÿ9£Àv2:BjôÆDó§jtyÔyMpJì»FºÔ´:ÂYF3¿•tâéÂKüs4 €û\¼:ýª=£öW®¡r¾×^ÜüX~×ÇçØÇ'xß”/FЦ/nnRŒ‹Ñ[fbIt«‘z™ò¥éðG®M7B€,'´jj F%<û™³,†CæÕ9^ƒÑ'Ÿnþ Sq‡‹vNQÔËÂ4û_húÇó3.àÁù ,ƒ¿â®>¥E؆ñê“J žŽ<±G ÈF#ëGÉçI&w*prbeµ{$‰I½Fé¡ †.3ÖŽ†¸^•h2s“¤ÙOZ‡PœA:TN€ƒB0£«ŸèpO]Í6ÂM|¢vš¶c›T0J?ŒTPä!¶ãäÝC"S3xb–¡=BøôzPïRëq•[œA™uxE.Û[-J¤„­åoj7L%Y’yX”—¥¾…\ªd—«Èy<ƴ̬Éç B·¬1l>)ö03&ÜTd~±Å™ðÐ@&`ôsO@¡# ÙE¶BÌqŠeš]‡´vF7ņk½•ŒâƒÒâ–#΂ÒgŠv¡R²še1pjÎà'ã´{F[Pq«ßŽj0Ñ fX¦‘ß,fÔ?óVgÑ{桨rüVo!Ðÿ™ùîf&q fÓGÙ­àëúªýæIóYæêü&¦¬ÙΧqXíŠMÜ´õùÑñ2Ñ–ˆ–œÀ½ßlöØÑÍ(p¢Q`B3$Óh1LVVˆ^gõZ¸\7Ûjl’²ØÄÌŽhâ@îmÄçŠó’ÀëkyÐùTgMakhzö§PÔt Ò1ÐG¥ÝïΚPq&”¥peƒ#ÄK4ÂØâ Éìøÿk—‡_µG´~ÊÈÁ>­Þ‰éÛŸ‹ÑEø%RY¶£ò«FL­­#Å¡Áêÿ:ƒÛGiG?%oÒu¼¸ËCÈéWzP^Ag%>re°ìQ²ÆæzZ)ñébÓóCrãóîN|ŽJYSÌ–!é§E/³×Ë¢Œ TK¨ÜϛӸø™ÉJÄ‚¦ !U 9½8=iN·4m÷›Ú>¶õhCõJ‘8“ëþU¦¡Ôœ1Çs3æ‘ÅRh…mi|^§.Nè"{`±o5G£z±tÜJ‚ðaÕn:R’ÿŒõ<Ê»T—”º“Ka,5ë½µ™Âñ?Šƒ›"6HykzKJz„V¡Š˜ÊwÂïF}Àž‡$i¢T¡ä$è…ô¢`87P+$jÉ ÚαyšZüÖ³%ØÖÛµ ¨‚MžÊ²‡®ý¿4¯¬æ÷ëQï—â(8ƒ¦=)|Î=q[«ÄëQY2[šB ,2ÀÝÂ]=h­YÉÖ<èO‡0”ÜD©Ö‹Ïœ)¥d‹n–Ÿ&Š;Ä!.×óÑ÷«¢™8{$u˜Q(-À]©VI2eÉ“8Â-5”ƒš;QP¯ 9–”iˆ¢’è´ÆÂo8|µ«­®ƒ6Í62SBµ}8¬ŽÐséW«“$!2˜½Ÿ1M:õÊøod« á‡Dµ|ªW ·A@iãi Ò5d™' Ú˧4B<±³+>µglê›hÃ=)¶ù?šÄw+šË*BÉv,î.¦å.H칓ÑJ.; A«$`»CQ;X¨ëR¼C[¤6©"t«ðI÷Aë%4äŠMk3j%{…™ÊÕ#1hzQÆ¢NSu­&Àè—+a êH|kM H7ÓÉ~x¡$ã=Œ ¿£šÙ: /»Þ§”.E+8·¥˜UyK¬åà±\C†Æ*é4D"[Ò<^­6•è󇊒¬h¤„ì -ÈCk8oô¾gç¿,ÃßP&×, ¡[*ŸmaæWô<æ‹€-I|ä¢Fè¹WçíZ\Æ÷ðÕ”@)8C‚þ´ƒ01Ç•E4^ ÁÔ=TDØ7xwÉàª)|6…À­Mb²°hdh5yv\,YzL°Ä¬ENÒ˜ë!qJ•ñþ×/¿jhý¡[ªÑ®][ntk?µñùý‰; é[í17$µ…Rš&–Ó?ð7´}†&Ã{.Z×j‹' ë=ZGêcÍqÒŽ»|TôÕƒAôšçÊ?&Z4q⦃)ha>Ï1=œÂÎÐ%ÑÉ}óÒn 2ÁÖ\J=e-:КG„¶ëÚj÷†`Ä‘R•a Î'ùIîÒ8Óz‚¬ˆcÿ”¶¶µuŠì¯8-P\’M TMŒ¯5s”\ˆì:ùÞ”Ó¦ÝP›uV£NÎ eyÂ[–ÚÔ>6fè y)Nqu-­;AaÆLqP0l¾Ý³¨`"Îm¶©›`²˜ví¸?¸-{‡v!šÙ]ˆ‘Έðq“³­c8"dŒKÝ{–Š4ÕëPx”<-x:nÊ$†ŠœKH/}èØ^%teG>ÀûQLÍ ÔïüÏÏ| @”4“ÇZ9˜ëøJÐ0òvËé’H-_¨Û“E瘴QBU’gÑÿjÑ,l0V ³í){biÅ 4\ŸBÝŒ«`(Lj‰ÒÌUÐÎt{Ó³¶ ]ŽºöqFGêÆdõìrmÂ<‰@÷³Ëôi1b Ëôi@j­|µËïڣÚ?hpIÃN$Z`õ½y&aÏ”ý§ÏèqÁW©׊¿K°²¢$vˆÅC5u^|[OøÚ>»Ñf$öè0$1¬9øëXú“Y\ä6b¤¡-Äåå‰'Ò¾o—t lµkµñ—[CC7l‚5ÌTk ]ÀV˜ºçâ4d†;2äŠ2^ްþ;²7Q>¹«)¯æƒÆnÉ W+‘F –bóû§[Û`l²É1ëÖ ‹`Fƒ) I'%HÐ3òOh€N¥ ºeo+P‰™5 öÚe„ÑSfÚô@,Ɋ̹|ôò ;½)‹)gQ³’’+¢80È—t¥•ÒrÖ™ÚºEÂöšTµ´º”Ls4Þ›,¼¡;÷þgç¾ k¬ ë>hùaËÂ'Ðúr ¤îV׿§ãDXÆ;€Ì€m—./·]íLKiÏ1ª¬Ç•ë]Œb– "±mêH—± >ç΋’‚nÿ Q„Äì.ö>U;Áp1ntTø®¸yIœÔÃÖ:-ó>Tð*R¨G{ãý®^~ÕÑûS”“›&£­J–öÀ8aÇÉ}êÑfQ‰¹€×G&ÔŽ«cõÔž2u2}Ï¾Ë ,—“ÙíB¦TßèÜĨ% –)›hê*P-%µM{mZ™ãí=£èa)˜åt«(™ ÛÛsºÊÂÉW «»±ø¡ž{«JK:ú´Í$J<å´öb*¤’Išv¦hšŠwRlâ¬An.[Æ®õZub(miÖ“Î30÷£mÌzQéRû²S Wuh¬qÄyO•zwÍ&(¸ 7ïQ’½“8:ךpëj`Àôjõ(¶Æç€3Y¬«£®Äyõ­Ô©$båã¥@† Ì®ø+çœ^)µzB5s’Ÿ<¥O̼Ð2Îoø—ö®?Š€•¬å­m‡r^rJщû9-ôü~}é¸àÞ\MVø @°HBDQÞ†Ú®Ñê5¸=y¥|ƒbb£!ªzª¿iíU ¢X±D©0łօ ‹AÑ?4È…²§¢›Ô!-«*0dñ1EÚîRÖIµ@ˆ!ÀëØ4GqÈÓzŒ h/“~•6É8{Ï=·fx:&(ønûÅMXÔ’™h±òŒqØ€n š> ÍåÍÐÙgŒW³aíeóJ韅3L\´—ÈOÚ'ÏZ„Žâ2r‡˜#y…Í…´pW1@0)6G!ÍNÛPy%š˜|Âô~Õ6Ú_œÉHÀÙ"Bvèl—ŠV$D†ÇÐøt [ÌI§p5íGTíDÉ<„”8˜‡lª¶ˆÎ…èˆH ”Oòu;ÿ3óöžÙ¢ÂC„Ÿ% ˆ ó#<4ïX+l[àŽhA‘ÊÇ§Ôøÿk—‡_µG´~×/Ñ1 S‡&[ŒÛjHœá+MÌ&/RMyL+òî’šz>2kVÇÔJÄg7€ço:š6%ËóŽêȆ:‘ãzÕƒ¬>j]MÉ¡ÃQÍÜl!RÖ8çj¶RBÆ•2Õ©´õ˜bv•+œè˾šQ X;>o—a¿!búµˆawb(÷PÍõŸÔvÍXêkè`Lù=§ 7 ÜIJb[‹ûk_Ã1¥6¾SFyst7Ö_>Ö¦@Rƒ~ݹìœÔG ±գǢñ É®lPpÕ‚J®yh‰ÀDÇoCF_£¹à§0ìl7NÇDnT-Å€àÖs¡K$‘s‰OÐ¥VOÁPÞƒ8°Ì$·8õÅ(¸„eV"¥P1,›_¾à ‘z¿t,'oМÒ_mß-ü»¿3óSÛ¢8ÍþÃÛ4rõGzÐQˆ*BtÓÓ8î—m¸ÁçS«Ýž~iïL„‰";;ßírðëQx!¤d‚[ëš%"Öâ¼b¼b¼b¼b¼b¼b¼b¼b¼b¼b¼b¼b¼b¼b¼b¼b¼bº׌WŒWŒWŒWŒWŒWŒWŒWŒWŒWŒWŒWŒWŒWŒWŒWŒWŒWŒWŒUϬÃÖ¼b¬"¬¡¾éL½­²÷º¾ûÞ” \f•œhJÿræã£Ý áô6dçGΛs[¯• Éþj ‡%®Ôy;1 ÊÀ%6 µfo°¿ÅR`˜1C2R;?mtµÒ×K]-.i°‹oWòL¬<ª„™/Mœú HgÄÔšLHà5žjøâ®ƒbRI=é‹`ùÿ” µÁ,ôèÑv0Ê v5ZŠ(¿‹2ÎÜb‡{&ëîÇ,i) ˆ¾µuϬéCE@ Ù+†Íµ¯¯¯¯«ò3vô+£¢GR¼b¼b¼b¼b¼b¼b¼b¥þuããããå%xÅxÅxÅxÅxÅxÅxÅxÅxÅxÅxÅxÅxÅxÅxÅxÅxÅxÅxÅxÅa lüë4vˆœó@ä0ËP0/¯O²öÚåâÃÛ’äÔéµj>±P6‚¶Üï·=jÎê—•jnS Y%aÒûðëßèå©V—¦ÿŽÂ¹%  8žï‡Ïº2c³1¢C=k+ÁyÑ‘¢`³î)Ä*B3=tšåä©ÿƒ½£èEZaÅ_ë DîµmѰ‚6 E„ÊòÏ•!‡6õ^ú¢^úÂñÑXøì—çß›åß÷‹ÍÏhÁby¡†É¤ºbüS$‚ÍÂ;Þ3Ÿg°Á9¥s.ÙÕd|â(ÉL‚èé{pgO£.\'9²:DŒ±-Åáö}ªyÖ…’ø=B‘W"³mзZh |ê«QŠä³Í¨‘ m4y6ÔîQŒÂ•¡a Kß‹ög‘­+”Û­`Cm+@®p‘iyø®YìÝñTˆš–h „J|M±‡N’_jl D ½ï4ÙŒbÉi2˸_­Cw…}mj|Ø–\rn9Î)>…óXò§U™Ì,|¿t²$Ü2z>kY=ñ»§æ®ö98Ûë{f´’b›££®¼Q£`;ŽÜ‚iIÈ£³rGè.4–„i´Öͺ€|>;¿ìÚºRBˆÒ gyµ æˆÇlöÂbMµ;"ÓÄóøì&6àÏÔöÚåâÃÝ‘ÏBfƹ¢Žšj>¦Ô €‘·½Œ%Û7Ö{É6qH¢Ýõ&8ž•~q:çߟw0‰tH©”:ß¿òŒO'gâ¹êáH0MŒ·ü=(@$Ä©°f€‡i¶…hÃqiµ‚ ¿Þohú°OJH³SqŸ’… åZ/•-ùæ‘ÐÔ7¯à?º˜&sÖ‚¢ðHJ'ý^”s’{~o—}CR íÑôNý{s-“…´¢irΨ»ã½Êú¯V¢5„{‘Ýñœû%t/…°i‚Y—Ì÷7í7ÙòŒ>T ÂQ¸ÕåQ¹Ò:ŒrëôpÔ@‚^1xš°ˆÜ M­À_:éZXÔ£fׯõ.N‚€™:Õ¤(p£Y&ÍìÍêpO‰¤iþ?(P² +0“V]õù-jq‘AÑdÓ}ÔÁêÁïI9ÅC°e­·¬ßé!Íq¼E($ [¹¼ ƒ ¢¹ŽÌö®•¨‹¦'ÌÁV&"°ÕëñX²Œ@WeÈÕ~ zÃ=°‰bŒ(܈˜ÏIv®”‘Ê¢8£½›TlW.8>Ü÷~?Ú`biøûú(i>‚o—`ŠR5;º^q<Ò=J’5j ¤á‘d¯hý®~,=Ð`…i’J SC mfžåb ·í/éÜy7*ë·î<=äÅ :éAˆz)Ýðù÷mI]l6¡D〰Ìtã°á²-ûT@o1ÀŠdÛ†v(€$¨˜ËŠV&¶Â×yb¤!†‘¯5r0R9Ú›`&) >}¾kÚ>€¹lqIKÉ$¤Š‰3`½Ê`-ÜDÄï<{P½f’3׸ÒÛ!¬#zÇÛä°ü씄°{. öžÏ›åßNŒ‘„d¤p4³4lËz ÀIô|g>ß—Úm}Ò“Ëõã™|¤Ù ÂrT§ ‘ÔöÏHÆb¿…A$EÙ³^¡Ö’,ör 7:ÉñÞoñ¤™¬9w<¾h¸Êè‰%¤Ô~#".ÑëC‚çåÒÙrGíB €ï‰Þ+tI9ìxÅèœm3½µ°VÍðô ²½áÖôP•›%˜údÜH20Iç¶(x ÔA©‡{Td,…‹ôÍI€â VúÍZ‘¬ÁPµQÍÕü^ÉU‹J°;{ÏÍ 0ÀÇ¢ý\ €IjëÅg!Ëb‘Ì-cý4ä€ÈwoéHX¥4%Ä}¶c­ ˜"]{Ah‘1!¢kAZÚÝwüCXÔ¥y f†¯wãý®^h@| JIòÒÉg«CþÕÛCmŸ“K2ÏV‡ýª,¦Y\(m¢߬ùÚÒŒþZ¸›Œ)ò£®A_R)By4§ð–Ç“VgZl:’ûкNO& ƒ`Ijye17‰¦*î†áj rؘ›M.¦tj 0Y  “UÖƒHhâ–¾`ÜÏZ‰Î÷Z¸Ü±[Ô KÀ]Ö’Ú §­-„˜XmR,»i«À-jJŸ¢ÕÞnÒD±G £_èªöî­\~M–(" ¤‹6fô䤗¶a<öÖ®?&°•.yw­X,`8>ͨqù¤ÐJq>Î)³Âf=(¶x¸9%Qš ÔÐ[ùTNî­\~M ”l üï5{wV®?&†Ê.’y˜a„–W¡]0 ?b¤HŒ¡åy´fë =ÏžÔq±%<šÁÌûT¦â=e]b~cËNÑ6Œ 1¤®ý+ó ô¸BBIwÃè@Ì@ß_qð=Ï“åÚ¦Ïyž~–ê=’`쩳˶= |úrÐ|†`Η®”¦7V€“d®9´æÔ)c‘ÕVÙ³Ù[A\ÜÒ›Zq¢ÑÃ2÷½Â¦ï,P8%æ‚襤“Ûì!€• A–¥ðY)f‰Ó±ãšV"¼ÏBjïºr9µg€Ç§·Z^”úßè¯P°n~Ž^~‚$¬Ád·cêûGírñaú„qPšw΃£m¢®…æG¸Q3‡tŠb;ŸŽŒu¤•ƒ4#µëŠŽD«árÔšùrn©j‹µ=¦ÅS-Ö£$ÍÈÎO*¸ÁÁ`Œ÷=’ÂV&lÍD%R&Ĉ©DÉœ¦ÓÓZôÔ¯œ¿•5oˆBÛʇœˆ\ÚÌ)6Fj[…‘kܨõ§ ]Y·½_KCb´õºßií…O݈½­§‘‹µ&PÝ2Åi!Qî eà··ÍicÊ-Þ7²ñ•tíù¾]¯ÿ'pP¸á$.µ$yJN –lö¢ ¨ ï`íK¬4 I›F_&*&DÞ¦Qîr•±xó¬0È€A”àLïBTF´A'Ñ;~*æðó9¸>´Í®û…âS:ü9§l9ù \øå)ÕsÝ]*ÔÚÎUÒÖÄ«Ü)a¬N«mù¨)N—Û,ƒéó—:Z÷‚æÀ…Å¥ oϧwäùvÚ8<ðWÂuh¸r{ ³¢Ó¸Ó*/aFˆ‹²¢ Ò¦U¼"b)yIœ&˜•"ÒAr¦eI=Õž&=ÛÒ¢ƒÀ M9;BI²ÖP–·.™î{…è b˜¢õ.Á [ÔìXcZºû­â¯¤Ê'é†éY̲K-¬Þš o㊷ơ®Ôá-q῜V t#ì²ðëô›?WÚ?k—‹Ö˜ÅJDfA³óÍX ï'éÍ?®?{gØqNv³xêröéš™i9œe⬋HÀ;â’Q§µ½é»3)¸bf)sQAn퉨¥ØeyàmVhè¾Êñ®<^•VqÃVbåq—hë4”¼H€Æã~i5´™ÏÚ{Glöâ=êì!`zQñc8äOÍ0ŠØž6¦Ê”Ûéä"¯Ú—0‘ž k~Ïö¹xuûT{Gírñaû+S‹écΈ ’ •Ì õ‰¥€²9Á䯱šr,Ca$ X™Ž±\8O<<5˜×,±ÅX¤"˜/êúoP¶ë»ÌEª ’®R·¥µú‚Ó½F)p{“pN3¥±vâÂf,†sùŠùßkí¢½Jé×8©ØM0Õíº´ ·8¦ž1{šœQáÑ$†7(jĥćîаÄ3pÅè‘ÜØ±6Ò€0fýݱþ®Á«I&°êù,_š@°%‹ÝÌÓ6B\¾wß´2ÈË(©žëÜUMå~ögõ]O–>ïÊîɨÉéeë§½p³©%·LUŒŒ{ÜR9{©”óª%‚‘"²ñÇk™Ìdúf– Áåñ5qäU½­^DûøP9'²ýÁn}+ÿ“åØ{FáõŠŒ”M&b;`l4Êb¯Îó]^74í¾¡¡?Å7Gõÿ*ddMÁx†øv©ÀŽh#‘oH£m Òr'ÍMv¯yņLg¯ë»ìš¶+õ…d×ñQ°š~AÊ`Ò‘**kÌM–¯¤ÂJX”=k ms£âh+ƒŽÏõ‘Õ”ž«ú4ïåá×íQíµËŇìŽ8PLA‹¬Þ‰¤r° ~hƒ?;æCµE-<êÝþSJšèÄ#&Í."Y3à½Z\PJÄõmE`œéíIˆI– iÞvLÅ­uÚ–¾GD´KñQ`¡0’h"G]¢„nœÐjsö¦ƒu8iT`R&UÉ+7³˜çÒl¹:ÓÔеþÏÚ;äJl ÊX FÍh Èf Ù‹}•}{!Šýwó¡6ÕßâõÆ3æn5t`”/µ.͉‹fø·lëFC0åî¢ÄÑi¯±mi€ÐìËþ*pÓ› ј‰,€ëÔÏî“·µíPÍ=t~û’²å :¤=ʹ„4ÉõÅ zªÔÂŽ?Ú%Á‘¦ÓþP) mƒ'róa‰Þ¦Ù±DZ<7îü®ãWA*Á,KðT;Äpv”‹炎] |]RC½µ£…!25ÚÔèL’N‘+þSÀŒêñ~Ô† ¸DÅbê¿æµ>û–7úÜ"e@{½§¸Î`Žª~ÿÉòìip®‹@à&ÔF7:ß±bíÈmíÍTPÏ!7œË΂‰o÷X©%"M¦ÍCŸRÕ˜Ò¼åÄ@`üöó¦ôÆ&7]'ÕAÉ‚eYS;¨°›w}†–i†4tjÔ“…“:TÅêj°ÉŒž•"ì‡æÄ¿U‹XzÍVã)£Ðô¡(¤‚zš×Çû\¼:ýª=£ö¹x°ý¯‡Ï² EÂßQá××JžùÆBMÍMŸ ƒ©åPúà/@¸½q>”£#Ø!ÀIƒŠ¼H³T7Ê&ŒÓ¼ú Þën&‰?†ÉÄÆ­Jc=Š"*‹™Ybæ¨ÄjyÐø‚dzÐ¥µÃ'Z ’ê3‹yP©¸„"Îi03Âݶ՗[í}£¼+6M¿Út%BKdÔt^F&âItcôÔæÆàŠˆˆõ½gzM³Ræ´Y¢Pu„õÞ¤{ð‘*äsúhðÂdÔƒ Í^z,¥ÙÒÑXÄŒ]8ÖÁáñRÁîVBÉo¥I½˜˜´ëÒ Á­2愦%®¢µ”‡¼àöHX^5¨_Pî qCæË‘bQ­û¿+´¸Û‹{È»ÊÞÑÜÄâc0›Ç•&Q,vMÞf¹¤2°2hÓäóÛ5vfÉÂË(©Ã¹ð4ÄFÌÕžS«Hrh6«Ä™Õ/ÔërÉZù½$çÖ/·ºRR DÂ4¡b7Ì/nÙÎ$.¼8ˆïüŸ*Q£J% R¢äÛÖŒÁ؉KÄ’N•ÈÅ6Ý«ÅQ¼þ¨µ’ Æ¥»ç¼#³®"¡Ãq¨.@‚ÎQ{ÔH ,NͲ xØ5]»à” jóc3fö&U€1ó­ ›Ž£‰‚*È¢«5%±BH|Ï©4‘zAÖz@Ü›„UÂYùWÇú&{d†ÇH;6DBvÞ’Ä1;ß>UÖ†¸Ùj'ã%‚nùv;¢ B ûÞ—È%ù Ä%·f^~ÕÑû\¼X~×ÃçØ¤nQ+àT <€AN©„ô©¶¸Œ‰c–4¡™ÚEÇþ=&–àó©@¹6\ˆâñ˜«m¥‘ÌÍ…¤Ïd£‡èßÔüRØä÷⃠vô|б[lÑ)õuìYZÂWoâ½&4zð÷6(Y³`)Rôò«§9n-£¶þê‘1e/QÃ<éY›ŠaÔëû§)V[³Sëö^ÑÞZ~B¾5ï¬bDfˆØd×ä©ëÅ˵°óÖ­šh›©8ùQ˜ZIrÚЖÊ"¡°áíA4ÔÃ/(+®¢$œJ=kbиàÃC ÔÝ1 g:sµHwe’P»3ó)\ÈŒÑg¥›«Cu §»äp ®Å8ÍG‡¥ ¹e¡¸Gç¶(T¥Æ»‹àb—vJ*n»‡såvÉ- Íöš` ÈšHX°°Z_Å u"ý™·.fc›T¬³3KÒ¯7Ì·-c¬‘“*ÛBO­Ñ-4H’bÓSÔ@ÞZÊÑååNè¼$4+qoZß·—hߣ‹9âS¥ ܸ÷¾O•&#çZ\ˆ¦#GžØð`2³×4Ï‹Ñå©ïO>Y×±' ’+vôZ<7›ó¥[(ÎÇŒ_I¥º¸P¡ãtuM ÜYµÓŽá-¡’ènR$†€®êà¨áÌñ«è~· p5bVîsVí–BòÃlÝ $Ö].ˆÚEjlD.ëù«$‰°O«º#‹+¡Ë@ŸŠÀš4„™+è|§.)$¢î °¶ô‡™©[êº(¼Vʃ)¬ä&‰a¶Þ<ªÆLcŽÜ¼:ýª=£ö¹x°ý¯‡Ï¸´·ná1o*CÅ): ·7«­·sˆSÉ¥Ç@T–¡Ýu‚„“–X†-;élUß„É9êÂáðÑ‚€§@%‘¤6LPÜ"Åôd÷¤L ¡ŽÈˆÐtý)" §W4VgÊ#ñØ2²A2G‹Q{" L;ß„çí}£è™½LI sú¦C ËÉ¥š~X£B ‚@_Tï$¤Î¥/Ó&ÇæjH¶XE}Š»a$BéºÄ—ʘp|Ÿ*XH££Ã¤ÇRœ¾07³X±Ø"µÐL8¥bøÍËGò¬f¢uXüva`Ï,> £!’“ùƺŽh™!J¸ ÌÁáH¨) Í…×Z eÈ0E÷½ø¡¤žß•ØÒ æ& PU°Ä“u™¼wÝ7±.ñŠ3ãœ|TíC8«pm9\ѳ×â¹HtwëØIãZ¥9(ÜiP’]‡B¡ð†ÆÔyÝó©ç´¸ü=9¢††N˜©Ø ¦w1îÕìBúïg»Ûò|»•:)yŒ÷'lî?' \Ó·ðþ;fx*’^@ÛiÇKÔøÐ& q€8õ–‹ß´lAÉ­ ŲVk)^€G(LJ‰U QEü'ê2Ü.¨Æ˜Ózl$¡–%¶¥f`†ÇÖ}»¬9‰2Ž2ã ΚÖ!€GÑ^À —c0˜vjRlɤVeúCna¤RS_"‰±¸5æ†qY2¿9é%÷&¶“Ó|Жs6#·/¿jhý®^,?káóîÙÌnâÌÃÆ²¶G„â4w–€€±K;‘µ`¸Ó Ò†Œ8토& NtM+n.]`¤‰^0ëÞPàº,X^OLÕªåºmLÊ"@»¢çjž ±æ~bŒ.[¾ËÚ>ˆÅÊQ'>UÑA-z56uÁäézn!rÅ/“Òh¹Ñ£©.·sïVE!éRìp²Ók:ØÝ¡ÜLQyXâ=Ê;xtôõ :Í!È-µ~ĺOp7âƒrÒ1uceÏz‰tŠ›¯cA£KtšiåO÷ų¬ä­BI%Ò8柦‹DçâÕƒ¤ßF)úYŸ-]Ø¥×H㚟«Q‚ñÍ,à5&G“^쥯 "lÒvž¹Õùú/“ #Ãô\6vy6Ýò¬U»ˆ<Ëö~x”°åž›>]Ù×_@ÍEµ'¡òï“åÙ¢{q׉¢ÖSÓæcu^§1™/£ëLµüÐïÇ|¤0 zZ‚›9¿aUƒW€ýQò±ÅÍ™#•LCœ©¼R=ˆÕ¦U5d ²Wü”¦\]œ‘ÖüRä@&×m@òBLl75îÆmèZ¦â¬ZóëìcNöŘ/*ùÈöB© 6ÍÃNÉ4ßêÉìOÃiìÕ°6Ñã±)ޏwñ~ùÅY{‡1×ùFšK¨,µ -熚\P‡!?=ܼ:ýª=£ö¹x°ý¯‡Ïè8°r„\ †ç+ʲ¶n¶h¤éÌTÂp%m7÷ìÈÄ¥I¥'}ø¡q¿J{”1Ä”z¿/-*lÌÁ¢D…k঑‹Ñ‘õšŒá€ôó’­E±k—¥nàFãg_.Åaìe»¸#éûGÕ´™œ ¡}<²U¥’D’¡/þÐÄÈ )€r 7«³÷»m3Œ#P²×jæõ-^ÝE¡—¡Ö PŽ–eóZl$ܔǭ'ë?ª›Id’1:‰½û*Dë¥ ¹Z®«Pÿ³’Ìg@¥Šc¢˜]Ý+P‹ mH¦áºd˜’Ý)VÏ–xýPåe{7—‰¡XIXgT´í8–0) F)—u@¡›ŠA>?u‚ìÝ,þLÛ³åvæ…RJ‚@ÈŹŽõï*…tíÑpçwLV*ÞÌåã¯ØðxLšK–1­¤r3W9 L’aîaÏ“†ò5–Yê:÷¾O—mì& œ˜ÔOÉçëA”0\ŸZ„‘¦†4&CQóJº57œÓ¹-Hé¸s¹çŠŸ¶È]Þ´,¦Ug†tËg ×\#åRŸ éš±Úf7¨ˆ²IBÎuÅhâ²Ìu¤­ß ÍÍhp‰ü°×"|éƒ`9Ÿ¶N–jÚ9„yÌÁÖ t¹p(³øô¡g©´"^䯴5úÛz) 1h»öMºDŒP/˜uc·&PÔ( ³˜øí¨»gæœ3îÒ‡Ázoj^0%\6žî^~²ô$Ž[*ât4Ï{Ú?k—‹Úø|þ„MœS µCª(%¢Ê2d£’ ¬šÐ¦ý Éû;C£§K'Ë=;Ñ%˜Õ¹ž±`RO^µ`pOæ(Iß;ÓØ‚MÝ£Ó™p„¶·«šX\^ùT'’!3cò’3ŒÈÊC8uØšEfH $G Io Y£Yâ<Š u€Î1]TùOÓö­×ØäèéI˜‰AÃû`Šÿ‰8òŠ–›ÙÅAh1ú?¯“åØ`E‘š´¡µLÄZ×K”üP0ždRÎàAíI61ª…Ó%ºõ£¬$Ü_S¢†ZèŽ ¿4©!ÉùkI¯€ómÄÉOCØ›Ñ"-@ç¥DbÑŠÅ:‚|Qº’dtÚ§KyOlAЉ¢ ˆ"9+˜DaÕ SûÌþ)ŠLõ-Ã:¶ŠLd« œ—/4ÚéR[Ôó(í’°‘¨{~L~Rs¡Z1Ê8é¨MÌ5M¢’¹JÝ(×f&qàõTýB‰67ƒÕ÷Œ¾4û[—4ÌIDbnSêÖµ¹±Z¾Õ^r}“åÚ€n šŽºØ×w>&…•™F¡€$%¬õÚŸâ2ŒzùѤ•q£1Ÿ˜J¾ëâ¡´%-¢>jÖb“R^­ ®5\/êzQœLv$ÌY× |¿1R/ŒF'2âÚiCDšò¸ó¨’P°ØÚHŸ:j B¶ìnß¹-™ ç«°2åÉDü4ö˜·U¹"¢¾[Ð%±tÊŸà€*ÐIfmi›ùR@Ïókü¡–>hÅÁJlϤTFeFof*ž‰¸k#Ú“ñu¡,ÈÏh%KI•‡Bü©L~¤O&ÔµbéW¿—‡_µG´~×/µðùý¬Ì .,±›{´C¡«¯=ä‡t‰ä'z)¥†ÄZ<»"pàBh†2Ïk‡hˇ1X‚"€Ð`Gô¬Þ·6òhÀ×*eš)9uiˆB0Nlù~¿´}§Éòì2¢†š±Q¿+õNƒüýÐØ±œí4AdŽp‡­è`4ÅÅèð4þ(»Í¾r‚” ñ ?>tYkÒ?#ñô’¢,‘2™7«ŽåÚ ©ºq'‡q‡„Çfz•j lE6ØhnÔ}mØéñ&fÄDíQš'`rxmFhD(AûO<– f5cË­_¤%Þ½:w“ÂØ?Ñš—¹‹héWüãôƒø¡×äd!¬MèØ¬^€í…¤Ã¹¸ÔÍÅűçšI7W^äÈÖ¦üT[Óa?ZÍÙÙûO“åÙ 2¦Ø"3¥‚S:Ó‰Áí[Ž h¶(²6A–.üP” „üÒ¿:¥ ËX›´Fÿ ãæE+ö §˜Å…œ?sôé—8+Ò®‚Ä:ÃOšBžhÔ@a±ìZÃT-]ŠŒõ s®~ƒcÂ[ÙtÆÚ3ѱϥ Ùœ£´¦¬‰áþTE¾ŸÉòîκ,ÙË.Àœ‡Z‚QÐLg²Pù„ £í ÷ÎK&ƒå¸Q›CdZý&^ð„x<*x¹ò©Þ÷$(b,Ú­~àhÌ‹ÔMÊÜ„ÇF®42º«º´ C•µX%60Ã¥‹‡%6ú6G6íµ1zBòÚ2ÛT‰ ´'£(š*Þ­ï½…1©7ƒz'ÂIÞ¹Žßõ%$E%'^‡u±k‡m‹³Nl)\våáÖ±y7¯ ëÃzðÞ¼7«dìêêE.u±êž´“h« é§ÉCt3^׆õá½xo^׆õá½xo^׆õ”Òï^׆õá½xo^׆õá½xo^׆õá½xo^׆õá½xo^׆õá½xoWgïm^׆õá½ s^׆õá½xo^׆õá½xo^׆õá½xo^׆õá½Ai.W^)²å ¬m-ɯ ëÃzðÞ¼7¯ êÒ¡•bÐ4%2”c΀ä3EC)Ö‹jlLä¹¢·}ê)…“"íMSh½f‹ŠY2ÖÍL¤ƒ[ŽýUeÆ{ÕR€&®È’äš“Dd©2‚éiLS…`—=+£ß^׆õá½xo^׆õá½xoXm&õá½xo^׆õá½xo^׆õá½xo^׆õá½xo^׆õá½xo^Ö«Y¾õá½xo^׆õ὿çM•ûVí+ñÔhÌQÖ”i­ÂÆ ìJ-> #+ßôÊ—g­ ’† ·yä; ƇÙ2bu÷©T›  ,Z1EE.†û/yu((°–0&‘àš V|ËL«i.j4Mä_gÛZ³Ë†´{x‚±k¿UîMÎBÉ£#„Kç(0 Âî m®y¦{dãGGÞº=mtzÚèõµŠ#dÈ\^´ (A€˜¯ ëÃzðÞ¼7¯ ëÃzðÞ¼7¯ ëÃzðÞ½½xo^׆õá½xo^׆õá½8&¹$ÇKW†õá½xo^׆õá½xo^׆õá½xoZ-nû׆õá½xo^׆õc‚_°öuëÅ ­)#‘åò®Ÿu :ºTaÖÖN³h§ÅÕšBc­xoE¹MúKzIŒvË >õâ½x¯^+ÔÌp ¯¦9«`䪪ÝujX×7h¡z>õb’;Ï=51Yuƒ¬v5}è»BÈM³a$¼R[b‚ ù©Ãä¦Èu¨Ô²DBS~R+Sn^_j07b´6{Õ¡dÈÁÅŒkH Šx©ˆç\#LyÓûL¸òOå¶ôjj0[\2´ñŽiy}ŸÕY§¾­óð6*&#&bÏæ‹M£"jXBÝaû§v.Ùzçz)±HΫìbךðÔd®}xo^׆õá½xo^׆õá½xoSpI\-ê ž‹· :Òµá½xo]úb¬CtÉZÃoŠP@p;•ÑﮘcçZ”äæk¥÷q”åÑ—ë{GírñaíÍÒ“η«¡¢[—‰½3¶‘û©r‘f“¤Öó–dÇ …ž_©áóïi|K ñËCµl» õ«A`píÈBÌt,{vfÍD'2Ö:<ªh9€x„«8òËß×׿ ÝÚ$A¾,ÕÉzÃ'ÉÙ‚Í•ÉïÍ;%{Z¢ÄÛeáåÍ$‘ÂwpÏg´}§Éòï£G–“à}²TSBäp<;êQ¬2;Ñy½ózpJ®…0«„±Øƒ·‚N4÷¨¨+š‹ÀL—M¥ö-¿Â' ¯;D=qRZ)ÓXj éOXh.ªm¦Õg© ®‚f9­` }Ô…, õ&‘D Ýó3ðâ•Ù0‹æÑÌáo¤°oFá?ð»äùw‚¨0J[›Q«QYV‡JÍYè žga1 OV{uµçxœÐåDd5³–†ùfm M¤ÑŒ”Õ0$¦öi$#8A#9mJ©Z‹ÎÓªŒ’GjJc’0|˜Žg±åIcp1Æ•{˜”!oy;u¨/\zº×Ñ/Lâî+dji,¬IçJšÔÿ<5޵>ý…Æh¿ûCRmgrà†oK¾í›–T–*/e<ÙÏìÒÆKn˜‚(Á…obýóØ!Í)We„ZFµ6‡ˆªÏ“åVô0Éé9©›• ´Ñ:Ð(ŒL.ùÕŸ&´å}•åyT·.ƒ"lû#¦Jä­q ÷kÄÒÿ@Ì'JÓ÷)U$®¯p’è"pÔë7ÈŸu"ýÊéÏî²ðëÞG:îjõŽñ/§ÓFpåí{GírñaîÆ¿aáóï&™òÆ¢6“«jDpÜÙ3}ês<Gpû[Ö:´ù¥p«u¤Œ|¯__¯lºÆzF§Å Kö3éD@3Ù*†MÏg´}§Éòú2(N5µB[·#bm»j,ƒ^¦}?4­vÈy:ùzö¥µ$Ù¢V6[9U±&PØG”ë½€ 8üŠËpê=®`áWºÒíWmIÃh«!­ *LÒݳ³yö6å®´ðŒÎ¯É.¯NymÀq}÷éN(¿X©Á±|ÐBný?e4a6ïš X$¬Ü©D,f;!‰Ð¨s¨×¿ò|¾‚™¨îl•™ 3ŽOgNÉxëÖY쉘Š]ipF"’BZ2Z]jy ƒiR(w wÇîjÝéˆ@AÍO-°ùf¯X}z>&ä¦q­¿5?9Ù2B?×}BÛŒ`@”×°¦.ztâ‚™²‚d³jœ$°D»ÒʲbFhÁ¸,†zÕ ‹]Ì5ðÒs3¨œCíYˆø°üÅ’\{F»‘(šˆˆš`‡@Ýq w™K°Ô˜˜»Š/s(T¹H&¥Nl±I“†jéé`m`W ÅF…Õ’T˜Jà{gûœ~¶^~Å 7Tß¹íð¹á0Þ-[}7 ±5bcÒ(e›Z¸úDý<¼X~×ÃçÞœã”$gÚh¶¹ŠNàÁ!®Åªãhë]ôuõû9*9w7 ŸÏhûO“åôX-#7Ç•}_Ó~êM–×è‹æDõí’ M¢MîR­ŒAF:ë;Zð-mg¤R¹ 7-NŒ^=¿RhzÆ2VLþé¤L›ž?JàfužüáÉL,0Í›˜dòQ-½_¸ÀLæÏ­h ƒv6¨8Ê ,Û­AÚFi8„Ù6š20F½ï“åô! &&+2 Š6‘MÛöõ­=æK.]ÕÅ$úò}-óSºEÈn&æÕ „¶_Ö­SG€ÞœòT-Û&ø¨1éŽ§î†æ à˜òfŽƒºýJ´f–‰¼‹žuyÐà/°¦U11޵&8i,…àlâ;Á +Ýè›Éί-Z”Y'rhcoó±wÎ"8/; çËK_.Ï’) f.MfÂKï%*±Ì¹ ­%DE!ꃂu©‹|_É=OÁÖ–p’#Î˱ÏBLä…ÍèÈq“ŽÍMlä0( 9‹±ÍaǯG[íXÐKûÔ"ÁÎô$­>†^~ÕÑïMšÌW+UQoþÔ?¹«ÒsšZEÕôòñaû_Ÿ ¯|98éØÂ@ÍJ2 6£-ÈÔ{ý-}{°Þ¤©úSœxÓ¹íJ?ä‰w¡›˜j΃8šÒ÷Up”o l\jÓÁpÌy1Üù>_EL¦V%ù9( y\{uÆ7Õû 8½:ÍÐX8§üF:1~î¬M·5*ûypºaôçOÄA&Ž~—ALß «ú%É·«óKÇ@ÒÜP\lM5㔕™LhgŸ¡ò|¾/’†êн] Ý/TŠ…ás1× `]àÚµšfo–Ö›c$Ï^µ¬Ù ŒÁ\öt÷ìVõ•-Bè¾HìГ֥Á¼H­ L“’Ñç4ÑÈÐQLŒÌ‹Oâ¡‹³j'X ¸Ö}ÔÛE f‹³"V}¶>›êÂÊb¦3ô5õîaRýɎݑϙLLFÑy^“@šÔùúYŒ ÊTã¹íKTWKvBE.ùµ°"g-æ‘B™Þ…%Ôuˆ I{½Ï“åôRUÉ£Á¿ï2ˆ“´X@àì@ÛÇ¥béÜn,@nÒ*{ÝŸò²KÇV}Ç/¤Kydõ« =åû¦ôlùî#²’ç=(L¡Iˆ¹œýŸ )•+—Ö­ÅŽµbG[¿CäùwɱµñCŒ2Y ¨ë4!ŠFÿT­¨°”€xÁåQ›W0õßΡuâû JKø ›CfjìÇ’ØlõÍL¬À×)ر;ÐÁèžfØ šé-ñV$Ç™Áˆ:u ´úHœ@ù¨šÅËß,KÓ¥EÏ¢¬ºÇs:Ð8H_Âho=f}R£ê5Ãw+æuš& Àw BK@" ’S·:@¬ÚÙòbÊνr*Q6“Ê †]1š.&ÂIJ­†RXŽ=ë@f7h8 ÄÅÌ‹Z§–­ÂÇõ? Myƒš–Hä’Ëj !%-€Æ[’ðàÝÚ®&»1‹ùõ!À¶//’:jQ‹"¼1Ÿ©—‡^úT,ï×´"eºƒµMž§¸«nDŒ£×»%h#™oA‚n:´W:Gž½ù,"B8m5„#q²ŽŒN÷¿¥)*°gêeâÃö¾?¡¿QWŸáÜ_—L9éÒ—‹$ú®b›  _wJœ:Ñ?øµBÈõñåÜ×׺ý1ƒ·0+¬PÄ¥"¡6)«ž 0Ä– g_¢Ô¤ Y¡¿/wÚ>Óäù}6J{ªP;»û;¨n5n‹˸Ðç%¸]µ˜Kjµ1D_%ûŒ^Wsò5?9ì&ksX&˜.Hß;Õù ´¢'ºÃF®•&$߇íûäù}Ç›O ŠŒFb=œ`Æw eRæ7PX™[ž{î0Ä-Å>`®ÍÇþ1OX¼k4´P—að~+M¢CÓÏ/ùQ‰±1Ñ›VŸl´LÙø÷¦‡ÓsLl>ŒGÄI]:w2Š,o7›Ïj!,‘¸ÑöRÍm@ÒÅ$Â`#!è/V‚è]C›•~pŸZ;Ìð÷&Ò!)Q¨Ɉ¾ÍªÓ²„Gå¯k¿()÷ ëäÒ2ïÏÐËï}0C>JµbsP.{Þ­÷®P97½L€x>Æ‘a­{Gírñaû_ŸÐ‚ì°;:>´e>¡?&ÎãqZ!#R4ÊÍ“ÜÉÒ5¯.ÙײÀ׳ó3Û¯¯×›TR†‡>¸ !À@ßhûO“åôŠõ”üÀúv ô¯í)À'b̨d¦+MB¤„b/“¸ãÊÕ—€ya7­ûfLíQ R ±¿a’ðÅ%Å7RÅB,ÃüRc°Ý;Í4}%1BW&¦nbƒvá‹¥÷(™Ï%zÏù¥8ßY¬­ç7Þ£XFÅ,PD5¼ï“åÜ&ÄùRp³Ä‘¯îwŒØ’­ÎÓZ™p¬nÝœLÍòسN¢]axÔÜÚ †¶„Fúúši4¨Ì–‘²iÙrk $B3v{Šbw¤3èƒM;rbp÷>=KF—B±V0%gI¾œ0Р’]ûluˆfÃPóFîãÌ+à Ÿ^Âc”BqW iÕÍ5 ˆ\Ì=é(±œpÓ¯­eá׸‡×",¿«±Áh&)°­]êð%o©íÑA—ò”ê·›F¾tX 8X we¬Ò–’Ft’ˆBÓޝãëeâÃö¾?£¤(¹ Ÿ““ºèDâ7X÷ Â»èR=â†n“q·=ëM,=k__­zTCdÖ„™[NïhûO“åôfDÇ^“Òa;'Ê2&Ëhuu·ùLª‹‚ ÿe¨4åŠ7"¶Ó»·kˆ\#‡Ú™‹ ág°GXV5½.4ó¤b/FIø¨Ãg&cF·œV(#oúvféN^ÿdœÄ'Ò‹PЇf£sÜÆ-Jð#p&:ÏÞwÉòï°HO.f¦ Úƒ‰T€žX„Rq¬ÍÈ¿Ó÷ ;‘Ù à,ªº–¥ˆ…‘ Õ4—°T»²(#â艸ùÏöž8‹äR²°“¡¹äÅïGK'ˆf<™Ž;Ÿê_r«¬™‹M+›¥d‡nô7ÊI¹s³/½ˆr ¹ Úï-—~{b &æ³®lp:S$»Ètx4PI.ÿSÚ4vWJ“¼!±ŽpúÕÝÁ‹%?ß6¢Ó0í±iŒÇbßZ…n õòñaû_ŸÑy%°¥8¤˜;N?g%»“~€ºõŒsØ–„Ôä1¾)î3w³Øâ yu+__­ b'©ÿhûO“åô|\ wޞ Q'M‰¥"Z8ÝŠÞ×=JÅtÌ%Ч`xÒ­DDšp‰níZ|{DˆÔÒO!£æ_¹aè½0%Ç’\sGeÛ'Ö>ó¾O—zRË ò’i}¨ÄML/<‡óJ„‹j&ÏÓ÷ ;)t® gÈ-äÓĆñwâW6 ”Mµ`_o:R%–U {Š…t›/"¦…•‰Âv4·jcæzPa ó)…–-KDN3Í["ÆÀ2h=›vü¨ìÄ &í)äz {´ÿhõâ(Î`½TýT0,šóú¨7E; B0¿_ÓN™5—‡Z\í Êv ì(ëØ¤Ä 6kõ‘„ ¼øÍ!X—µb‚å­{Å<¾&¤4±( dÖ}o[º ´†2>X´¥‹'UǵBÕ² ìA½ßßÓëeâÃö¾?¥ÌÙÝñ†´Ÿ":?>‚¡PÖS{Ð »×áÓ{šöIìE,Ì“ÓÆõ9Œv1ë%tCã½N3ÈÑú¿«Ñfž÷´}§Éòú ‚«¨TC›ƒ•r½ð#t"þö `LÖ¤ôö¦D>tëRdŽD.t±\qŸ5دŒõûY5#µSÞi1£®¼v¼ 39Œô)Èl°pE9f€µV{Š€y&ÌÎ~ó¾O—xá-Ž"—†tÜŠ 1dDMßéû…ü•t)¢­jÑyM|éAaS2WnI‚݉¨Ì-§4쓸“gÝÇ×yÙ÷R <¸sɲPŰ:Ò^Ø;Ú÷Q0’W¡Ù-òß×oÇúƒ$(+â“¥Ù[$¼@ÄsLp‘„h,z•{"g4³w±Vˆ6_ºU,W·ZËí%µs?¨àvæñ8s¤:2ëåÞd|¬,‚¢ ÐY2cÆ(–© “­`^;_žÆÁc”¤€ !‡µËkÞ oµê  ¡¤Ëñ@š'n zS3a ‘±µI1¯ÔËŇí|>RYŒÏÈ¿ÒôÄ¥ù–ŽO8¦onGSæèÚñßùôdõ™?T²âÚÊïßö´ù>_AîPʈ†Ácr¦ÌøZ§¦kÄ?jEèý×RàßDÖ×Â4­‰t©ZÂ̰S×%0$$Öé µqV\ —õ sÏÉ[”˜3Î(šï8}Ÿê¾s²‡|Ï„–×»¨‡vÕTŽÄ,­¶¤‘à¶ÔÎ /8ÕB%>EÝ´‹v¼°Ñ%}7ç·g• qmg¹νœ·#cZ>‚§W÷´{­™ ¨Â¾ak4UÌÇí­ô ¢e5Å2Ñ0Ñ,‘sDþ)7"„¢”„2â¥1ßËŇí|>T™Ôu‚ÈèÔâYŒÞFsSɺÔy;®Ã»A}¨N'ëÁ@©Þ~>¨CÕ=ýÿhûO“åôw?^l¿™Å´ï"uÑM¨øµɳá¥Uħ lÚûÔ¨7¥ÀŸÝ^HNƒgÝ<æ€*“a‘hßšo2öáþQäKy¶I%{:ÛR¯hBé/Jö-ôîAØ=GôíUÄn¬{Ðè@Ý_7»y÷‹Q’Y#Låù¦a-¥4t½1¸°V:–ÎjÝ‘3ì£à ѭOç2õ^ƒè„HŒÇÖù>_iîËbÊ`T¡å“ÑÐ÷uûOÒ‡7¨Ã¶å6WÇïªwç…C•“ݧf•ó=ÜjZjFƒ#¬ÞÝÔß°¼Ô¦úâ­Ý•‚@kØ>˜»wÙ2³¡†Š3*[ÀE÷úFŒÄu,Í6„³O‚Ðyì%’ÒœxéN°ÈYdž‹m²ã÷Y(Иh ev„Š#Úw')»Ünò‘0ßÙ*CR–pkJ@XÙ(•ÐXCÒ–™¥ˆÄßËŇí|>Vw&ËК.KGE›ø¿rÃv%ˆÈ¿º Á  aê}@Æá³½ú¢àxx†È@BLù(ÁdFC ·ûO“åôzÆ› >M(ÙÀl,û÷|g^á²2›ÓŠ u æ¾_b0ìKø+¨oVã½âU±ù|¨ûS/åR‚Hð=íRRÇP5)òSìOÏq9FúÜ6œùTƒ7G}ÕFÖÕ—íí1 ‘1@cì¾O—ØúùM1#ÝœvùÚ‹€jhz’ò<êð¡ ò/¼Ö‡¼Ï¨ÏÙ®&ȼ4+…ŸJX$ÎJ1ÓK„ãË%!Sùáùt<ÊhêÄb°„f —Ž(ÅóÞËïqœ!E›Õ‚‚5+P¢Pñeƒ7‰ïÇÖ.t«ºHÁ@IòYŽÏì©‹†Úö©²7 :œ,¿•cÞ•Ô—]šf›5,i…ýgtï"âh˜h­œãÓ¥µü÷l]¤‹5 îìŠkäá¬YûOŸÕl|ÉÚ¼+M½«ÅÑ>§Å(1CÀê¸<é0ò…êSV5£´9‹ŠT@PS©±sƒ¥IBõÉ>Õ&íÒ!6O"Ùl)be-MŽºÍÏ¢ U€ÊÑKÆŒ¢þ‡´}§ÉòúVÆð ´y°ù;ö@‚RÉ¡7±ÕzÉeðûU± ³Š $C† [ w¸3çQû-B /Í·­¼œ–+gRê:Û¦6šI°Cå1y-|¿è¤CPö2M7Cûì¦a»ÁDË•/?£@X¥oÍtôéLq÷aw¬ÛjT"uß>½³Íht-Á¬tw¡ÒIlM>†ý·|Ÿ/°9ŽX ¡>2-Îeâ ¤dê®ë«Ïg½Qöƒ4A43Òt£ ¦ÿIÓB鮘>Uh˜’ïéäqIW’TÏ3ÚÇ—=À¢(bÎÔ)À´Dºvåá×¼ˆ±ÈÎåp&î{ÐvV­æ²à©ÒGâ²J9œÚ™‹J8æ²”æÿ; ¹U'†³l8¿EJc@ÕŒRÂ!UÕµX6!£ôlM<šÇ EîŽ(¦ „ÝëüÐD“(3V,ˆ=ô!„ù³OQ¿Nç4 ©µÞ_iáóú „ Ö‹KÒ“¢B‘V (ñ/ ­ËgŨSœ¬‰6ö¥A! F"QT’«»N‡,%ÂløµN\\bxµWÍJr½„=NNù°ÞX³Ïà¦`ZYÂzEN¡›¼DÌéô}£í>O—Òð|ÓÛ‡°ÅÊ×Á|µÌúÔ×Ç16UhrÇß«ØO??=«ùÝÜ¥æE V ßöÏcfnºP£æ×߸Â*é2üªM¢NgH«n îý·|Ÿ/°åÞX_À ‹í÷ª>†mJ^]ÄÒ¨IHa®)¾å Κ_2N|ÿ@CHRjli ¼Ÿ‡Ò¹iÂh tm޳dZqxüV" âýü¼:ýTG `ÔÒr&¥ý•£•+­K²1ž;žÑìΗaîýItƒbÐóH=É’G±˜ui@)ËLiÛ—‹Úø|þ¯åuÍÿTäŽàÅf`]¾h"Æ;ˆ‚L–wCÉÉïJäMˆjs§œv-ª.yÒ³Ê}]Â’ü¾âÑ`ï'ÛûTj_cý?hûO“åô³–=T¹¿^ÐÎa, ÙŒöbíùÔüӈ͎ÚeÁÂUƒŠrqýïíJ”¼UnÊ‚³a“±«K!œî÷ÔŽrU çR^fËžM©yè"ÇáçS¬€0¸½ P0ØY=¾£^ÑöŸ'Ëéð¡Âc¡ÃåO ŽËé×|êòG0Ä5„oÍ)ä(—¶—uéJÞG4£|o7)fÿñkäù}„•Bžh óÔ;žõGÚ|nÉSBzÔ°¥É‹-öYxuìC'¤LÌhQ(Ræàö!² ÞÞÔƒ’¸ ³š±aRÌŒZ¢¡˜ÅC1~ÈÒ€" T&Â{€a£Š€>Ï/µðùýB±Gƒ° =*oÌOõôbÐ[ÏCóXî%}^ï·Öö´ù>_M&ÍÆ² ¦ê€rÔ¸ç.ûB¤‰I4¬é«~鬜KÇåÒ¥˜š˜êO›;|ž,bá}bi˜ecž®…I2OÁ€ÛþG|Ÿ/°tP¡FZ!MLº˜}uí÷ª>Óãv­y!/3o/¥‡_vý±·ÐËïb }E3ÏÙûGírñaû_ŸÔ²Ž•®Vq±äZ¥¤áˆàúe8‘Ç5'íu¨”ŒNÀ‡´þ¶ç¡­c4]ßSæýohûO“åõ=Ü!£äZ¼þ ޣɝeÖ‚œÒî\P¬ ˜æ¦6´-µCFvÏã©ù£>AI.emV´[!Ò—ƒ÷¡4hØX4à=ó¯üžù>_b÷Ãø ='·Þ¨ûOØ ì¨?©?e—‡Zw½ƒª„´ élT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿TœµKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ª—òýROÆmRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U+=®õ/åú©/ÕKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ªmŸkõQD·ãªŒ ˜Ö¥üª_Ê¥üª_Ê¥üª_Ê—ôÈÚj_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú¤àª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ª—òýRu[7©/ÕKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ªK(-µ]æg¥Kù~ªväÆŠÂéàñ­. –7š ïÔ¿—ê‹ÚŒ¦Áà5¦ÿh„е×WÒÕ/åú©/ÕKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõIøåKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ª—òýRu[7©/ÕKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú¨&—­½î¨‡€KjNÕ?åú¤ä›Pÿ—ê§ü¿U?åú¦`òýR6¿¥þ Ó+¸ˆ{Mf¼i{ÿJŸ1ð2´y¶è|Оýja(3ÅOù~ªËõSþ_ªŸòýTÿ—ê§ü¿U?åú©ÿ/ÕOù~ªËõMÑ6©ÿ/Õ ³m¶KÐÑ °[õRþ_ª—òýT¿—ê„Ò Xcª_ËõRþ_ª—òýT¿—ê¥ü¿U/åú©/ÕKù~ª_ËõRþ_ª—òýT¿—ê–]ŽŸªÐùÑ¤àø¾”à"Ú€‰ßKÒ,Êè}—´~×/µðù÷˜O˜ïW13PÅÏTè=ÄvR;Å»ìG€·eŒBça5§I[ –ÛMY"Kœ±Å©ºd¸,Ù.›'­_ëKé—ùÙ8œ'¨{‰ ïR²Õ¿ýã\]|þ©ØËOÛŸ°ö´ù>_T–=sdéháã¶ØÛ°|Õ÷®‡â”ÈYªè¦ÚÊ<³yÑE^Cx68?å×|Ÿ/±Ì‘ËÒWML߬#³Þª úZ¹˜l4óD<»e¿k};zÍ` ïŸq,­Ѥ,4ÃÉ£ÄV׺ìµbÏká÷&“ô>5g›䂂T80{õú×£)*I2vµ+kͼ£J€æýª=£ö¹x°ý¯‡Ï½œ÷¬´©¨¥!!8«ÄÒŒ¶Y-i,7!Fp“LÑeE&€âÓ'¹Í[™ô5{ÁçgÀ \oªÕò9ûhûO“åõoŤ<9‘-\ÏŒN·Ï­tp>*—œ³pê²ÕùNö>¥è)Aók¾O—ØB¾WäÜÖ¬”2çä‹’FŒ~TΣ‡GͯšÜP8ñÜS/5Iß³ñôÂhšoöµA¼C ŒÁÉ[ûUêŒh\u]gè|jzž‘~3·×˜óBó_™Þ¤`Eà·Ú;™xuûT{Gírñaû_ŸÓP6ëRúgäñëŸ:N1'N~ŒœE‡f† +Ë”u¿ÙûGÚ|Ÿ/ûõß'Ëí=¦m¡%”œj³û¤ÐX•f’ xGG}†¨ôOsX•®~Àµ¢æ¦ZlÚ“æÃSÑÐ\ g·/¿jhý®^,?káóúbqdŸNÁ–‡èËl´¿H‡¥ûsö~ÑõtÑDP·±jŽÓúTÑ‹æ¶$’üU˜Þ¿’‘9 WÉòÿ­\ÁI‚äø>ÉòûOp£ê¼ÒeH„zï¥LyªŸœ:waŠPÈ–Ó4êŒ(4UÇÓ1 Э zV”½+ZéÛ c ]“Eé¸~¢Šcñqá?1B‹h-ê«°þ×ëö¨öÚåâÃö¾?¦.âÇ­ý““]ÞvÀ\“(7׊Ð÷4°Gçý(] ðcÛí=£êÊeœ×6¦MPLîÍE9¤ÇKÎPE»=É#_'Ëþµv]ÅЖÿGäù}§¸Qõ¡ÑÀÀtKÔyÿfµÝrow°ò¬­®@õQó*í$»‘î&ï*Á€FÐWÇúkô)v)Mèå ¥)L[ßö–¤Ú·=ª‰%ܺÇ_*H¥++.V {‹‡"+ºËïڣÚ?k—‹Úø|þ›µÐ_Ьzã` C¹NòØ[XÒ‚¸3GB¨4ç}¾ö«8²Ìj1@.¬w¾O—ýúï“åöžáGØ ’±2D'£åQ‘DW o3˜ŠL.©w¥G-À+|¹&ÈÎñ_ì%~8ÞƸ¢hè´ÀLøM"!—u0»Xë^~h§—N̼:ýª=£ö¹x°ý¯‡ÏéÝØx ýõÅ>BÄ%ݲ-h?qW‡Y(ó}j6^‚·ÞÑöŸ'ËþýwÉòûOp£ì^M!¨èØáëMp@z´_tX¬º?^û$'ª8(R£åjÖ=¡·ÄjègÊ•…ñS˜âhà ³/½¨B% =‹8Ù2}´~×/µðùý+l1ù©¡îÕ¦Üãå&"Wió·¯v<€Ê´¬jm­ºÕÖH=_q½£ê–,õ©Ð†tL_zonÒã„ÝóE$)Ûò|¿ï×|Ÿ/´÷ >Ä u²Y^2kOh ÈеÛ9(Zd›:k3í_êœP`TK@Ž‘ <Æ_O:ok·Á€éP"‘ªZ:ha“$½Ì¼:ö£­:b_±öÚåâÃö¾?¤_ IÊ[4µoŒ¶ÏùN+ %Õ5éµCìNˆµ¸íÀÌÄu`¨2^C<þç{GÖP*FsJ”Ï ‚ž½Ï“åÿ~»äù}§¸QöŸêcóÀjÕ•‰iv~sÙò´ð`j ãJ‚,c¹—‡_µG´~×/µðùý!‹b”á`çâˆÝ>´â<”Å2£&Õàž'î·´}§Éòûª„!•Å#Ðdãè\èÐíÅ?f‰¢7ž»ï“åöžáGÚ|©e&#k6üÑsš@™ÁxðÅoÅ&ë ûižÙa νì¼:÷Lç‰Rú¥§kÌ`¯ZÅlšw ÕðPŒ¤í;ÓË0F´K“ø“«R·-ÌöûG¸fêŒ[àžÂ*y3Rö·¡¢@‘ù*j`Ÿ…fåý¨Ã w‰”ð+Ç·ì#>ÿ’„ à;²ñ½e›{öZk3æâ­¾Öðùý(‘¼€ß­NP\ÔsÖ‹4’3Üu„KriM²†ü²éå¹ÞÑöŸ'Ëî©ÀÜh€0Äï“åöžáGÚ|¨VãU¡Õ†ú3Š"ø;Èð¼R”éõ%’œP£Í1íP¼‚ä›ì:Éi¢àI:•î?wñk ț҇Ì!£8$0TÀf [¾)‚i.õZ¤µ­p «ùÔjxDìײWÐTûÅ-*+è~kËù¥ ³ëQ‰ºØÖ†7Lõ+Sq#0!òJ@™d¾*l"H ½*µ‰½xn)žÅñ7¨‘Y€¥išJââ›veáÖ†öPMa·M*â°#ÎÔø_×H#ó_Æ¡;iv¬¦ÐÐ/+¥<£dÀöjJèy=ÿŸÒ/O)÷\éìÉÒ¤pAf?Á~´ 6e{Y^ñ,Å u[»¯rä(„o5`dͼqE¨ÉP ¡kwâŠ(Ýå-XEÅ¢ä >ÃÚ>ÓäùЮ‘½÷¦Nˆ«'Û_­ò|¾ÓÜ(ûOö€uP,77†/ÞG…âH´¤Ò‚.Ð`ez!8¬/§dç¯Þµ{Á^ïâ×‚æ²ø,VN¯fÆ| Ô¼f+Ú5ŸJÁÀÒ¨'J¸Ú(nÊð{6…eì­²$ø|Õ»¢y·¡šCÎÃCúѤ9aóRè!Ò+“ü"£×þkâü)+B²Îµá¸ Næ!I\à¯sù+Å6ìËíKº'BE>£»Òh¬ ¸à±_ â®RÒFYbU§\Pðz׸ü*}SñQçÿáóúJ7Ò@‡"12éIBÔ¯q«Ç¬†Y;h&ËñD”¬‚eÛccºõŒÍüÿt\ „¶þÐtRàɤØK8¦ìIÇ$V9# 銟r—ì=£í>O—ýúï“åöžáGÚ|©œ^4º>Ž^{È;WŠB¥ÏÉØ*Ô/S Ô¾ãR‚Š3)ÐcBˆ€Ë/@&÷£L(áëB›0´Í8Ôˆ‚¢H굩^ƒO‘È£ILÀTP2cB4£*T´(•ÄÞ¥hÕ;D†<û Ò^t95®vc¦•‰ˆ]Ò„2­»L 8 Ê…™èF¦q9Rù2‚*c²zv#Å-´7ˆ&@t#O&*=C§cQ9^´N$“*'é º–¨ëWVÁ‹O—Ù&Ä«P.[*ÍlÁ¥I%Çí@—½†zÓLlr ä.l,ñßðùý#Ò¶'˜ÖO:ç _ôë”2ÇÔù>_iî}§Çú•3R/½äG~;‘QÚ}² ƒ»B;¤ŸìvGdTèC D“)EGÐðùý 0ŠÈܤ LœŠ@H9^(…þ±À·¿•ó#¥†øœH¢^e™Š3\̉)›îmCZð~ÓM.d”ƒ-<ä[¨w}£í>O—nqÿB¹2TÄmQù'»Ÿ§ò|¾ÓÜ(ûOõ‡ˆB¡Žþ^{ȉ“—B¤á±@7t¦À-¬ ó¢HL]é3}""¦Á1…[ JVó§BˆŽ%Ú­äL$Þé¦M’´ëÍ)Aߺ9(Œ5jyŒÉV~*ÅIPŽ)‚ætãC—JÜ–õ5¦CêÅ'ºÞ¸j*x¿ÍC@²Þж¸Kjà“7(J |É Ç5e2buÖ¤èaJÞtÅÊmýFô˘à¸¹.TT5 ðëZÁI)µX˜`y¸¬ç"a.4BlB|©T@Ä!>t/v‚ :Ô-¡4+€V7³ðùýFÃN8Æ—µk@°³çg¤ÄrFVÝiÀœd·—Z3Ty²÷¨û”!LZ׿ù¥šˆ >~Ÿe°( ¥ÉÙÒ€Áh¸êžzßhûO“åÚFÁp–ÿ÷+¾O—Ú{…iñûæk%¸[Éå›çëeá×¼‰"©1Z—INÕj}ØŒDR(Ú‹šëG4»„òÑ&‘å§@Ú!м-µüR)º8Òh)±pH—«<qQ^„g2fžEŽDÚ ¨™E6KiÅBüKÖë­¨è&Þm >_(ë[!˜ÉwY’6ª*ðà4é™*É–1êŒ-w±W!!×4Ö—-¡Ý£;™0›UàÎÂ&µl2œFÑ š˜,Â9§2±êóhzôpMD5j)-ÒƒCiÀ‡SõV€I'¥`¢bЏSæk:­¥) t”?–g>/.kpš.Œ}Ÿ‡Ïë7%sjGBC| Tl•›©û­í¥Ÿƒ-‰ií´ýGÙ|Ÿ/ûõß'Ëí=´øÿk—‡_ n±®jÎÕìÉÇF)[®~‡´~×/µðùÿÊÞÑØL6v'•„8uû/“åÿ~»äù}§¸QöŸírðëö¨öÚåâÃö¾?ù[Ú*"©É+ AþÇÙ|Ÿ/ûõß'Ëí=´øÿk—‡_µG´~×/µðùýÆ—äÐëã­ZÑ"Ü׳Y»O»?OØ>Óäùß®ù>_iî}§Çû\¼:ÔŽSð9«ñ‘û¥0$–®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥Ã•Äô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰éR-y8®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJÀ‡—šâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž”èÚÐ,Mµ|Š€Ä×xö=*ü›2ž‰Ö‚ëh*¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž•€0i\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ´ts\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž•¼•Äô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰éXÉ\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž• 3£šâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž” L:Q´ô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰éZC +‰é\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJâzWÒ¸ž•Äô®'¥q=+‰é\OJþv$Lº+„‹^¢#A“sóXP ]&ç­CËì½£ö¹x°ý¯‡Ïì§Gbq5; ¼®Ín$eç¹ïW”•ÃÙ/êS,TQj 9ÖûÓ:ÅùYLaê R!ÏÙ{GÚ|Ÿ/ûõß'Ëí=´øÿfVµbH±w­N‘aGŸÚ£Ú?k—‹Úø|þÃQZ–䑌^‚O’ðÁ"¯ël_^6Ú®pß9ð·4,ëT$" •#Ò<êQ6S™Í ¨eF/ö~ÑöŸ'Ëþ-bénŠl‡Pƒ ê}Ç|Ÿ.ù³Â ÄÜÒ¡ÄÑì–bŒo{È2ÛPµœ w=´øÿk—‡Z¶jq@pÔ )J©3¥â€á¨biC5&t¡P5 M(f¤Î”#І¡‰½(e¦M¤4#І¡‰½(e©3¥â€á¨bI¥ µ&hFåÃPÄ“JjLÔʆ¡‰½ ËRi Ïh†$šA–¤.Ôʆ¡‰½ ËJjLé@pÔ1$Ò ´¡š“:Pކ&ôƒ-(f¤Î”#câÔ1$Ò ´¡š“:Pކ$¤iC5&t¡T14ƒ-(f• Ul‚˃;€¼±@ïõ¢É&²æ(‚="À, ª(v™ŸÃ€¹®Õ$dõ†u ÄÐgp'Z%"eP,´¡š“:PŽ* „¨R”3RgJÅA°Ô )J©3¥â€á¨R”3RgJÅAÃA˜)C5&t¡P5 M(f¤Î”#І¡‰&”3RgJÅÃPÄÞ”2Ô™Ò„q@pÐDMçåJjLéB8 8j’iC-H]¡” CzA–¤.Ôʆ¡‰&e© µ#r€á¨bo@JÒBíIš†¡‰½ ËJjLéB8j›Ò ´¡š5©YÊoHtÊzeò¦á àOe,Á6n=Z4cèS—¡éx¸,¢™–,EX–Ú3éEœ§:PŽ*šA–”3RgJÅCH2Ò†jLéB8¨6Yh¸ÛŠ)Þk<Ò—«ð©wžÂFcÓZ*<¹Aá˜=+:™À³¼µ7å)€“áiCuáû~ÅÂ(Üo'8³Gd˪f„oD‚õËÒ(U˜”î6ñzV¥å|´ŽEAò¡aê^`Ÿ„ÏÍ@õçî[ާٲĎ"ÇYšÐ7|Ó8eBa÷'æ †ÕÜó=ëÞZYVm‘>9åBö;/=ÁïLqÑÅûö£mÞt‰ ¡_2Ýö¬¡0ë-“'½Y÷€®‡4¶\h@pZiýH~*;×"6Áú•á@µ™÷¥l)Ö§µ }é z5ˆÔŸ*j˜<:דÒ_bŠ"R§#RfÌÛ@pÔ1$Ò†Z“5#r€á¨boH2ЉRÖ,·ª¢d­À}_š.ºš†$šA–¤.Ôʆ¡‰½ ËJjLéDÀøÍ&€`Š à•%Ù4ÞwüTŒŠÆh· Ô:+ÍeØ¥€¶Ö‘58€¶ÓšÜͤ’bÕŠËÛZ`•0Ò´u‰š}i´õ¦Ñ8Å8¢"RÈ«Á,iòâ’Ü%·zµxÈ¥¶h­- °E€ò«Óô¤`µ„µ°E€ò«Óô¤`‚BZ€X(Ä^˜'¥#Ô%¨‚Ô`¯LÒœÒ%¨…¿F€*ôÁ=)À !²Z X-@Àz`ž”âÙ-P P0^˜'¥8ò¤6I¨¨¥^˜'¥7€|© ’je¨Ç­^˜'¥7€|© ¨E– `•Àž”Þò¤`š„Ym¨¥Ep'¥-t*F ëGÐŽèßr]Ã…ÁzÃ왵“©NV¹ïeïçA'¥áP‹-µ2SÙBX-)k ùR0Z„Ym¨‚¸Ò–ºÒ‘€zÔ"Ëm@ÀÀž”µÐž”ŒÖ¡[j ®åK] éHÀ=je¶ `ŠŠàT¦B`Ò‘€zÔ"Ëm@ÁÀyT×BzR0Z„Yj¢ð•Mt'¥#õ¨E– XF <ªôÁ=)­A!-@,F <ª4Á;Ò0Z‚Hµ°E€ò«Óô¤`ƒdµ°ZŒ@Ué‚zR0 !²Z€X-F*ôÁ=)À !²Z€X-@Àz`ž•†ˆl–¨ T·½$=vó­¡;Óˆ*Cdš€ÐÕµ-`ºÊ~+H~w¥—ÍŠÆæñòAíZ7uŠ›y&ùïçQïlËièE^˜'¥!ÈRJƒj‚(Àšö ÅéJ8P¤ÖôU~•Ïæ¤ù^Y(<õ»â+_Å•&D¨&„6ø ®ô¥®ƒåHÁ5²ÛT`Ë¥]ÞØ*þ;âEúL†ê悱é«Ú €) Ф“D²-/Í`Šp) Ф“PDE¨P Pn… äš‚"-@"€\ ƒt)I¨1  @.AºH9&‰Ð €¨f ¤9& ÄZ€Ø   Á4‡$Ô‹P‚€`*‚iI¨1   Rs0…‹ßš…  Ž¶}ëFat„Ý!¥ ÓZbk(eI¸Öæ'!Æ(è±tMJ`µ«Óô§4†Éj`"‹@)E±Ò¯LÒœÒ%ª‚Ô W¦ éN i ’Õ€µD·çÜFƒÑ\ú«ƒYõ¬;Hò¨jW'4$]bh*šHåZ-êøiØ_*±m¦|èÄ0SÐÞ–Jê$E{Gírñaû_ŸÑP ÉÅ~Óúé„q­.*Ýt5”ÇUn)ymåRöK¤o© ®CãËz¸'K~´;-nŠdL4JÁש·J:¡ >˜vûmíd\!ºŠ8¬%|Ÿ.óaHÕã¥OÇb—XTÌ&è¥jU€®¸tJ‚¯ärŽØ«‰u1Iô—¤ªÓp?4WÍgäh4Ù©í¶F£EœÍç®õŠw(ótèÛ¥h÷OÝVe? "Š0®unéš4X}_…Þº–9!M'cZ³ó½,]Å;l´æ§H]*H3ppL­HàK/ÑùŸŸ´öÏÚüÏâP€H\µ&…»Æ25w)Ïø‹yTu±ùmjðšOŠ)’l#óŠˆÏÔËïڣÚ?k—‹ÕmšAžVÕhwa{Þ?£pewлS+ne!|~:ŒéH—aêSôwƒö¥PÓ] ì¿pó¦|ˆ/¢NÒ0Œo½9ÒD)Œ 3¹·Ûohú€. ¨ < ÷—ãÖ)!‰‹fOPõŠB¨XƒUþ´Ÿú²‹¢÷sÓ¥~òßÅ»°¼ÚH‚ÀT>UÈ4ïAc„CÕ ò¥‘à•]º!üÍ/Ÿ"±®dÖ@¡J !Å¡æQ°r_R+£"tÀùæ´ýµimxôÅ/'"/Q9¥ïw¡_O悱蟒”χҾxÏÍe@`Å|¯µø]çÍ2U¥zàþ(]Df`ݯ“Úô†>§Ìüý§¶~×à}¹‡­?*æŽ,½Ò{> cþ*ÛLdé0•r …t¸ È~;ÙxuûT{GírñaúC¤éñ&SÒ€±Ã!n/ÍMŠXH‘Ë.Ô0ˆÃEÓ†‡è¾–Y«Px’ú>CKhR·VÖ¬H-yT¶¥}ËŽûjÅXƒ'@U‡Z@WÚõ:ΡÃåÜQK P’.†kh3"ôkƒ|ÿÓΓàü0º óµhË\5&Ê,S,\¥»ÐÏv­vÁt0Fü¸¥/X(»Ng¶Ô^FjCài )vzb+?µ½£éH2¡måRwš>1Æä¤ð¿JÁ¼6hŒ–#Íîº÷>~×å}¯Âû_™ùûOlý¯ÀúÀ5Í‚&]‚‰³E‡œ¯{/¿jhý "o((ØWñÙ‘;Ðå\Nä5-ªÏņлp-Þ†Z"MÑ ¤Þý)ÃÜŒ÷¬y°âu~(þT'ää¢É>F/dÌdêd¬f³Š3œ‘B8©ö1Ë.õh™Xæs ø*4rJˆßð(¬5¸”ÖVu RDübŸÞcóS…±‹bÄ:~ê”È"TÆjpC¯\ï^ã…[ó?˜EÄ(—‡•x>:”Œñ©é%½O:Å–¬á¬ûÍ`‚Œ`že2$«„³xõ©”Æ!{)Ò>oõEÅÈ·ßû­\<§ðS”üÇÅ:ÃǽL"î\¯W¹1š@A=Ÿm½£èdaÕ§çNÄ(i ž©¢ô-ãòþZ0zEºqô~cçí~WÚü/µùŸŸ´öÏÚü¦FD ±WD4ë {Ô®z?çRÕ€òÎ{ùxuûT{G´ÈÖä|Ö­z¾(®ß–nzt#`Ýqò—Í3Ç©MåXyD¼µ#׽旀ƒ‡™W½_‚— µW悱Gê¬ï<¿QâŸ5`YøOAÊü;Ô¹Ÿ5ã-Mö*êçbŽ¢køª@‚ˆã¢K£¥B„¬¶Å!ÈGÔ)¡&¤@]oBµ6ƒÆÕ#)„""¾`ŠG-ý«-ì²æõùúÔ!d·ÇçßK%æRyôÕü5MBa¼Ê†ç`Ž/Ùs å«|×'¤Œˆ’ÙÖ?J±ƒ2róSÀNb'Óí·´v"ëjûÔ݇¼E:·Èù¤°äâо/XœØ½÷}êVr½ZƒŠ€>—Ì|ý¯Êû_…ö¿3óôaí”XÆõ ¸0g½íŸµøFJ€©Yè1z‚A'¢OjˆK/‡Òeá×ì}±ËÏ>Tœ«·ú«Áµ>Ê4´°Ð›¨œréd÷©S(m¨Ÿ4Œ·7Þ ù«#|´rü¾Ë/>×ÇçÚF\¨ ×eû©Wà÷¯À3ìO½]õ % E[$ö‹ùVð[Q­åÅ9ï¬U„UBIŽ¥Sþ@ZoÒà—º5; Ü£1öøD4Њà3LV-AÉÖmïIØ\ii¤¯VÏ9C½ïV½žû˜ùû_•ö¿ í~g翯òm’<¯äTB :ÎÃ.žŽ¤Í"ÜZ)´H²š˜Y°Ñ"¦ ¶Áš¶¶O »¾Ùû_ôe”¨æ­Á@ÄWìãèåá×ìpSTN¨k$ÏJŒäàÈ)©¦éJ~( hqùš¿ò^õlA‚>Ë/>×ÇçÚœÖgàË5“æÀùßÞ¬„\y“5'è†~ªéKú@h82I³N±D1æ¿F6ùŸ¥x n´@nÚ‘’v_²ƒ“÷³êÊh{TcÑÃåô·>åAE÷AïVh:“äìÛým†‘ú3Ù*û@:«š4™K\‡ÃFŸXžÈD P¡i–sñ^ÙÞ=+Ú`ŽÏl}§Ì|ý¯Êû_…ö¿3óß–z%ÍñCu¶TI™~ZKùd"\C½ë„Qîê6É3Røì¨Ì œ5T}ü!mÉwµ÷=³ö¿éÓÜã_Jc†Œ˜3ªK džI4\“èeá×ê ö” cõÙÉævœT³‘í_ßÖ¼›OïŠÁ>!£Z?˜¨ D%ü-ø{ýž^,}¯Ï½6BR·Cg4i6[X7y¯Çfºëfy†ÅuÏb. 1Q.iùì–x’…A¬Ì5ø‡¿JbO­ú˲'Û=™QÙéRä,‹ÄëÚ K`«Ã='£øå«ˆõ·Ëo.ã‚#W ÙB´áG-áÓýì†Å Øk„2W4‚ÎëñO´›ù§L<Ô-S±â*FT ºý§Ì|ý¯Êû_…ö¿3óßÙõiaó@ÈKU›ýui¢.’Vû…Ñ  à*?,b±H£¡`DPV\*ÊZPa„°""–J׹ퟵøô )å9ö¨/Î!ûKVþýsIñx¿®jj_£—‡_°B4‘O ׸þ(ØÆçI 9žh~ÿT¹*ß F¬|{RÕ‘†‹[¶€úYSÒ‘p‰³ìrñcí|~Fê¾ZÆFÊ?/š±/ò4k¯‘MÊdüÝó¿qOåXq:’˜ê›?#Ú·‡UDÄy…ª•ïóS2Q;†^Ùi‚Š„Ìh›Tvèkø5ØøPÿü­£Å»Ì|Pö¾ù۲Іº¶ ZÔ£ùi~)‡ò¾CHºÁ“ÓÔ¿ëVÜ:)^î¨ÒõÂSò×¶>Óæ>~×å}¯Âû_™ùûOlý¯ÀïÄÄÆO:âa~A#ÞÔö]W‹ßêeá×ì‘“˜àù<šŠÖ‰…é >E\Á-¸¯dý®^,}¯Ïì3š¸ú|TŸšñTu+ÁGÉ)ˆáz2{ÑÒÆÆüQlžñï~•…EûÁíX3Ðþ)Lû?‚—3ñïHÈú¯æ¼%¯k‹ØEn£í´ùŸµù_kð¾×æ~~ÓÛ?kð>ˆ°“’™od—ÌmPåºâŸ#®™z–÷£qÇd|ÇÏ/´UE7~ fÏ"¨ñ=Šü8VVt¨£éBlV&Ìõ§±N'â)evJ0ϲ*>Ç/>×Ççÿ({cí>cçí~WÚü/µùŸŸ´öÏÚü«6BüPXÌØü5ïéû•ðîüŠð zÑ®þf…‰ñê£=ñGîÚßžÖGji\ú*þ9Fd¦0J 5ƒþ¯™¬¸êÿá“ä(=_#ù­ÿ^»ËCúúgÚðЉü…CqŸ£VM¿Qú®jqÛãÖ¤`z4Ú¯¶Êo"?3H#È¿ƒêåâÇÚøüÿål}§Ì|ý¯Êû_…ö¿3óöžÙû_ö¾!ÏÑ`‹SÙ?k—‹kãóÿ”=±öŸ1óö¿+í~ÚüÏÏÚ{gí~Úø‡?jÏdѱkI,+Q˜¼MÚOÙeâÅCž&ƒ0ˆ3 ňI$~ÉboŒÖ l„êTÛ/Ù„%#fb(Ä>Ï [iX¶ÃjCÙ|ÇÍHw&Ÿiò¨™»ÅI$~Î „*$~jô¸™³†ÕTZŽn#[Q²öÍ V¢È`ËI¼ .“ö_¡ÏFàoÒ‰„$Ü~ËÄ9ûV{&­øF•¸ˆizž­¦»ý–^,So`”"¬fLiÅ0ƒì>7£sH"§X‹}›SB-BS‹b*,«/ÙÌ„D\oK’TQÀGÙ|ÇÍ2É~ÓåTäMI ´e9„gtj;² ¢µ*^v8û<DûÒA º^·GÙ{f£‡#éRi8%-™_&ÿeð)žÐ}ë"b ؽ茲-ö^!Í5ý‚ÄûF1ŒcÆ1ŒcÆ1‹Ú˜Æ1ŒcÆ1ŒcÆ2vïáö¬cÆ1ŒcÆ1Œc›oòÊcÆ1ŒcÆ1ŒcÌÆ1ŒcÆ1ŒcÆ1Œ)8Ï´1ŒcÆ1ŒcÆ1Œ‡Î{¾ÕŒcÆ1ŒcÆ1Œc:“ûSÆ1ŒcÆ1ŒcÆtgö¦1ŒcÆ1ŒcÆ1Œ5ÙßwÚ1ŒcÆ1ŒcÆ1ŒIi¿jcÆ1ŒcÆ1ŒcÎ¾ÔÆ1ŒcÆ1ŒcÆ1ŽX˜šÿÚ?!`µqWqWqWqWqWqWqWqWqWqWpWpWqWqWqWqWqWqWqWqWqWpWqWqWqWqWqWqWqWqWqWp×qWqWqWqWqWqWqWqWqWpWqWqWqWqWqWqWqWqWqWqWp×qWqWqWqWqWqWqWqWqWpWqWqWqWqWqWqWqWqWqWqWpWqWqWqWqWqWqWqWqWqWpWqWqWqWqWqWqWqWqWqWpWqWqWqWqWqWqWqWqWqWqWpW qWqWqWqWqWqWqWqWqWpWpWqWqWqWqWqWqWqWqW"°ßþOg¢°÷ƒÿ Í¥p½hn)™$Ð[Ö*-J ¬9P˜…[˜PŒSÒKб5¦¦šŽ­þÄÔªàiúTÛÿ‡h¬÷ZÅk•ˆ/µj•8ÈzP°­ž ôýT8üªEK§f%§suObaY¶¥½àºïZˆ)º~uz* /)Õ7R-£%# ôÆJ3d|¨Øµ6T3Dz;‚0÷O“>kÀ)ÜúW3Ò€ëíI/äðÓªºb€GÌ¥¡J­9B¿”ózRõ¢2}‚JB¡Q 5>åXëz>ÑVœ9¢å‚„×ÙýPˆkÿÑX?ë c¸”V uTD ä§%#Ú„™Ï4t/ÅR¿Oü¯Ä%«—éCÜ}”Ôózk0éÜ–ÙW"+üƒÂ+›P©›"t¨Û*6ö RÜý›X¥žÍ9=T»M«]ö£T¾1¨OüPr­`ÇÙÔ1P쥹>”d…öô˜šdÈëj5DÆx«ø¾>b†{¾Š&KåZvF°ò±ë@-è3DÃUÔHêE.qéØüÇ•7"}*U•e¯äT¬QÅ'¢ž‹Ó4_*Ó1Í3*~áï`ÑíDææv+ oö˜EÁÂiÖ±¥Pƒ9ðÖägý*ñg¹7)…A¢,FÕÁø¨ o2ýÒ4áÿ(È×w´&ß6(Cògêg³µŽÆôµ„ïú‹¼ö—Êmïô‚·Ì³VD–OF“’7ÃÑüb‘ÿE-ͿʸFV>3XØêÖ¢·ÐýÐY.ŸºÈ!ÙÏûÿSE`ÿ’ÓpªÔÂŒÉZ˜ùR•…¶ámùæ‘–˜©ûPÚõZ´»ÉÅ#oR N_µgKP“ H̺Võz)lc¤ÛÒ ªñDÚÓ‘”–;b⛑Û8ɵë%ª……ö¡võ*H_º{³ Ê\ÚÊ^«±ö¨kŽŸö·ÿ7êš]=hË]äÇâ®KFPñ4P( þ),½%ï!GüÁ'Â{¦eíY,òò¢í<ÕŠ‰üÑï§rê¾K¦[̽JÓ4HþÒÑX?㶉E§˜«‘+tüù4i¿[æ je|ÒüR7£2v¹Išµ†9¦ñÅcíQíé@lj”éMJùסŽiܽ I–ÐÒéMRÓ%…8-@ýªÅòõ å}êhÏwšûTÎ=蘆›Oz5… Z,ô¬¸ýgŒ~é †Ô>4£O?iŒòþTÁãz­?ÎãÝOý”x…òTf¶ŠÈ|²xéDà65ëÜŽÏÄÖiz´¶¯­,÷[üj(DGç¯|‰ùCæŒ7šŽR6±>]ƒ†ŸT“å@V¹YÿU£÷*ÀæÑp=mû§a:^µRîþ±O‹¶+\Þ@ø(T+±à1çöš+üh{èã·*¤²µ–ð6¬,ô(j4¤íV4òëô¬dèüÔ¢õ”EA½ ÔÕãç`L·¥'â—ÕAµ”zZ„õ¤#ãB:Û )ÆQ[s|• ?:rª“K@Ó!¦ñdöOš˜›:xµk ­#dmšQÿIbœƒàQì'D\3ŠpwÖ+'KR‚â§ õG®+#ªˆ3ófƒ,º”‚V‡Þ¦NFuýÐÐ{Õꥀâ‰1EÎÇé‘ÌêÏ¡>õºŸ_ƒò­b:KÞ‚¸žnöÁïXô§ 9Ú¸þô=>´l}Ê»a®'©WãÞ©I¢ó@’·ûMC.Qnð&}*ò<ÈÓËnÉE©ìŽ‹>›P,¯JË' ÏëîtQ`§­7=â >jîÜß°&£Szƒê诲4mÙ†ëFò™’ïj†yì@$5¢`Í1@ªë“Š›ÙRcj›þ(k½H¨mÙh[ÎÆ,ó¦ö°o@nâ)—ö§9Z «êšÔ Ûê7ÈP, $YûÞÃf„X®—©V¡ò®oÿLuçXŠÅs«4‘jÀÏÐÈ­šMGÇî¦(®V¼È=v«jÁxéë½ šm6/Ý`CÕó¬ti'—¹¢P£§‹w_´;s(£^ýo@[Ôz\fOŒÓÄ2uhI›ñÙ, «Áà ¢d‘½%«[}*rûKëL3ãÒ±¨ókúÍK²:¶üÖ\ô§– ñ ÁÎïõGKŸi¢¬=Ýfœ˜}Zú+iˆ+üWáøÍ\7ô(™0ÚÏ]hôyÕ¼t›O5Þ\Ô0HÕ úM/WÕ_À¥ŠÐÇ[|Õ´õ ̧̃a]è *³i¬Îv(¿†½‚ EcC²'áVÓçF§²Ÿâ¥p*dî°²Jy½•™_f‘/j©’cšwÌö@½º0ÒÔc3é\/¥ )šLª|b‰…ÎÜê*ižb†¦Áæ})èOâ W¶H÷¡ù…ýëætúáìÔ„C×Z×ÎiRç©A8ÝhÚgóôRh ~ß?db¤E|f’ƒ4Œ¡êþ«—ÝV±ð<¼-_ÉóƒÇZÎ59ùÉú¤Y¯ˆ<¾±½/3¥¹½ÓõFÇ¢†²¶y´ôuìŽâS¦ïj, Oâ +K™é:R`Ö zÒ­ONÃ7£§e¹¯åÝ~ÐﻆJ³ØÔèòÐ騴‰·fºÆÚ}†}hˆ2lßý£û•²:¬jç“>šÐYŠÈÇ?çye4mXdõ©îh«Ž"/’±Cóéô]´…jNƒ÷n¡>ï×Ñ˺Z®?4}¨˜ÏJÏII¦«éLÜvrŒ‹GJνˆ¦‹üšÐÐ_­?3(T}šÿƒ‚Ô¸OJˆ›”‘A8¬ô¬äê­_éS½Ï[>ý™M*³üºsÍX×N(›©€žpÒŒ5[úUµÐ[zR¹\›nË –(-U¹š‰4ÌŠ›LŽônr`4ó¤Å®Õ€GmþcœÖ.ó(>_F኷m)=œ]¡8¬©É^E_ƒî–(uÅÇÎÚSKƒY‰‹dVœ’D¯¦7¬ó5g/¡Wv|ãÉ-W";fi‚yéK4–³F–ÀRêҗ˘±WH=o\?zÿ5ýÓºž¡žÕŠÀâ…@“ײ$0j_ƒƒ±&hIçÒŠíÊ~zÐúÅX§kF¬Ö6‰½ ½òµY­nþóMÊt½MVg4L/Çë¶0ÖÇñíAh ²½)äú ÍÜ~á>‹ö‡Úô¸¡#·,ßÓÀÎ ¯î¯(>OK²?«0¢ÍÓ눩Ä"ß°-_Z W«_‘SóYxê?UÀ~Õ›Ëâ™J†·G[Óc=ƒ‡qøríoŠ!R::oX½åJ>†]ïwöQ›(Ýî©sVôJÔž™§ËÜ´£ð1J7^¬ÄѰšÙ_åVéJ›P2fõ•v¦TîJ¨½"샎ãì·T{ÝJ !Ó¸\j+KóEûÖCÅ]µI“ÇSê¡ÑñÜ÷ÿpÐú —kã´…$P]8©1š‘©•ù*ç—~òÑo™¬ŠzÞ‚·hœV}±ïM4´–§¢ä*j4g»:g¥¡>6§®~‚áW@jôŠneÞ(Âó˜ø¨\fGžjVÁÍê.s×R†œŒš$Ðl[Þ’u–ïM¨¶7Ä}œ-‹Œ·î 4™9ˆÇZKžäÙZ‰"ï q4ªÒAyÿ~{-m›ú.ÄóŠ“\kj7­¨Þõ•dƾmg'·ªÝ¤ ¶Ý6ÍÁ©·•]tµf׫õÜ3š“>áQ e©Ü¦ƒ÷ÏÔ~ÐûSíròþjCT'Ë‘&¸OJ0bîlÑ3ѽk3YíÁDÒУOÑ_ë *}‰ëÒäBï®*(&¢¦#R<šô':ºÞ®ÓìxÒ³›Ï˜GÅ/DÌDó&ñMÌb0tõ毩Žm ŠŠŠ¯üD­%üŠÄ<5,Ö³ëN°Š3'c ÚŽÔY™óJÔDvâÁžhKZ,žÿACRÏ]êW7.÷äEÍô¬KéÝ ë'Ú)Ň@pÐÌS“ÃØƒÚ®&§)ÇÖ•sî¶(®ÞToGÇØ¤˜=nu¨ -â”h·gÏ4-KofFÛè½-û@ÇB¿šžé-i<èµ|íVRGm—S.ߦ£°7¢nn‘P幣Ѭ«_Ú›©ÃQQQQÙ±QQÙ±QQÛ‘QQQMEEEEEEEEGdTTTvEEEEìŠŠŠŽÈ¨¨ìŠŠŠŠŽÈ¨¨ì***;b¢¢¢¢¢¢£¶*+\òÊþ)‚Òí3¶ö³WOŠÁÞx£1Ø‹žzyöÇŠî†CwŠÅ—èxò©Bô‚ö¿Ð˽ëu‘ÙçìÆ(Ìʬ •¬+Pâ‰Ð5¬õßR®´ëHn“°Øë 7tžý€qP†šƒJ;Fý+Y[m^ìMfÇ hó3íõVþ8öÅx¢˜™{^…aã¡`˜ ÒôŽÝ·Í#•È|ïÌT2”ñ—ð16ÀYm×Y¡ ,°Œ5Xµ<öŒ\¡"tî[¿ñ´>Ôû4ÂŒç)%jM`ú"Ý"ÐI<ÒMEç3ô²ƒäÓ¸{¾ÿþ 'z2üOeÞ!îÓ ÃXÕBÈT Àâ¬ö|TäÒ„CII%=žs¦V¢GÑÍN›‡Û¸1z"Ûâ~áÊK}©HE-Ûbß@·º°º¦ï©42ML>ÞºÞ üTň(ßWgh² '¤Þh„8ì¿ëww+Ñ>Ãò¬X6<_þ(o>üIP“/ìפٮoÀb_z¬/6œzRñ¸óý5¦#aõüÕè]-æ÷)/‹üv¡âéöÚÝofŸr|EIJc^j=[ÓÒ™ oÒõdSéÝBô«“ù?ªzåíñAÀ„}#â€á&°w[-5¼'7Ïr,°Èºâ–\Ô— ïø©0a«“½.‰ÙÛü @/8:ÔÐ3¢Ç¡AÎv¸áÒåñ­‰×õK,F…Ïœb’†Þ1äfÜæ€ð<¨yA»ÏD}"›?¾ŽÝ^?N<+TÈîZ‡Þ¤™Úˆ ¶¼v 'Óš=µ¥g=jÍ°ÛÆkq@'O¼‘ÛWžUÞ8:wØß÷Zþ­~ÁÚ_ Ñ..þ>*rÉ”¬Ýmö{Gæ=*+MÅì»qO'ñR)‰T â…³äµ'2?ñ§“>|Ð ‡ß&q¯â¸ížf ‹14ܳƒÎá¥øÌu¥óÍçíJšéûøïBChûíNárte¨“ç«×µ­ðyU˜ éwP-íR£æAêÕ¹ô,àÞߥ'F*Ⱥc¼E`úŒZ56“ξ”lê2~è8#ceàù¨½r»R¤P¿Î~è.:À—åQãÍËϼް`œÅ¨¯ËÙëOÜüúEüWÓ ŠŠŠŠŠŠŠŽÈ¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨!­“éÀkÍEEEEEd”zN´µÕËCnhgþ<uBóP—/yÖsÝ·r&Óãe $s1Ë1yoû¦²Õ©T©r6ô Âi‡¥nŒõh{/Pê¤Ùknî~=)æìÆXÊkÅE+³®aö/QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQIQQQQQQQQQQQQQQQQQQQAQQQQQQQQQQQQQQBkEÛjþ«Àk­ëW?jvçDÀ]«*°ŒúX«¯&µ~åtÝqÚ§AŸò¶³íAùjI]ÊéÁôôÖû€7C$z:÷ïª*XHü¨6{]~ ùÜßß5r¸mN¹t£1aô›Ò‰]Š ºãz•Ù‡ñúûšŠ•GÐy˜žŠ+Ï¡„M0¿=Õ§%{F*/pûÅ–”˜Vš´L@e‘Û*Y&Ï`õçZÆ‘Ow¹‚tb %#–h­jpñ$ñ5è!Ñ7;b'>¥kÔxÍÌøš—)méFK«K¤¦¢LH‘š×P¬m<ë?µùoÚ[߃ø)È]ÝümôÕÎt­T:în{%ÈßX½œº»ý)­5‚£¼W&S»G‹Å ¸Q)üV>—9X¡$¶©w•«kgÛ­J.‘³×êů˜¦Ð'PãŽ|u Éõç}Qù*ñÒ>?uõ÷šuŸÇÒеÜ{ÖfÝk(žã&<¿m7TrÞ~“ãG9ÿ‡*]ŸT•”<ÍaTXÚƒ’yY¤Ôhpíu« J$ÈéúšvŒÅóFêâ°\qš´tŽ :HºŸºÉOʧˆ[[Áó¨ÑÕG­%ÛC21ªTØèm2ݽŸZ›ö¦ÏûÉÿ%ûCê;‹št[ºN:Ð>œæÂj`Oã§pÕõ£Ù>”VšÁôl¤;kéYyÒˆÃt@ýÇX ¯‚Ó«G¢ͪ<}•“Èætw*þyi4öy¾ºáRÞ¹)oÕÊ"ƒÕPu v $¹Ú­óS-§å£Ú ÿàºÔj-—:NãÚ’ÛÒð÷Ù"ù/ø¡!bÝôÃÑY´u ßü¢0",Úe?ŠxPž$ãè?hVa¾(ë«ëþPéê?Tì>NÿBÒœ-êç°Ñ±ÑÇ—4DŠ#gNiÛYìý ½0jQ¢Yž±ônÊ3'sE´ŒsWš¿{ÓšžT4?j†*2Ç;˜hÆØù—¤9Ò‚>ñå¹µO‚>ýöŠ%9¢°ïçR­¹|9Ï¥Ó÷ã\ñ‰¬â´õ·Å|}íYüÇ*@.…üÚ¾(r·ïÚ¸QSÇ›|yÒé*è›]¾ž3R×"Œd‡•Lì­×}9¥i”{©ø=iÍZh‘kO·KnÈ1OJo²–°M£šƒH¨S;°Ï™L‹ÂÝjZm^× ÛzÍ^âbõÅzù]ê¨C¦,ßÇÓy×C•≨=Æõ«ò¦ƒNÅ‘š*ÆÑ£¤ñAƒóêXVÌü<454-Ú’;ÏuÜ‹èôÉôj©úŸŠºðz?Œû}B„¿O'^xî|K¯û¤Z.»EF,<ÀÏYú='óZ6çüú`Ô ßŪh¬ñÌÇRÍ ‡äþêÇ^¿ScèðÊå(\>+£FùS%Ï+R$-âÅB·ŽŸð‘(ÉDJl/8ô¥…í"KT̼Úþ–­  Õ†ùÖ‰'/¸â7h¸I³^à %Zqó^‚´„å|ÖÜ©F=õ«ž;r=(³^ÇÍø¬²~{N„­<°Ù‰ß¸Ò){OµoÝ" gÒ¦1Æ|Ò).D3QI›wVs¸Œ® ¾á½ú:‹@íö_Ýg§mNé½<íP/%ŸRšÂùHÚ¤Y«•ÌmõE+P½)x¹oŠkæoóS÷çƒ3üS¸.ÒEžÃ‘ä_Í¡ç~*,zÕýW/pøh’ u&:'ú¡±D4„ 7[xà½^iÒ{<[4c1Ôü“ñN{=˜|Q¸=þ#â¬J:3ægÛ¹!+¾.ðUˆõzþèk„í$úTE©Ü}'~™ïZ?Ö¿¿¢tõÏãÊ…·èÞ =¡ÍO1ïRe”–±Óì4Vø‘W[7­i<ú.µ!h7æ¿ Qü þ_Ð+ú@¯èý¿ W„+Âá ð…xB¼!^¯W„)<žqçíW õ‡Ìk3kVÆô˜Í'¹ô†Ô ü¬°¸¨EEŸE C·clö‰3×°ÆÊ+4§M¿áu]ÃL‹&6«Q.æHÓlž”,Ù1Ò1P¬F¼þ»tÐ_w*|m‹u8 #öÕÖ:ÿ–«£vût¬éVÄ|üS‘´A7{q4·b½´¦•Yo>úRº¾?•ÓÆ+¡»Ôƒº!žg~ö÷cý¤˜ƒO…øZŒ`byÇîÕ§a±E|¸82ë×Z–,ð¸}S¹+h¼£â(Ö>¬~~iØó-_Tö^yÃöüu©wæ—ÑéX‹žç_ßcÚ^™Ä"F~h8KNyРN¦ôæZaý÷­,¶¤gÛËËìtVøÂkuhÓžV º[¥û³£(›g×ý¬ÑõzþFìnxó¢]o¥€KµE‘j¼¼x+ >¿˜§rU D´©© ïéHbS¥s*#Z”ÉmôŠYÆá ‚vÿ‚M/iÞ¤ûbQÙ%Ø(y–Dÿ” 2èÖÁæÕ’Î|ë¡¥ ˜®%Üoµt›ûUÞŸ7x©rŸhÍr\¥¼ÝYEóHÏÄ¥œÖuoçV°(Ž~Y¿úið²{Þ˜Ž­¶/òÑLùÈ÷{3x¨I%xšÄàÚ'~눮B8¬%£÷K~!ãœRP½¯ÈK'꣎bÞ•ýÑQw›º‚.&8û}ƒþ1óßò;O¢Áé>Ô@¥‹m¨üªù£]µ¤c6š¾.›y’ùÄÔ‚ñ J1.9*ÈÜtòã/L¦Óè>œP¦àÕs‰“Štë—ÍÀyׂ5Kb x“,–Ÿ;ÍD:>ÚVÅ#£>•bc¨½ƒíZ‘ñV #Ó²õ]Çqs_Ë>iÊ¥„@ÕâˆÉº_‹ÊÐ4þPd[¬_õQg£‰?ÎãP/Ø O®x>T#´},èÍkPW–ôy Ò…i9Õ iñµ0Ôàõt¨[L!Ëô½¨Ð3ÛÑë¥jÌTd¢³â±)Yl<ÚèՀ˽9&¦‡•?M.öÿ1FR\šÕ'Æ2hÖÿ=˜yS.Ç•HǯÍ4Õ1ø¨&=˜BõƒvYéãÃDm¥—ãRWÉyœR´-—2\â¦Þv&v½E³S-j„M½¿ÎÌ úR_oËú¢0Ÿ6ž_]$†æÍÊÅ]Æ=?Ṫ[ÏÄV.ïë5:—ÓÐãjv•$ÜÞ‘’€Ïy'Ljò¥µ”2bSÔC¾1YsPS˜ûƒþ1îZq¿—iôP³,Ç™—ÍiOóñ‘Ö¤ ìëQÎoRÌ£ÕgÔ|©²E™t™Ãö¨Ç[”ŽrPö7)æ@ÐêA7ŒgÞ”Q¿êzT¸ø t”i[†Ev\×õGXõˆ}ëO¥ÌcþVEKoüÅcz3õœiìÞ‡…qÀv LÄènG_íazׄÞ6¦y‚Ý·ÅDÙŒäj~Wh­7Zn7ŠÇ‰Æ¡çõRÜË#·5çvzN´Ì>‘ïLÑ!õ QºÆ*6ÞwýT9õ©óØS¤ŽÍ  ˆ°ò«­)Míïj„M40ˆÂ#Ô¾üvDÑYÎȨ¨¨š“ÇlM!mØ.ÿ ò=aüÒo~‘þÕ•¢<ßZ2ëÈýÖŒúÍYcïYE.·>Ån‡Å, šŽçÇ4b— ‚ýöhÓÆµÐ.?'Ýh¬ñÞ¢zÒ¼§aöº}ø• 0iͰ|f(“ñI!¾Í«—ÓCH3¥1/Ûžr÷Šhµõ߸*Øš½g»öM â˜)ѳBƇDö E‡J}¾qx§³-5?e=~ߊE‹¹Ïr]þ„¤âEéþ1L-gs_+^ˆºH§ž­±OÆã—nVsÙ®«WlÔÔöö|V@¢*×Éõ«y`Äþ8æŽ"\Wóç@È/ˆ=AsÄжxB>*#ÒövÄtûéû$eïØÄNŸ—š °½Á½Ê#'~p5«½jº˜1Ï?u¢°ÇÆÝT0 KâµÓèÉÜ-÷_jâŸ×ü(qE!>¦Ll|éBK4W9ëA©}Ž¿ªÖع5ý"¢ÄMæÔÊ·‹oåGÙ}ýª3Xˆ‰ósYñ1íYÖœ¿ÚjS9ÍB‘o÷­c×j¶vÌcP£nçzìH˜·bXS°,Å ¬tè9 ç>”5ºš:1R!ìd¦Ç›ú?ºò4Td’fmçÒÎû0zRÒŠ'x·ùE]}µ;]Ã-°Ù¬úb§È¸Ç•cYƒnŽ´-‘5d1R‰2"-_~*Û ê›ÓM1x¿zrϾ˜>9¤1Íiô JQ-!8M)Y²óÓ5X½Ú&NŸŠ<û‘V+y¶÷¥D5Õñ¥väuì‰OΘúÅm¢°ÈŒî³ÈüQöº}ÃP:Læ¦æbvoSÿBKÙ™ÚÔsYãájA–Å&ƒØJÌvüœÐÇ+Øu¶ÓÓ³ÂsØXÖ¢¥¥ÓʤZó©`x«"&X«®_(¥3ŠÙIHÅ'74!ŧR¡ÁÚº2—(ƒkó¿nR:1Y2õg»1м…&ï9­F~ª-%0¾í—“×Yæ§²ÅR°OJœÝZ¼.½Û}eƒH¯ZEàçóôÊ϶”  œéûÏæ§©èƪ0C…Æ–×·ü¥kKÛ÷VbúN‡M'°IQH5¤ù8}»Å&´¸ñÍm¢°Èp4hr#«:ÌÇ9=~ÓO †!»Ž†írbÞŸºÀ“Ç¿üõŠ)Èÿ”¸Vœ·Î»ÕÜ1ÎG}á^ð&®\Ž ¿u¸ëP'±—Ç(}ÐÈ ñ@vìß½i©F¼hpÉÓjŠUNÎ@ñ°ë¿l—ì&=S¤€ÙQ|_z"´]ÝÕ ÀîÛØNìRÁlXÛ¯eòN¦Ô{z´4740S@¶Nº4NFþ­¡\ž<4·?OšÂÙ;’ªâu¿OÍ^bñ•„?•21ª¸ Úao?ÓKrgà¬%éhͼ½êÒ0±ÇãJ„[¯æ¢G1:K›ýÞŠÁÿ$-&(†öðÞ²¥îÂq|×KÑû§`}Žÿ'ÁB Ev¨Ò½ ¥º ç3ÝÞ‹åâ²F61ã­p¾•Âúv»Ëu™?U%{ÎÑç¯c "ÞJÔ#­=ËúU²}~…ÉP1/B2>ÕÚ…–PzTs¸úPãøÓÿ/ò¬qKèšÒ#Hw)Aç·uÌFoŸ* f\lÜùí(&îÄæ°t«|f¶.IÖ×i¼R½‹ï«? ÚçR{ñMj*%½²ŒÉòt#o:+K»ÿ<ëT>2ž´Í%uØWõ½MmÎí\H1Ï_ÕJg\γNåKØñ÷š+ü°GÔêb…ÉŠoQ.ǵÔåødø©1=n÷·½súr|3ïSh<çÚ ºdÑ1þuŸJ4›ÿ”Úª9–[é­I$@ç­ÅWú²’ëöÔõ(¹díʈg¦´,P–1§Ðk#Ù_ ä±2š½qKˆ †qÜÓ\/l–lÏBçùNâÇï!­ $£|í½Ï¦Ýv*_{üÿƒ¢°Ìvg†‚`Ï[;G뻪ÓsÆ6«£¹£@̓vßÚÎz—=|=ÖÏOž›ÖSÐihnªÒq'¬þ*—¥r­íŸí93*U>@ßüì3Õkò?óRDwKLXËŠ ÕάÖ4<šñ86×ZKžá:Î#n±Eèß~"ôÉÃÞQÛinùOŒöȤ¨C_ƒiCè;Jt]þ-Ø1K7{^”mõf¥C!m‘WÛÏV¦Z™e©©ã~È*èm½Ji¦2-dÍ7²dˆcW¾Ñô0 GnîÔ)¸#«Y‡øî©æhnå—ðqÜž¼1 _ÊišÖu¿ê¬äs³ü¢––ããé]ܨ¨¨¨¢ÙBlþ6®0mH-ƒŸßSJiÓG΢¢\¾3µ]élùoA*j'Hã§wG!#Ÿö¡ôZõÑÿ˜­5ó@V̺VÐüÒÊõÁ·ûX(­÷ÿŠ ®„úU¼`ØÇžþu[Z–ôÞ¢¢¿Ïk(;¹ï ü¾****+w캸~iä£~…)§y !â¿n†¥9áÅ$ “5!¯ÓDõù®7ù[B¢¢¢¢‚WVÚ;Ô£“¶*;±IêݡЩ©©©¡Àƒ*×ÞnLb”¹-ïÅ­ú¨1~”Â¥Ip¥ñнFÊ廜¿9 )ö°òzÔHNXµÉÙyô¨û Q¯¯vj4ÿÂÓX?æg]4~éa͆ÜÑ{oP¡âs“N½ø‘Þ˸KۮߞÀy/7úBŠmL4À2_Òˆ\$ÐÓ¦ôò@¾ô±QV~¿¾ŠàT ÓËVÌÙÕÑúˆrG´ßñÛ>ju;Ùü¾ÄÀ¢<>¨KØwðÕ˜}rV šÕÄP7ånµ…F‰²Üµ $;Œtì{ 곦5ôížÆæ4'óX[®_W¿C’j!‹žSŠš*œAæö Ñ§§ŸÍX„qQÀF1f¿ŒÕ›g‡Å¨“r²OÛ@’Ž»½v<©l†Bë:ÒtFPDN}ý¢¦YŒu·Ï×›¤{÷ƒÇÏÐiÏÝi¬ÆK1Ø•gûÿ!:– :Õ볎¿íNQµ=„>žü,óqöט®‘yä“N|܇ÄW PµX°:é=†Kv^køË¾i”Ðî`i9®w§ûFTÊq·C@ti&“Ï>oÃPIFŸ¤œêtmåã¹Þ¼>ŸªGܾ_gžÏîðh‡mÝ‹<ôì™ôX]HìS SPh0ÿ‘µNPÊ4f¿Þ•³z¡Z4‹|?†¡±I•hªQ›t¡›ŸK¥JÓt·¦)¨@t!ôiP™Æ±í\§¯fªâ _ö!µÈrõÛËë4 =Äšìhï,Q{ýÖšÁôüÔÔ1Q~òRØk¦ÏÍC¢ü¶|©òæ´i££Þä*} ¸¢r%ì1/‰¤D8¢Fq‘üTi¥Š…F£I*5ø¢§¤6ìX°ã¹/-ñBÁžu:5ê©Ôüãèž?‰¦àuLžx¤£—Ëàï \¯|:™¨Ö¤Ëè•FM}(Q‡J$˜¶¾îµ="ÒüÅc4Rí;0¬nÛ¸õ§¡Γ:§Ö½t«©dêæ’*pïWƒý§L[ô–£h.K“€ìb*70:P²çO«ÂþkLRÅ«âiDæø«Ôx=ÆŽóGÝh¬H=’×°v)T¦hE>õVÏ.Èç#R4w?u4ƒ f=O¬Â#=cùR8·ûn·Qó?K­E1¹¬mEA4¬è ˜g£FMÕ Ø% •ø!óìÑIÏB‰ƒœóQQQQ—Éíÿ]ª…PøÆô555555”è]¥‡¦ü¿m~ŠUô‘ß?±ZžÛ¥êégÅg;¹|¾ ššÕP¢’ÈúÚ ŠÜžÉí ;…%Rm7o‡§Jj>^^9©ØŒÞ\uJ™ÊuÛaÓõÍ3r°{TvEEEEEEEEG`й»÷éjºP¡ˆÛàѱîý©,¤:¼[æ¹ÞO¥LæëúÉÌv%'ƒFÃ|ç¯ÅCž <¯>T#¸Î ÌÖŠ>hÌy×^cõQËþFšÁõuž_튳X¶:ÕÁOÕµ<âýxó£.ÆÑøv1!39áý(€¤‡O¤{Òw Î[„ë“Ö†—£-Cº©J›~è¿Ö¡-‘çQ¡¬-6k™†™Ð´È(…ý7 ‹Ëb‚uF£Q¨ÿÁPRÒËšñ+‡s»w¬|w!„Ÿ6¤ºŸq×÷ÝëŽ^ËYBÕ]yþ;‘íævf¶Åg;VÜi§üJàzWÒ°~3\/B‰8ÄçÆÕäô§á¡%D}>P}ö§ÈǸÍ8¹uÀZvÌøöï"“%KÆû?ï,¯ß¥^y}OÏj±á"þ*ò^Ѧè[Í&§v/˜î…YuzmC¢,ùšÕÔ){°3c¥"f×­?j½0çóþ)š9}çö¨;pÕÀ—ê~éRi¥\Zm):ÒðbIò¤PÉLÆ$ØËÇ EwJ*eýä.óÝ<ô¨1ÿ#E`úà!£8h·ßLw ·‹»õéހ¶ás})fí b†W~Ƹ*]…Ç–•?bQP¨”hˆÒš&j{>ò q‹Z-ýíŸ KâSâõb`×}&>)dØ·ùû©ÓŸ“º ÖYnS&aŠBv/SC¿£Ï-ŽôNˆá߸“A±ñM±J[}#¹Ù‘LèþT·Í@ÞÅ“MÏWè$ÐE÷>‘â{^Õéü)Â:iËmðÔ0æ=Eü÷îY»ø_ÅéÜäö¤Ù¤Šì"cü­i…=;„ª–´è½º™HffÃfŠÕÅ`»ÈíB­¾õ À|v@FýØv—~Áü^9ìeS¥E-Môa£¢°}„QAy{?w»,?Ö²ÑåùîH’ŽÇY>b“&?šÂaV&ÛÎô0+ È‹MÿÅ4<-¥Cµ 5ìÔg^]—$i¬IÖšâjH=Ô²f±¯*BŒ.*D¤°YT¤š“š–Ô}íaԪõ{_ÇhkFÇ\*Ÿ¥?]§qÒ$Îo§5ƒí°)I@ž¾° ß½§è (9ïÞ×5S‰±7éB¾¹}{PUæ‹}%kƒóß3-j·}ö<óèöŒfÙ}q­û`óUcJÛâ:ÔŠ"î<Å+篯ã°ï ^lÓX xÓ°PTℤR+òŠú;¾ŽŠÁß/üäšs3ÙýRz¹/Ná·uÍË_Nh¡mJ0oÐ?oæ–fŒÞ§tùv¡¬+«s͎ۢÇU2Í!!¬UÞž±K—bL}¹ Fº5°=Ø[X¼f®eu;Þ_ŸG¾'Ïø{bÀzQ´†}@Ç—k \ùw´ö™|T0Žàe§Ãy¨PO#&ÓCÚ¸*½R²ÔéFiʶrzÖˆö~é‘kíÇÔNu¼zw€ËOηèÆ|ÿ+4±uºá£b&ɲf²Èžaò©o¼ÞîóÒ°¡Ár°óQŸGïñS ¿ÎØV¿fNíËŸÇâ…Y·ûØŠI@¡Ÿ¨`ëÙ#Ûˆû-ƒþÆ}ðÒÏÎ~ªùøžE?€õ)‘áצÿªrOsS&¥ëèqKkîcÒ¼Z;?_@È®(û3R‡šw&¥J®NßüPíLGÏ¥A_$ãÅë ñ¿ù^O/¥)g¸„ éBþ¦ 4hõïòËÅgB÷cÍ#èØGŸd*h[×·;Efñ¿î†ƒ¯•C}c d½ `{»}«’ßèÉ ¼ÏJZGN†•,ìãóA]xzæ¢8?'ê³âóòŸŸ:£#èWƒåq‹•fÈ×RJFë6yì-sݶÍùUäØl~{Ðj4ôÈCŠá§O·ÑX?íʳÒûÒSx5î»RIx×èâ¦Í­éþ÷5/8§}/°ySލÖ>¿¸­&).x?-„Hôµ ‡ÕáIñãã²áзM¿]¤jgÛOo Óé<õ{YpÃm-‰(å©tè`æÀS¶gÇ·b\«îZw[›È=#ßÚ‘¼¯«V>_I~{ínP91t¨ÊouO¶Ò.(›í3Í ý\±¤ÌñÙ ~ijzÓLv4Ç`*dzE`ÿÀ6jð›T·¹ïMñT[[Û=>i“[øðÔ ¿ ^Û±H÷j ;¹=„E,ö"æ{wîi&¡Q¨Tx‘§³eÕ}©ß—ûB_—Z{Ú89±çjþ 6oû?TÅu;G]û 9*NHÿ*m2}È©íÝÔT|´†&{›Âö7ëµ;“5¸žýƒtz^–k*ZÉ“±oñÁˆó!“˳ªÙö?º¼Iç*. ŽÕum,px>³XQQ~G£Û^¿ƒb¬6jâútÿsö YÖqzwoÒ3ãè%®Ë 2)=Úu¶“IóÔÇ÷¾Ô‡bänׂCÙ4kq6“×;þ)]cƒÏ}ù©÷x¨âYŸˆ;ÇŸ)Çé…¼Ga{'Ë›=’v8ç wÿשQ"¸~tž 㻢°ášBð% ¤F²CÙ”Å]M¯×Žèvãhéø§»œ›fµÓæ–Ö{Ô 4Dö'±b§îÉL<å-¸þ3@ “aç­B!Sk¦.»8ÊpþjþEXsxM/©Ù¹¦MJ–J‡v°ò®L?•%­Y›øhÚ?¬Õ”?˜¯ðþ*ã#ÕÖÆ±’B„Þ{Ùqe>S‰ïp5 ;<Îï„ÐE@G\ôÛÏãìb\’³l–MiI=Õ>‹‚k!w4Ü!ìm¯%ÏÍF@7ÖÕ²FOÉÅ}˜þ*>8¡šTWš‹6~ª(2S|³ÌT|POM(z¡üÖ³ØGa5Ý6Ú„w!C®ß°&¢KÛy¤`=ûîŠÁõH+½EœÙê—Íê Ìߦõ»ëC$7#Ûöv”OÐvŽß™Š² ÅíŠfåwÛ’£¹œj þ?´f˜øñµµfAø¥µ)Ú•>è \¡Å1®Ð¸íÆS…¬ÄûW‹ö×N"”Þ¥^cž¾"±eèTßn›¸T\²DiŠa?ì #}jGJ-P!¨ñCÂÍmÓgê´¥Ùaôk ‡™M’SÒ77­úQ÷«›•*Dž´Ë6§°Z¢…|N}ª5ˆA;»Åk)Fb=ë1oŸ¥Z¢úsH™±dšC›¹üŽ( –yÞ¡P ?S‹üSÌY*tgR²'§•;†œtžÇº„'7©Ð‚;¼ènÖè¶^Ñ:sPjûT‰B1èZK|wK\boÓº P¨âƒPñÍGš ¨Ô)ê "¢GE`ÿÃ%”i¤õ¤LÍûÆTy5ì2b³hçÃÚd^aÆþtŠU»`ïhëû¡,’&Í)"y4¤ãä?ÚD­®?_ñV*’‚±P¬—6ëb°‰êR4š>”0úú×ù8ú:Ï3L{5‰ãž(-–³}Éø}hg²{.Ü8ÿ{5LeW‰÷✌ý”qbÀç:c­)sRi~¡àF7ÍsŒ4îJ+ë¥\ fÙ(Tƽ%:ùÒ U¿—dö³^Ýô(Vzc¯‹ö _­@žäTÀ(µ—d_ÎÍN+_Z7ÍÙñZÏ?Š>U¡Kã­]:R#ãzlz|P3<ü¿^LPEdTj5¢°v–†~¡ïM?ç ÏÐ[SOç&ß¾½ææJ÷ˆÉ)jTJFI½üéeÃþsVNÌ®•¸^Ø_C¸*!“39=?Ú"1bÝÏN~¹6>Ù&Ôh5!bjTËïHÜ£h‡_óý¦Y¨¨Óácè/釠RZ<÷ií'>õ ×c7¨·dv|ÎÆ]ü¿´äYNŸcy\ìІ·Ïb½ø«u “¶¿Ú@M –Ù.ý•Ï5u6ó¡TÛEºÔ_€þi—,t¿­`>¢w¬R:ÐÛ»S ±\ ÙÞ»ö8Ø@fî*a¯ï… ZP#²B´VÄš0GbÅB¦€ÐÏqƒô%>{P¼_/x̵$5j .±=<¨Éb؜Ըíz-Ƈ‡ž½ó½Vú¡ðPôtÜëVPÏüuÚXòÏ«Y¦‹­š”ÇÂ.~;&§_Dýt0ù±Eèã3Í÷|;¿#ñZÇWn~:ýªS3ˆ˜£Ó?bQÇeþ)%x;JŽÚ‡‡—iƒ—¼L7¥@Íëeë¥ ’”$YM%Æs:j${¶ Dy¯h™Uhgÿ*º+tO`…%õ VbÞòÇsA*½JV=ü-SÌxM¯Ã¼Ú¥;©…H}ƒÕŸÅ^¯W«ÕêôM^¯W«ÕéƒHHþ¶Å7Ø/›ä:Ä¢ï:ëCXuÔèÓâÈy>=*â»ZüÔø®Pm·µ©NgӲ׬|ÇÅ^‚=й³«‰üÓ¥uÍs:iІ›ß7ÏPâYêòýyÁŠÏ—í kÙŸ¨W°4‰×zFÎÞ[ÖøiÀ¹8uг³Ÿ÷¹Èþ|}´}“—«;"eŠˆ£$ç{mL™'8¼PJ N»Tâ{°6¤冕ÈüOùC”¶=‰çž¾}½€ÈI¬9úiÔžv SØ|5®»M zÓ‚æ³Óâ´¹éIcJ_ŽÎ’öšÊਠ’PÖûœPѲJàÍfßÕ+CóMÀc±.) /`Â÷Ú¼\Q›øéJJkŸ¡â1vh¬ÙųZ=ÿϧ?ééÑ 4s½ôÅ7žbý?T6Y Ÿyî[Ý×ðݯ(#úô­?þûç×qõÏ×ëÒ–*{ ~øÅAEuã­0´“ÉŠô6ëÚDuTxéoJ·>1¥xg{P˜iÛbùõZŸ@)½¯Šãcs§ê°µ×é§~>{4*?@,vOÙ€†µƒáײõç±Bi‹eXMY‰ŸoÝNÈK• &¾µlzIŸ;1)­(ì‹"¥9¡n£†´ùéÛ›ÙöTîÅÏiP_]j{YŸ­¢°}†´ù}(ÓG`ȵIÿuÜ®ó¯vdV!€ú'ØUhcäÁBŒF sø©ø¿UÅðéC%žÑLpѶÛ|ú'ÌWBŽ3ã¥@{?å:ã?Ïz‚î\ý\˱~WLó íèј)ðìpTT•_­1Lˆk1ÁΔFY½©.µ“¨¨ô$Z1éRf§uŠeêVR­=÷ïN7SÉsu©îÄY>{3{›”LdìÙ:;,ÞŽhn;qAD”¡Qú:+Ý4Ç:5…Güs…·¡»Ïç±–‰!¨M‹q0Ûð\=œ˜úGv>§çúÀ&ÎÄŒTj5ú²‹õ´‘Ùj߸tjÒ/2¬!%¨÷ ‰rq8©Eû­3em¢‘+%»Î×gÓ¦ï½6[ìDªÏ³Fa¯™MeOKÑ¡jJùÖ%U›ˆ¡¼ži ³Û³_˜iZ ¶æ¬Ýç@$¨">O4f& vªjjw{&žøHj’öy¤ Þ55ò¡ØÑØú+t.(O? Ò’ÿR!&´¸‘¢Cò÷,†ÈüÞ ÷âÅÂ¥«óѨí_ǃ¯b§­h¬©{=mÚ#ë[WM[SV_ùµl¬y~ûºÇ#Ó_nã2ÒµDõMk"^Ÿ4}iC‹Ó2çþ669¦lc+²=iÉ—Áûµ7/¯ðìS5¼4¡€‹Îgðvƒ7ò‰ûÚƒd›$H™·=±ù8¨ŸÕÂ&Ÿ§íSYÅû/}ð"¿§±Uï¥H-1ÿ e ; ý”€Õ£I>â÷ uç“§pX‹¼ùùæ9z‘°…Š'y'³E`¤îE¸ÏÍJÉg­µ^•…}mDä#¶Þµi«^ËKÿÍÒ%CŽG¸ÄœŸçlv™é>húÂ|ÇüHŸ/£ >~ÄüÇ­¡îÅy }J=‚m~µ.>­ªC"D¯àµ(vÜ:#å6w;D•ã<÷•3L†¶ÿ… ƒjΠýeŠ 1yñµ;H-ƒB˜¯‹ôâŃ~æa4Ñ”<#÷Ú$J’Ž”ØFY¾›Ö'äúçž´=àšN‹öB†kE`¨ìfÏÖ„em·bçØTµ©M*%ñÓö£[“ËÊšK¨Éj;yÏüØ%h›v|i(QæÝ˜¬ãÉþoÞ¨÷öV™þ¨ò­ëÿ AHÞW¿rï·ap¼x)O 1[«±ŸåÈ™›yæ¡ ŒûQ’\5<³YhÔÕÕµÊ^®Ñ>¼·¹iÖi]vZ ÿÂCP¨TOª±Cðáš³ ÃxÙÒ;H¦Ç» øçž¾~µ1d÷8{0Ó«Õ_^ÈQºù>¹Í¡‹u‰¬Ôj=º” ­áéŠ-úP˜ïfV´»ùR“±°Žgj]ÿPœóÙͼÿç(òGr¢BÚh|mR‰eÁñ/¯Ð²ª¨™x·ü;Š“ù_Ú¯˜éâÕÌûSºûT+7çŸ>gbŒêy?¦™Â_3óJ–Ù$ÙôtflÕrü¾Ôö`Ð0xß=« o…xÑâÔc L¸Í_éø¬Šô-þûýº½é©ÿ–`´Qѵ¾ Ç“óô––è¦sHù‡ž>+Í¿ -%Ê2u?¿\I$ôÍÑãÆô!8Œziš Íyí Ì|Çj³ÞœP“õ\ ¬“QŒ/¯dÒÇü €žµfÀØ¿ÍcBwV+œ¨ý‹¾X÷ú¾íÿFƒžñrSQ:PaR eå ~wò<é…š³Çnj×›ntm¿„~(j·L,³í¾k LÔ‘/gìßjdËQîDíüöÀ jwTZ¥Rí‘( eØ1Ÿøjm…4—;lÛÏìTPÜZMÊ'ÐpM);'Nä‹Ä[~êMãoVšÁMíÙ(vúQ#3Æ{ HÅcQT±#¹`5üv#Š•§PÊ@ì•ßó¹v)¿eåÂt?¿P 2Ç÷Ôb”ýéÌÖ¦ßÇq †ùëN¯E=;oõœwzMC¢lÚŽ÷QüSã^z‹aòÔ;ÂÅc<Öí3üBA»ÝŠÅÄúÐg·äà¬*mïV÷xÓÓìá\3×G ãéKs1V¹eY·™Ë Ð’Éú£˜ï謩6¨qP¡{äa¬-MKÛ²5 aš…w¥­^gPh’3ôÓÿ}Bƒ«ñEËý½ |RrÞˆiüÑž7ÿhœÐÂñWâO¢ €¢|Vöÿk=;CDÅ,ôµéfÔŠ›4MÎÛÔ†ö“M/ú¤cûMý’”o•LZz^„ÅܺÖ‡3|®ý’‘¬?¿ŽìI¦w^²§RèOr¢lµ4Kþ0™iA—Cù©ý+Ù—Úä´kŸ* >”:{'­épçôV.“®Þ}Ó·E`î³ïéuèªÚezCzPR’‡¡…ªQ~é’*,vÝÕÞkø&V½5–._j_=Úôñȃ\gÞ™ÉZã¬ãâJ<“óéšžÒ%°¹ÒôfKã!äüµÏìP¶36þÿÇösåüv¢¥ðì“@ì=={}§çº“jŸìÃn¨±ÝÅQøÝ_«ÜËé,‘±ôŸŠ­¦¦¸ø}–¶.ñkK@lúҕŪú\ÍõßÏ¥ Fj'дV¨–£P¨ÐNðGÔŒÅ"—ÿ{mnÿŠBw)'5ÔÊyÓ¡K=ÄRVÒy,þšÍ¼°øéÙ,W ÷¸&îÆ|ö¬çËO÷΂?äÀŠžÙÛˆ"hÌ”f[Q™/@ëOvœ8çNSéZ»áê׈Á·yÈÃíA5Pó…ø(õÓóÜËè€Ê$¹&ù'7™L\£{NIÃò¤‰:/qS'/ê¤ùTѲ÷Êw&hf¸ðýÔLb~Š_¢è¬p:V¤¦}ÆQöK(Gç½çÉô©§˜e“›TTvÅ%DåŽ7žèóÓÞ¥j²]ÎoSSK¥SwþdkŸžâQ¯\•W•ÒÛU‚z$î:õ)§NË}«øóçIã%%E ÒTTPTTTTTMEGh”•-äe¦°wg½5=“ôŸ#Þjý¼8û¹ö„zÿ)“Îÿã-J"ç¼Ô$Cܦ+]Öߺò硯VžÜ\]«h’˜—ÒæAõ:RÜv|?C3Sô*õâ­8Næ_jv(f¡7¬t>ËE`¥ŠRâf¹ë·•qámÞûØMè?°ñóF`îs9àý¿ó[ uëëÝqˆ¦Ü-î±¾ÓÆ)ÂqS`¨‘ÿfù¸8Üývåö§i]öp>'é^xú+3Ñÿ‰X«äõ¡ˆ‰µµ‹OŸ} º`cExކĴAÓËↄ8gµÂfâ<í/™æôŠ\¿ó­”‘Û‘KðürTµ‹N÷Z …ýª“ªý÷·/µ;`N²ÑX(*wÿÅ:ãê% MMMMMM-[÷{¯Š¿U€·¹$Cr­Û-·tf3Cy›bc~/Jåò ýîÏbXWœÿ¶x€ŠžZ1éSJ¦¦¦¦“Ðk7ÿ¦j%ÿ"—ÕJ\lþ¨Œ'RåMMMMMMMMMM MM.òÿ{¡õ´Ö*ü¯6¨“_°w +ï£(ìGÀ¾” d½úZoåWp›þß"d×òka|>)­ V±½ýéF|-û¨)Á9D-§ôþˆàñŸú0K~êaY QáÿrKôå©MÊúöe\O¥.EhºãöÕîöíÞó£‘ÉVVy~J†RìT4‹¶újìjÐWWë ÷tÖüWJ]òÿb±¤Ò²wì4<»p©>Ù¢2E„÷ø¢X zõŽa©ðOô#Ö³n=ré¯ÍÀ9iIøÏê™K=qÖ·Ëø8ÿ¤YSvh¤0¤eA÷‘‰+B:hú5u<œyuŸÁQHÈ úe3$Ú§¾%•|iJ¥™_¶–ç@­ÿô7yü}pдáÛ¹¢°â®LÝå§ïΚÂZÚCi_A¨Mëiˆòãÿƒ¿ÔKãJ 1,!޽þy{kíF`Óì>Gû©vS\ÏnŠÁÿ‰?‡óÏ¿ch ê/ùú "24¦ëÏü (-B¡ÿb·ùÿ-ö£³QÞ>tÝ“»`s?Njjj{RmNÿ”fc±&ŠÁÿ‰`'d‘kk†ú÷‚æyѵôk(}…\€w×þjvHQט;{õÿ˜4"sz>B¯Õ&ÂØãš<‚RšY~÷┟QiA©ÓŒö­<éJoÙ£–~+E`ÿÄß\]ç§î²‰Ö˯rvi­mV:Á¿?ñ#JóGþ¨@;ʬՕñ~h)Ýj3+s&¡´ê}Œ×R>‡5ê“Ùš+þ%’Æœ½6â‰:‰ÃŽìÀ‘ç¥] XñD›†§OŠP…µP˜Ï'Ëì'}{LÝüR–;tVÈ|+¨?ðª$&•Ò}|¯F. ØCO޽ܒ¦V£GüU¼©{ê\„‰9¦dI³%ÍëÉâ&¿Xr­àûSSQE0îh¬§þ§æ¢,R‰/ïÔÎý´ay ¾¢!õí, ;Ú—Z™ã¡ÿºJøÛ³mv€wtVüFjþ­^ªïÏ”ÄÐc z<º½†mÄÜÞŠ$ŸøˆÔ >‚4ÄGŸOüƒ‚4 A隊$rÑ@ïh¬Üè «ÅE¿th c~À©Áô Å“R*’—¢ ;ÙÖ=ÓÓ^iÙ+ÜD['¯\Q<_Û†ŸˆœnÏq’ƒwÂ( þBN´}ëÍ`îçE :´ìQº¬G¶L¥3†”šU…5r 9§rW5@àZžÇYŠÆ¨“ØwjBˆ¨Ù]» ̽¾Š´¤9¬ ù)ð®j)W)°«ÝFûòYQ9Èùs>TÍÜ ëiЫéF Øí0þ(ÊÀwP*5 Dègÿ(TÐÐÎïüZÅDÁÝÎLÑΙáYö|ݳ`vzkbŒ+Ã4ŠýÀ¨Pv »Xö9+W`k:ÃÝïEYêRM>JÐÕÃó؃ãZ«=jûÀêàñé@Ñ ­ºŸ%1I0±Ø†hõ×½£A¨Ñ#AübrF=ú}ƒº%¬]ªÉM•g(OaS,”ÆVº[ŠI…pÓ\´f†;K-P"³ªjvš£Qì jÅV*2´3‚“°ÏqKyCDVr¿ \jßQ#ìBÎæ?#ÖŒ%Üä!î´"d½GË™ +—Š€r´M8^qRi£¯XÄ÷lë© þ«(…É`q5Ù:óÏÿ„ÇE`ÿ¨eSJ~ÓzÇH§Ä9Z]Ù—âµÅçÞ:mPÀIµ/¸º'8¡›Ÿi’Å *ñ€aö¨qÿ‚†{ú+tnj4š BÜÔ[Pš³&ô0¥G­p©Úu©H¢&5¥ ö0b£ø¨þ)$ñQ¦ÊJKñVT  šPÒ PQ%GVLÑÙG@)#ïÝJTG´Ï‹ïF³¥,¼¯Y”aÚ™X yVkÓzüt²+ZÆ Í¼þ³?+túÚ+u˜M+"1HÊÚ¡½´/Z¿5¥â)ÑKÈ©Åéò• `yR䶦+<ë9iOŽj4’&”Z#Y«‚5šdóI)"ŠÅ1 êEí ñ4wÍ-æÒͧ֙§>qMwâš\Å%ÜT%™«–fœO?z”yÏü-¸Ç6ûÄ—=7£Ãÿ‡è¬F;"óä~-ø~ŠÁÿ©B“gñÿÃôVü´çeãKwƒ¯ÿÑXªò"¬^…X£å¡.—ÿ’é£eGþŒÊŠB²‘\•b%©ìÀ-`­;µžT¦WïmŠÕ®¦·¿ßûÙDRÚ½hZ×?µTÞ•™=íƒþ˜e؃ïïJ^je¥pó¥lh¢õ=ˆš@SÙ™îMu‘XµZiAILZ¸ëŒ«ßŸ¦˜^ÑŠ|D¿à,vŽžïã°b²ÙîŸûdq•YÜëÞÑX>ügˆBŸD¨¨éT¦ÔÄ7éEMmÎ(ÝÚe`¥¬œâŸ³Ò"ƒh¡ŠÂ*Î* ™fƒÕR°·¬Ë4˜U¸•f-}©´zR)Þ¯"ÑaAMu céNÏuXɇ4Í*^Šj¥%OvõãïÜÊ:3Yã­g¿þ÷³Y‹õ¯Í»ú+Û°Í!‘ëZ"¹}« ŸjÖ\+,ZvU.™f|Š7ý©z×"µšçW:¹žµÊúÒåC.ËõVšçÑE¨Q¦iš.a­Ïu °ó’“/­À×éIhÔwBk©lÙJ®ª¹ šÎŠG_j;Ub «úµ?ýàÈ&ˆß5`HúZ+×½Ya\ZŽÉðÑz¹×5Ñõгôÿ”±)Ee/V€)ZûQ¶¥è®cÖµpÆzV$VPO¸U$M1ÒÒú+>´³ÿ—½7ŸjÔ :•jm­5Fõ¨NëRíQü¬ÖWÓþEÓÑX>¼ùÊ‘Bñæ×'Çî†X¬"Ð(̦–æbÕd/K”èøOJfÓKái4öQ2|P˜vgÒµÉéz#$ùÛé,VtV>ÕÆ®5M‡ÑØYò½ ¿£[ï³LÀö7†Õ”ü)N{üÔy÷«@ëì×ÉDH{T‚p«a^Š"Úµ~_ñ²ò¬µ[åGE`ú‰4foÛµÙ3Ù³„÷¥ÓÙIjSÅëXççþth­Å/jáR1Úi |—­oIX`wC6¯—w-w>Ô¼¯×pbÖ¡Aè÷iÙ– Ñš÷V_ù«ƒ¶(/æ‡:¼»×Å]‘zv£`«ßðKžo§¥cQôôV®±BÕêUÙ+V4rÒõì\ UáJ#ÎÿŸkXž´ºû»':“™ì¦+üÖ·U€ó¦¶—Z8švíÊ|x"µJÆŽJ OzƆhõ£TúS&þ(-Z W­s¨ˆKOÿ\+4FÕu>¦ŠÁö:ú‡›:Ñ(SGý¼X…Zåj‹G¥hŠÖ'¥?î¤méJëJj¥õW3Ö¹µÈõ®w­eWè¿üb­tŠ'ýP?CE«0+®SÖ¹ÏZR¡õ ÝVUS?ùGÿ…ªµêB·=ÔxÔ +_jËqY;Ò(=_ZN‚‡­ jÔ… ú£À×2¹ÕÌ쑦 iÙj‡Íx]¯ ׈¨mñú¨mñú¨l©?ñÿÕY>ÐÿÊ?üGUdûC²*>Ò*>Î>Ö>Ö>Ò>Ö*>Ò>Ö>Ö*>Ò*>ËUdûC²j~Ò~Ò~Ö~Ö~Ò~Ö~Ö~Ö~Ú~Òj~ËU=B¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+Џ«‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸«‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+Џ+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸«‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+Џ+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+Џ+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸«‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+Џ+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸«‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸«‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+Џ+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¸+‚¤)ÿÚ?!ZšššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššžÃÿÞÿz?øpʹZÍ!eì ±FÒV´”#ÚhF´#O‰ Ô®q®¥¯µ“ÿ‡Ÿû¹ß†µ*Â^Q©0ûVÛÒ”Ñö¬)z~ª¯Z™SÓ³Ò©¤±ÙŒ¬ëø®f€½ýkU½d5v'õQá>8š|M.‡c‹È•@ëIA•i>êà +¨òTȤQu#§¹û¡ÿÈŸûpý”ªþÕd_+þjÈ~iÛ)¯W/@µrý(M|”SVøÆJ¹¥­X‘_(j‰:Sh¡½çûÿ*;KšþñçD?÷i¸VÉ!mè]õSø¬8ž¿€üÖˆ•8 ÐzšBØ6™ú*>”çù¯ôÙ|­ñCË~OZû'þzh.;™Zµ3ûô­wñH ÅŠ+‹{Ô—W¡Ká}(™>•¢Õ;hh*¿ H¥Ì=;›Ò¬Iißh¿PýP(à{Rzž´¤SÍé[ZR{OÚ½í%ª0¦´UÅK°>ØJó¦uÙeY÷mD¿RD¤Ó CÏé5K‹B·™ÙÏŽ¹£ÚJžíg„Å£Æ+Z¾Z{´ýíãŠá˜ÿªå[TV¬”3Yiô‡5’ ~DvÂþiiBaQ¡‡Fõ&EéØ+*)¯•çjM¾+4TzU©ŠF(ܵO„~=*$Czþåi“\êlü'¶ñ/)«£¥hÚ…ÛÔ©"={¯Ú½Ù[ñHÆ>ÞÊ•-­ÎÕ/üÔúÛ‚„Üùc¬Vó`¬§¬üb…€œ“ÿLÿÇÄ&ŒPu¥.ƒŠ{õüPwVô޵dLºz¿>´3ØI¨í½ŒsMÕ‚=ê,‡½<À¤¢2Þt¤ÉÅ^ O­·¥b_ÑØzy{T£ÍëRù¤ù¥‰Ot,iZ{ÕÝ+GÏëGˆ­W1Y1ú:¼iÑùšoPœ'ñDGÆŸºÎÿÒw?ªb×ÃÞÞÔ1Óöwî¥|׈tÇÜ$ѽbzS•Óõ©˜Þ<©ËO c’òýÔüA>‹ãRÐõüg¹1ÚÔCOr¦½KÛÒ€„yñjoÐÏêµw¥èÐDz?žÌxkéI+Јƒjý4¾ží gÒsXì|zý‚(h9ô­déQ¢õ~~+íQDèl㥩ëCjvEk¯Z7ÚúP_ÝXÚJÒ'«ø)·Ž,÷ÏÅgÖ¥†V]7®e'w¥xcý§ñ&åp>•­/J[R¥–w«àµgMöÿi”1äÏpb—.°¢Þ´é9t#î’pP§üB!E\³ØŠ%ê|@nöÕ5zå®ZE¤ùPÄ3K!@$¢1K/­b¯óéR±¥ èÕàˆÖ‚MøíC uìO¢“ ö°oO7)…改ú©b‡ „Äßê*Ë΄mIcö @ŸCša£¶#­Y<])%ÕÿMœZvn}žÔ3NÖÏãè.ÃAùýTvB¶Þ9§ÝŸ5éDºwÏõQ‹ßOJÏHúÓðžC>”¨\»§Ú=¥™N…ºZ§¾x}l‘š9ÔÕËÎÔéE»‚Z&bziH «âªS3[TúÖPzWÒ¢kéà¥"©¿‘/½uýjMågõI`µ Åì}=2­Mo=+‘ëY:rhbœ}«¡Rz46²‡ÐX»EfžÐŽÄš]oözØ52 ÷cJõvåaßßlÅ+æ{ {h>!¥-½`ßÒ¸ßJS4™Uº¯…þ{'³Inh\]S>™!;6÷©ÿ ëpç®ÍJGî¼w¨Ò}hÖÄSm¯Ò“ìæ›v!Q¥ÔûõÔCŸ]¨¨…iKçIÉõMtŸŽ»w‰cnÐ:„Æ<5àKA‘YAé@c±…©¬o^ZwO´{æa¬Í=èhõjöDg³@¾úý”eJfb+‘­zµÂ¤T#=iHñz·xX)fNòÙ_šÅãè ¸ŠÉný»XŽJØzR(Mœ5.ev {ÖÉéY—ñK4ÎJ·ôdfµºQ¼­XQêQ1ð©°ž<¨¢Í$Ph¡4iyµû¥-»õ³\~å)ÒUü)Cʬ¢=ë_>•R}šŒaë?Št»’1ZP:׆U¢øõ¦d*|Ç&t4ÿ)0ÌíXvݦ9Í&Ô+åôr5 ]4¤;¶çë´¸Vø¡Æ±o»žÅP½f’ó½C ¸m\‡­æ”ÑEŽxüÖq>Ô4{¯Y tµs)ÿu9éÝÌ”¢ñØÉ}ß`Mc3Hµ*ì¥ izVÎG‚¤²j"¢Y\/B°¶éú¢ä>ߺ ‡¤\²œß÷L )ÌQÀõjT~æNgèŸhý«ßp5(Þ´y±Qæ\Ð:+ÀZeÇ»PïCó½Ñ'bÅ´®Ò²ƒãâ°’x欯3XüþhÌ7­*:TâãG±#¹‰h”Å¥OÐ{ÞÑû(Í”mvà_~ÊòñšrzXÅ,ÝzèV×Ò´…¥;-,¼©É»â²ïjeNä©.Þ=TŽ^ƭܺô÷ È äw5Ê8¨KÙïê@óWëáæüRXËÆçùõ*.sAéW&¿š‰6hÈ)vËöi/Ž;g&µqâ8_.Ì#ܾƒ^$ù¥/qÃØVûÞÆÕ-ïÚh¤Å5ÏÓ5™õ~¨pìŸuþh#´;©9¦`°¹qP`Ƶ‹Û±v~¿jZ'“þ}íµ~×7âºìó¨ŒÙ;~ШI‡ŸûE@å¨\¢±ŠËZ…–}©¥„¬ÄÒ(ªmçǯsØ?ØîÇr*;±ÝŠŽäTv¦i/äVFíKî­jzÒ6Q~Æ‹UiQ=S>iZ¹ó¥¸¨Ë( ”cYô!?îž2÷"£°Œù¢Ôô÷ÏZ2Gà Æ§:ùU›O€¦“ò¢µ?eÈÓYZÜÓ±±«6§”ïN[ØEý*ÞÑÙÈô*{j2ªôåÛ™?uŠJc›éSSSÙ55555555=“SSSÛ555=“SSSSSSSSGdÔÔöMMMOdÔÔÔöÍMM55555555555555=“SSSØÔÔÔöÍMOlÔÔÔöMMhGšüº$Ç­¯˜FßehŠ{îÞæ6åËè=ïhÔTTQQQQQz ŽÖŸ³£0*/€µ‚ƒ ‰†V™WGÖ¢]=Žv$ë –{AŽÆiÚéÿOt&³ˆ÷£Æ~¨–µë\q½+ ýŠ åNÚ ‰š¹ÓoéXkV£õ¤˜¢)¡«ø½x?u:+V6:F íI¥ÐÌñ´~Õû0ÊjR«ÒŸ£¬BˆlúBËC=ßhüQÿ85£0}h*Á¡ëZ¢™m+k|v×Ò‡F¼,Ï—Óë 6îH’Œøø>à¤ñQ-"Ç?AšW¸^ ûeo³Ùå7îM"oŠ[6uýMb×ß_ø©•¼ù½JÄ=4ë­qj|ÉŠC‹Çî›e<Ý 6ÈôüºTá ­¾hGOQ¥x±öÚ½è:K.5‹ÄÔ𽔺Á;¡KŠÅ'ج$9¥¥Í`•&¡Otœ±Ü&šUhoWÝá2ÔÜØ*øìaLZ„ÉYÛW-;bQ§;Í4îóQŸ¦B‡Ú¤å„}â…ý£2¡œ}7¦šE[KV•F`ûÈâ™fóî2¡Ü2Jž}§"ÂôÔYâ±M/x¢²k¼HyVâ?ãG x!¤­Ë§¶º~k›Ž"ËMç^S4|“Ëô¢RÙ^¿ª3–_,^ôK§‹(È;ÏØ?j÷ƒ<éSÃôv†´¶kH~ÊÚÏ-(ò½_úM^»WÁŽtó«lé~ò~¢ågÖ‘z…¾iº¦˜T¸b¶sÖ‘—¾§5ŸñjcÄú´ÓÓŽ”žÏ­(¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨Z%­ ý4¢¢¢¢¢”Y%B…&ÄÐ e§üˆÖŠ´ÿ¿ðtîtJ0TÔÖ{Ò¶Û*&*ß—4‚ TÇ¿ò¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦†¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦–¦¦¦¦¦¦¦¦¦¦¦¦¦¦Qê]ô?uÓô®¥tý([S··Y%¯½ZW’ï«©žÖ¶o¥c®îø·cŽ(cêÿ¬ï„ÓÖ;2¡WºUã?_šYúsD^õðñú¬}€.*U* íø1\ç­ú 9 î‰JJöŒU–éöÿÓBó„E¼¢™‚4šÝö¤ ¨¨¤î–±W¤#Í@IÛ`Ú¬Myã±bŠj¶g­@Š@î?åŸhýk]¿Ú±¶8úpïÑ÷|èÌ—¹Ú¶±õ3ºd…C)‡CÐ}tŠjqýÁëûš “ë´Qù*àqº‹ÿsÖB{ k.ù­f:ÐÎ;ŽÊ+œ}$RQÀÿŒ$͵\/„Õuõ¦àÏ¥MOt;Ò¡èRfY® ›Ójð¡AZ*õc’¢aÖb§fª«~íײ**;ʧùÞ£‹æIEYG°Ô¤íïIßœ¯½CÍœÔqDxýUøcøŠäþU¤TÐ$Õéö±Å)yÐôzÔp‘½!±çznï"ÅbW­=Š\õ­ ;2ÿ-kN¿DûG镹 5œÓÄvi¿…ò¦Ôî VZ} “µ ½‹âOš{ú§¾+ñM?ÃíÚðæ®7i¥tø£Åª{ )õ;+×¾WÖŽê¬ýéØÞ„v*…87£m˜ì2 6ÅcËZšš±?ÇZBúTûŒ8¥œö:‚¯G©òìº8u?=‚J Šà ±º¸VÙñëöƒÌjmK9ï±*S·%¼»À÷ú.O´j$Ë]ŠN?¹Q/ϯêC¯Ð„’óØš7Üüñ@7ÐÌkò}>-@¯JrÈú0&†oÝ1Ø!"ôÛÏâœb]_ÕBE'Gæ§ï@¹G~Êa³â†ÿµ;éõZà kZ÷i ³ïÎÁ@ŠÌ ­½*Ü í 9¦¥Ï‘lÖkÀoV^/H¡"(&ŽÁ"вxŽÍ OjJŒ^¯‹¼X+„¤ÑO= ­Z¡ñWaåcÒ°þ(gé&ô5§’‰Æf›&zTú·`4íÅ=%'¨òé©BÞ¶ô3Þ;¦e©’[Š>‡÷VŸ¨Ð‰K7èÛ~äì?‡éÍæÑ½K:sQÏ£ç?ì­øúêiÿ‰#/&hmþOá£Ú}ú›?Fúb§½1‡ñ\¥r TüQ™W#¹•ÎÚѺœžzzÔÒèL±¯Nòn,ý?GÙbŸ³Ò¤÷.} ÔÃfzý‰ÿ‹zÇJ~mj~ŠñÅxâ¼q^8¯WŽ+ÇãŠñÅxâ¼q^8¯WŽ+ÇãŠñÅxâŽG&ÑLEÝ(9OžÞ‹½¥AÐúBnS+"—ÇŒÔl¡ öPjf^™ÿ{qyÞÅŸuIšÌûÂ"isZ·˜£ ÔÏjR⌞µ<ÉÊ,"Ç‹•? :Ì{P—'0ÛÞôeñ·ï~•„<­X:R=ÚmÛ„MfHîK=ÁÌ"ðkÝ„â|ÿmß“·ƒìR!}ZE±¸/z]ýÿÞÇ1¯k ZÇ6ý÷ß ´¨«¯zÍåGæ­½ýiK«ëJÚ_^ùÜñƒj·Ÿ¢†.qú¥Ã>&+£ýGÝøÃ¯ÔB|oMáÄSŧµ@‘íCôÿu`=3HôgÊÅ\}T@z{EØ·ìkBm­C¦ kP£KåÅz #šSØ Š„ÐGuØø¯qÛyQÒ­²oû«Æg QêçBoÅ*<‘Ž®ôr—H÷¥LÇæ¥ÐýV%çÔüÔ ýÊ‘=‚qRÚ’;4¥ °§5pA…O¿kƒÎ¥òÿ‡â_é úÍ$vEKA¼m(µp5~!¡©´Ž`gzÄÂâ}éÉ–>{˜ŸZÊżMËTüœí±ÍXæô1!Ø‘1nݧ`*ÔÃ6ñIAšvOZf‡ÑEïô¡žÁ0ÖãÖ‘CØ,⊅¨0Ñ •=€%S¬IWË—oŠ,ÇñGËÎ)¬=T•©ôŸ´*¹ª"GØ ŠÁnQS½Îò ’²íЏ?ò'÷ö±‰£‡sþ‚Ux„öfŸ›Vjõv²ìÁØ)8Óè$Ò1Fô±WQ¼Ò„–éXßz—¢u­dëK7ÚœC¥DFÔ9¶¡žÐȠûL± e)w9ŸÅFøyÐÍ<šÏžeo=+,Ç}ï¶Ã•~?šEЇóôÂhïsíÙ54ÑÊz{Sw;H¡ØTÔýÁÿ’à#αoÞfklZ‘KÿAF«ì´9W é¥Aš{«_ì£Ê•KåÇl÷³Xc¹4ØR³E(¥%%ïíFKRgM;]:>”GeOnï ÅÔÖ5eˆÓ¥;%)4Í žûßµ\þ<-&_LäXåÓDF*{kB&¦¨PGÞŸù%Å`9⯳èи+.iÜ¿N;#ï”÷xV¯kÜ×9ë\ç¯bÈiS=âÿeKš7ëE*°¯­»¹¬ƒô& 6ͿʓPÆYÕ¥oK=˜E4 ÿ»  ƒµ@Å)k*µZ­M=Í{ E%•ÊŠŠ! G}ûB »Í-)(ÿƒ?òÜŸJ…ŒòYñçEîoºÉ—ÿ}ª6>”ŒBsè>Õ7†ÏبúßM0ìššŸødq­ýiÀÒŽ=>hÛ4öñ5&i:f’u}ZˆïTÈæ=õ¬ÚÛiÚ{°ÉØUG`Å­ RäÖŸícÉXÒä¤íþÒ¬¨¹)Ma­h„L¾Ø®6•µé,{T6¥"~ŒöŽæ Íæ‚;Š,ö µ*‡¾ý¡Q¡Úg®|ûÓÑoøGþf»ŒrRx'CV<ßßtÌx)NjfÅêtî§&‡Ô¬‚„²Ô-ªæ ÚUòžmÿD&Ô@Ê(.hx¿JÊØÛ÷AbV&v§œöÛkjí™ëI¾7¨˜¬6¢4w0bMc° ªÓÖ’ ¼4`9¨;H¾w¦#±• Á&Ÿí ‚>‚VØ÷ì\+=Ñ‘M'§q\3ñYµðšœºz÷^ä³ö]ÏËþPþPÛ±Ûš^¬®=U0-y:xÍDfÕ5555u}¨Ì*Jš"hö[ÛReݸjë·üɽe"h+Z-_°ÿi$)%7vMMM5=vMMj*)T/éoj****)ÔíSSSS^›Û›œ”é}:´¤ƒE»¦@šNôÈ”°æÕ*jj{%¹[z‹R1Ù k!»¯hɲ”àJdŽØçJŒ†qãҙŕ<Ô%¼xŠOïÑL¬G¿Y©QM¬iÉY<ÉQQQQQQQQQQQQQQAQGo__ù'þhEˆ×Oò†|ŒsÅs‡Ò$a×¾%ž}9éØéô€z*\RùqØ„rõö ¿Á^QbCR {§¿,7'¶D•-©„w½§`dÓ¾zö²ènUñ‡è‘ =úTúÔ%Þ³Í`;Žæýƒ—d5À5”wÝÍ•"C×4Æ7¼zyÕÎ/½_Ðù »~ÎÄŒ²¤T˾UaZ·†‚Ùºzýb¸×ü£ÿ8¤6›šT ¬ô¬¯W{ú)>+Ý}1—Ö§Þ{²Éki”Йk¯NBiÜ•§ê£2v½ùfOÏl2«^8ýP¸qÝöÝžHªSÁ¨½¶önòײÁ©ô^Í êÔP°£ðsÖˆ…Ö‡­upkÖ¹YeÃGÓš> :ÔÓ‘ñÖ¢ŽÅì3-ÊοY PÊ?óÄÎm:”ˆ¿¼× LŒêw¥ö3ôÂ/؆ýب¨¨¨¨ÿ€P‰h87¯WX;—Pf l­ñØ÷ÔâhB}JH·ÑöÝä›TÇŒiWÚ¼vf¹ª5ÍÛ Áù®jŒIS-î´ÀöUbÍC´ÔÂ}34jsP?µ[yÒ±FŸK±…õñâj3wÕÌãæ±5ŒËÝ5î¿òO×ššŸ¾Š ˜áØïR†}u*(BIÄûþUzýxšƒÍÿËû•oKé(’ •tÑPæëìáF2=4ëAqSSSSSSÿ_ŽËSGiÜw‰5|oãJ ;æe­?U]Mû×øÍZ(í§ùN@Žï¶¨¨¤í@¬-—Ç¿eÂŽÆÇ²eŸìŒ„ïZGVø¨ÐEJ…SSÝ’’}^AZ–“™vT©5®jIȶjkšCõ¨:šºb9©®Îã8+!oSàÅvö¨ ŸòOÖ©Ž­>x¡œ¹³Åéƒ×Ó°ã#ÈþéŠ /ôõö¡VƒÑ^ZúQ"Ö•–ÒaŽkUì©A341YG°ÈP´¼é{\¤‹4±WM½èÿŒ%rªWºîÉoW!îÛ.i9î2yû5y³)XñÚ1â¹¾ÕàÅx1Pí\Џ÷å¯'¡M/7d9úvíª ¾¢;ðÑ*1ÚO²‘ÞÀE0¥ €ËÕV¢¥ãÒŽ*4bÌ~+ cš5¸¨ã1C£S`­÷¥^ŽìÊ?`fZ'ü-ÜlmÞƒ¨g“¹Ì-R‚imŠ: jñuAZ¦„Lê#HHÞ¥¹SSSÿv„÷NðžOj‰475Å·ï1Ü6•|QÊ´~j%·Ð¹8ìkQ‰>‚Æk[ž•.^È>©T÷Gt"äÔ9ì-­«ÓkÐ ¢øëú¥2¦9šY£3K`e·ƒî öf~ÀrûõÖô½Î•ÁƒKutÝÄ«Oõ{Ë¿ê—&‹…Îo¡þ÷c·÷c`\æzµÒ3íj@vš.ÑÒ˜hv¥Q#©D‰/÷“SSÜǹ.Ī#Ú)òñ×bMÂÒœöÅGlÞ^÷Éú1Ô{+î“*(ü¨ã€õ¬â{O³ ïùE»ν¡Š ÆóV´i–øìdhï|aS?b!ìŽÈûCÿXìrµ¸ëBdî¡¿£óG>”5¢§¹4ÃXÓ°ì!ŽÉìoØuÏ×JX¿ÔH9î=öžÓÍï{´È£V4 Œw%¦ &j)rì@¸«5ÎqëC¼qRæZà¥Êš-¾;¾¦=òBöø9§E:–™¦à£¸áÏÓ;ýJ**>®]“SSö‡þÆGú¯ñ´Ø÷õ¥"Ç¿•9n¾3H¡^ߪ¶q_à5`ü«Wò×ì;/»onî/§`žÞûÒ@¬Væ+‚¸* íZºYM"ñà<èÏa¦æ–]{–”LB#èÚ<߯Ô©¨aS_7hÀ¦1C“Ÿ÷÷Jë[ਮHîMô€¥‹JЧgKTUM"šBª‘,xšÃaú jÉ©©ú…ÏÜûe&>¾/û©®¿ª˜È5¨I–’›X×¥>sKõâÿr“ÝÑxjx‰BÅNMxtï,Rßo¿ò˜¢ [¥Ú)2ô¤ŽïŸw¸£=~’‰ Tn5æ¯È4½èÔׯn4·ec±Ì>¤CStg¶}cjµ„ô£4ƼÓß½­ùüT.•ãCo° ~Ä#èûeEE-‰Ö£roH¶ï4À™-翽E=àšK:üOzj/?xTÒ‰âi)=wö½0‡“>”]›ÙŠAA{n›¾5ö¢tÚéÒ¸ }£÷N7œÅ7œùã± é—ùØBKm­A®oŸZŽäœP4V©ì8¨-§¥ëw[ŽvÇÑWa«Ž” ŽßOŽÕ+ƒFâ…½ÚˆîN´Oùõ’{ƒê (¶¾üþèZšô2–\ÿff¸¼E@o×ê¥o5=év&Õ?YInðÚ³ìu§ë˜ùï3hÌÂkÜk‚§ŸòŽí~iœ=«—ßʉ2ÄzÕ®ÍaÛÖ1¼|Å$bÑ-)I=„X»¯T*x½šÂû?qY¢¶ŒS¦kFÓ¼ßÁÒ IÞfd£ÈëP/ÏvÙöYóú°¨×=ÉaQY↦˜RJvSŸ±öáôcµ^¤W€â±Ø~ÞÛQ™jÊY{ÍNô ìI~´ÔÔýGºwþ©j"¥(«€¥Ï°SðR‚–CÄÕ“nÌ\E%8  §P¢Fæjjj~ê{&™Í”Ww3Óz͇ŸùÐl(±°í¥='éÒ ƒ5žDáçO~Ã%ïàÔꙸn§ûQÛ;©Ú² ±Ð±DÜGro<~茱XÐýß¾©¤#×±§“ÄPDö-¼”"–{b'µŠµúŠ):ÈéÅK¾+§íô÷Qß»ŠŽáÿÁ$ÐÃÍ&ÊX‹ ÇqL3?t  ûC‡bMx<©SŠ~ÃU•·>þ;- ˜™Ò ·SÀ#-æ‹*35•bì°u£KüvÈ]HÄûìW¬¾÷ìÐ+ø§rR#3ܥоéC.^‚,v(ÈŽ-LÃ=×­<å™ï±-)Ÿ­7ÒEMöm)áÚ¯´vGѱ¬;V{ˆ¨ïí­ŠVïOÝÍOpÿàÄ"„‰›GaŽŒ4f×»/Åt=ÊáÏz䙊qT›TS÷“Ù íNå©åozUqZ-šyÅ®óÒ’DoKΑܽªÏ7â¡ÜßnÆkSVZaì"âpÓÁ¤ãÃ×¥Afô7zÿk…ñzÈûf­Íü)yIv—Z;ƒ¦gÚŸå ýͽ‚>À—t†{”:GÔ‰ [z%ßšžÄ3üîaŠ‚‚jô7røƒìè³¥N;Ãÿ‡(gjZ¹Êf%PÀ»íÝÓ™ü~«2­ãÔr–#ozy·Ò²ûáµ&jj¯ eq­ Ín {n”z”¬ÛÙìâ}) õ¦3âÔ“Åʰ7pÎ&ó§x@ ú\ÍÇajm=ŸÅ9*~*%¦j;"£¹> Œ©~ËíLúPöÇïÓèü<ã‹Tè:Ö×I ™ŸÓÛ䟘Z‚`¥%«[ŠKäE­†¤ ÊîÛQ ïH|xÚ†ÅD‡_JšššŸ»²µ„GjÛ=³Øc1X {Ѱ¤ÛJöHb–f¥V…*-+(Š•èƒ%`ƒí@sMÊ^ÕÕ"xÖ‰‘V%ÐëCý¬QhÅy”%hÝHdTJÊ ìŸd"Yém?µsõã÷JyŒ”bJx¢P¿©CSKSÚ°¼ø+ytusO…½oGñ7œ^HGÅŠ•£ÁÜŠŠŠŠŠŠŠŽÈ¨¨¨¨ûSÿ†€ÔÖŒ†åEcØ¢ÓÚ‹ øÅ–µ+9çöÃMa¬¶Zµÿ***))Ø¥ed‡Ò§4>3IX¨ù~‰“’E0¨“ÙK±;R_³‚æ×zGç§dÔÓõ2¢δîJºë¥4Z}š“Ò›TTTv9b¼`jÑ ³ËW³ |z÷Ö/C?I§ìÒ{b£°ý|§ùÿZÅ­M—.zw”³Sã5(S(¹»¸@ì’°lgÖ‘KÜQÿjjjjëµ*‹Q’™7Š2ÓåF/¢i°™Þ¥Í"ÇgOfÉÙ¿ö’NÛô~Å®¼Öϱ–ô=aÒ Ñ®±ÅMM¤êœ´S©>QÅdö?Ú<ýkT‹DvHÆw¨“BKé­/Zç=i}ã~Ÿ¾Æ“‹š”_ï’{f§°ÿÝ—çÏZ˜ï6©ÔÖ¢Ò—¾üúÖr¾ã÷V‡j,²}R ‡üŸ8½LŠé£ ¿Ê‰O*•¡“ÉKQf²»€áY¼¾ÛAo>>È$RAéØàÒƒy}?½ F®ô#„LLÞ£·µ.˜JÏ7¦PÓ°¤ÇpP´€Æ3ø«‹lÆõáEñ]*+Ð÷ìÉ$Úšìй\(ìýTq½žÁC³OTЍDÿáj{‹zTÅÁîÕÀß|½°¤˜/¤Z’åõ§µE/´“þÈQ2P 1Y=‚{™ì§x:e $ÑþSZðRM{:ž-P&­ZYÿ’jÎ1ñØ1r´,økV7óR‹ÖY@ÈçJÀÞ’Æ£:» x)Ω[sӵȕ\MÕáµ=0š5áëYö‰ÒüÔÛ`Çb\TÔM¨[«î´ §Ž´ÓöGÿsù›Q3sÞ¡uëÜæ«Ekƒí·á,…4ü(,ÃÄÓ­žÑŽÒ}ÁW¸ÒMkÁ4§ö€AÚ@ØÕÙÿy«—ÕŠ½I³M°øìÍç¶**)ih#&½‘œí_%2Z1J{sÝ—kh×-ÓõÛì¿5i–•¤—ŠŽÈ¦pQïrŸ³º***?ãÜuASË÷Nà bj_ÍKù¤£%ì8Û¶é—Q®µ<â|zÒŠî}Ïöˆ6}¾´ÔýþQý)fŽëõžÉR¢…:Ô5 ÔöOp̨Xì·|Æþt’;Û§`ˆgç°z¿`-¨2☼=˜Â©nì"5ãž±QQQQQôÝÏü¨“Ó’Šë¥4ÿ“L;±õ 4aéC=–Æ~ÐÜTÖ®0gïy@ÿJ3&½à»öʈ¢M ŠŠO³ÙêÖ‹=(”xÞ¢ÿzÀ­À8 ¸úÄ?ŽÒô×ióÍ$Ñì\ZdÚ¬T¤T5f¢E/ØhŠ-µ*ÐPÔÔÔÔý#÷jÙÇü”Rw4s*d}|}9°šššŸ­2*aÚ™¦žâÁâ+K«F~«%n¿¼PäÏÖýÅŠÿŒ_½€M/¥-}h`ì8á㥠W?>@ :Ç‘H¥¡#*f8­˜:Tv2#»|¦üÒ¬÷®j1'^ÏÅ~ªÉÙ§TéÙ›v!¯m<ëW“§‹ýcÿ†Kž¿=À"ӱءZè¢ùú3©ý&¿ü8Égº“Xˆî;HCPñ+Lw5›â7îEGüy©©©ú"+ɧ†Âßš³w¡hî(„š²ç5¸’´Ó0Ðïó½ nÜý#ßûã $î$æ1ü¡{ ö`ÏüE»Ø±QÞ§²D Ò aŶìIŸG¥¨¬vcO½c±Ï4³k=æ‚G*š¿cÇýШ wâ¬Õ÷«¹r§Vg¯s®äútî·_ïÖ=ÙŽÉÿÀ9F¶î¨ Gn@¨ïè«~¹šøyŒ ÁØaÊ}(ƒÃS§`ôþ½›§ûLχÏiriKGd½álT†‚S#°ï-)–Ÿñ"£ì’ÂÎOpÓ¹F Ž¿tj;±]A4xÕÿTsÿ>;e=÷.ÝÑÛ½—×Ëþ$iÝøïáÚéP&„ʶ¾´M˜Ú¯‘¦Ÿ¡ š™jÿü(¨¨û #4%HŸM!B;Œ‰LÈí)IS¦‘qöÙãös§r"Å5'oùèä©íë¢Ç²Þ½…ŸdxöDŠã¤Pý\ÿâï–ÉËØ9WF>J1Ñ×µ7(*2¨¯íÖí34½¸‰…ÿáMMMOÖf(שžÈ Ë»q³âÇb|;¿ëƤ<»ÓÚ~é`š¿ü÷B[Ò¸çè_ª?â<Äè^¡¢z8õ®­o{ª3¼Íi_;PÒúÕvjð¯×¨ÞÔAçXô¡\3ÓÖ'×^Ñï(—d©æ#¸%ÍÿéÝ*ÈÔ÷úXME2íLÃR\÷Æ{Ý€+=ÿ4b’陵r#@[õF?âEgŸï¼=ý¯6ž”²(Moy7ãõNßúÿµaL¤ú~éESSܸíÛº‚;¥MM=…OdÓÿ ”;ý‰"ÃXú=élþýܤ“Û} ì*jæ4#ÍÀm;SzjnÏÔ›þ(jCºù;‹Bô~ª k~Ø-êý]|R^iãñO‹Ö”KJŽà[k>âOüBUŒWÖ‚^±Ï³´3óôÊ\Ü:éëR'ozg~îT}Ãzñ×þvUùêêøÞ†A¬Jh%‘¡›ÿ¿¡§Áãj—Ò¹_¤B?ãEŽOó².cZâPø÷« E@"!ëQŽ:•2èV¢û»Ë"¯y©mZ»Þ§¸(2ÿÈ §:ÇíD³éÊ ¯n”-gAWîÿ¿/¢{³SÞŠŠŠµ?ä‘ÍMÿ^ÔMVL;•!$JžÛ‡~Zÿ‹Ó¹ñ÷YŠwìò¿¢›ìGnïMMOüh^ãô†óþW†(O9öÍ!íï ÒÙ1ô£²*>ÿŒÅ{2??Ú×}½*ú$jv ‡rZÿ—ëE0l{ );í$æ¦è|TÈúSSSôÜEGüý‡dãÏæ¦¦¦¦¦†”j·^6ì`7¿MiD`ˆôÿ¡!ëëÚmèÕÊ7ì„ÄŠb{æJµÈ¨¨ï€Çüõ6{lý©úMeögÿ`>|tïpçZÌ» }j,©ý¿Šô¡ÿB2|Q*Ϙz?@¦¯qà TÔÿÖCŽãö¯bOÚ0ÿâò¯XgñÝÖÿ3ï¥2YUÔ¾îö`•¥t¡ÓN¥ÿg1“µûW´­}™ÿÅÜÓëÒtÚv žsK—i/ú3'né©‘ãzèðÔÔ­Š:U:íFÍû_µ{T¿fñq{Z±5iÛåñïJCCÓª(ÍÐ[ö>Éf{C3D|ÛŸ%(OcÝCR¹ž• )à¦l«rúod×§Úü0Ïy†t·§a#Ö’R{Þ-çV&ñoÓæáÓð+©ëE9ñiWj–õî·Ž´ÿFqí Í Õ©Và§sÿp¤PÂ; ›Z˽äRT>hµré§Ð{ò?hñJÕ> h@ø{ r>ƒùÅ2°nøù¥Ë¥:tŸ:ŠIü¬úV!ϧÊZSV9:måHÛØ"ãþ’3AF/JÖ’æ„cþû¨VÜ£3\Ù?Dej9éBóGÐjDtùúíYÆûwOþ)S2X5ý}¤–®t÷ÿàé­#ß$(³ö˜ýÐîüJƒ²×›ô -)gytÿ‹ú7 ,Ö!¦£ì‘zkZµϰÿâÈõnòÛÈfç­cïYÚÿÂššššŸý"a@jÐ0UÊZ„-¿Õ*jj{v³ñR&‘Ñ­Ïã°ÿâ\ µB¢AÜ‹œM Å íÝ?â0oýg† PœS3káû²§Jl<ÕÈ»Oþ%Ú“9™ôîî€ûUR³ÿÄ2µ Ïö2žE{Ь…û‡±b¢Oþ¬¦…Øîã×O*_øÑQQÿ©u%(½G³_¯ÈtìlhÓÿ‰+*”¥O£µI2ñVN?ù šÝ;%ËÏ|ÿâ• EDˆf)ÝOþEax¢âbö£ß=òf“º)íÔhý€ܱ®'>äoañ4¥ÿäAF÷ˆŽÂv h¥ìc°=Œ÷CO`¢±Y¤ŽÌö±CÝ,V”=Œö,TÍ'ØÅGïÏJÀûfÎiœ½Ø¨¨¨¨ÿÊ¿øãôÃNÁÞ°TЧ°ã¸Ê ´;P«QZö³( —¬F-=’ŽôŠ{ÓSSSSÿÆgéñí™ì.ÁØT¬ÔRúí³X¥š{SY î =Š¢‹V´•Š_²e_*ëú´«Ñ^¡;4î™ÒJ¸â5n¼ÅHfðÇÿ&?õ¶YAJÙÕÿ~Öée“ÿ(ÿÅôTTTTTTTSÙ‘OlTTv¥EEEEEEEEEEEEEGü¢qmWÀ˵Ÿ:PÖæ±ˆ÷û$šbløøA:ýsÝ;³Y§±ížÉ¥©£¶{“Û54TöMOrjúyÿÕOÿ?ýèÿå§ÿ’ü”;Ò˜=Ê‘ŸþLò1Ý’;U'¡óAÿÉgÿŠ3%rT® 7Ö‰wÕ@-|õ¨£´ÿÖ¬}ÿžñ¢GÅYwÕøQrü~êóF`ÿÆš’¥iYf¥IÞåbýè¿Ù¤ ¦f‰*/åq´szQq/µ(¼/@ïJá5|éMZäk˜ëF Sò'½ LÑrËH¤¤ •råk¿ŠÔ)ßù£¶–ºÔ¼Í$Ø·½X1Ö„•­ WOÒ¼1I­ÉíO‰­@Òu­B‡`Jm÷'ÿ&Õ{—Í»Tð“ÝAVìô¢ÑëFÉJAýV½ßЇWÖ‡­rûPWFÁ^)ïÓ(þiÙiŒYóKêúÒžãö‡Ù £îö¦aµs•5¤ìX»C=ð¦ñ;b::S[µÈ7Þ¤PŠ^Çp ¹fˆoÿB{‘Û=ðÊÑCÖ‹G­;–žËùÖ‡Ö­Ì•Œºý¡K÷A™Ö¸”=¯NÁEûCíNÎw­+ehè+¡\%‡¥Ÿ¬?øv…‰¡tzÕ¹ò? KW@·f “ÁI{P7öÍ/ø€±Í4±`®SÖ—áÄ¥ ÖaüÐ14étû!f® ‚¸¾•ÌÖ„µ,v`Öº~•Óô¤ëKêúÓ¼. VéLŸ¶¾9ú¯Új} ¢)mTpWB²‹f±¯Ð?õa`Ò>Õ 0ôø¬‰:‡CíÝ-Ÿ¢,è'À¤°&¼3HÌUòùÔÏ´1LD‘·ü‚Q¦^{/Y·tÿ¾ý¡ö§ÖG°wÏüú+ ‰»§eº†Í3¨¡›¦a ŠyšÖ™é ñ4|PŠÆ-/Z™k‘X5ip«-bš8+kéP7ÕØZ5w­‡¥'j| +Nïµ;µtSÝ<}üWŒ{ѼuüR•yçèŸ÷ß´>Ôú‚-^•j>ŽÜ$Ö©\¿5‚½/3Li+>µ”RºûW'±]?JWù\ôk\ŠæW;ëI›B1SíÅâ™Õ ø¤æi“JÔR‚èj{ß­*àk…ô¤4}*]КП0ù¦WWÝå’ú"×§âzÏ-K¦ß~ÐûSè”É5œðò áôÏØUÊÏdœ>E)½?*p<¿_ò–%ªÐªÀP”âPÚžµêg²+šÂܬšJòQ).zË-)ûþûß´o>ÅIX&<š´6‡Öi8­DpG]ècyyÃBK…—5xo/£Ý>Ôú'‹Aqõ×Ì"¤]ñæ×7Çá«Öh:ÖUüRÎæj”|Ã[—•èôNëIÑö¬ÂüPG`åHkézúy$¬%ëÅ5:ˆ{#ÏûCoèÔ^J±è5JL»´?ï½øåŸz°ê{=˜ÃV-L­iìkÅpЀޖšçÒýÓíO¤å—•\þ ÏÒ?T'6Ùü=¸D×±S·Ù\…tëHô¿üááù¤¬>Zfñî=ð¢/}ÖÔ3Žå³/ŒÖMýw.Q¸Ó‡xÊ“®7b/Ú÷ÞýÁÛöŸ|Ðo:¼îÍ-éT¾Íš‹PЮ¸wOµ;ò =¼êàF‚1õØýÆWíG=þ”6µÄ®7­&ôF‚Nøùø–²— ê{× ]zVôJPÝõjMSÏ÷Iû(°"¹šŽôj­•áš6kv@–‘×Ú¶¶JÔ}-@jú×2¹|-?hß~ÐûS¾˜TŠ IõOÙ`·”¢P¦ûxE ë@Ú‡µ;${к•¨ú^¶ž´ñÓã)ݧ}\ÊäW#Ö¹žµ™_¢ý¡ÿ}ûCíO¢1Meß5ÀЄÉC?DéëXD©¨T>²FVZáô¨ÿ¼ý«ö‡ý÷íµ>¬Ð4j‚ƒ©\M ½ ­gUï»Ã(_ZRCâþQÇCÔ¨ÿUÄ× \mpµÀ×G%u**ð»^®Ÿ• ¾?UÅñú®JÛGýwí_´?ï¿h}©ö¹ù+ß}¡ÿ}ûWíûïÚj}®~J÷ßhTTT}œTT}œT}¤}¬}™Qö±Qö‘Qö‘öeEEGÙeŸ’½÷ÚƒSö“Sÿ54ý¤ý™Sö³Sö“ö“SOÙƒSöGdÔý–~Jp…s«\êçW:¹Õήus«\êçW:¹Õήus«\ê7Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus©ßW:¹Õήus«\êçW:¹Õήus«\êçW:¹ÕΣ}\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήu;êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Ôo«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«\êçW:¹Õήus«R°ŠÿÚ M¶Ûm¶Ûm¾ím¶Ûm¶ÛÒÛm¶Ûm¶í-¶Ûm¶ÛmÓÛm¶Ûm¶ßµ¶Ûm¶Ûmº[m¶Ûm¶Û|–Ûm¶Ûm¶éí¶Ûm¶ÛmÓÛm¶Ûm¶Û¦¶Ûm¶Ûm¾ém¶Ûm¶Û~I$’I$’_´’I$’I$ÿi$’I$’Iù’I$’I$“$’I$’I6NI$’I$’y$’I$’I$óI$’I$’I’I$’I$šg$’I$’I=ÛI$’I$’4’I$’I$ˆI$’I$’Id’I$’I$“É$’I$’I%’I$’I$’O$’I$’I$ÖI$’I$’Iô’I$’I$“É$’I$’I%’I$’I$’o$’I$’I$úI$’I$’Iä’I$’I$’!$’I$’I%’I$’I$’O$’I$’I$–I%üн©<’I%?—¡V§I$’I'ÒI$’I$’O$’I$’I$–I$’I$’I¼’I$’I$“é$’I$’I'’I$’I$’H„’I$’I$–I$’I$’I<’I$’I$Ûïe3uQp 9”ÜÔ &½½–8I$ŸI$I<’I$’I&Šát’I$’I&òh$’I$’O¤’I$’I$žI$’I$’I"I$’I$’Y$’I$’I$òI$’I*’Ï<@H±q¶1èHo™$’}$’g§¯4¤óY$“Æ|£ð I$“ $›Ý_ÂI$’I>’I$’I$’y$’I$’I$ˆI$’I$’Id’I$’I$“É&’I{òΓ—S£'KZI$’Iô’K†”xÖË$’I<• a’I$“Ñ$’odÁðÒI$úI$’I$’Iä’I$’I$’!$’I$’I%’I$’I$’Oéî"2ß÷µúÍ•ÙiH]Ñ$’I'ÒI&žjrï$’I$’I$–I I$’IŒ“ÎÁ¬©$“é$’I$’I'’I$’I$’H„’I$’I$–I$’I$’HY‚<×ÀÚ½“2&ߢ/@7}ïŽ@8’I$ŸI$’|›é<’I$’I$’BðI$’y%(Ž풀âO¤’I$ã1.âI$’I$’I"I$’I$’Y$’I$’IŽÿˆ›ÿ’W%È•$$’I$š”z‹xkI$’}$’I$–I$òI$’I$›£ê’I$“Û÷ÖÛ¶ÛEo>’I$œ Œ—/$’I$’I$ˆI$’I$’Id’I$’I.õuÍ8 ðTïÔP1²I$’Ol ¥rI$’Iô’I$“I$“É$’I$’øå’IdeÁðÛm¶Ç$©¾r­d ü)ä’I$’I$’¤’I$’e%’I$’I$ÿØ'¼Ž³ä¤Ô uCd’I<þˆNêI$’I'ÒI$’I$›o$’I$’kZ–I'àÀ7¶Ãm¶ÛM¶Ûm’—ùn_Á…II¤’I$’Hð’I$²ÉG.x¢’I&öZO V$R\ÜäŒÌ7I$ò}èc±©$’z$ŸI$’I>ÃÕcbI$’I`pÛ”ý¡hýNÛ ¶Ûm¶Ûm¶l8á¶Ûk¡l­6× :I&ºûm¶ãwýsqâSӫ²2ʬl94$“É!@£áÏ$’I$’}$’I2ØðúD’%G2ç ÞÀ£¶Ûl6Ûm¶Ûm¶Øm¶Ûm¶Ûm®ÛmP»ÊáfÀm¶cݶË@€ Žºeþt%Dܬï$Ü(cé$’I$’Iô’I/*ëÜwÛ!k*k`[m¶¤Ûm°Ûm¶Ûm¶Ûa¶Ûm¶Ûm¶»m¶;m¶ÛmÒíE­J¶ó‚ÛÌ @/µ§’jw‚¼“6"¬óY&‰mH’I'ÒI$ܽî`ÐÄóí¶Ûk¶ØUÛm¶Ãm¶Ûm¶Ûm†Ûm¶Ûm¶Úí¶Ûm¶Ûm·]ó/¥ÁÖÃ ŠØÉo’ßs¾Ol¾Ù=³]Ü%Õ4“ˆO—ûø¶ÛY¿ÿoú`Ïûòÿíº¶ß3‡n\ê/ò_ä“i:Ò}¤ûIö“í'ZL´›É.Ò_¥½Ku–·-þZ܈â[k*ßåÿÿÃÿ“m¥"©)$’I$’I•:A˜ºŒ„Úí´ù$}[m§Ûh¼óm¶Û]¶¥ÛŸóäg ²§m¶Ûm¶»m¶Ûm¶Ûl6Ûm¶Ûm¶Øm¶Ûm¶Ûm®Ûm¶[m¶Û8I$ÒÃm¶Û·à´™Ë “€’O%ž-ÄO$’òI$ÖI$’I$„Iô—\u¿ ‚:vÛm¶Ûm¶Úí¶Ûm¶Ø˜É5½¶Ûm¶Ûa¶Ûm¶Ûm¶»m`º½¶ã6!$†K¡6ÛŽÉZŽЖI$Ò€S <’Iü¸ $“Y$’I$’Y/ÒMvõñôÞÛm¶Ûm¶Ûk¶Ûm¶Ûb®ÛöÛm¶Ûm†Ûm¶Ûm¶Úì@ w’H?ÿÿïµÝ|ïnâ€I$’E$’I$’IËÏÿÿÚIpúŸÿÿþÏÿÿûÿÿÿÿûåóÿuÜ@dó’ €5Y–_ïqÿ¿ÿûŸm:ÛIA8=¶Ûk¾p  &VÛm rí¶Ûm¶Ûm§Ûm¶Ûm<_²4¦pÿªz €&Û]¶ÔÀ6Ûm´2I$¾IüÙd’I&¢x€Ž`ñk‰¹ +$’I$’I$ÖI$’I$¶¨H¸añÅ5$ÎÛm¶Ûm¶Úí¶Ûm¶Ûm°Ûm¶Ûm¶Ûa¶Ûmµp’Iä‘öË’I$Ó¡$’OÄi%’I$“g žü–3RI$’I$“Y$’I&ˆÌE“È=à7ðSm¶Ûm¶Ûj·Xh÷»m¶Ãm¶Ûm¶Ûm†Ûm¶'’`È"I$’H„š}’I$–I$’I P7hw$’I$’Md’I$Ÿ¦èBéû-¶Ûm¶Ûm¨°¥m¶Û ¶Ûm¶Ûm¶;ɶÛ`~äžI·I$• !wE’I$’Y$’I$š`j ¡$’I$’I5’I$’MÃÑE¹Æ.¤dÛm¶Ð q|ûl6ÛöÛm¶Ù€Ûm¶Â’y&´I$’X6I$’I$’Id’I$’I”A€|ÉKòI$’I$ÖI$’I$’ð{£Þvw¤`&9¶Ëð=@{’8oC-°šxm¶Ù”IäúA$’I$’!$’I$’I%’I$’I%ÿûÿÿù$’Ia7JB¯©$’I$“Y$’I$’J&1@zK§¸(&Û|ÔÀ¤-·km·ÙuÌà*±ûaÙ'“$’I$’H„’I$’I$–I$’I$“xñ¶Ûm¶ÛmðµšO±ÅùéD’I$’Md’I$’I/% V-q÷}µú {müüàöÛiVm·JàòlLžJ$’I$’I"I$’I$’Y$’I$’I-ÆÛm¶Ûm¶ÜCcˆÛì+̓ŽÉ$’I5’I$’I$“¤ŒoÒ3ã¹×m³Šð ôvûm´ãͶñçͽ·’ðä’I$’I$ˆI$’I$’Id’I$’I&7m¶Ûm¶ÛoÛ>©°Ïo§$”Œ$’I$ÖI$’I$ÚMТ¿ÛÅ“” «¶È PÒ%Ô¦Ûm¶ä0xZfêÎd’I$’I$’!$’I$’I%’I$’I$žÜm¶Ûm¶Ûm¾êǵ›p›iè’I$’I$“Y$’I$ž’?Jè‡äó7‡$“_³ÞU ]ÌÀ|®ì@˜þ¶æ€àñ–b”’Ê'’I$’I$’H„’I$’I$–I$’I$’sq¶ÛoåOõ{n—›vI$òI$’I$’Md’I$’lÀ K'ŽZDÁ&ˆÐ)QѼ=1©Öø<`€=˜_¤“Á$žI$’I$’I"I$’I$’Y$’I$’IíÆÛm°BLyôí·Ûl©I$“É$’I$’I%’I$’J¶…ïZI†dƒVºnÄÙ¦®tÂs- €Ý’O(’y$’I$’I$ˆI$’I$’Id’I$’I$·m¶Ûm¶Ûo¶ÛmI$’O$’I$’I'¥$’I&p"ÿòh>ËFê0…ä"|s p+]À&³Iw"Iä’I$’I$’!$’I$’I%’I$’I$’ m¶Ûm¶Ûm¾Ûm·©$’I<’I$’I$“}’I$”ßJÅÜ ©¾©¢Cév»§m//IE˜þ÷3ô 3WP’I$’I$’H„’I$’I$–I$’I$’Iñ¶Ûm¶Ûm¶ûm¶ßä’I$òI$’I$’MdÒI$’I•'q=ØÃd‘@|2![O¶Àœ'ü``á÷‘ižI$’I$’I"I$’I$’Y$’I$’I'ÊÛm¶Ûm¶Ûí¶Ûg’I$“É$’I$’I5’I$’I&®'mïO'HåS|y'm¶ÛaÉ@aH € _û£WD’y$’I$’I$ˆI$’I$’Id’I$’I$’d½¶Ûm¶Ûo¶ÛoI$æ d’I$’I$ÖI$‰'fðÝ·Ëx‚{´“ÌÚ²o1¶ûm”Úª‹Q`&qI¶È,’Iä’I$’I$’!$’I$’I%’I$’I$’O6šµ=¶Ûm¾ÛmI$“:I$’I$“Y$’M$Ç7ãvÛCˆѳ#ÎŒ!0vÛm³ÛU˜À$›©$’I'’I$’I$’H„’I$’I$–I$’I$’I>Ö&Ûtmï1å¾I$’I$òI$’I$’Md’I0Ò*J9ÇíðÕvGe5µ@Fm¶æÍ+là\RI%‚Iä’I$’I$Ô'y$’I%’I$’I$’O$Ø>ÛlZ$–I6NÝ™’I<’I$’I$“Y$’y ünvÛ`G—Ù÷m¶Ú5³œ²Œ&2-«£6—Ù$“É'’I$’I$’z†ÚM÷›ie6m¤ÛI¶“m¾ÚtRm¶*Ò}¤³@ ˆ„öºM¶“m&ÚIô›iÃ:rùÛmü$‚I>×nr´à“$Äî0mz@€ 9è~m¤Ûo¶Ÿm&Ûm´›i"ÿ¿ôk½ÿ;×-6’=Ï·ûl{Q9ï/ß^$Ê>ý¿ÿÿýïÿÿvÖ\’’:Û$þNüùO_šÅª“=ÚKÿÏšI$’dL6ïo©š½]þííÿÛ÷·ÿî¿ÿïößîèI$’I/Ieºß÷öû=“É$’T÷‰4Åãn3å"O$’I$’I$ÖI$Ÿ¿mU¶êŠ"Á'·m¶ÛhIÒ ¤ºO³‡ ¦ò $’I$“Iä’I$’I$’!$’I$’H%’I$’I$’O$’é+m²¤²Q¼’ «›É<’I$’I$“Y<®|¤Ð’ÀM­ w¶ÛiévI$ÚÀ`¼½îÚ†åôä¹þ$’I$’H±«‰$’y$’H„’I$’Id–I$’I$’I<Ÿ$òvÕ°ÖŽ”u¿ˆ‚AI$’I$’Md ù$ ¼v‹BÀH¶Û@:Û}¸CY$’L@;&ósƒG‰R’Oü’I$’I,-$’Iä’I"I*’I$’Y$’I$’I$òI$’Kžm°0H¶Ã™+H‘$’I$’I5›$€€[E´ˆÛjò˜¬5{k™d’I(„›É€Y?>žÉ=ûݤ’I$õ+¼’I$’I$ˆI$ÒI$’Id’I$’I$“É$’I$–R¥nt$’I$›±d’I$’I$ÖMÌ–Rp¤íî Êd€Qp (‰%’I$„L’o$ÏÙYó$º ’I$’9ä’I$’I$’!$’I$’I%’I$’I$’O$’I$’I&¹¿î’I4GŒ’I$’I$“Y=íËmÉàmm¶JX’1$–I$’XI¼Â~Œ£’ȼ’I$’HG’I$’I„’H„’I$’I$–I$’I$’I<’I$’I$Æþ»˜I$ëò¬I$’I$’Md’Kq>µ»\Zo̶ƃlÃÙ’Y$’I8 I&ûÙq†Nd’OÄ"I$’IÄnI$’I$’I"I$’I$’Y$’I$’I$òI$’I$’Må;Ùg ÜCí¤’I$’I5’IÎ$ò3Wm¶ÎÚD +m¨µŒBId’IµÀ$›îe Ù’I%‰$’I&ù$’L5ÿf8ˆI$’I$’Id’I$’I$“É$’I$’I%ŸKÛQ»1^~I$’I$ÖI'Éž¼ñ¾5–Á nˆ€†FÎÐI%’I$‚t’oô˜9ÈVI/ܰ’ãô’Iä’I$Òˆß!$’I$’I%’I$’I$’O$’I$’I$–i^iÓæÔ…ò’I$’I$“Y$’Kˆ@y0p£ME÷" 7 hй$–I$“Ë,¾Š|äIÓí¯IÂ’I¥²I$’û¯˜É$’Id’I%’I$›É&Þ%@Õ)*5óI&Òy$’I'ÒK‹ÿàÇ?ÿÿÿ/ÿÿÿÿÿÿÿïÿÿÿÿÿøÿÿÆt€lÿÿÿ³ÿÿÿÿÿÿÿ¿ÿÞŒü6A.Èÿÿÿþÿÿ¿ÿÿÿüÿþÖ4û6Û)vîœÒ\Wÿÿÿø×ÿž¯$“I$’M%’I$’I$’O$¤d’I$–I%Ú_€@còd“Y$’I$’I'ÒI8÷g_qY$’I$–I$’I$’I¼’H¼WŸ%ÂaeíÏt,í¹$’Kä—°„’$’I¨–I$’I$’I<’i$’I$’Y$š“nöUd’I$’I$ŸI$’¯~ˆã±’I$Ôn$’MåþI&òI$Ú1¬üÑ#œ{,J(]üdòI$’I¢I$’I'Y$•’I$òI$’I$’Id’kmâñ5’I$’I$’}$’H-«™I$’e(’IîüÜ$›É$“E5ø»"I$UTWf(ÄVòI$ˆI$’I$’Id’K¶’I$“É$’I$’I%’I=ö äÖI$’UÊîIô’I!mð~ÛbI$’I¥’I$$’o$’I(¡t¾k¹Ù«OÌ䑹;ñI$’!$°I$’I%’IpI$’O$’I$’I$–I$ªá,8@Y$’IE«Ñ'ÒI$¹£Õ€ Ëí'I$–I$’Z$’I¼’Ici¬÷Š¢/¡%+ï+Ž)ºI$’H„’I4—‰$žI$Ò‰$’I<’I$’I$’Y$’snÂ^¶2Md’I$’I$ŸI$’vÛ[í¶¤îA$ðY$’I$’I&òI$•¤’L†,D¤ÒN'Š$äÉ|’I"IѦI$’™$’I$’I$òI$’I$’Id’I!¶mº€É5’I$’I$’}$’IÙ}w–4,d’Id’I$’I$›É-I$’I>šIóKɲ’yq“5,²I%M7@I$’ImâD$’I$“É$’I$’I%’I$öÛFÝha$ÖI$’I$’Iô’I9í:a¼ KU2I%’I$’I$’o$“#t’I$úI&nܦIä’I$™I$š!ô»Ù$’I%‚’I$’O$’I$’I$–I$“ón=D$“Y$’I$’I'ÒI$í¶Ê³,¿`+$ý‰$–I$’I$’I¼’K5I$“飰ÛĘŽ'’I$’`ŸÓ¨lÂI$’I)׆­I$’I<’I$’I$’Y$’I´6RuZ’Md’I$’I$ŸI$Ÿ6Û&ý¿“ªÃ$’Y$’I$’I&òI"„I$’O´§ÓnøÖpžI$’Is¥Íà I$“I$¦;Œ…¤’I$òI$’I$’Id’I$Ò¶Ú0RI5’I$’I$’}$’DOB ’€’yÜö_$’I$›É$’I$’I>ÄÛm¶öMÞy$’I$úI"ðI„òM%IdšGÅwy$“É$’I$’I%’I$’O¶Û`à $ÖI$’I$’Iô’I¸'>t·RšJÂIô^¼’I$’o$’I$’I$ú[m¶Ûm¶Ìä’L‡a$‘ ¾’I$ŸÊ*‚Ù%qD’O$’I$’I$–I$’I/Koê€3$“á$’I$’I'ÒI$Â1Ó’I>@É"6a$’I$’I¼’I$’I$wÛm¶Ûm¶Ûû’I$“:4’H„¾á$’)aÉ&ÿ±$—Ýf’I$’I$’Y$’I$´œ{‚h’Md’I$’I$ŸI$’dä°ãB‰$&0&ß™$’I$’I&òI$’I$Ìí¶Ûm¶Ûm° $’M“É"N$’^X’Ià’CÅ,I$’I$’Id’I$“":\¼0Ä’I5’I$’I$’}$’I B5KJ$“ÁD™d’I$’I$›É$’I$“«c¶ÛnŸ3m¶Í¤’I?ÞI$ˆI$’I$Id’I$’~&É$’I$’I%’I$’M­í» ÐÔ’I$ÖI$’I$’Iô’I$à7ü T’JV’I%’I$’I$’o$’I$’hmŽÛm¼G™¶Û½I$ʇ$’ÿ¿ÿ÷ÿïþÞϾÿÿûþÿ}þÛÿòûïÞÿæ¿`Ÿ¶¿ÿï¾ÿÿÿÿíÿß|¿ÿ÷‰õI%XÎÛå7ÿïÿûýÿÿïÿßÿ÷ÿÿÿÿz¥ÒKd‘"m²ÒmÿÿþÿýþÞ”È£Ô ;|}[ûm¶Ûm¾Ûm^Ûm¶Ò}¤ÛI¶ÃöÛZÅKm¶Ûiö›i6Ûm¦×m¶Ûhp#m1æM¶Òm¤ÛY¶Ûm¶Ûm¶»m¶Ûm¶ªØí¶Ûm¶ÛmºÈ¶Ûm¶Ûm¢{&—é6º]¤ÒI$’I$òI$’I$’Id’I$’ÆÛmsà I$’I5’I$’I$’}$’I5±m¯eÉ$’I$’Id’I$’I$›É$’I$“Ûc¶Ûm¶Ûm¶ë|’I$ÒI$‰I$’I$’Id’I$’I$“É$’I$’I%’I$’o»mµ‹$’I$ÖI$’I$’Iô’I$¯:pá$’I$’I%’I$’I$’o$’I$’IMŽÛm¶Ûm¶Û®I$’I$’ÿÿÿÿÿÿüÿÿÿÿÿÿÿýÿÿÿÿÿÿÿûÿÿÿÿ¦$’Eµ‡ÿÿÿÿþÏÿÿÿÿÿÿþÿÿÿÿù <7ÿÿÿÿÿÿÿñÿÿÿÿÿÿÿ÷ÿÿÿÿÿÿHŒœ»K’I$‘AÿãÿÿÿÿË+’I$’I$–I$’I$’I<’I$’I$’Y$‘&Ñœ Ž’I$’Md’I$’I$ŸI$’I6Ñ(œ’I$’I$’Y$’I$’I&òI$’I$œXÓ™I(øÛmºé$’I$ÒMZI$’I$’Y$’I$’I$òI$’I$’Id’m¤žæýÁ²4’I$’I5’I$’I$’}$’I$ýŤòI$’I$’Id’I$’I$›É$’I$’H¦ŽI$•m¶é$’I$“S$ˆI$’I$’Id’I$’I$“É$’I$’I%’I$’êûmµÀ’I$’I$ÖI$’I$’Iô’I$’I$“É$’I$’I%’I$’I$’o$’I$’I&úI$’Ká¶Û’I$’I<!$’I$’I%’I$’I$’O$’I$’I$–I$’I_m¶×I$’I$“Y$’I$×ÕßÒI$’I$’O$’I$’I$–I$’I„’I¼’I$’I$“é$’I$…ÝzÒI$’ͤڄ’I$’I$–I$’I$’I<’I$’I$’Y$’I(=¶Ù I$’I$’Md’I$’3¿I$’I$’I<’I$’I$’Y$’I'I&òI$’I$’O¤’I$’PŽI$’K|’ âI$’I$’Y$’I$’I$òI$’I$’Id’I$’6Å#É$’I$’I5’I$’Jd’}$’I$’I$òI$’I$’Id’I$’I$›É$’I$’I>’I$’I&Øù$’I$’wŒˆI$’I$’Id’I$’I$“É$’I$’I%’I$“rÛ $’I$’I$ÖI$’I$’Iô’I$’I$“É$’I$’I%’I$’I$’o$’I$’I$úI$’I$’÷ä’I$’LÍ2!ø’I$’I%’I$’I$’O$’I$’I$–I$’I;lÑ<’I$’I$“Y$’I$°3'ÒI$’I$’O$’I$’I$–I$’I$’I¼’I$’I$“é$’I$’LË’I$’LžòH„’I$’I$–I$’I$’I<’I$’I$’Y$’IG-»$òI$’I$’Md’I$“käŸI$’I$’I<’I$’I$’Y$’I$’I&òI$’I$’O¤’I$’I&žI$’I†I"M¤’i4ÒY$“M$’I$òI&’I$’Id’I%t$“É$’I$’I5’I$’I$’}$’I$’I$òIH’I’I$’I$’y$’I&߉$ˆI/TIÎg­ô˜Ì#PI'Šá¼M&’I%’I$.ÚbO$’I$’I$ÖI$’I$’Iô’I$’I$“É%²I$’I%’I$’I$’o$’I$’I$úI$’I$’Iä’I$–)$›!$ß8Å ã–Á0OCPHÐÉ$–I$“BûjI% $’I$“Y$’I$’I'ÒI$’I$’O$’I$’I$–I$âI$’I¼’I$’I$“é$’I$’I'’I$’I$’L„“ev´i8þÖ¸„šLj>mê$’Y$’I5oI%VÙ¤’I$’Md’I$’I$ŸI$’I$’I<’I$’I$’Y$’I$’I&òI$’I$’O¤’I$’I$žI$’I$’I"M…€º¬g­qCÞž8q|tGû<â’Id’I'ö$ž‰$’I$’I5’I$’I$’}$’I$’I$òI$’I$’Id’I&’I$›É$’I$’I>’I$’I$’y$’I&˜I$ˆI4’i¤›M$’I¦’I¦›É¦šiÞ’I%’I$ž€K$’I$’$ÖI$’I$’IôÒI$’I$“É$’I$’I%’I$ðI$’o$’I$’I$úI$’I$’Iä’I$’y$’!$®8f%4•Ô²g6޵ä’I$’I$–I$’Mfñ<’I$’I“Y$’I$’I'Ó‰$’I$’O$’I$’I$–I$’I$’I¼’I$’I$“é$’I$’I'’I°’I$’H„“sÆD” ,§Ù„²ÏRI$’I$’Y$’I$—Y$òI$’I$’Mx’I$’I$ŸI$’I$’I<’I$’I$’Y$’I$’I&òI$’I$’O¤’I$’I$žI$’I$’I"I.ÒY$’Y$’I$’I$òI$’I$’Id’I$’I$“É$’I$’I5–I$’I$’}$’I$’I$òI$’I$’Id’I$’I$›É$’I$’I>’I$’I$’y$’I$’I$ˆI$’I$’Id’I$’I$“É$’I$’I%’I$’I$’O$’I$’I$Ö!$’I$’Iô’I$’I$“É$’I$’I%’I$’I$’o$’I$’I$úI$’I$’Iä’I$’I$’!$’I$’I'ÒI$’I$’O$’I$’I$–I$’I$’I<šÉ%¢I$“Y$’I$’I'’I$’I$’O$’I$’I$žI$’I$’I<’I$’I$“é$’I$’I'’I$’I$’H¯¶Ûm¶Ûm¾Ûm¶Ûm¶Ûe¶Ûm¶Ûm¶ëm¶Ûm¶Ûo![m¶Ûm¶ßí¶Ûm¶Ûm²Ûm¶Ûm¶Ûe¶Ûm¶Ûm¶Ëm¶Ûm¶Ûm–Ûm¶Ûm¶Ùí¶Ûm¶Ûm²Ûm¶Ûm¶ß¾ÆI$’I$’Y$’I$’I$òI$’I$’Id’I$’I'_–„’I$’I5’I$’I$’}$’I$’I$òI$’I$’Id’I$’I$›É$’I$’I>’I$’I$’y$’I$’I%8I$’I$’Id’I$’I$“É$’I$’I%’I$’I$™ð’I$’I$ÖI$’I$’Iô’I$’I$“É$’I$’Õ%’I$’I$’o$›}”’I$úI$’i$’Iä’I$’I$’´“i&Òm¥“I&’M$šO4’i$ÛI¤—i&ÒM§Ó=î’m$ÚI´’y&ÒI¤“I&’M$ši4’o$ÚI¶Ð˜]¬M¤›I6³Ð\#¶îÂ:j $ù1<ŒgšI4˜é$ÒLê®ÂV²_[ì·éo²ß/¶_l¾Ù}²kdÖÉ­•m÷mnÞÝ5º_v–ù­ò[ì»ýo’ß'¾On¾Ù¼8ù¤BìéÛ§Æ}ciÒM¤›N¶‘$ÚH)äû%¾Jl¾ÙjÛ¿Ûm¾Ù$’I$’I$òI$’I$’Id’I$·ŽÐÉ$’I$’I5’I$’I$—ý$ÙÍ\¾I>Öp«@¡ €6i$’I$ˆI$’I$’Id’I$’I$“É$’I$žI%’I$©O Ï$’I$’I$ÖI$’Iícã{ü^[ â€@‡P&x@‘Uý$’!$’I$’I%’I$’I$’O$’I!ö!>Ø$¹b“’I$’I$“Y$’M¸d™@˜à”’H„’I$’I$–I$“}§›M=–qv„£ š&‘ÒëÉÀrI$’I$’Md’I¢ð`€'æI"I$’I$’Y?`Ýþp‡xyŸÒ6„$Ê,JÉ$’I$’I5›UÇŽ @ € œØä’I$ˆI$’I$#€š›‘Ù?ÒO$’I$’I6Ò%€P€&q–øx@’I$’!$’I$ÒHÇ”@Æðà´^I<’nN£O…4@˜b!°àcW$’H„’I$“Ivh W&Ò©¶2i ¬Þí@`À¨2Ѐi¤’I"I$’I'Àm€`—Ø8ɰôô¢ ˜í @ €¤’I$ˆI$’I$’…B@€J®+ WP€&x@ké$’q¥I$’Iä Ô€@˜à!¥ïn?ˆà9Z~É\`@`€€`€ @ €HJ±ÀT¬ @@„œ%,}€J€ e@ê€ð  PÔ °“pÕPt@ 0 @€ €'ÿÚ?„gÁlh„!B„!B„<‘„ãS„ Í®éM¨0EÉÕ¤ði¤ôçÛ¯Ù!B„!B„`sy*d¥tZ5Þôö L>TÙ!B„!B…ðÑ8¶^•4qSרÁBaÃöaB„!B„!°Â ÖáVEÌs¹‰¶‰ÍAÆ"dO²B„!B„! F8 Ø%V*4©¬nh‚@"f?d„!B„!BàØí¼Sõ™2â_´B„!B„!BRD“5ÀQw ¯  Ÿf„!B„!B¢ífj4Ž»(&YÒ a’IëöaB„!B„! ôŽa|J ¤Ò)ó°J‰¿Ù!B„!B„¼‘„ãWƒ„ º…ö/H‚bƒ‰TýFH±XÏì‚„!B„!Cn¹'Œ5!2‡$Z5Þôæ$¹ÜæbÛT!j©e>È!B„!B„0NÁ9‰eñ^5¿ÚxvÔÁ1çrÅ1Ctü¡øÚ–¹ÐC~ ÄN¿gâÉZ¨ à`"'w4ÅŒ“0L¸4û)ª=h¡Éa1Œ¥¨D×Uû;ò $PY6i€ÎT„ÈFo™Þ%Ðàû' ,˜lC%íG‘vq0é8h.Ñàû/Æ›Y -Ë›â±cìýÀ¤h("!ÐŽ(ß@™piöf‘NÖhÎ\Ð&È£•ÇJJ à 7û5!®ØÉ Ô¤aåÀ¨³6‚úÔ ¸DDª_wì¼SjbØZŒ!äÖ銇â B”Þÿcâ8iäí½ŸªX"‘G!q­÷¤Å$Y"|›}—²£Æ·ûOÚûÞ|º¿Ú8kÁ6ûOÇí}Àû_gð~×ÇsûOÚ°:iâ8~×ÙQã[ý§‡mGüÿ8#9X+b§ªÀ¹VŽk7EufÕŽ©St3DëC~pù ÇÝ•ó2y•br Îõ€”ÞxÍ©Zc yE¡,> h:œÂ„]°Í¨QÉ‚»æp_W·.¯ÁôQ NÇ0nÇôÓPÝ„•"ýY‹ÏJH½ôç()?ªMã|ÅΗñ–-¨šÙp§¥AA›ä‡,ô Ey©|©¸Ïzí³’>µ `zµ ˆµ*Ƨæ£R%÷òÆ?Úž×ÙüµñÜþÓÅ6¬‡Úx޵öTxÖÿiáÛQÿÈkp})èÀ„€1r’qj¸ãVû¡G™JJ6_õJÇç9é½9éqlX‰$a¼5àó«ᰳά/HqP¤BHtÃF• ?Dd,>8s ‰ç¥HgA¹mqRÝ®a~ÊD %uUʪ´w™GœM .9¦Tr½€AóLYÉÓÍÇBÔµ'ÈŸ„0°™’šH”ãSã©°µó I¤f˜Êèæ—ÑH2‚<Ö@º% ‹P_ ¨¥}—µ ÀQ&FÂÞò’ .º= |"‰äÁ³kTJfÖ§‹=‹Ö#Z’úŸf¯„E€“Y2ÚóÍCAÂ:ʶˆ”éRß²äŸóQ.sú;²‘e´›HNS}Èò~i`,¡‘mÀ¤RÒ÷ƒÖòÙáÖÓÖ‰(x¢ÎŸË±²z:I;Ñ%é$„Y„!<èòóÃDÎ ÔsBì_z/%™á MëPœ Ûð©•[ÈŸÉDe™'9W©:ëì ú;ÐéÕ£6ÑgRôÙt"9ä¡ã”01ëº*Ï/ ‰÷¥PئÈέJˆº³‹Äâõj$Á2ƒ]Ç­+ d»: éÞö?ìBqRµ5ôͪ‚%“x0Æ&1¾iôÅ74É ÈbœTÓÄpý¯°£Æ·ûOÚø^]ñ¸K"å&i"Ïh6¥¢nŒ¨L¹«£üÅ$”Ò‘ä#öKêºÕÀbî":¥BAª—po"tša‹3[vÆ‚bW±GH dá‡Õä)!£YÝ`õJÇ¢¬ÖÄS¦zO“Mm€‹ÙQ!Ä$oH—ª@Õw•#Keµ;´-·ŽÈ{š5L•fÜP€›Aæ}¡…EmI°&ðLÑe½C 1íǦŽ8|5Q…X‘då0.„š‰D AÕ Äíz3é…¾PXW:V]o‚‚â '½#3B”Ž…¾ÉÅxöÔˆÂD$GD¨ušö§Ag­:‚wœ¿…[2S2Ë.iiÌ;"} Ôµá rM â”'ê@ê*êQ®ƒäïrô[œžÐ#ÕG…w,<…ØôçÑ#[hZ&gh%¾B‘üA9DÈsÜtÁ÷ -…—…„s³!0ŸAVgÜùÒ›°Ë½ÍMć•‘'8¬v€«^Û§Ã|©Ђ¦Žµ4r²Dâ¢Ñiw0N‚}|Ô"²¢ð ú(96Ãrl“J<Ô y`p·DÁÄ‚Å$¶3lì‘sn¸N,#åÔàM’^ƒ«eÅ™}IéØdaˆ‘ù¢õSªh³„¨CB ¤×í¯jŽgÃ$ÝK¹sJˆ1,|z¡Õi Ø^RpTØkAÍB:„Ï:xêÔ\Ì¡äx)*¶gb>tÜu †at£šZ@ýЯn]oƒíWmÞ žsl-¤õR’eÁ‚"‰¸¸ Œç@bE•Ù-VË(úž;|RC* €Õt*d¼Ø|­räMÚ€l d 9àȨ,!}Y¨UΔ¹”Âo1 œSýB ’͈A›žqD'Ð!­´„ €ˆ§æn, ñzI HÙÒè]®T‚¤†S’eëF¦H‚1!aÈÌWJa‹É6ŸKP=’Rei87¦iåY {cnÄ›7>Po#X±M 4Œ‡$ƒاL”判§(zP,fÄveF`Rv\Qã°Œƒ#szA1”K•Ê‚£ pú`.bÝ\Ìò ÓÈ‘º@䔾³?GÇs "‰"˜E`-0CZ V”kÐÌG×f‚¸@•t¦,äˆË  OÝ'½%H–óu¡u4([ÎT Id(½$æd`S«E3NLX+µ j"è•æÞb€†Ê žY<Bn R%Óèx¦ÕÐûOÃö¢åßí<;j>Ð[¹\ ŵžEIˆÔÃpX¦$I6O™PÍ 6a¸ˆˆá¢–žDh²ô$[t$•„ oGFð?œ¡ó)õÛŽä—Â:‚kD:RZ‚ Ø‚\NÃJkff‘}D¸Y±€$Þ§IŠ.€Qq$Mñ%ÍÆAºà(ËäÑbüá)`*ÄÐPÄÖòÞ–™,{ÓD8¦JÊQxÈÞmR 7A—’>Bq 8+!¼;L¶«Ýð)Ú0!,€ÍäŠEïk¡˜exŒ]+2Ðјƒ¶j*¦Wu°„Úc¸¤ä«ènÜÄ1D±–º`ƒš‚´Htx<Εª„ï’„q<ÐsILä €6TJÔR cIo‘1 "3 ÚhÚ C(›ÂÅ(.ì­êB:M%Âö-­„ an[)ܺßÚ8¯Û»:(m¨( ½ Œ@YƒXpTgaÑmç 0^¶%fBo©ã¸ý ˜’U„JåcWZq4”j #4;X0ŽX!9°’Ù(&¯a‚ÅQ”­\­É6âp-±X­Ä‹•ÙÚ‰¢u})V‚É›X˜5æœ@mA S«IÒb2©È:°ŠLfH²p([gœM€ˆ0,iµ å0–Ùt>×Ùü쳩œ}fH‰\p^UÐ&¼w>â®!& A‰Õ¡™ˆA²$‰åRJã# Ö²çÊʹ:ƒR‚\«.@že5°À'’<è¼e› ĉèùR7Éà ”)ËÞ¤0@¸Ä#.èV”5ÿÚ°:iâ8~×ÙQã[ý§‡mGÙ#öD h7 I’ÚÐårHÙï4Êâðz‹RZ6hm¹E•ë}ƒž‰-ÑKoX€W¼KJrå”;©¡2«Ñ¨d™BóQ$d kò%ià\!2j ” \J¾Í aI`š G%r,Åè³@nà—ð—=h3 *\ÁPuÃ'H² ¨€õ Ú`³Ö ›¶ºÍN¦ÑÕ¾¢3ºò„Ä‘<”w®Ã&$K`<¨9iùH޾ƒBs=D /ªž ¤U ?o5#ï¾…Bu<êñ†ü€ˆ5_ Ô·X¤È‘ iˆÄ±m»2b›Ì^Mæ‡IzÒøos0¢#Ì(²HTKÀS8@R `ÙôI³}(5Dê@›ƒ9J P•”8:2,£¹5„㌠?Ö2Žj B¿ek¤Ñb›ÓFÙ#šVµÒ`á’œ„£6ÜùÑaé Î×áQÇ.’O58¡”Š^ /·Ðµ&ÍCÝ®Fí RÝp´ 3Ë•ì±g%LÁp”è!€’ÀšW¦ˆZ@Âä˜ÉQì-ÀBˆ8¬oƒñRŒCÔP,]ÙЧï7D9@ ¡ïD–FÆPä»\Wmݰù€éÆh¨o>îó¨¨r¤!uEÀjb¡x\`Áõ‘ãȯBžÛˆ—*â#IÍŒhe„lÄÇZ’3¾óÇ6µ"ô¾á8ÈÑ«RR³—¨â“¸pF<ÒÇšTÚÉÔ-äÅâ£Ç,+ ÅôpÂ1„¬Æ•"`¹,„ÉÌ_í}•5¿ÚxvÔ}Š’¼’Mî,×PçB†‘Õ¢È)3]©%Xä-ÊEØ^Œb»®æMhŒ‹3u‚qV0ç–g)1ͨ…¡8XßʯgYä` œ^ÝGNÅë(yP.$É6Yfn–ìÒn[=!hP %‰eR¶½¢£Vó9º™%™¹j]šˆ!”PJ³t`Élгˆ€ a }î§Àl<Ëó…EbK@€ô)o`PˆÙ(™¬ž6jH¬P¸BÈJSn≢DsbØoRÊP»}ɃÎalÝ â—S)Z°—Ýõ¢*IX…ÆB…%£Èž2-GLÝüâ$œŠsIÚA½•‘ŠÌÉd)©o«1ŠLY`ÜjöIzã@7(-x¦ø!¬;?*kÞI³EÖ4„(%•åìæˆq‚ˆcƒ`Õ4$ÑÙ0”0°#’s@BT(0¢K[$ÕžT Á)ü‘K›£ ‰")t)} 7:Ï` "Šéïš ÈcÀ€˜íq^=·tƒ¸`s$4›Ìš•RR†€ðª€P ÀØ ¾êœÂICaZ=DÝú~;Õv,”aƈS"8nÒpëßö?ìBlPi”1SRc6 «NPR­pa¾R/A¨ˆ\…2hb™57Àk5ÎÌ^Ÿ6“–ý3ÝOM°R›+ôYâ«/x9Þ ²Œ,I&Miü܃!„ëP„‰S Sjsó[X-°j$‰!g‘P8DÒ!$Pë£ÐÒW¨BÀ:(„êË€©Á>u·(Æ]?EtñÛÅdƒZQÃ%›S^MÆ c/}ì,79‹,.Í99S¤¡fyÅÉl65K,Ô”f’í¾˜™;Á)™¸U© HX¶· Õ`kºÃ1Ô™à֋̃8›ì,³!%]E9uM+ÛüðzDp U±JFÀ#µÛ+ |Ñ·•‡˜É&¥ÐL5Dƒ¬tšµ®³…m ,dµà-™!º²9#%*" ²j^èe MšBÇx °¡g4ÜMã¤Â“N@ÄéK_sŽ«?­Q“Ú¦Œfv5‰–§‘eÜ ’"¹~ÏÏéR•t)!@n ì¨vÝè¤ëè§ŒzeÑÃÑy¨e‚‘Õ%9ôQ¸,£¢ò¦Ç `ˆH³[ÒìÁÀãÝ>ôb<´-PËÖ€Õòj¢mEp 4S8RžaX³RþÔ“v_àÒQ}tK„ÅŒ¤–"¥GjW‚Ò[«D䡹!K3me©­Ô `n!›†‡ÒŒL@Êõ£¸RC7ó¢Y¡˜†`'«kvHÓ$ÒÆ-Žv¥B„µºäªÜºá«O:_}èÊ5¼¶SI‹¢†¤1QÙâ8~×ÙP¸2TÌ;4É+Tlžk~«„*@­E´õ&’,öøvÔ}€¥ËðXCcäZ¡XÅ„`-C¢ñ¶j+‘2ÁïGbÂx‰h7Õ@Á@ö‰H¦Ñ2˜´4­F².^éºóžÉP¨‹+sJ¶£§ªl0««“™‚´3¥5HI4”܆Ñ$œE‚Ád kÈ™6¡Ü,59Šg47°~©´r„³¯³ŽªV¥)bKuL@>aåSkž¼ý@?Ühvµ6 Šbö©Êe\ ,,”¨’UH¹’Ȱê$§“É:¶²–BBZÞ™ Ž@;â¦sEÉÖFDAl,ÞÄvd=gÚ ò) Ћ4íѽbM¬ö[¢€å %ÚmRƈ€Skbb-3Lš`Q¦'°Ëuؤ÷ZÜùJy *gÍHÞ*† ô^Šos|ëD “rÍ£U¾Ž]_ƒ±$ƒÅÝ/B¢Sbƒ€êÔÀ‹~H«Þ¥5êIõÜV›ÄS¤, “NR,Þ¸à$ù,žÔPt­E¥±VIæe]å×S0ÑÈ„ö ™jfïÖñÜ~ÌÅ(Éì~×H¸›„BIWkE,Â^ÊD’LdŒÐDpdE‹DÂfã( lbÏg³ø=ÒîdnñŸeH©Î( 0¿1P,f ?4êäm+€˜ uPæ¤ô€V€YXÝ0…c luÀ&-H±sD°N6&‹6‚inÚP ¢%ºôµM2J¤à›&ñB¥èèÌ@ïç¨Ã#d‰[%‹'aÇwÁsûK|u¨ÀÔÐ@ƒÃÖŽÊuùàù" $oB†m’ ƒç,Æ€)°ÙŠ c6n …Á¤†T¥ÀË=ƒU è'Ì5Ç’Ka%p HzL›¨1çf-„ ö“öqSá€}IDI Å0" ±@")9ý×>+lºñY-à=‹kΠáDÂ2I§ÉÇeØßŒp˜n'¼ù -˜¼Á&a‚s‰(7Ïl¶j?_ÙP‰²­K•žhé+ C¥¥ïK;Ž$ºòý5­ƒÍ¡¥§D¥9Åe`á0‘”L^ÕÆþŸ9C©W+ÝþÜrò£I*k´†íênY˜•¹:”LhÅ©&U‰0ÄÄÂb` ¢°ÉOœ~*'”ÜPi=ïCylúª7ãEêyµ¬íúM¢ òt0É N>G¢[¼ M‡58pHkÁX!0‹eX ²ªÂäAÌ% —x/RxÎHh,w‹š‡d¾d¢ÉãdÑ¡X3F‘µ…2ÃÒDõ¥.Ôˆ.`mUš @ƒ"BJ6¢7Ü>^‘%Í£=nö  ÿ¾´+ÚT½¦7ÏÍûª,‘"°2,Ù-jZÏKhl çä)º”‰`¨`] d,Â$„]Ü/‡\ÕÈÉ)Z( ó©bŽíÌ7äØÖ(UÊå 6dÌi@¤ ‘‰J¥Y7d”õ]‰ÄÂ…¸üÑè%MØ2‘Ð)> hD$‡0`]5£1ÐIÛdJl…Ö4C$ŽáE€ùëO˜VÚÔA¤ŒËØ•½Œ°,`[S=(Á¥’ ŠnÓk9§’ d ;›4Á•™PºŠÍ2Ti…b=à±ä…š0I­t¨1 2 ´JY5‰l. (‘ÞË«ðv‘îCÈIa3q#5&|Â,Mi×u!rŽ”&Ž-1"…¬)Óë8« – êBh(|Pí’Ï †¡sMjZâg9jQ”MÔÞq-0–ˆ/MFŒ.›˜ï6bŠã±r›(—4µ?@eR‘Fa.[Zç ¶,hßÇêˆM,ê©Sš2á[0'Q­'ÄÝ9V0ÚÔ¦ú[uck9¦Öнbà¾:µmÀÙ±‚-¢DhSБ‹¾ðP;¢œ}a<ª62ù¦‰X‰@²@½ºWÁAú ˜H#dG#KÒâRÆAe`^A|SÚ,2”bµ¼¾Õh9l@‰qI^‚ }“ITÓ "†êâ­Lâô¡¤LÖ)Ä”ÌÏu‚›­µí„ǹq4LP­©V¼eÊÉfJº„À¸-*DŒì¢"OäPȇ‹Ü …åTVZH¡Æ;4•í«-‡‚çöžµ`}¦^5žøTÑÑÒý—"– ÃîDJÎŽo^ææ„ŠÁˆÚƒ ™•î‘ä*r؉D™‰•bÊ7"¢$¶—kàE,½¦e€m*"rM]•×ôV@xö©’Ý7ºš905¡Ü»˜Ì¼±‹Y¬ s¯“½êÕ§‰Ö”°ºÄe¨^„]§,Á38ˆ¼ÐèÓ6Þ Í\X…¬Ïi§ã3HŽ8¦,{ÛG -¦¡ ´¾Ò™™’…CxìÔ›ö3Åb‡»íŸ Éëõ¼.Ö IdnI%·¡yqìjvð¦B¹g%$zmOJo Ñ̦b¤‰LIðZ¬É0´êP‹¬½PÁÒ$BÃ)†s&ŒÉ¥@”ÀÄßù«½„W©æÒKù$!ˆZ¶Vk7—šÖƒj–G €¤!Ú(FÔd·1°@†*hQQº$&ˆæ¯°X— ë絯^j;±"# 0…Än&(Á¢É‚@\Ó`Ò J¨(%EÕn¯d[Pá± €¡‡¢0ÓMŒŠ2B‚g$Ì,vŠXsP£ çFI‰`R`kÁ\ûñÝR)L„t$­Ä“$eT“¸;8ѨF¥†å( ¢4н„` SêYæ„ÓˆÔ'!"Mä1{Tz¶}Í)É—WàîOøÇÙ30 ¸WbpPÑó%Pä|ø4èê‹›®Q®H wçÏéˆï„À ðeŒš”˜dò2 DaBa²T6¦ÛÄ(î“´UéÉ^ÝåµÛÕݳT½BjM–ßeã¸ý’HY ²ùoSp L€R ²[çá „ ^P]©.7éDS¨ Æ*×k«„ [;ÑT“$¦ QÂa`–ìƒÀM’žl<€ô ¤ëÛX EHÂ$"nQø‘PY„m{¬ÓÛÄl \;ÙÈ êY>ub†Ÿ‘ å’ ñbà2^Lƒ¢æÙ(ÚÆBÒ9*œ@Öb‰ ŒàÓ™°D…"b‚IÚ5©a€¶È€kÍ„ÖZOþœ>­ „mñ… ƒ‹Tr*•MÈ2æfu 4p=Žä.°D°ÝçºüI9*P%Ê TÙ‘¯`•t(Òë1‚ ª‰´gÛJЍĠ0´ÓöÊ+ÛFüˆŒÓ¬x´ÍÚÅêýÏí<;jÀûL¼k?A ë~>¯ŽoUÖ4ŒpK ´Î•¢Z˜È*û6²’ÊzÕ†ˆù•¡ 0†A_\X,ØARµ‘ ìZ^…p›ª "h9»Xòw¹T¦£á˜(°t`VGxû¿&‡ð @ÆÀ œüQ¶Å—H!x¨ûv—AHX2™§zRí µfY·NÒ;¸†f/lѧAö“;t£H¡’:䵿ŽÚ@@JᎳʄD@@ý¦†¢u ΆCµ'B”m˜·sÂ𬞿[Àrú³r±Å¼Ù,t}f—ÎOçlñvV‰1­o8 àÀP>eE $ŽC*Õv«EÖº&0[CjLLÒ«¥@•tL(¢ˆØ±)‹yWxPËF [·‰ô¡{PEŒM€‚—{a¤bK¬ÑüM¢À"à–d^«H ŽÈ§ ¶S`Pòµ/Ír· t1‘8 Ì Ì†ð‰r„'VIº@'F5—È#RÒ$Õν E—ºK{Ö]_ƒ¸u!š¼AR…‰¦QÕ_%ÉAVê¥r¯~'ÂxP)·¢™QE DÌÑ‘Åüºž#QòPnH/ ëJtU ûÂ4Ç x&Ý¡è¤áL]lU¤¦Z0bšÜ VMuG@Ô<Ñ÷qIY¾ÖѺTó,™"îI bqC£~t@ƒ2dÍE3;›„„·šw”Ü3––Ä7EMËteÊ'Q²µ_£ã¸ý^=:yiçJe‘ ó9 ½j=VËÕ`ÙÕk%`ÀØ ÍDg²kà`"±&·©šÇŒ+Ù3"c&X±™Ñ2&G‡³­&ÐÝäêe½‚Ãj9®Ùè"|™8­4áräé6‘нi S,£f[:V7_H0—GðêÊ„…2ž-9€Ö¤®&?€<úvÓ,°ë@¤Z΋•ˆä… b(tœV¡º¨_4_£í¾/yýSr =Jf0®õ¿ÅZ[‚•@d¤]e§@â— À ±%Ý€`1ôü?´ðí«í2ñ¬ý¯ŽoTÄtF(àJàiYsâ-äBÓ$¬¢“›CÏ8H6Ý¡dPˆ ¹Í•ŽÓ~q<­MXÆsJC]ŠH¶XW8™À7Zš·Êû,lö*»Íp¡™I&Fú‘/<Z¥A ›N“̤)+²• ±YLkÖœ®ÇP¿JL7‚éL@bš9b*†öàŠ°°)€**T”É%äXJ2p 12™iK Æì^É~i™â/š\8¡p_­Jh9|”ˆ šNQ|G4àµ.!EúÓÆ‘–€¹¤ì¯ˆæ”ƒ$-zÓÅ‘–€¹­5|G4¬h²Â֧싟5(„¥ëM_Í!sG´¹dMúRˆJ^‡Œ_Í#sG—R&ý)D%(xEñÒ04pаԲ‘¯J” LÑDRP”Mèâ‚ÃHÂuéAMø{¥“Ê®‚DtXDÀ´Ò„zj$4d›-JÊSqƒ*?IAȰéÒœì‚]%sŠBpFK(pÃQO’/Hѧ:ð˜´Î•sÖÐAdòŠÝÓ qaK18"Ÿ™ÆŒíåÐh» RJHPñ=t\’H$GFiÆ ‚è&I–IBnºÄÍE¤Þ”æPƒ!’’;ƒ]'`†bˆ…} DÙ}hÑâkKjš àE¯¼Ðt²|ì4Cp"FêA:Ì“B¥« ¸Ú¨)’­[®¤™£5ÎÊ#eAZœThöÈ#wÆ/4{!H\/¥êàA›9¶(h\  Q0KIN5Mˆ¦°mz1æõ ‚XšJqÊ®°X$æ¯ÕISH XRa!@Å@0Ì2„@£­´²×s 1NV#,ÝÄùÒN™Ùî,J4¹Ey™ cxšŽ­ÐÓ…’CvŠ2J ( JÅ€P°/Öœ2ÓQÔLT$ß|Ó Äа/Ö” i¸ê&(2Hæ4¸q4,ˆzÒ‚#-7 DÒSâ/š|7*õ†Ô•ÕÐËBfAafU&§š€` 8 DP;n6V é”mzL7¡Þ%#b†Ãi:v"“‹±|ѵ±%ˆš¡=h„Œ’ªÔ™œ!ÄÍæâ‚t "‹%Ât+ ^¢Î°ƒRH”º¢•¢¯ˆæ£ÂDk±¸/¥'è"II¦¿JÊ ¹‹mÕ“ZbdXÂJ .>Á!é%‰I ilq™"BK6¨F ‚ho° Ja BÅñÒЇ4аԖ“7éSC4BÅñ´!ÍD5rƒ¯J ¨$gKDÅ$}!]ZÄÍA£„Rñ ”qBÅÕ©È¡š‹'eA¡²Š‡,O]NE ÔQ;* ”PÄqRÞë©ìLÔQ;* •Þb³lÉóÔãišŽ'eG²£³1_,ÑKÍA£²£ˆÙQ‘KŒÏ]³j •FÊŠX¤fêÑL ÍC£² ÐÂ*6)b‚§yåW" ÒÑ1PhN"£B–)•uh&@f–Œ¹Š‚Âq±AR½h6`ËKB\Ó+ÄsQ'cǯZLÒ2ÓЗ4ŒâøŽi€$âƒ48¿Z ’ ¾M= ¨m¯ˆæ˜N(´eõ¦‹#/Jb/ZrøŽiˆBÑHKZh²2ô¦‘zÑ×ÄsLBMè¢ÂÃMDߥ0„ŒÐö×ÄsS!숛ǠGQgq"u L1%˜(10Ó`0šüçáWoS9©Z–…1JŒ™ˆ—v©'ÜäáÔŽ” »yà%&eѲðUËTÆ*ooÈ”EþV"F×BK† 9S€ˆ•ÂÁ3xŠžäÔöxÆÝ“SØÉ v \Z8À„œ¹ Re`·[úUæb"LË8|©€¡Bc'â•-& Äð^¢I+˜,ÆcN˜®Á†^šÓ‰Joz{9g™‰b|³RŠE ŠAxLº”¦ 1–wš¸Ø–v·†• ˜X `rÃQl‚IFæ'jWæáw…'Î+Àr©©©íššžÙ©©íšš>Å@‘‰W€ô®+¢h‘I€¼ËÐË„çQ#°@,EHF.§l¨òkH]1•7NÒ,¨ŒÊFÉ`ÈÚž”Îp Ý3ÕkS÷€¢á9H6‚‰àqÐ쀗'7Šq?^IifØ,¢“@è^]ñƒ3EErõšpî„쩊°YøÀKvTæ&bô,1/q‡!&¨s\Ìà}ºÔÓ+·80›Cz "Œ,^<êu©œÒg«ðTµ1Šjg4Þ&ðÉ;ÕÙÁ)³AjçlÑ ¥€ ´Úx`ù2^IPÐD%Œ”œo*g5-KS©Ö¦sIF“ã1S©Ö¦sS½è݃i†CBÔ^ªIï>F´üû¯• ûKÔÀàä¥EÒ»ˆ‡šS•©ŒTëSR¹©qW*jjW4Û|Q©ŒTÔÔ®iEx¡‰€Á;Ðüòh¤Á& $Åv…¤ê.¼ÒRjΉ•m:„"Ôas@Ë …£,´( a*Äö³PÈC¹-ÎçZ¬éó;¦ÿ¨!fì‰Ä­R:ˆl “(1"ÓA"ÜePÊŠ«ºÑË-t343IUQÐ'[5ˆ¤\8™¼)™wµ1+†®Â0ZV¤iêMl™…‘áÒ{ &ÈÕ–•À0:EB@0}mñ~×ÅsûOÚ°>Ó/ÏÒEÊk7úH+‚ú´j‰¥iVйAØ#~“„¯ß¾$a&æ.Ë> ]ô!‘ÒEƒj‹S¢Ýc%wRÏÑæÓ ÆË4,F„¡ŒéÚÀ’oÔ«ºÂK¤"á«Dt‰õ¢à ’j͈ˆÓjRd!¶›TÒëbô¥@L„Ò*E!D JG4¼½Ÿ^É‹¦` £0Š00W€åöSLø¦Î ò–’ZÑ¡1‰AÂn÷`šŠDFK2Y¬XÅ&,{¤I·Íê¤ShQ#FÝÕÛ#¾j<$x§)¾”ÊÀ‹ YkÔa*!ø¥z™WANK+é‚Ã’irÕo:[$%%Ú­À½N²òЄ$ƒ–òjFU'"ËšG‹$Ü˪\`ÁÛ—Wàï5Ç„z”ÔþD‘’ ÈêmF·G¢vžò‚á!¤9 5Ž&{BÍWúðM¾º°œ„ËÊ,j¦—• áD+¸šXIçŽãÜjŠ€µfËÆ*!.=ŠÑ1KFÕ‰±tY+a‰³C¸±êÚôù¼Æä3SzH PbÂ¥JmÛ4dÅS`šÄìÉb¥/22ÜÉÈ«Ø(@{ C–öíDéPLI(¶*d!­‹IRÎ-™û,‹"ÈÕl\à“ÌN`‰8:¯Ú{o‹ö(n…zXuÇCc]i³‡€Œ,r.•úÓxòÒ§ò`Â`‹9£è‰b…’°Õ&”†ÀjE -V¯2¤­=6Á#h|ÙÅe\i¬¡BFkF¬™ƒ¸>aáÛVÚeãYî¤á^ÄI”U¢ {ÌïC¾|‘^·DÔD 7I-Î):,¸™/‚`‰ÉFn}„™’±Ðhœse|ÕÍè ¡+xºÄ!Ü*ìfÁ²f@y朆ìá†nè^YJ@¨ÄâàpsCôôˆPE”^ù½Oì[\)rlDÉhG0RJ•Ú°ì-šP,‹„Fô¢bÓâT6¨mPÚ¡µCj†Õ ªT6¨mPÚ¡µCj†Õ ªT6¨mPÚ¡µŸ¡µCj†Õ ªT6¨mPÚ¡µCj†Õ ªT6¨mPÚ¡µCj†Õ ¨š?¾¡µCj†Õ ªT6¨mPÚ¡µCj†Õ ªT6¨mPÚ¡µCj†ÔLʪT6¨mPÚ¡µCj†Õ ªT6¨mPÚ‹¢ŒÂ0“C±ŠhŽdŒÉ RÒ¤R Úƒjƒjƒjƒj$Ùüƒjƒjƒjƒj]ÀB `¹ wP+ ¤ÚUïX APÚ™ya¾¥0J…,Ú˜AìCj ª ª ª ª ª ª ª ©«W¡Qg$³!™ÒB’Y-$:à c+‹@Ì™‘½Û”``8;¶BMd¤BE’F¦jõ2À‚][v…ʪØ*Ò¿P°ð8- k®…\H9æó% *]Ñf[¸MN"Ùb]@°4ʲюEðKkµGBÇÙ4 ±©O(æÊ.·]ùìðí¨ú2²ÌðaÀ9˜£ÊÞH"JȱtæJ’©è›Â”f.*ÍB9ñqB—!pÏZÒ›fN,Ümrö‹÷ü/¡tqÆ+l^׿Ôì[ohv:PEž g”. ÄïWÌ¥ÏZêo¿ÐøÒ'×5-\ÔkÔR* lUÕ\@Vn½Z+ÔσZâô„ìCÒ’Èö¾)R~ ´ÊI X%8±ÀBÉ` Z`£H/Û`©%¸"ïÒ\”I²’ lÐ1?YÂs€œÐ¿WàûG x&ß]¶&6ñJG 4=]ÿËèÉÃ^ ·ÕjXXä½ÛK~šÓ¸°KÈ Ö+B&ve'^MÒÍHébЉ,‰KȃOj2‚Ž# 5: ‚e,%àˆ)Pž©qû&¢šÔœ+£HJ#¥Ó`TKLˆ§" X@Ë¿‰(LƒµÜž‰C¢TNÆDšÇî§.8°ÙYærئYÐðÈV¸lµ“È‚ÑbL²lÙ¤ø´1Ähas¢§bE@’Ca%2û@¸™†DëœS¹EŒé–c «†ÕhêxÐ÷©¤êë`vUÄ­,†³Á£Ø†Ì‘lTÓ’M)„ fËšLÓÐ[jÃuln⥅…ä&ÀÌ!¶Î°È¤ 0kè%¨m.:Þ ãK±SsXÀ¿*öYŒÜû?mñiáf ôÍ8(0-3M’GJÓ_ !¾d Úo½*ÑÐo6~ Èzo6^iUDa j†ºA!X rTÀ‹ qÏ`N̽y1ç`N°PH!ehýÏí<;jÀúF ÀGÒ” $$aµÈbDŠŽI šAQ 9\Râ]=k&'¨kÅ&N H.€JJ kL© Ôa2g¹â8{ƒLŽ8Ÿ¬öt’c˱ £€.´PPH:™ÎŽŸàdÕ‘P0ƒ/lU¢ýí¢:ΚoQ¶µ‰®ÿCÙQã[÷ $HMÓ§` $(ä Ì.ìQ*© kñ"œ‹Ø¦k"KMÜ‹P›=•&*˜”Uè¨6è}/Ú§)Ï2qÄKy¸âè阜ܠȴ% Qý ¥:À[—`@ÐÅkS˜ÂÁL“ïËî¦LnðQu%]¤,¢×]b1:v¡*Ê#‘¹Í"i95Q>ÿQ b°Ç =)w,ÆÆr·UxÅ4y—®(‰×F^ÞˆR†å2— Ÿbá¯Ûé•È!3‘´ÅŠ [ì \úæ‚™ ½i¤eÓ0Ò«è10irsBl€ I'¤‚]”Ö¡P ƒºöÓ!)ÙuD䃰”:vX…"ÐT‘„¨‹J\„à¥ÎHbÂê:“C2Ç: ɾ‘4dP ƒ#Ê!dp÷r Ìä^Z %ÅUL\³@ BÊÙd5:<±A¶/‡f1=ˆˆ a½èP•0¤’l>gJK`ʈFÇ•P§!! ²@d]!-@ !'Am %XÁ6¾‹çFDÀ5°Ëéô0ÁSD3ÅCoƬ%¦­­Ùv‰&Ðê }¿›Ø¸ ×—sÄp÷(O=L}8‰Ò,õB¥ÅÜÑïÑ dÅÜó*C¶ú Ix"DªÒ£„XÐ :¤É§sÙPá¥÷XÃ}^„]PÒòìƒHJs6Å%¿×½CQlà\2J0T 5ss)Èd% ¢`a±ÚðÂ$’HÄF ÍÖ_q6€™n¨3:Чô )ÒÞßWö£ê !‰VŒ¾€ÅÅ6mÕ\6ÐÌóîø_u"€˜øZQ¸’Që#XžÖC³J»*+Ó Õ"Tµ¤žÉÀ 2±7ot¶˜ú¹õ¾ ‘”A o ð„‚±›EµAfà8 M«J³ ˜´AöðM¾¬d*ðà(‚‘d)Q¦¸“]İ踦¨ngb<Ô”'”„Äëb¦9=6w(‚¸b£ÔD¡8¾åÕ4 Àu&ÓÅx®4¢gD%0Ê:³Ð‘`^ж­ªCÑppIPÜAD©˜2 pI)3†*J€{0è­Dœò …`¡,A,M#? àuL 7‹Ä©Æ”0wáJŽ Š˜ źÓE$„ yžËv2–4»Øð´5nKs¦K±µNu\eΦR’¡"Éqä§è‡‘h½ßº´Ñ`ØV<ã>”eL>¨^©åD-¸í-¾tölE Ø eÖzæ)2dÌ, "i ”ˆ‹@_[M@‚˜š ‰™fäÄ_4\dÕIô‚0—Pà ùè^„ƒHÂ7U‰©&¡!©FäˆyÏ 4]·5%‘¨Ð€¸7³RDC-¼êQN¨ž¥OáKRÆ•sq*‰el½ßßÔ'$ivý,¯,4a_Kë¤S¤A‘F¸ß»à¹÷bÜeë€Õµv0»“Sbæ * SšÌt‘ªÂ!X®t…õ5~"h,‡– Îr`Aî]ÏUËyÛÑÞ„HÜO¢¸I°p¡ê RW¦ÄÊ@ ˜[¢LDÙ òê™?+iËÜxŽà-‹ÔÂ"è§d£w{%û¼)…`[-¾‹€ ":#’›„¤Î AbÀ·aN(!#2 bÆ­7' ¡³Çzñ­þÓö£ë£A^n¶“zZàÒa`ƒJ%—R b¤·–xPˆÏg€å÷’:¤‚²W `¾~Šõ2b`r±xŠKªüPzDУ±y´^Ô8Ù\Xß1Q`0}§âàd) ‚ÚÖÿƒê5ŒÖÖ.•:ÄR† !4ÉhÖÙ"a õ[·š×q ê”a5>ÁÃ^ ·Ø(!ºÈ,Å—‘AðdhV‚¸x Eh¢@l¯*M+D d%(•B&ÓŽÙ¥†Í²…+ZAµ#©4+8Ú%ˆÌ¤ôf¬§VÄË‹êÕ½KÔºCœ "à“QbÂLˆI¤´Lb¢®qH'‰¢$4< å1r8-ŠOD^\´Eïo<Ršñ‚àíµèkzKΉŒèZr!Lb¥ 0 ffàDYšX43ŒNŠŒÚ’3Pæ¢ih £»WÌ„âÙî{OM³–ÒaA3 Äâ EyÀåz6é_H¦â &&#i/Q†lÈÐ9¦Ã=hêú4¨XCC©=hâµ¥˜vvx{°ÔK&r èæ†Z À¤„‚¼"uiMË*º»«w¶9Æ*`áýb´¢õ¸m‘5=ê}ÞÐÚ|êh­™Yê¥y”ˆ !,&½*Øù•˜ ]Ùú®ÈŽ¿½Út·«÷$<Š·œ]Ÿ"#ʧ0X ˜'„k0»- C“n­¨øBDn#M®à¢rLüS¿â%¶Ÿ&h)Ðe>y¡y‰+ê§“QJ°)5 —ÍA8&zA>TMD/ÂH ‚ æ“kŸsãó¤â݈‚è Nˆæj·ˆÒ'Ûtó©ŽO“犜Gðpû |/`›d-t.㸷Sá°ä@7ÎỖ –Û%Øj7Ü\Ñ@²ÚqÍJÚHÞqCT)0[Ù`Qè€îDÙ¹³WîT¦Ýõ'Ê}ˆ#£.Þä¨ n;Ág}ÛR@¡ZL¶‹Q·%€Â‰©Ûâ8{OƒH@*ºJž„Ò3yŠD«R„‰V$6U• ›Ñ l¯rqGoöžµ]SùBÄŒ4«†M,ðØ¡EçKÚE©’F ”"u$%¢eD†I>#üÐ$ýÍxó^ü×€ÿ5à?Íxó^ü×€ÿ?IÇqÇqÆÚÒaUí BŽB*(.:0EÆÊ•fSkx¨X“©ž›Ð’ÜKM$ Ñt6ZÔU³x?!£…rѼCE‘Q+ )²4}"E G j óÀ‹ª€ê±Ö %1Ì{‚Hx…žVW3-š±À$Çé:ˆËbÄ™©3ÐP2 ÌBoOxÈš 9-¦ø¦ì•—ˆ¥Ã¹šDÖ Za¥0Ë®¶©3—\åi,S'6ƒbU)ƒÌ·™0‹?]Ã^ ·Ø ‚@K—X Ñ>%`¯#‚]ZNnKv8Mû”la¢)‚ìÌÊ)#ŠÉ°G¢FØ×âX©`%˜]u­³˜%!¥‹LâÕbê‚ËpŒ†ûö´b2‰:ùÅoб†ŠäpÌoAF‹ªP,ËaA~€&Jm¥Î†`ŸŠªŠv´@›& ŠˆCˆ Y[«¡´’¬ÈF³‘;k1†…&ðªKÙbs2”oÚ¸†L½öŸ¤A)"ÐÒ…¿42î@H$æ–òÑÿ@ˆ1k`±Ü.ÂD“„Y´I£"㺛ÂKÜ’a9ôâ£]{Ë ˆ#2η8Et§× ; tì£E˜vL…l f`¢Å¬ù„&f$}‚ œPLQ–$8 |‰o´@[Ø¡ü áÄX“’6hÀ à »’0Ô½ð•P@9P•Ù º *Ð/!€yÑͰÓä òÕÁºÝez½“‹$µÂ&Ø“•:*Y,Ë`/!gΈ °FÙÔ×TôH'“N$¯b½B8KÑRª¸JÈ™UÍ$¿b‘,ˆ{Õ¦h4sÜî;®¡`Ê ‰8‘¦Í*dfè‹:ŒèÐTøâ‚äª.![œûb”;|GjUc™¹ewDX”‘*sì@KI‚ÉÑö>Êßí<;j>ÀbåM ²’å¹tM¨ž@}Uš"Òž>ÑÅFDÙFy^€Û–4a5‰ž ¦¡½GÆð+ó(Y¢“…n[_µYíËXäHÓSü½ YiĹôA,Åm‚ö Ý è2ÂÌðÜ‚ JlpµRp&&¢‡`ÎHjVúH¸$›¤êSµX”L†ÙŒR4Â+ ÃPsLËMTä"`Ö¶*õLi¬K” —¹Š(:€e ÆBlÝ®z)JAƒµ :TÚˆJ‰{¼Ñ˜h1 ™’™aúîðM¾Äª¼}RºÂÈõ 6ÄX ¤»‹å&Å1¨âæPRo I7Šñ|;¾!1Q;„É’ÒMd,h+´3ØØÑiem)mé’ÏGr°±«ä§p¸¨ „‰éB (ôûT%@@’"é¾”ÊÂ1Zˆ²l²Ù拤­’ØèÚxÌѪ[Y6^âÀ@sPÕáÚ&.ñf3õ¤ã MÅŒ„›G5nê… eªöÖ£gÉ=×!‰bÀÔº¸Ó ÐáÐV˜lË¡ <ê d0]”³ :QÖR"n\Oy¥:€ ¶Í€\©žÐ]%¹±¯J³Y"9L…8]Dô×G¢˜–8Û°À¤te€°T-‘µl¯¾ãz®¸ˆT`lOttµ'"‘¶µsĺ˜€Åâ‹ÆiH7jÀËEÞñ÷¡X&='´±ÁG"áf}(Űƒ‹°Ò¶»v§T|Ar gÛJyB1²€Ë®¼ŽNbÔA€bí)B [¨EÉ…„ìgîqdùÖôâ&×ëAÔ(.‰ùDu»Ĩ)<ܤ Ö`˜½¨ !àJm¹éR0ŠÖEšf½Û!hb}B R³ ‹ u^ŠÖ—*)…0!ÀfBœŠ½TnX¸PˆÜDDÑïΑ¡aB9F‰‹9…äDz4!K.°•¸Ë½ŠÁQ’ê—"v,]±G>¤ñ¦?ØÜÓ 0€¨,¸¬÷‰1é„:AW˜:2KÏÃÙ.1¢ [&’Xœ¦ÔÇK"ZÄI€Û:ý—²£Æ·ûOÚ±$ÖŠ ÁòCÔ>ÝÀjºƒ‰Ð¼yN(<éÇúN-Ó‘•Iœ5r®UÕnêÐRGm…"¾iXÃ8ø‘7w¢×>„qVDØ © ] cQ3ůÅñ1iŒÅ¦c¾È˳ž š¬Q¬q;1ò-Hf0£nàËPÄé½06*âJ1¢îƒ:ëÅADp%ÎñÕ%ëKçYæ~|­+Ê-²Ì›Ƀ7·dÕ,ãH¶tÞ®i YéäJ£ê%Š1†Õ¢bÀ@ Ï P#}yÐ ¨ºØ¡âØæb)ÜY F!3gký7 x&ßf‚@¤Å‰˜ E°”»‘0:H˜œ&€62 3 rM3z‹„I¤”hm ÂRnŒÝ-æŽJÀ‘ BˆX ¹X¡î¢>8‚²ÎÈ…‰ƒ•xÇÉRw(ÙF"04š±çÉP‰tHi7¢òR× ¨‹Ä”‚œî—P6ˆJYD,Í”âÃ7FáW˜ðh¦T>QjRê@ˆÙXª¦ºÑ/dd΋!Ŧ÷½éD(!qD¥ñ3í ÚDÖ ÁŸ¬Æ”«iIp¾Ñ§ÓÈ@ ÁbqyÀ4a£+Õ[ˆ©¾]È©/‹Ñ²“8)¸ïRîáÅqȃÙ¦òWƒ.·¡wrF¸®Ñ.ð _3 ×ûK§:ˆ4A † eüÑC7Øb¼ XИâ’×3l% žÛ‚´È ‡Sè÷oñj6˜‚Ô;76MÇ"Y>² kѲHŠI&$ÛTB`MÌ7¬çµ é®é÷Åc ðî¹p–ŸMqHB¶.GZ[H ê¥éxj“YNL!‚Å<% ôÑ£ˆIByw7Øù¢œB@%”–„Ì"ð‰¹—ð+Ê“Jð€†QòCRª®ª­Õn­×è$c0I:”.ä 2pN7DÆý©Ê½lH‚fPîH\` N9nÔÕ "‚3$—¤¨F6LD×£µè–N=]x¡ŠÊf@¾Z-(\Öéšlé0ÝP$*Ãzïš½UÊýŸ²£Æ·ûOÚ±»dzùû—t*y$20XˆL_$Ô.@ Ð)ÐE¸3¶C¬)s`ŠLeŸhgµc%e7:M²®!Le•µH0£Ë»ÄNÑcP–[²þ°PàÌ4JDíU½Û¹…(m[+‡k¸¨›k‚K`F@r†F3=”ðUCb!.`¥ª0êXGŸ%"AÏ(JÌ@^íBAdVL8{Y20[sn¼PÊ À¤ÌÞ‚,ÂàŠ`+9ó‡Æi¯”XB·Y`.–y"|Q@‚*9_Ô„ÀáŸIŸ¦á¯ÛëÊ&-QÚÍàyb{´X€;¬©‰›’'C-Óz„àˆ]–1L‘˜óqV1J³6™´1Zýh‘½Éav"áʆŒ!¼LDQ‚qAf)ä@m„wxß°´´ÐÃB`h.°—„Å«'¼'b"Ä_5Ó++†ôîPÃFØ:¢6¡%¤-8¡4¢ÝZÅ\¨xfÈ)k†5.ÅH•§!*ÊË ‘ɇc$Å)65—ذ"5¦ÂÇ Kë3I©Â C¬C¨à ©\Ö/4 ^Q"n'u‹„ˆBÀJ ’ˆÁLìÄP ‚Xh&ôÈ¥Ò–*„ܱp!Љ€r‹öw6I$½]Ù6WÕ–¹„PӠʾ(Û7X€ºmMJôÄÀ3?U(, Â0(»×)ô7bnùUô-$·YYnËÝ*R‘ÆtpO'ATdçï:Ž)°±4]†é£1Mú`R³ÄDGéÌ¬í† š¦­ {Á«€ãªϪjJ“1ÙÕ°lРž5µ:wãZpÚ¥éi'&” Â+nÉ3GfFÒê[QŒQ·ì¡ÜW—p JÀ) à ±¿Š$…Hˆˆ›&‹„õHP½­ÅP§n³“ȤDX‚ÁÈ *:ÁD¬œHÍ‘šZØ0K-6’·Yè!ÀL¯«u×í}•5¿ÚxvÔ}Ž›oS7õòƒ€û§q­9¬"fho’À à4­§(\$,ÜëDÏK]¸LÑ ¼-/aAy¹ ®JH‘d—š¢õ­ƒ'H •ZªÜÊ-*] žØÈ "¤B[°\±0–d9¢]O`ÅÊ•t&Þgƒ7-gEg°¨4ˆ‰¨”ú²' ±—í5à›}gÎDŸ É€„Ë¢!¨è€r ›(ª»ÂV \ô…æ´³“”c !ëEZ^äȘbr K ‰-Ö(•T„,ˆ:Ð)h(°'FmIf÷^@T0Œš}ÏVt¤l*Q ØØ1%ؕ‚²³uxUbí8X…D°õwšˆ.$`ÁbzSdUì|šˆÄå…7@–¬m‘Aè©E’(”®} ²P‘K¤nÌ,ÙIMÞk°²#ÝI2…ˆÄw\ÍíZf>"HŠŽ4ý`€Á,šÚ¦ì^HlBÑc}(­uI!x¶ aƒ< ]zö?¼1r§ñÄ6ë!ê΢•,H! .‡C”ÅCœ:\IN°Ük6GD DX ·sµ§4ÉD v2&` \"œt¢ p ÖbŸÈϘ6G°ŒIRêd%’¤Ûjë@„;#¢Yìâ,ÖqK9hÃÂ,Û4…I)©µzÄàKå0D2è  ï6½LàdºzÞt{ÈXÚ;Ž«v…CTÀl^ªÄ ©‰b𣠙ÅÓ4(¹¬`Œ‰{J6Æ÷è%‘¿AïH2ÐGÊ5b(ŒÞQ«¶œÌS›c½âùwy¥ ÙUuqÉ à÷uZ‘Ã>ˆjÜöÒª :D0ó;’Ûô¯BÍ%ÈÕµ$V¤šØ-¡‘±)5o ¨A­0JªÙîøÖÔ`éØ# hˆNyؽ#–ˆ‡ WXd+¨Î¬ÇGjbTnX"/r»S˜¤ 9a˜´fŸ Hä¹’碤<çDm„aÊ•#k.&جˆ©ŽÒÜ0鸭Àw…PåylÖZ/ Ô^QBBD'“S1Y`•• «,ÏKR˃#L⎠fór‰×³Äpý¯²£Æ·ûOÚ±8éFˆ×Rg£ê8Nˆe>å"ĈB›6ÞÊÇo¬á` ’ļPƒi-À¤6S‚ê·‰°g1`£$ ×ÀàIéÒ² BjB-š[1&,‚k”Òr ÑŒ&jÞpÀç¬\×4H%kD[¶È&|¢œe`Ê>gu¯ÁÄ0ïDtž:w5 ‚KÄ #¨G( ¡%$Bè“-&ðî ê’º¶*\uÀ)+Å%Ñ_*0@  ðhŒì°bXïåÕø>ÑÃ^ ·ÖN`h½õÕ‹ž&\õS‚. È‹ÆG¹á8SÉE«™eÅF#År™1WX‚3Fl‚Ʊ,LoÚ˜ÁA (BZ Ò½:2;I¾8˜D+®J†ªÄäÛÇÐÍ#` ɇ ÒaI„, '”óB’MA¼Äž(KD‚j§{SÀ íƒmפ54 h)™bVâ '&„訹`EÆF­.b!²zâž4Rì£94tµMšÓïÄe€’FÁj!º¹cBH_{g,Y¹ÐHsÏœ<Å.†šê¯ ލ˜Ðí£ydÅ„@ÀQ‰¬D¢é˜ŽSŠŒ¸)¸N%þQCR#Z›âÖB²9‚Íp…kârUÖ”«¸X ‘ ¯LÍBQJ`C£Ý@Öc ÍÁgÒpðM\’ÎÆî̦%†ÌŒžÕÍâÀ€‘ºõ¨€äð’D^䔬Я÷ŸÓ:™Tæ€/oPX˜%(˜œk¢Y½"½qµ¦Ñ{Ô`#8S&D´A{‹,x6hez"$…¨ÄÑH„’ÔÒ4!÷Jk0(ajY½I²±Ü—KIRJ<%#Ñ″b’Q@MÐÁ¶ˆXŽÔÐ BÌ€nĬ¬ÏÃö¾Êßí<;j>ÄÑœuOuÚV>›‹³2æw6‰›MÊ×du¥@"_$ÃEÔ'H7 Y!½ ‘ ™ Ú¼¨&m³’‘lfô<)à‚ßqNÓˆ¥(A&D FcŒïè4UH1 ¡eCWT£ -ïr1z`–D„„i%»ÎcÔ㖤皖l°¾U*ëÄ’‚ão%#“¬¨<…K½ %` —=üº¿ÚÂI75+Á6ú¢-bÁ‹> xŽ’ CB•+« 3–TÄgJhe]êÝì%awqþ(P^H’ wq°T/8Ú’0³ªŒºUÁzP§`„“ò2ÐcºŒÂÛ»×´uq$dMà‹è¤†; ­»ŠÅ†âÌ–²å´Bv¢Ò¹"Ö#°V ’XŒ :”³PÀ`l¶fÐê¤^À l*ÌÝ(Q8ˆ–Ir”_Xfõ6é3;tÞú–æ¦h@8,D¤ ™A‰¸’*DȰ*t]!²¯"ÚŽËJl³&’@½ŠÀ†ŠQpl‘Ú×½c  2%ÓLQ0XÀî°6–0P¥8EëÅ­{Û­qYz=£6ëþ2›.›Æ'~Ã&e Ñ&KzS,ÓØñËvíB> Up©‘v+Y,™ÒšXÀ•cKŒ˜¢âŽH°IÍPֈɋiN!–J²Æ3½nþº`.”Å¥Kz$ Ù6mQã ®´¦Â‚|t‹{S2G Aƒ!da[“S‚p‹h1!#¶õÀ›Pw>"P¸ª·Ž¥‚§WA¢Ól0–¥œi‚%ŸÊ´°jjM»TËF²Ä#I©tK‹_ŠR|‚¬a#†o­IÚU%Ljú` ÀX>‡‹åö‰2E9X°.®Å(o¢ÚÇ % @¸A|­RÂŒN PŠÛIµ©¾^Um$``3ª,¡*'+é&ܬec=-V!ņðb0Iö 7ï ›îR(,Qz',©eV깫æ,‚<Ý-Ž£nïˆáû_eGoöžµb…ÜG“ññVhŒiŒ-ÈÿÀq€ñ™3Bç $ø—¤•M•Ô7•%W^©uúYu~³ Ü¥ 0‘f‡ÊYp­x bÁ D %€]…FßÌ„ØD)ªä¡Jl Ø:ƒQô†‡èº™[Ñ­ù$td ¤&hš0 –Ì…Œ«µµí\­Šmê­L‰²Âò ™Ô©é§HâgÆÍdG  ¥t·¶´…»•r­ÖïhW.}– G ³½G¿ ¡o ‘iÊöc 5B`TКò ¤fŠAé LD9wl¬&ChpüÂBJÄ$ÔÓÁä’’! ‡#QàÉعPë{(ÀÁ ƒ…¤ –.24›EÐ’õ·I.E¼Š*( rBÈnAÙì~'dÃ-·Ôå€2\/ ›„1¢2Y–:ß~è¦éBà¾ìuÛ¨C¥+CY­`(˜!ij-åNT¬(ÜNƆj؉„°#c«k%eEÕ™ST•ç°¦eVÄ ¸É4ŒÕØÍàZÌ„‰’ÊKFûPᣠÈ'Ö“ˆ››Î®ÁJ#±,®³x÷¥W öBsï**È)7YH¦Qî¦éBmLJ—Ïi`ô› ”ŽeôìÕhM›lÞ™R–l µŒ6R’tYXÎX|X=Í`p&›Z|þ“D‘{ŒdhhŒ•êä‹Fsš8€‰„qô¼_/³’û†"ë°€%±çGNóµL„­[½RŽB@ZPÇfË–VÈÄÊ„" Šk=£…ÝŒ¼´Ùì7¸ înÊ_ &—I#’‡‚ðȪ0C03¤mo¢h“DNöQÕÐ&0JÝÒˆþ[.M‚Ùž5¦¢6ëÒ¥Üý 5.¬‹=ßÃö¾Êßí<;j>Ép SV4º(¹†$$`m÷ço¤3\F²‘»ÃÀYƒ*2:ÇÓË«ð}£†”!â*i“*ÉÀ ”¹-Lkk6…AfîR}6,Y.Å¢Bí p_fLËy$°)ìd¡#r–5ý¢ÌˆîpsBK]h±¾² `-}Z…Å9ÍÂz޼T·«« 0Љt ‹˜XIb&Ö#Yn`2C!Xb ˜«¡²ˆØã8&9)väLâ "›LÚ”WÞŒ+œ“0³j(kdtHl[3•9_VL€²"3±@ãàì½*ÀÜb’ÃigeèÄŸ¹Ìà ,-Á£[>;*&X—1Eˆ`MO!%Ä–ÁBIcê ©Ê6Äç`‚v+ÚüJ‘ìÛ “'Slö;H/ŒÖ³O@JùPv€@%ORý(=¾õ¿é#æÛš“àL îÂkdQFHÁª NK:1zËt¶”-ÈY†i‚—;„£Øø¥@Ôã ÉE!ÂÏ^3/  Å´œˆÛg"•¢n‹z-\8¨$«Š$”X]„ Ëå»6Ø¢ÅYã§FŸ6žžÝKïC:ºCЂàÑœ>gd€mÂW@5Mƒz€£9!€"óxͧ,B¤Ì€. hR3 ÈÕ'°6 ‚¬&p,ª…Íô©f¤Ù&u(µ# á›(yðÊu¢eÌußM D±O¥âùwì— †cjæz4‰tÎ>–lá«1ðºàAnË[@¨ÔQ<»Z ËÄj¡¬5—Àâ,WÉɵ0üºP)ciÖ¥)6$1°t›]4X­¥vF èË©˜n@…àhv¢óÇÜ,ÚVÔÍ&¬·+0a‰Úñx#¸Êé`–8¤}€¦PÍom&— ŒJ‰p7ËvˆJHL/(hNiË¡$hNtUæA –ø$VV.À²g–J‰'7†+Äpý¯²£Æ·ûOÚ³GF¢Â º‘пߎ8ì¬7 Þ€K …fÃ2ȆÿO.¯ÁöŽðM»eH[}[hMIœ:¢—4’NZ@®„è¡RdG,¡D +„äK¦× à¡j !ÝK4¶Ù#È­ú¼Åbl¨^…ÃÒ¥»C3¥ ØD•5ž^i—¨Ñ{"ÖD’ 3Y(`-ˆ¤ec+½~§ ÌÉ1@n¡ Ãa‹ÒD†7Žãô%ÔÆf™_)9"Ǽ¤@®T”®#Ö™‘³]ÀDŠ—,š:ÕÉ2‚Ñݬʱ—Æ’ ÒU jcØÜ¸ J©h€™“^•0Rˆ·eÐ’. ZÜkW•|`B(JŠŒ3 €.@%Y¼Ðd0 ‚×f4Š}ºˆµˆrŠœÈ. ™€Û=¢Ÿa™¾,\1†²Ù„ÅäY†Ògµ=žè¤ÕÕ“U½Bjg¹6 ƒgFÕ"àªÄ£µ£P‘nF2— B)7Èð}ÒìÝÞe²" LiCy40ÜÔ$S”Hßr€q4À`!l–£Ël›(/j ç]¾±Ò—ª¥ Ⱥšž}ÿ˾aðJJä¸Ë+ÉUNàUM¦uú`ÙJ`ëV fˆ9Œ fiêÂ,eå½1ˆæ ²JÙyÞ¡Ä ˜Ü]Cʯe$m“iÌùe( Íl2gH«k1Ù Æddg±¯¹ð ªØ)ïpé–™¨@bê—AÁÌΰH9ÂN¦GFôÍ=лë(™P¢Ó½„;±š~ñ˜¡g ÒH I¢«šÓª –á@™ Úñ"Ê%¥€X¾ž]_ƒí5à›}sŸ@­N« ‘gW@n*;fE“Q¡"b‘"Àk›*W2°ø­î¿BHÀ¯¥‹a”<¾8b²n€Ò2—›i$˜<5q)(K˾ùÈ’M»áa6Ø^#U 2 ,2Zäÿ;¢nnà —s4î1ŒL领ŸÈ²ÍÛö-í0‘”bäÑšÒ‰@3–#bRÂu¿Fõ,8×¥+‹-1•©R)Лü‘-(LiŠ‹8èAÞñ?kì¨ñ­þÓö£íÉi"Œ€±ñÅ„²—‚°¹ºDÐbLe˜a´\+þä÷¡í¥ÅÔᨂ}ÃPe"Ë2=òeZ(%È¢2ɲր è’y °.­ ¨t$)a²”ÉF-ÌÚ(3‰áÊBVâjÃ-›¤²øD~ÍÃ^ ·ÐŒV¾DE<‚¢³*f&GRýȪ yäݾXÊÿcY ˆ¶-ƒ{RpvEó Ñ¡wŠL8±@Œ=; ‰{&I®PBô ›† ©Ý„¢ÔhG•cÙMÀ0lÄIEExeª?Š8h˜1ŒÊo©¡Ú†JÐ ½RÐ}*¼Ðæ®5‚MKdžÐ ª•[&8mÌ%Ì†Ž°ËÆF²Šå‘pÆãÜö?±jÈnU´Z¹Á[.œ²t¨ñŒ«–¹TrEÀÒ!r!iOE`¤6¬‚3 @.ʬàÁf@¨è uZôÁFA âø€`¤–J,˜Í¨OFÉË´ôä3Šyè(PpÕj9¨‡5Vò$›DôwR Q¼7¾ñHÜæd¸‰ˆÒ#´Ç#ˆ& j;·‹·m¥7PÃ%™„M˜”’€©“¦)Öð fašq2Þ—D!¢ku¢`H Qµ˜(=ÙžMaP@Zë«`‹¢Ic#£Qñ¦ˆ²–UÕ^XbÔjhÖs‰c†h Ë0rŽxò­1*@„Â:ÿËí”Ù5-­íšWÌÍñpíÅ ²¿8ŠÏ”@2X®%û7 x&ßEVíûS$J'šVyY€ ÝesQ؆&lÊhê$@ ŒÀîG4@½¦ èdl_Zޤ€€°1b­„)„Ù(6&èw p‘% €hˆÐó,™DDÉ„^HFKnˆü-î]¶ÐðËÈÄÅâ™Õë zÑ p1¤šñK¶S[$cxQ£H²5ca³•ÔCÚiØ„)cB5DR12¹Vs€ð˜¡ßLKÊhÝÅåEÃÎíÅ9zQ) ÚI³q¾”˜aŠA½Á ±K€2ãåÉeîPDRiÜœ"¬Ú®\D o-ÆŠGR)Eˆ'ηAJ/q•“‹ð´úB\‘¸õ¨ìTUI˜„äqWS`,Ã6c_ dÜ!öj&3e,¬k » ¨(bi3 Z7Ž“V5H(Ô,ŒàTŒÆ”4ÀÂÌÇnžŠ^R‚ÍíXPMØ#=’½fH!EÅÎ.ÄVHò?ïÿËíeŒƒ–3ÅÊE.l1„l‰:ä©‹÷Rÿî¥þ/ÝKü_º—ø¿u/ñ~ê_âýÕ©µÑû©‹÷Rÿî¥þ/ÝKü_ºWÏ5/ñ~ê_âýÔ¿Åû©‹÷Rÿî¥þ/ÝKü_º—ø¿u/ñ~ê_âýÔ¿Åû©‹÷Rÿî¥þ/ÝKü_º—ø¿u/ñ~ê_âýÔ¿Åû©‹÷LV>{‘¸‡M=ˆ5§“Ã@ˆ3Š–d$ îÌNŸeáÛQöþ ó*©²£Rõ)ÌäÞó¸“)35 !)”aí±cePè‘YRraä¨ÖPS‡˜"%èžþ¬„bÛÒÑ2 œ”FÁž›¤T2²ÏÒîgĹ@X@’¯aIŒ~i2‹˜Ug‰:hû„ • EÆðY ‰6 'f0ðß¶j~†]_ƒí5à›}®ÄÝPÌ2yаŽ‘ØÏ  M4Ò)Ûa$nÙVÆ„à ò)üí™c u ‰Oª:ŽË»‡Åo~ÓMKÀÛ¶C †A1Ѭ)ÌD%$$!×¥r‰fBeÜKqRws=‹#mAš mô§f8–›±kvu¡áˆÒ ïÌÉDP^Úˆ‚2(ÚÏЕKX»d€µ•gB-Õ˜¥àÄ4‹c¡€Q”XuÅLR)ËQA v.Ï[ÿ¥Ð™ÂmSô€‚†΀ñP…H ™PQ hžÇàöb$Œ4… Ú@ÝØŠÌRQq¤µ,6FI’d·«ëD±QC,†ÔÙ[̺ƒç1u«_é*=ÇwÀb2!Ñ/J’bì&n©/5h‚ ‰,šç4æ¶6ªFEVq@¸0dBĦ¸ °É6œ=FŽ%ÀÑ«ÑoF3ó *€3¡ M™§õBvÆFrâ™—øps2ušN£ó¢*ƲSfLÌCÀM·“*Ód ‰@Έ3‹[ëxÖÔŒ9š$b< •$avŽÈ`j†PÒôW"b [UlÜUªyÁ’Ïg4«w¿bÊfªåá Ȉ!j+s6ÝÝñ?f Y¸¦$A /°›Ía¥(‚-£¥@û#žZ„bF ºqÚ^_ãil'~˹rt‚Îý…Q¦-›–úžµqàÅÊ…@ÖŒ›¬ŽT-KÀ€¾Ñ•¬>ÑÖÂKC ³Q&¢kfé&{ÀSB7¡­¹‡Šèl¡l,„D*‚HKg?KÈE·Ì¥µ®VÌXNØ:\Ê­4 d&G‘S¡V€e2Aƒ¤SÞŒk]l*ƒ!¦„ˆÜGQ«.q”èøµ3<= üÄNyÍ[Šlc[bËóÍ 5AˆEÍ»ùu~´pׂmô¶Ò$24˜óˆTàˆIp˜ºTIÖDy›yEæíJ‚$&dZnÇWYˆ ¥mL*% ÜK•?¶Àè[ª*ÂùtáÜnváñ[ß0%(L2„&;@.l…cwNhã! "ñÖýMA¶S3 J a/AÔÕ}2V´,ã^înÅd´ˆ° ·ÊbñIÛ`J „ÝBMÞØ5Ž»POù2Þ€¢E€By”›(: ¸èK°>ˆF/AP°\)(&¶Dݙ̨‰SžÉ D‡1˜±š[9—/˜d£Èc-H–e©k¦—£I¥à@ „ÞVJkHQ ƒ+6α¡˜TºcbZñM¨ž$Ó°Ò¤ž¶è.„P€n}$$ ÉK/’ƒíLÓ‚®YLúqE¹5€¥"âz×ô»± ’¨“P4ÕCZ‚ŠçK KùlR±Ô' V4¼ýU µ¤[3+b¦HÇFWŽà(”Ù… ;1=€¶*5HÃO#(iî“ã2À™"od €°BØZ¤k|ıj;±€¥‰v*ÕfR÷IaÝñ?g›4ê !ƒMºqDr¤ §oß¹z¬Œ¤Ò¯9tÁ§b³C2bL“§wUù ¹ša!Ó˜ùµ>±7ëJ†d¯Ûí}Ÿ°ŽÜG…'[ц‘D”ß#¸•Yˆ>Rˆ‡Vv@ñ8µ‰PUŒÐ0܃&ƒA.ñfÀ{ß2¡îŠÀb9ˆŸ?¥Ì€c­ X”¿J|"£WJÏ+Ê*6 ØÅL,HƒJà ".+xQR*°«Ø¤¡.¼$Ó¢ä³y/fÏ}„11-iDAf bñN×_…òADRõa C½N\—`1kuâ€n‘ S6©ÉrI1IB™X#"Ýi£†¢,ç³.¯ÁÝŠ­ƒ^ ·Ðµj,%”·¼T¥”…Ýdü°0)Ì)c@cÒ¢ :„0+ –PíKR+a}p«­µí¸æ‘Â&jwãHgÖøâJ›>N?QQ¬>+{殃Pd¸Œ&@€œK°@w)E6âIõ%öß´2’Q!) (Îa'뮳W¢Q@‚u@IBµ4çHî·Á0,çBL4fíãæÔñ² Q ’Þ³+¨Áë(R$!,d5lBÌ ²*(Iˆ€¦éñ3âÖ›ò"ÃÀaIhoÂ¥™Ö™èØi†XÍâßB]¥fmg¥U€Àñ3:íK)3– g¢†¿c¹.NÈj…i…ˆþ†I&ûÔ­Õ©OÖÜÆãŒLÇ,TDšÓ2lgzG€È˜]ƒ60Fs@Æbâ\ }3^8€d܉R˜G>I’7],i¹!aB:­ R•e‚† Hܶ5Ũ¼Kl†V Ç™y®¬ €±7] ÀÊžåÒŠI7#soªLC;*.PÀEå*Ú±À³¼R[7wh„dvð Ã+…‰j5£ù5@ŒÈ—\4k&Qž( €JE‰’ÊKØ/M:ƒ‘KË—µÏÑzàdÑÝ裆¥î˜;Ðù@ärÒiH8‘–!«—»ŸgêGÑöTâSÙE$Ý_•¨Ï)òÔ@|‡°ÅGÁcd|%+4ùÙõ™¢™!Ì«òÔ g”ùj7NiŠ‚Æ! MÃbI‰±|ÚŠd2¯ÍŽjä4YvÔàÙ.°óŠ„’¯d•E:&aß­ê Ë"Qj/Vié €5.£@ÜA ^ëÖÕ( ”E”蔂˜-Y±¾iH¤uå¨È8æ[BñW1!öÞbtÚmPb *C%,ñú¡Ð¥…Z\ø½£$Ë¥NêH° EÛ#™ÅDaByÀâŸ(‰c"Mé˜"4³ÌÄ¢Ì/Tá JÜ K˜£>!ˆ¸ (ÈO„#ah¢Š@E!imž*ZbÍJõnOµ1oÂÊÍÒú• *)2ÚÚÚ—9@H 7Ñ(¤°ÙLr3W%-jçQf˜P‹k§åkCnžN“ä –°|%–)ŽFšÍ)ÕsËz,$($#‘!«ôXQ¸°¶ˆS+`øj7o”`ZT=Yɳa¶ƒ¢U–wµÓòЀ—Xü¬fÑS”à«1/jQ!•Ò Èž`µ–Jsl•VÁ[½i`"æSò´@Æéÿ Šrq`øh2h6Sá¨.|ÌßœÔÐ$ëO½hÆéÿ Šr&q`øh2H/e=Ä¥”ÝæðBžTµ+£2z›µ—L6ä±rZªT5z#;“¨NDTþp@$‚ÄÆ±E©iÑ3£O¶ ûï2v&ò³œésIj®“2QêaØZK•%yÒÐè3 |Òç­Q¡/œ…D&ÑbÜDkPE–°ÝÔ·LSµ*1„K—Ȩ¹D^¶•¹K–}sSðIÝ_•£Ý&CE¨ô°Ñ@6Sá+FtåŸ\Ôðs*ü­D©=–¢ ÐÃQÀXÄ)ð•£:rÏ®hä 9•~V KÔžËQèaŠ‚ÆÊ|%K›NYõ™¢’@î¯ÊÔ z“娀ᡎ*> #á)Y§NYõÍ’u~Zu6aÊ|´J<3CIŠ‚ÆÈøJVió³ë3E2™Wæ 8º|µ ›§´ÅG¡c„¦U>v}fh6HwWå§‘.‰ -…dìjH§/‰¨Ò”FJ “P7H+ÌQ7!åiÙ”õZRYž­Iú†Éa Y!³nfhÚ*ZGD•9kz\˜÷G¡A•ÝB¸N’SBl†‚©„j͹™¤ÓÙWå§ f"éòÒ“ÓÉÒbšÉk#á¨ì¤pLÊðJÓçrHfäG™pU»ÙW™ÄÊ” ’è–ÈÔ·%¤` E:=JL¡¼°Æ™ž’«”©°îJƒ”œÒó9å_–´FéËâb—’Ï |4)4#Tø«æ–»êÒó;´éËâbŸ 0l š1<º|UÔJu\óš’`*Ž4%Ë¡­3- QtI!¡ÕiÃ%›&£z—°Z²›‚ðÚ)²Ö39“0&òÈÓJs)ùZT¨@&[\Ó’„9¡`Ý–gÞ„¼$n1{iPAž‚Ú”äò§ÙgU¨ nŸð˜¤Æ @Ÿ€è âi§ $K)içÌüæ’ •`ê­€ËVq- ÝEŠ´ï)¾³&Í€Á¹£•¾J̧UU¡¢­›lá©a:Ì–VJ~ÏÃ’c8‰ê¨Ý?á1QhXÙ RTÖ}sA³Cº¿-98º|µ!Óþ…‘ð•rY·344;«òÓ#‹§ËJN7OøLSB1øJ4#TÇ34šxwW朘‹§ËJN7OøLSB-d|%šªc™š&l²+£«N@ŠEÓå¥-7O'IŠhB¥¬„¢³B5Lr3M–êç–ôä¤]>V´&éÐô˜§Tµƒá(¤°ÙLr3M%”å\ò³L(EµÓòµ¡·O'IŠrKX>‚K ”Ç#Mf”ê¹å½0¡×OÊÐ Óþä ÄX> ,6SÜi¼ÒW<ÌÓEÈŸr×oõY&ÝÂÊõ›Dê<ìŠØò‰6 ©4Ææ LïiA¡ÈB9KC#Üðí¨©žÔZß>µêmjLâsx˜š<|ÄØÈ’Ó‡Z…#DAf ´ãhš2d¶|—*H¦ðÌÊKÊÐOá#.™iA\R Ñt_{ÒÀ½•}úZ§H £^“ŠLH ظBÀÔ`dÖÊü⦥”HòLqBå“0JÛKmÚÀ¡Bth’DA)iÔƒ4…hÛs'­YLÐú~$ç²ÔÅç¡„ˆ7KÄDšÃp"ËFh5À 2ˆLº êW_¤ƒ¢Y}UoVÖWdd¶K‘’œè\Ì’däÍBÖ#k©' ÜͪX§ÐªUKY$¤WO%Ž®~”î! ƒiá›Þ£n¢Ó*MÄœ‚µ*Àl‡\i• „ʬ¸ï?¨wÝ+ @V‘AÚN¤” Þ+Ì-/6-Ðnœ°9$›¢JÅ„#£;|0Å€li €D„JoІ܃¬`ij:XÄìX,ä¦h! Œ§(Ym9ŠTAÜI=i2D)`Ëå>0Žx´šŒÁ+±>̺¿Ú8kÁ6úˆˆ8WæSðÂRäºnU{¸lÔÂ2,!¦Í‹—GL%`jÈö°rÁHæ@K!s¨öÞµ%¤Ô‹G®:QE/%c/œÄZ³À³ÚËšwB±áD’ò˜¹Ü©”©9ñ‰då´à¡£vCÑ©ï’ÝŠ™´©LQØ@ĬÞ)~]Œ‘Gæ€ù(¹bC]â†F”ł΅OG0B‘0ˆÔ-ÖX•Lmd’*¾B§ÊW¢YNÐKgpZÞžJVbG"X\RŒZc€B[Š2ÃY dçs9VŠŽAÁ-¨k"ØÕ›ÁqJ’b.jÄáQ¸7x•Fà3 g=€G‘†¼Sjœ¦Ž°@3,0ê•%U`‡#aöøŽµöTxÖÿ\É 6MAq¹Pï¶™«¤+ £Ê28„s¦Ïo‡mGÝy…¬’]VÅc¶K€ –¨ ÈáÙ‘Øø²SR@€ê!ré =¡hrdA'$žT?@*_0ƒX&) Íà6kå{&}ÙëBot$õÅõ€Y€Ú§é6ßaõW˜K¸¨ZD•3”ïL°²i€-4ø-¿þ£‹t{Ú£vò³ø€([.µ'ͼ‹$¸†É¢è™Ä 7=‡H@3x¤aÊhÆêŽ4ZÉ`ˆÅ¼sC`æDt ±¹>'¹—WàûG x&Ý¢å K¨ujl™9‰‰Ž¶¦èQ/I‡MRVR!š=‡Ù $B‰JŠ,.bæP æ´Gió&ÄJeoHˆ-í ÜØÍ'.G!6 ä¼ÓzÄfe²éÂ’ÜX9,ö¡nPKÏå©`€m¸oäÉ%MA!ÃS1³,\Y€zf&›h"jÖ•M´o7ܛ대,Òn¸e<ˆDmznÉÛàÝn\¡©!‡Ûè#Që;×*f2I³Pl†'ëg·ØüNéàÝ-Pü<û'7­¸Ùº¦y‘c·¤&OrD~†4@eæ‚€7˜$Æ&Ù:“\ OV±zã\i°u¼û?ƒßÅž:Œ”@a#Àš†$‰Ëç$âÔ¿P¸ºÖ&tX<¡%¾sµTHUËÂéNI¿j-‚¯˜qK†¶-0æˆq"sêÉâÆÏÎ…‹®ÕÖÂÑ¡YzÚÊ®:û¸iì @– UÀjÐñFz¡PñnéBBн°p•Ý5©h€º„D‰M”/¹(7‰,ùSÆ ˜°”"Ì0•m\Â@„¶QŽL…€á²‡›MíU¶– QaMÈ)2 E…`Ba4MNÒÜ£aO2¯9^¡)‰Jù׊mKlJ UØ(eRiÓ"l}‚¨¼5Rºe«h¦Ô ¸@Àa 6ÅAÒ›‰’#„‡z"xP ¯´•va¶‡\{ý5êHy9ú>Êßì2*vJÉ“F&MBÀvøvÔ}¿–Q‘+Àoµ"¾0¥&; ƒ&CDŒæÐâÆ;O³ÂH„"9´á¹ܨè+¯D‹®H;1¾ÝÀšHD„¨Ð¤Šs±¾W¦¬@£²£ÒŠ€Û.–Þ~ZŽ%Óæûvai RZr® ˆ!b(AheböµF05`Âáæl@ÉB¡fX#7;RØ!9[ t"ôË-5–=%ÒUØ bRi1) F*èAv ¼4±üÊù:AeSv*kC)T• oDE”²íA4L19†*wì 䚤4jÏJÕL.¥² ´ˆdGiÅ£³An‚`%ñz…êsù1Ñ™‹UÆìr€2ѸqY4¸¡ð 06}£†¼nÓÊ‚½Õ‰éK ¬‹5nG¥EÚ Ù[ V!ylqxí›këøím¤ä‘"t¢ž’D’›$MÛäÓfíù˜1ÃÒˆ;d -¹4Éîã¼—¶7µ‘³ 6jèâÂ&BÑ£d½0RŒa.[RŠ ÇIAZ7·|W«~Ô–JØAQµ^÷(ÞH-nЙí•qæ}ã’ÛÜî>Çâw@€mð¹O<-Šqe O¿£nÀàŒ$¹‹“ ÇvCAÈÊÇ(9¦ÉŒ ¹a~žª5­t‚ÀöšºËɰ­ €ì˜ÍEÌ£”¼.‚QëÍØCدgð~Œ®åÐu¹Ûªj̲¨…wB./t ‹ ¾;Ÿaf\RiEêá¹Àì$âa’Q™I hNƧ1‰`±¹­1ž”\`¨’Cšbƒ¦q0½õ|ЄF†0´¹(Ì CR2u©¸Ï”ÁKÂKHBÇwÅ6¢±IgBJPì¢$$)Y'”}‚e TN-#Ä”„"æÁµ¨9MÊú ¸¹D¢¢À«Ú=hß$E-Ô¦0[(Ùºvæž$±!„}¢¶ÊIÌ}eGoßRDà {¾ô2 É&ö~§‡mGü8ŒMô (T®³ÈL8‘¨ZŸ„÷Ÿ’Dóž(ªÆOnìåˆ A ÈÖU¤´hGz˜È 3yæ¥xU¨–%™.¸3SX( 6Ifu("ëT¢@o§IVìDv@éŒ7GÁ3AŒµÀó[†õ_À@Û7ºn¬Ý¤¾pÈ„ºN”«43jRNÌì–°³ŠR63KU@ »8n<³FÖ›0²¼o NØ nÎóJ\a!-˜‹Å= 8’N˜m)yˆ@É ”cxUÓ=Ê´@œQuÛŸ³pׂmØÅ1éŠ%˜@Ì€‘))©˜1…¿MèÍbœ ©Ü‡Ò`ÖØp^ó¡÷Aîˆï怀K´MHÝHá;|7Ð$ÈÏ%~;ˆt°pæ0m@Å¥9dÌ¥Æ[ö¯¶¶Êãv6¿ÑlJ˜9”Æbo­Xʬ(ŒÌ(Ý9¢TŒì–ìI~â4u¸Á$ÎX#­õÏ ‚P–¬T^£13x›Óv ƪÂ&Blmš:>$L°œ3ËQd dºûT…ž¦ÔF´žc8°‡A5+Mqºð0&" ¨»8áôv¡„åƒt4¹šnÃd†á$‰R9š ! ù’JŸh ¾X:ÚŒ@°Çg³ø?FÝܬzƒåÞRÄÅ$ŒÑ$V]çAÝñÜû[ï¶údB(@UpRR’‹Ä&ʆ1K¶L$ĉmRpð SÈ¢`!³$f¤ýÖ n16íOì¤r´RÌg\R h€tÕm’M# ŠO¨…¦1ÙùNâ@ò#] :3@ uáq·sÅ6©]¼`^"fwZ  ]‚Lâ²I,˜jâËTÐâ‚"$Áu±qªÌ&A0Ã{ý4ì[À%ö¨ÄòezZ®™@ª6Ù).v -~)³àbó£Z’.¾Lê³½O‹%)€‡ÇÙ{*´Èæþ—/g¯Ïj«p@J‚f‰ˆzwð14TÑлW¶il‹dÅ=¿M `R ¸Âˆä`^-nï³ø?D˧ȤtZ*„K"n6{–J$†&À+8EZau UßÒ;Þ;Ÿb‚@ Z›_q˜4pÁj=!ª)NVè¯äÑÙΩ¡ ÒáÑ’MDQø–o¤ 0Ô0% A–4ÅêiR;¬tD²6K5lZ ÚÑk1ò/• ‹1Kß3È5ÒÂÔpk×i`´ÅâsNí\™LÙ’2²°TKµa¬¶7oÜm‰ )‚@–[09©¹¶±P­ì¡VàȶjkÍêÈœ«è9äõ£gúÇ~eƒ„®ŸLF”„IU.DM!ŽlV i/~ÏÃö¾Êßí<;j?ãynjkn“!¢ñ)çjŒx‚d‚E…ÀŒECgRD–þâŽC÷$”@˜T)>@pÐy˜ÐCPVéf ]0Ôk)-c7Œå+A"ž@2 MÁjÚ‰ ' •Ò`ê²`ˆÏ}Þ‰À¨­Z$½{†”ì*,Ìu¢wdu ojµYåEI”²Ú]FC+OÄ}£†¼nÒæA€È’ÚÓ bãq…½{AC$„ y¸¨ Öˆˆó¶E]ä%q0ªdé’ £ Q… *¤ŽìÒI° Ö$pæV™9ݘó’‰o-J¤JPD»»½Õ *˜©  I’åË)“#"¤>h(fÉW™šŸ´™Ñ,{.Õê¶.X `㸱w ®²¤DÂÉ9›[½«@&Ã0FęճÜö?¸f¥÷±€8H¸Æ‚5¨©BÑ´Ænç Ìòò O uAŠIfߺŒ0¨ar‰Ã˜™.=GdQ ‹½ ¯"£Î£b\ê f"X 0eñô^Z.P°Â\¶ÊïÞöéBlØZ(ì9žäÒùÑ^ÿŽçÙê––ñÈ$[¤Ô)Œˆ( ¬Æ÷ì² O2 d¬sJ+)H’He6„íDEY¢åG-vŠ`âýò)0nÙ»ë\ˆ LqYï.d·b†s2ñƒ"ˆÌPT˜–Vrª* IŸéÝHæ(DŠ{N•l–Ôàì•äŒÉy^jTº`X²•ÊPªBh‰¢2&ÿYD¶žTZ—FzZÊ ë~ÏÃõÿ @ˆEC)0BL#¿ì¨ñ­þÓö£þ7VÁÞ{T`±uº&ï%5ŽPð›AwV­ .¤‡ŸzÀT@ØHŒŽ æ¦Ùª&B¤ÈE1”ËIïI l)·±Œƒ;$á ¥c"V EýsCa/@- ›wèKìKUlרA‘̳+„(ø¤]ç©c õRw»°ÀMï""YÞÔ&Ù¾àNžTíV°Z&ÑlÅYDa6:¼é×úØ ¯Âk;´ @JÁ,Îi—Ф "RÝzŠt¶+éQÖÒÛéßdá¯Û¿ cK–H÷£-̦‹é½ ·abërÈ4V04 ¡¥ÆcmåÕ‰nƒy¤ÈÈ•3F8Y!¥‚1N1˜KwŒÑ¤ŠVŒ™ ¸•ImíÖ™Fü§VD˜€«(2 ÈôV6æü”my‘pPÄ É{èÔr„Ü^È !¹cPO0^LŒ^0è4ÂÞAˆlj੨½6J“# -é. ìÜ0«Éïj,é/%,2ˆŸ"+#ÒŒ4æ@:,i>sS43 ¨’ÄÃ.Îe\?GëÜA@˜8YiâsR»‹yYe·d!´nw}Äî ©hÞ@ÝpÂàš!B`@[‹vÀ®±“ÕÔóCRùy ˆã´•§*BI+* “¼ZEM×»8,5]±ÙŽá+á•ìÍUUƒ#jb˜tˆ’é‘!¥ÄTqÒ¤9Nç³ø?J2ÅYIÄæt$ÃÛ7Ý&^ëßñÜû,O¦²†¸[Î…‹›Fm`‚>R épH–Œ°5HFÄà  n%'))—D"E´€cÜQ#æˆà"@€ ˜ÐèÉqÀÓbV ·y…ÐÙC΃Ka%±A! g}i"J#r~F”ü̦Y ‹|ÂúS·ÉS*Ïíîø¦Õ’B¹$ÒÑÞ®4å¸!‚6T­¦†»ÕÜD¤q$YAŠ;YƒQ D·«…’€¶%ÕµöI @u^à«`X¢D#p‘|šòÈ%‰ àq[Þû_eGoöžµò<—~ß2ad Ø¯¼‚â¥&)eÅ{bˆ¾°.¦A¡4@Å+®i*‰±6* úεvn=‰²¨¼ ¼ ¸:¥ÃÓêÁA˜Š0#S;I",Y/h¤+½[Œ!5$ÂڤŒ£å¤¸8,4˜ÑØ-“ 7+UŒFg`¨ AÀD@‡N·‰&¦bA½D„A£æízȲ[<ÔîM€Ð]¸9½ô¸**¾ÉÃ^ ·zN€1‚¡`l¢3ÐâñrÒÉY"iC1{V\V” ¨2TFÃ?_ dKD”°ÁF„­I€&›î …f˜pF¹Í/±½„ˆucx© D¤$`# ¡bÁôÌ/çV€1b˜5éÍI¥"T '@"ÁHÛA’Щ˜¾Õg£ŒŸÍ¡£:ö A´`ÍêȘrAÊÀs¥C…Ë€r]ÖÖ3.¤ Le¸1ˆ¡¾@¶FñÇËVN,lKd((jå‹ÝÀ+€%V5Zœ¦NÈJI0…¢·wØüNØŽh"å  Ëíb#ƒ¸¹€€À,’H'kÐñˆWPDO µ-’ïHÖöp—&‹)¤=wÅ;e7ä¶2Âfø«ù›dEx"5Ò­×÷¶ DÀy¢,…Y°„ã5u2²õjKKÜöè#7L+ÂÆ cZ´ù !`ÔÚ¾…® DÜJŠÐ¼d:ØvcnÕ Ú,8"Ž©ÿΊŸD:–š—t/dMÒ¹µ‘ªL€ Ù ÐtîQ@)…ãÖ †›| ŠÂüŸ!aÀPÕIpJT…Œ‰ t’Z2D¢4ˆCR ãÖ´°( &Ff3d³Z~œ@1*Ä2Ò!  s™±› 0ï6sVr­H2]0 \\dnR=á Ô ,­ éPà³³^À@”½ÜNÄ--—Z0ÀEˆtK‰„~¢÷@Pœ4Ùh”ëÍ)1^BMËAŠE.]Y½g/ý£“¡ñÀ޾x‹ÒG  *È,’òC£zFÉ1G3.ºÑvµujÄÔºÖf¯Z!á3²ñü§6¢²ÉÈ̹C±blal³M(l6"$Ø1xÈw±BЋ­¤v{*(Ô)¬Øg’ˆ’rlй è ¡$ `,a˜.K)ÐOz·Ì6–@ƥ㢯¦Ö"ÄjÝ:ÌëØ¸Ék-Þ&`¢®Ô¤Ë ;˜M“D¹W%@wAäÝÊM䀪à)iú$ψMÕ`/G‚£ÈÂïÅe€ØàóšÎKQQÙ û}Äì<àB@³:ÝÚ¢{âi#v#¶ ª;[ÂNáŒX'ZtBL>€—çDù Ë}Däh.ÜžeM£¢Ó ¼hhAÞöè˜ý൰Û˰€=%Àa=xÊŒõ²ÄÈf'ŠCL·1!aœÔ^…%‡†”Ä 6¨GTŽ´¨á Hï€ ­ýôŠ$õP03çÕÉ›²ßùQŽÏΆ.P&:X„QM[À‘g=-SG0I¶à\iÅE€‹Ë7»@9/rXÆIÅl­ÛÕn2<›ª¹Eâ]üûLË$éc¯óZˆ©ob3š`Cq0í@ ,) g„0Ki(*B`‹qU[ªÝ[­`t*ÜxD°5³{T;`ÆEû„ QMŠp’Öb ˜9¡ æáÙÝÅ\Â(O”Í ‚£š,#•Hˆ›„àŒÚ†1Þ{#Ÿ˜‚ÑQà¤Áný,½A{Ìh}!ÎtˆM(S"RJ;›40 ’0¡NTKi½è.Bn;Q*sÙ†c\Õ葘…±æPr D@ÆRêÍèí¹S¦Ûì¨ñ­þÓö£þwœÚ¨à,hÒf´c,` e£N€@TÑ Æ‘+ [p«äîîóÚA!”bÆA*G2,ÉvKö…8tÐNÍÜKàܤŒÚ{¶>¢RÈ"î)Y̘'D‘“Z ¦Dë ñPp¢BRMÌïøTFbÕ0EÕ˜bá¯Û裑䩣JF,o‹[.+N/\²EN•V^Q`TN ¯±!zd!P˜©¹“bî 1~4zð¼É)kªÊj-l°[äužºÓЀó{ZbŠ¿cBfHBѨy ˆ 2M”¼O%0”Ð]ÙPŒ­L º…’&¤ªDny4€xb z±RÈ@86±:v‹ÔbÆ»]pa â‘Ë 1ªcC@ÐÅOu"dX6C;P•¶b'%L“Vö½WaÉÔ³D^”r)(ƒÏ”•s(¤™À]4:Hó)‘D-Å1E(¦(Ë~žx«€l [$mª9ê –öIÙì~'c·¼€“^uYDÞÄt𲆰ÆÂ>gÐVnÇ–Y&,ái• ¶žÓ"Kåàaê1§Ùü¢¨”†À¤)tãºð•˜„dFN޵o±,É=Ü9SŽ÷ŽçØ]Z)—äA–‚äÍtDj˜Rw†'´bƒÀȉ»\CPm+1º4½'ÈáÔˆ³„ÅÍ{èÆRŒ¼-éïL±$5Ûš¬!ĈEºov5Rsö…¸ÁXZÌ J"pM7‹CŒq4U”-ÌBYŒ†]*j"ÁŒ“ÈŠö|BPK¡-ÚBà(þà8Nî L”.™cZǘ”Ž&ëHXd÷X5¨M"À8-&›Ð³¥—q I›ÃÙ‰·fõ”x!c×°ÉÁ´KÜf[èlvì&t¹ÀÊÔ¦÷£F”jp,€n°0B`xï"È\¶ˆpÆCz9:ø¡x¥ÝU Š :Z"Énµ9åÁ$`˜¹Œ÷}•5¿Õç¿áÛQÿGÏ9ˆ›|ÂÕ*IL°×^µ"*áˉ&Œ2L#q'¹²ÅÁc.,¿'a¢æ#î(¼U…Â,ÄK¦”¥]û‰}32[„à‚Q žT.„HÔFÍ%‹.é¨Jùž´)AöPKªšLï,wHOPÍ› pAlsuvŠƒ‚ù˜,bóÐÂ%I½&!Ñ0¦c¥I½ SââÔ’˜ž}>“†¼o¨ÜJtY8Ž@žYÈP©"`O‰´’9a´Ú (2Hk-%‰ÞµÉSLE ,„¹0˜3qÒ¦ ân‚ ’ToK@"ØÄnºt¡cÔSâÝGÒ)|ÞŒØXEÈ6Û1…Ж6ò¤î`ÎðgQ:T6³Bà12˜¨X¾8³òŠŒŠrýBþ±DdÐrØQ Á|æŠ2ƒqG$œÉÌp*ÄìHzÚiK*x ©…­j’¢ YBïŠ^  ø=j¼‰E!µ^A˜[Z¡å\—ÙÆ(Rð E ø"/xjQ€PbQbè¡ 3±‚Qn7@ù¨CÃ5U@·EXz ªCì~'laÅ1¥“%NN÷=#"v‰šäéÌusWÄ^`‚¡$™E²z‡AY»ßöè°•Âèi$¨¹òÜúOP›÷8Ê÷É I%„„šzi%U½×\iïÏ´ÆÂIi!INÝÇHv²£Âë Bì@–/ Ìd ÈŽFƒãµO&µ¦à]e“ž-Pl" KAåau¦ROؼãÊÌÔ 2RòĹڲñóoÖÞåJr’HgjÑî-Ô5gÔñ“s-%›ÎjK`É—æùCšB Ùœ‘·'mBVÕ-CÝ!NN±±GÎ’A“ :'¸9¡ p%­I—–dbÄ‹$ä£WË2eÙ ¢€Ê¦%¯FD€¤49_V¤æ¢` l¦\Z{‘°2q‰QÁþýG†ŸíŽd¿äÐéØ|Þ8\¸³ â XXNÝ©Ìfšd²6I¶Ä1c·ÕÏ3-ƒ§Aµ‘.$BÙhXdbÙ¨½J-††YÕÕîû*.8Á ¾oi«ˆF•˜Ïƒj‹Xd$Ó¤C¬ØÍ(RKHsƒØ4ô=ŸÁú2'zQÑ *vFˆ‘0Jâlœ±-êÙ`gr}ó*ÆLÏ6¢GÆ‚bbü3ôÓH!)ŽÄœ¸K&lîBƒYQzÆÂ7£þ&»–†DÁ < ڔʌ ÅÀÂ5ngD=–¶šRe¿Zq‰…OS sPÇB“åbSµI„‰%uy‘º¤|KwÞ¢¢tl±!±ÄÙ‹z¸N½&Q   á.ö ¯† yY- &ö¥H·,\.(dc·8" ‚·i$2fÒqr:(l91¿jçZr@IV†AèCÔ… òN(¢’T½ eFÜUº@Œ á)éJe‰!.¾ÖˆŽµ¼ë–Àܤ óEÒäÐiÆi˜ŽZÓ$2¾Qq£›T„ÜX„ëð6«*˜Bð;0ÑÍ'ÆÅ”¶Ãi½Kˆ‚GŸ}»8ïbçqY#D­ÊŠç’¦enñ0lAßöTxÖÿYg5›šYîøvÔÑóØÝei–”䱡N2‡$©ºU»Ïz'H°À¤h-P¹›:âFDIg'cЙ!­Ä&ÖŠˆÇbË£]’äŸ<Ð+rNLßS%7+å‹Ù³ 3º¸€0jœžQO¤.Tfò“ HÜË+ :`)·¨¦€Cë8kÁ6ûODZQ€Í¥âõg¶´™4Öµ1A3û#JdÒTl=¸ú4 ¾Ì¬p6Ú•‘bÐ…& †–>Cg:HW€¨ŠŸÊV+ $’O8¢ ,h“mÆGR`_hža,ÑeEº}å ë#Ó—Ðöß̦œp.jTª"¤ô¸*&Q»µ+ ÔÊNNI‡±Ùop~hÔR”);±Sp8VÖ$ Ïc^UÅ”Dš¦7êÉÿ¢ìÄñv.„§^"‰X±{ýgð~Œ.±D‘b˜¤‘”=pVáÍ<-Št%ï¡Æž9T×¥!,Š€A»*Ÿ;ýϸÙÔô𼈘DÂ4Ћ *4.‡eK­$ Ñ—D½ ‰".4R‰ÌÍï²ãwQr ¹µÖ¤—J8¿yhÈ¥:J !‚I4w¦G~¢ÕS!24£d逵bÞQÚÁH,(d][©Š;È:À¯Bü¦5q"ÒjsJX U£ˆ ^ dϘ¢ž'8gšÀ[€ºÑ«Ú”hܬ‡iI‚xáòÎh½Zm®wó¤+¬6àdWØÕ*ÚÌ MF$Fç`²+ˆ0U*J õ¤F2j`(fØ R…’R—a–Ôó¥WË‹|ñN˜S´bðg•ÆeP9.—ªˆÄÄ•.Yc\Z‰²±M™! Í ÷4²ËÎEùÍLZ°ÃËR‡þÄdÝ Ùø›”~iæÍí‹Ù­9ÊÊ|Ã!-4[(Ti¸VÇ6úñw@Æ´e"X—kkL€Ù”+«kÔ%\‰`±_b98e¸mΔˆˆw‡Lk×mëÙQã[ö-‘ˆÀ –¡:"$ a¹õ !ý5!Ì3)„!;¾µò<0œ­$AÌûûÈ©@ζ‰¡’m5$1o„aoÑ6—<°7.¤¡VÁÍJa$ñD"¾xKFbÎ)Õ 1]$'@n±Nå È0 x#Õ{Z~óD@ \d­~b÷U¦ê­×¯dv€a$©¹©kšÔËp„Ûa. }W ShBKk¤üìý§ŽãØãÄfézŽl„É•#w5>Ë\Ž\óK*x"hg68µ/²’»o¬ùSë¶wÀܲФÞ9eÇè©ò[Õ©ÓX"h„f2ˆ7¦r4I@Î=hçÙb¡;ᨖ ÌaÌ}Á•P «aæà»N±UA&Aƒ­Õ«¥:r’v[ozw¦ eÓtÒ T€#Š4Øo·¡ä)¢*“oY´~«º óNj#ËëÙ‘lƒk Å£‡%¶¦±IáWUu[¿KÙü¢F pK9‰âÄÚ`“Z;½>ŸŽçÜÃÊñ$•Œ ¢Ü¼˜i×)°\ % )ŠÊ´‘BâÏ¡¬9P V„„ÈŠ—\€½Bo{‰b¬µp•` ö|T¹fÁ –Âià ¬¤¸˜uŽÔ©äàÙ›NVi+¤W| %ÇNÿœŸ!u ¶[ «ª·ËØÍØ0DÙXÁ¤ùPS%ÑÓ 'á›$ ˆîn¦À”@ÀDBC‰Ò(E( ìjmt¤¤º«BZ¼W«Z5Dˆ­)œ•¡hàFÀÅ-Ö,d,6ìŠŠŠŠ*** vEEEÀÑû2Qef!f›Cù¬XafŠr0¸¾i ÐH"ãm±IÆ  ×¶£Æ·ìÊP²D“45\(Õ%ùy^Í 7@–ï? Ö#\¶¾ô|%D»ŒÓL#4 ½ès‡ÈÃÜBfSèïøvÔÇðÍéöÐP#jPö!ZãtЈ¤ Ø0‘I{³ªÌ\E€j¤ ­$X–üM$á`¨gÌ%zP"¸f…|˜ÑHÑbÉH‘%°Rã{‹Dê ò!„y;c@¹)grniÒ;ùuþö¿a…š‚„íš1’aa耚¢')arA(õv ï&>›†›Ÿ‹í!ñWÙÇpI(%13ÚÙæ+ ƒŠ-;üU¤ø °m{S‹™˜%ôû.1Ô© E’.°Q{€Y´u`ªrgÆìD,ÄæÀ+6–ˆLûˆ.ÍÆýˆ$lC”D¹L,«ÐT–0¬[c¢¹fVÃOäØh»`Bbñ=üºÿ|ˆž;N34 ãJL’¼Ùn9^ÔÒƒ0(´ì%­ À€][¡;M©Gõ†»ÌÝ<Í!Dˆá'rV(0Â0š<Ó†¼o´ñÜ{å ™:Ú¨¸TM;9“]’JV) âëòsV×Åcܹn–(0h•]‚šl¶[ŠÃ騻ùTf%ËMšŸZÜAÚ¨!±µø©D¯BÀ€ì›<”[×’I†GP`A!Jœb¢CI®#Ò 6^|éGqÀCƒ°äÞ®­Ì’J%•Þ°½*É MËzÐ ÑïaŠp¡DtJ•ñ)€/&Á5†d‚ Éf1@¦we®ÁLD(m¤ *ªÊ«*­ÕU×êû?ƒö¾;Ÿx£ŠK#LÍ@¢` ºC&ù˜ _ý)ªÆDOF•â 'IŒtÚBÐx]%ÄâE®’0£¿`Bá2‘`ƒéPIèXÌH©3r(ëb@7¤° „Øh…&3u+a‹Îz½Bm+^ –suáJÙ夡2X-)0ok&ˀ©#f¤åjj±sd—,¨ˆ¦r‘" CœNµ#kˆˆU¶ Š±² g€ƒr¦åÀÇ ¤XT š” ZÐ-G¦½„¢À"°Ë1@wâ !K+—®ÈP w' K°—½-–”¨‘´“—krZÝÙlr B$Ü p•9pD€è9ŠÒ09!¤ÐòI¢;CL³ó^È-0^Úf¬T+1Þi$`eJ¥žû ( …˜²…œQ`@€n†æŸBdœÌ’})pQ¬S¥*T•y{ƒ$‚jže:P}·iv…´ìmM”¹…DÎ%5ì¨ñ­û±,¦Br‘h&“®i2ËèTÒaN¤êHbôá³7 `àýºöøvÔ}Ϲz}·vã£A“‹öDAa= ªž M˜RF u‚,D4g¸¹¤Äqç"[kCÀ…©¤…Ž¡&‰OüÅr°èß˯ð}sÉÚU”·ú°H¡!bå"tJƒ(Ĥ¬^¬›¶A¾à°emNðM¾ÓÇqú uQw ®'a.îZE¶–ÙD¦»ÃІjC•÷0mnWÑKŽÙH ‹,Y~×±ZfÚÀáGJHVJNakt)!*: nª—§E¢™ÜbáF%–éÚ7E “ ’Þ"ð ²²…q•ðtz*aŒù¥T ÇObBDt앆–.KÅê%[·ZƒNvŒš¡ðfa V¤Eœ €ËB‹7Â!dtjBŒ9l§MX§5`‘ï–'1ªÀJJíB $p”f@¡×nÁ‘ bÄâjÕë&bÑ1óRl:[¿ã¹ý {‹fq ¢âTÀ1ŸhË‹ÜÊðSL¡©ܘ:»ö$ñ)‰Š‘ünÀsÌûQ§¢Ù„€=)D½d…˜M™5!ê%›ª_‹ÙH7¤@VFL¸D#QÜ;]õ]¶”-kžb—7Úh~Ì„Ÿœ±‹‹3ŠyÒh †VK’^E‡PHa‚¬†ý–p¢’’N)g”˜2˜—LiŠY¨‘Ò2£-²ÑöÁfƒPÄÌi[k)[ˆj3@ElAðQq0¦é¨舂á»Jð٠쀜ˆÈŽ£ÙâûÐÒ€,L|P@c {ni^¡æXdÉ%À¼¸@–"AFjÉ}%ÄâxÂQá)ˆ…²ÉªÄ‘"¤‚é‚3M]žá@H]BÎIE2ZŽ’ñ Œ»‘-nV$†‡NÙN;e‡‘el}oeGoö`k×L™ç©8„-Üðí¨îÝLD¡‡ä¢V> gc<ÔaL̶~}ª\ àr¾ój“Ä2LK9ˆ/ÿÎh³O77ìÊY¨‘@A$IS¸ÉJˆ»¡O,”Ý%îýºÿØ\ÁêQÄ‚Çìz‡¯k†¼o´ñÜ~ˆŠçr6+[´³o~{]í @*6g ô\E ù”ô{ZD‹6°E¬ ¸¥£.¸Í,Wu"Ìã|º³ ÞdÖˆ´‚b&öAž(weÑmAiìŸ¨Šæ $\fÝ(âI”‹ê¤&¥­$]#Ù6:Öš bL¤«|½üԣ‛I¤A %Fn¹hXslÁ•ö©+Î4Íž2ÜMéi×(³=Gåš¹;t#^7zÐô\VÆ.ëÍâ —­µïxîAÜê’/)“ri C‹T0­í‡Ä|VMsC› ÀHHlsXK¿ÕäzQàJ$ê` ŽC@#¶¸^±%"ùÂT1ƒ:DÒÓdZI€H344b“`mÅ›ÅPˆ]#±2Â&½ gŒÜpß>U˜5›ˆ‰",#50­á$¹`ŠÃý‚ (D£uîY ÀÒ­±KkEîí3ÔÀ\q r%Ë ÞœÅ‚„`”A‹Eqöd‡@lÅO½VÌð Dˆ–4É!à@¤a¬ „ªÚÅ‹ªK(…„b&Õ.°ƒ±©~”¡Àƒã(j%±µ+D¿a…‚ì’eéL+j)þÉFC"EÖ—f9²´íˆT@àñPg p ïa†Â’@X%.ÉyÒ]x"óÄÔI5ìaž”HL4°¢c.†Ã¡E*‘HŒÚOjµM `HüUÛh™pNÇ48©%±{RÀ%# –ÙúÊßí<;j;©$©°YР̷ðX&,ZRAy¬osÁéSÈf’–P^oÖ¤Í" 7=¿ãyÐDI#„ 8`ޱăÑ!nÊm&ÅÙ'[ÑZÀ„$l±L‰#c„å¾–]ƒ»«Æi,¡@pý'0ËÇjá¯Ûénñ!Bó@G0ŽŸPºDŒµ{#ÉojnDDËMö X²YA íj* t¹ÜñÜ~ŠJÜ¢zìkMHFãØ ) ÊáÔˆyïW/FŽ”#9wډ²Ä𦡱B{ºš‡Cļ'¼:S"w ;b£±~4à:}5d¬lºL– €)@—w~Ïgð~€ <%ZáД%ŽY«€ÆfNÚ`E ] LÌÆf¢*Æá„Ï4¼…ÙM‹C$E÷â‘í‚2.—%ŒcèxîK6Pî#¢7·Eh%¾Eo†€þ²†/4 dÇ.³;ÍÄÈá"ƒfÆboQ©\å(ºÞ^¥Î ¡Y^P¬ØÚ”Êh ‰sFC*k›²¨Ä.—SØW¥ÔMâÆÕ}M‘yvñ6,ÏbJ‰2"ÝjÄbV e‘ˆ)“?t6H‹9MIRVC`ÙEý(ÆKàÁéD3äY$™ÄC3ˆ¦Ó @K) Œ9C1J^õYÀÔGq&‘µïJà%€X;ü”¹ó`¥f±Mx¤`¡Å(9À(žI³*±íSP“]—•¨­jAˆdÛ:Ò DH—µ5BÑ!{à…R+Äp÷ Fá "‰;nEª® À¬–§y`#-#`™¤ŽD0:I0¤:#¹‘( dD"!½óÔ¥K†fÓΣ\,“–KiPðhš@„Á¶÷¤´L£›T!‚Yƒ§ÐöTxÖýÖ0&\.r tœÅÜ¢ñ%ñ|ý\ ‘ [ÚœCA(ÔÁs¡Ýðí¨îŠ@n"B=j&üŒE¡*é`ÔŠM¸Úg/:Éó·4¹`.^š“e*çÀı4,ìƒjX“„za5¦ I¤’L1 Þi”K_¡—_àîdéIRÐDk{É%Å¡³A Ï™ð6µd TâÃ8˰bKÕ\¿Ebî)ÕKÂ:¸Çk†¼o¤ ¦R0ÚÎ+‚”Èó*0DÉ iù ÒP¹}Løò©u–ŠÀj€¨‹t 3W¾ LÞf8ÇsÇqú2Œ‹Ðo ·”  "EÀ‚Ûq·bMšãMz‘Q–"S^ÁñÜÒT&Ì ØÒœÎK6Æ’z r•\7¹ëh¬Íñ¼3Áâš‘„ 6zwVò%é‘òîJå[”UµEO‰Ñöé„bAIŒMXä]„2å ÃK‡œAƒ:iB¢–B$ï}~‡Žçß•iŒX ¯ŽEáÃ…‘÷¨„¹o ¬Íè4’a†Íê{ìXm„¹Ú¤ðÅ%’Éj¯R G™J@@F,´4‚šSVÈ çöëQú56ñv…ÔZ—”Ê!`o(´ý™Ôµd ¦ååt†k*‰s-t‚±BHB¶j ²§Dlô–jI“.èÂ3W2‹:HV$»¨_e`„ _@áeˆ¢ýX s»ÊªvÉ*«!"bE¬ù©|ˆéš-YCt"sX\ŒÖêÐ\ªê¬«ªÏqXšh ˆÂÛÍÕÖ¥3'ä”™ò¨<"Av[XSáfƒ "@“æ€Ô›+Q;NÅÖÉ“É"†Èy*0ÍÍ´q`ÄO²(BHòŽ(f€b ’#˜R—Ôì\R8©Üé@B"n&L87® Œ\ñÅ»DºaPIi/4 è‡$+],„"][®mSEä.íl; e_4悳6á1 ,‘†)eŸ§ì¨ñ­ûÊ®îel†,·Ïj3"8Ȇ½¬V æ ÄÒƒ¢Í2E‹HµÀ å¶XîÛÉ‚ØÁ²oB´ Ò[Ö…iµ.ó]5oy!Nb ÙFQ7ŒM]f'“,¡q›¥}‚“5Bˆ–æÌIgþOž7 e›N‰;KÜN] !R"¬ÆàÖgÖ²P—.1BcÔ(é 2,@‹ªâõm÷ž s– ©|jIe+kˆÑÀîe×ø;‰$8 Uo P"’/abÔ+¨¾ù€‹‰oW4Ê–pò D ¾ˆu%ŠL†á<&jÅ€ÀX:v¸kÁ6ûOÇèša(Â$#Ô¤EFì€W™È÷\D‘+llÌc ¶â„qÚ+2Qe³Æû×±ø„€—á2ûÄ`¡x" K~á<‘°# ͳ…:c²I–$`7»%O€‘+-¥C@÷N0ˆ9aœf{¡½R ÅÐ[M\fò‚d³-’ù“^Ïgð~×Çsú(hŸP$kpX:`mÅMò Úb.]æ®Á'”+‰Ñ´Ôp‰õ1òœBåäVªG­ê'U8ÂmòŠ«y%F"²51«ñRv@" +2£¤Z Scb‚#a&^¢"h%"A €¢n^S­ÅЄ±[mež¥H²~ê7ÓΡís¤x ”É ^Ô›7(+_54’ØBrX°“\,vÔ‡²6jùà]LÄDI b¦€!NUC© ‰hj„•¤ØŒýdj«B t€AmÛÓ÷g$‘¿ÇÓ¾@œ‰":ˆ‰ XÉØ#9ÄF—•ÑAe Dœµ:´,G, „ú…!Ç(!\¹úÊß½Bæ ¨æ¢,à&“À &lìÑá¶@Ùi,…­éEÖ’7QPœýô=]ŽÐ³mü;j+8©ŽÙÿ™çWB¥ˆgÊ@]Á š$0ÏRC´·Ä #f€F, …à9÷M1Ý¿ªHŽ,[²h<Á•˜ÍážÜºÿ×AÊ´.R´Œ Àw5à›}§Žãô–xb0˜N«<‰§cHˆØÖÕêéÈé¡w‹Ñ’$¤¢\ÉÔžÈEô"`ƒ3¡C BK‰ Q…‡aY±qfI­`æEâs Â÷C›ÕØ£Û(Ä‹"Ŧö¨Y>âà`\v0 )K¥õsi8;­Hz£|·?Ê0K+#xœG@à5Lq4x"YPe»@@'F„n0 Yå{p€³c“Š¿Ç‚8ŒziK«|”ïªo­䢪ªæÁB? Hÿgð~×ÇsîJ¸è…`Kbï•^Ü »‘‘¾O¯â›VC±g=Ćø¸ž•w(ê› ŽÂ„o$¨Bã`H¨ŠBIôrnv®¥ •aB7U­ÌNjz£Ñ/5È—°&Å·A‡ÎØ>MÖ´GÑîgàY­)c­ÒŽÔHJZ1‹Ã&`K¡•Q* T-|ö‚4rÖ³|4ÄbáAÄf*`#T÷\5à›}§ŽãôHba!€Úòë ÂàÛƒ‚UyUº­Õºß±‚'£-À¯yÓq f7J-‚ßu3?ÊXXI’GLQ[Ì È‘1ŸJkIÄà”­ì6톔DÆT%ó¥ Cß«RX&I9›vL´K$²Q¨„MìÄÒ\صÀFè‚£˜SËH¶‚qmcÎò@·ÁrÏg½|Tïªõ=Œß5DJ‚üÓî‰n`a‰g[ÐMЇÜǸåâ‹Ä¶/LÓ6›tB&$“/ÙüµñÜû½(î$—qoKºQTÀXEŽ]i%Š–X[hÒŽIE¾E@µ®mF>—ŠmXäœd¤¼© De$ÊwfÜÞÔ¤ bþ½t)ŒI€"&IZÀ,crÍû7Êš,‹}*ÔBÆÆP·\ËZtÿU¼¿@w@´¥SWºá¯ÛíðÜŒ-˜…ζ)¢@2f$_¥0€±}Zžº­©pv³|RL¡2ZÝͲ&/Nݳ! e+-†0Ò«– Ý›sxÃ\ßÚ¦nö¤Y¢%ÄE\‡ŠN„*Œq¢ä \º¶ƒÐŠe$a„‰#² 4Ó®Fá7‘‹4ÕQ»Ë‘9°±}*F Æ„‡ÊTöH»lúTó­àM! ÞËI2ÉóËa0ÚjàÅʾÒQ²K2e«é …¦§©nëÜ‚<¤Ôñv±ñ/†9£Â! ØÅÃx¼}T Q«Àwœ5à›}§Žãô1z²Ð¥§ú®«ºLb•¾\›)›b¬€:TbU„%X6']žj’^¹šÑW8„ˆá„;t ,.µfÁb+$C°E≈ÁœºDœ™˜äi,\Hô!yÊÑf”!@V@Í’â"ô !p5"–$Ñã 6†HId­*( Õ%ßZnÉÚ‡/G ¾ÞÜsÀ‡Í7;æ=Aóî°@› eD·•— °OªyAëPK30Kšðß~³KÇ2 Ö EX„èÛŠ¿0\ŤþÑûÄ’%‘ƽ/ç"(.ö…w‡4f[ÇsûOÚ¢ÚƒŠÃª¾…Û O´ Lõ %?boƒå%¡(#qÃN2L|Ù4¯Ãß~`|!£7z=)غiP™0\ÖÇbaJŠZØ6›vŠ\§)‰ðe¤Í\±žâ¿ˆ’!c1 OMœ­`n¸“j0 D «Šñ-é7Äè! ]ar›ÓìTì0èÒD˜0‚ʼn-,}f:V3i7&„ܲ+Ãpë8 WU% Ø´¬cÑRΔ#”;È*ú,ä)"n['Ò)Ü”I`\l±%)É T\4™ìÔ… 0‘d¸óQ5a6 H˜8;zâ›R¥ÙD­]i0Ê ÕNççh©à-vs ÿ”ÎUHKÖ_4QÁ ®&çÛ¬Œ…¿ãþOœ• p•žqM Øæ ÚTתC;§¸ %‰ 5ábm裀(:Reòø(Ó/(ç©xø¡¸nâa•ó¢žbÀ í«•Õ¿{PÎóA96Q?‡¡Z@¼dÃöž;ѳ\$sxnð>Øhî³Èùîæð߸µÔ„s¼›Ù ?Ð[ O5í¾'`(DŽW!Å¥z­….Ìœ”~JÏïlwܘú0³ªù ‘1PcšÛ̃ZmÀ%^¡èŒ+⫢˴ o. nëaÖM+7í.€ˆVŽTÒÊ”Ú+H ™Ç47’ 8šö°‰‘ ¯×QŒ( €û/Ïìa£Å&\K@¡x—ÐgqBØq$\ZØDFöBCn ©²ð#.ªåF'ØTN±öB˜¤I•InˆXR–è´ÌG²kô¥ ƒ(˜&´Þ£ýÈ¢é\ÌŒä‘*+ú"ZlÞ‡ÄFw0Ѽ"#¨XáJƒ…‘Ìb¤„„ “¬w½•5¿j—8 :;QÀ!‘*X €Ð GʬëÍ)$ Bå‹qúïÃiÛ‘t:Òmó8"ã â§–½Ö¹ª]%ŒKÙèÑäFû[¥U 3[ HÓJ8¿eeHuoÖ€[ŠnÁ‹çZIfw_9´ïVÛëXg1šNôfš$n·‚¢é7Q¬Î Ž1¼:6u­v X·A”V;ÄCRÀ´ŽMV舿¥"–qRµ›ãš$’n±“¥" „Ñÿƒô…®Õ—ž#5+þÐ~*½(•ü:(ø¹C$’;1+Ä4I bÅž¬SÀй)X˜™¬¿ÈÐŽsW5'²ri(‚a`$-|Ôëi Æd £€ê›Ñëì‚Æ–bí³(mW–ì¿ãB ƒô\2*.«‚…b£¦Abófý÷ x&ßiã¸ý)ˆF =º6rîv$>r¤F$ÁBŠë2N“½­Gñ¥Ë®, í (¥¶W,ÉÖ®0¤I0Z÷/H˜e„qRKƒƒG†Œ ¹Ì¬Ã­CPÖ‰KiÛ¦4­¬D†VƒI-H2À”Ïw•A&æC“Ó-[®lb ØÂ…q2 ‰^é2ˆKLO1NR®•{l¥í÷@$´n)W\…¸È@BL‰æsKˆMÐØ$`0A7½?HÌæºðÅ[°£Ž¬ q»Å:€À]fðQ´Á Î^/AÙfH°Ã¬I1Šöí|w?°O€Fvíé3Ò•Ìò-)EÇ)„a¡wòŠ¥•]5L½žµ`t>ÒÉNéDyTÛ’J;+Ѥ!-IHaÜIXlšdZ1êŽLiŽ.#Ì‘H…)wlÏ´A2K1¬:R–P ‘d´¾Ê߸’EfHЯgð~×Çsût9z˜”0@ݾµ`t>€(lP¥P²KÚ´Äž»AÇÑÚ§ Ä'EŽå2ÂC&}>úQ{÷ÎïAà d½Ë;–h‘]à\ °¡vȽ©"ÏÐmz„¹X†R}Ên™`ab ÞÖ„…"‰!#m…yUûû‹7º~;þÊßê6CU@JCêR"RI'>±H!ŽW,¹ø¡Xfå!—’™Cc•°îxvÔT±…ˆ—˜2»ýFéJÅâH ©ˆÑ¾[L=šBÖìN“Wõi Ø@ã¯ü_9ž 6?¹Ž†„”)BCr÷,Ò‹) ¿*3§!ŠÀÀÎ8‰OÅ…{Ô÷‡ Ø Š ´ÉÙØ¶qa î-YWgW?žã…EÉκџ#,~ÖѓҌ 1J}' x&ßiã¸ý+k¯v„“zÃñ·³ïÚ£Ý ÈjÀÛ.”3J PPŽ(µÓ¢^W‚ 6ÅõUìö?в‘ªô¢Èl°ºšTp5Ss!ÑR@C \?#/Né›ëÞ\‡E08E×G9Pz´Þ›TI'RÓô}ŸÁû_Ïë„°kOŠ%„<Ó¡÷µ`t>‚‚,…€³åDFR[ÃJ»"lE±èQä Á ¬É–‘NÙ9Âü!a…ø¡Ù– j™Å2ŽS6O’…‹,D³Ö"ž²bp¿hX²üRt“7‡N3L£”͓䢖"õˆ§­˜œ/ÁA ‹/ÅIKfðéÆj$å6§¼¢–"YëM[9ÂüÐ8²üQ mÖŒÔÈæ3dù(™É~©º•t20¥ËÌBÀâËñD)ë‡ã5;9ŒÙ>J½¶ê¬Iп-0o WÁZÇ¢†ô™“;Œô©ÑèU*áè¿*…ø©ouÃñšš”ÆlŸ4®ÎÉØâ¤ú oe64e9`Þ¢ÕÂüRFX"ˆ‘¬WJdJ@„‘7¼¢K¯ZJKôi9/Ñ«ÃöZ"†ÄZä–%º7µO®c6Oš¹†ÈO’­E²ñQó‰Å—⡽¾aøÍ®C6Oš¹§B|•j-¹ò¨ÙÄâËñHÌõÃñš)\†lŸ4Ó2‰òT[0å±ÅFÎ'_ŠFg®ŒÑJÈfÉóW$Ë òU0ˆ~b*6q8²üÊž¸~3A)!›'Í5xç òV¥˜ˆ~b*$q8²ü%/‹Ã¯ –͓暾bpŸ%Iyb!ÏXŠd.,¿KâðëÆi3H͓枺bpŸ%L$C˜ëHƒ…Å—à¡é|C¼f“4ŒÙ>ië¦' òR3eˆ–zÄS ánY~ *ð¢ðëïI–FXLu§.œ'ÉA$D?1-“[,­ºµ‡Z|²5†–ÉnÂzÔVÂL¤É *c$±¨&f Ö¬³ ËFy‰æ¦D_!!KLLX§QÊ_ òP±eˆ‡=b*J,.†Å0á`̈"$ ½®Ä™Å“4@ÈÕS1 \#h®–4B”+З¶ç•)ˆá ±eø¤élÞ8Í2ŽS6O’ˆVXˆsÖ" ¤É¸Ñ!¸ÝoÀ-F¤u‡N3L£”Í“ä­K1üÄSVÎp¿4,¿%-Ö8ÍLŽc6O’µ<ÄCúŠjÑÎà¡Ce—Gj!MºÃñšÆlŸ%\òQýU‰2ÂüP°8²üP±=püf§g1›'ÉW¦Ù/ÕXÓ¡~ ,XY~(Xž¸~3S³˜Í“æ¯E²_ª±¦È_‚‹/ÅK{|ÃñšŸœÆlŸ5z-’>J±†È_‚£×‹/Åf™Óý3Së˜Í“æ¯e²ä«Ql…ø*>q8²üV¯×Æhõr²|ÕÌ6B|•j-¹ò¨ÙÄâËñHÎï˜~3E+Í“æ®iП%Z‹d?ªœN,¿ î¨pûÒnªÂ“Uƒ±¸27„æ¬ù(ˆU8œY~(ªo¹¦Ù©ã!›'Í^‘Îä«>J"Õ@Ž'_Š–—Åá׌ÑËHfÉóM_18O’µlÄCú¨Dð¿Kõ‡_z`Ò3dù§'—9%3'8‰~ª$q8²ü=/‹Ã¯¥É#6Ošné‰Â|”œÙb!ÏXŠt.,¿xqxuâd‰,š;ÓòÊ„ù(˜£ Ú)•´/3dóE^^i¢È›Âc­9t„á>JêÌD9ëNY-ð¿xZÂæž<‰–)ÖR]²|•Õ˜‰g¬E9d¤á~ °°Âç¥5yxt¦U”¾ä¡bËzÄS¶LNà É /Å'I3xtã58¤L'¸¯ß±½ª*(2„ÜÍîZŠ(;Ë-eËÞ_Š×+2›ÚϵCt H]‹é}"7²†=7¥ˆI܈Š3gÏR‘€ªD\KÑê’+ 40Ð,•m1Zúõ¬`o¦n„[‚mˆÉë™i’HBnº¸½ŒÓ6sˆ@,3|Ò{ˆC0ƒF4p s¹|©‰$±¼T!§"tÑøÛ{³Ýšˆ4…°^ÀÖ„O$åS -¥ ö|ýªQ¤L· ¼ÄÍKÛ55í~_¸ìè BY"`™ŠIð d¨¶)1D)•R(ñcÊÔ ›·¼y"Su—Ϲ?SØü¡5=É©îMMã¹öMOrj{“SÛzgqÆ lé³¾µ`t>Ó? Ïa2WhEEB LïÜžì÷g¹ì¨ñ­ê:–DânZ\ †.gcZƒ1œÐb©Ó ¤ð³B #‘&hÔ4˜)G#_DzP`¨@–…ÞU ŒG•JˆN t T‰e‚^µ±P‹ÄiC' ÇZ”(`Ÿ^ÁVs8 8 €j­ÛÿÑóAÄÕ£¬tó3­ò{jZ9>üºÿa*|ÉËC,Þh;…ÀúS/εhæ(Å‚Áõ5à›}§ŽãôÀ¦$G"jQª2ÅÍÊÌ ‚0AAr>ÓÙSÂHŒ%̯%ÌAnî%aÂP 8Ðk*N$‹FA¢‹B6³.`À„¹«3ÌìLŒVø€ZI£cÄ„XÐ#D7—ìýŸÁû_Ïì ÚàR¯n™‚<úvžµ`t>Ó? Ïn5óM: †äõ>’CPÝ`}¯¡ßöTxÖôbŸÒ´’`Œ}Ÿ‡mGý_8(„+,±HmñÀ¥!ÇÞÀ ›†oíß˯ðS÷€ÉOv ¥t¤£”¢!H­/$íNáX‹ ß¶lµ§!ºt ¦®ZïÌ·Õpׂmöž;Ô€X ° Pè#-t¦I‹À4A‰Ù¤T“s6Ú)` È»ñVÜ %¹fï*EB˜x«ÓçWiÌY’B+jd€$+4‘˜s¡aau DÄqExXÊÊá!±BçHUc&÷+ý£ÙüµñÜþÄ­ <É6zvøNÕÐûLü+=’倾…X–uê¿eì¨gát7‚LZŸA„¤\Î0LZ¿¿öGžyçžyçžyçžyç†U4ž(ûO<óÏ<óÏ<óÏ<óÏ<óÀ\'È»µóÏ<óÏ<óÏ<óÏ<óÏ<Ü §/oÔóÏ<óÏ<óϱhUЙQs6-ÍAHP$Ò¼é^ô¯úW€ý+À~•à?Jsmw°â¡4qälÍ´JŠmˆ`œ•!!«˜Â0bF”ZŒd¢¥ÚœÒÁ*˜@·XU£z0"’ºåpÖóÏ<óÏ<óÏ<ñ¬R¤péöžyçžyçžyçžyçžyàe&÷WÏ<óÏ<óÏ<ðI &ÂAµ½c,Í~y)Pá‚δ˘Cr&í³úT,H Á«š Há/(1a׊׬{SÁ•ùyR'Äa)ä]ñ´óÏ<óÏ<óÏ<óÏ<óÏ<‰`.èxû_<óÏ<óÏ<óÏ<óÏ<óÄÊ ®^>ÇÏ<óÏ<óÏ<óÏ<ð—ŒCbÍ€ñ5<‡‹€¡Ñ=Ž dÐxt¨&ßhãˆS*  Ú$%‡ÉQò¤ô}Éž…ÅãÒ³ªBù+.~нÅY"Xç#å»hYÆL L&á2f#é¸ãŽ8ãŽ8à™§â±v¸úîðM¾ÓÇqú¡Aw5Æa«l'i€i Ên¢áæ¿:iSñSKÈoŽôƒ›Í߈‘l="7–õì~Úû?ƒö¾;ŸØÇ‘½€L„³7°´Œ}1$£Ó³+à D!XÏí Uä(Ì4ë@ØXf$“RI Ã#‚îü‹špXPX€¥[¬÷›? Ë“i*! D‰0ŒÛÞ i Š0 S@¦„Ù¬³rÅ…cho¡Ÿ…f/¾â0VðK+ -8‰€$TCvn?W6)abNIŽNÙ"°ÙÅ36 iYy¬¼¥ ²M´4¯ßí<;j?çùа·Þ¦.R­Û÷ ÂI5CY#i¨HRÀn¬­:°=X~#²#a€MÞãahq€hw‹0 ‘1%lhȪ‹,SÝAeêA¼QŽ2ð‘69º£a€¡ªa/9ZÉÅÀ"B™(º¥3;àXU‚zG¢Eæh0Z£¨€¿`pׂmöž;Õ†¡a¡@ ‹&–Jƒˆw²[@œÁPç^Gyµ,A’ÍÞXõi>1éõ¬胭µuröûö¾Ïàý¯Žçö,ÅAŒlÙØA0‹ÌXÍÈ1.iú§µ€cº¬oŠRÄ¡o3䬢ŒT4 @ázS‰ 4`OÐuÀÄÀ@%ðA¨AÒH €è]ˆ¡àÈ’i!}ßRÑßϳS9ìT=XW›µõåý¼!¡€0¼œÍc$À §Hì»ÜöTxÖÿiáÛQÿSÎéƒQ€ W¡LÁe²E´%*ƒ‘„o+ÀË#z#={ùuþ ,ŒLH…˜á pZ©X3VÙ-öNðM¾ÓÇqû_cð>×ÙüµñÜþÍW-x¦ÕÐúrµ!†(M¡³ —Áž3%•–`ï ݾ"˜Y ê‚ë••åúù³sfˆ¾˜=]ƒäo³Ãvû*;ŸÚx¦ÕÐú§1JfÉ „’ŠFƒm±°±t•™ìJy‹À¦ÃlNÂXÌ› n…7mô·œ‹ü¨VÂĦ Òl…˜‰LÌG­ L…æQ~{JSÉÃà·5y+3™†j¢I˜ *€ÉW$R®¬¤¡ÐM{ ÑÃ^ ·ÕGÉ»Ps%í´Ñ¸!2ÅÑ-ã½ã¸ý¯±økìþÚøîiâ›VCìÚGU!Õxh¤a26‚dî¥M%*‚kÚÚÑ""bɰÛ)2oz?`gð “j¬êDÛEl1ˆb à '*ÿ†y ¦cB\¶¦&Æftq]í•{*\BÁ@AlKÒ˜ÂØI]¦~½‹'#tB{Œ2@€v{*Ž+Û‚¨»;Aî{*Àe7á¡BâÉ¡'$¥p[²"(Rg—¸¿Ì(ñí@C@mÜöTxÖýèvjc?Wö£½kä}ǬbNÈ´‘«¬*4²k9÷¢Ì. Û¢é('eü\ o§]*jÛ*DF° 2»—WàûG x&ßiã¸ý¯±øÑpûeÒþ•W‚’°Ã çô'!ø·‰«=G⡸¯ZD¿y;¾Ïàý¯Žçöž)µ`t>ÓÄpýGS$u+Že å}YâØ½’Ñ Hb;&¤l=“Rb~Ë“•FÕY)„P y©å&ÆÛS÷ƒÌj÷$ˆÄB{@'¤P. JTÞdêÁ~æ]_ƒí5à›}§Žãö¾ÇàwEY#€r#d£€ ·×öí|w?´ñM«¡öž#‡êÚ ˜ºèS—Bã²)=ÕrÒ®^ñ|Q@ŠFFU‰M}%ÍxÖýßmøÓº ˆÃè¡2rÀ.ªÕóAŒÄå¢G¡³V=¶îqCÞpVû^*ûB‡‡Ù4Ó±¡„$âI@½azÉiŠ Å €Í­­>Œ#½™­k …Íd‰rN\´aHQè|é¡64Š2!^úPöbñkMF²»1’K›UÇÁŠÁà¿bSX—.f /¿ >óL¦œ¦ ¼ö~h­!žcøRxÛ—]t9hÒ³¡ô:!òaßiÖ¥iÚ×cɯ­s¡Ê¬oG0!›†OZñiþD;E+ÓÓªB‚ÝCž…!È $‹<öÕ쨼 &OÅk ‰Õ]y‘CÌŒ'û«<øÍõ€¡§‚?4>Áä,¢k¨©’rT{,vfcz8† Š‚nu  æL#Èþ>Çɣⱅ „$̤)<°œ±9u‹!0`à´.¦š#¹¡ø¨ç ™—_Qu~;„4K©ëƒÐ@“Á”C9Û.´(ë^~=:Ô("$q27ÌPp‰ÔÔÒ* ÝÆ:kYZQ®á¯ÛíÉðm ýeGoÝy2PNô‹ˆâ2'`ÂC4KM” ¦(SN÷x õ v}á7_JC©à9({ Ôh ƒ½ª_&ÌKi#'ù–Nb"Sδ`ÞZ‡4H“"r?š•⸹Òh²-*!Š’"•EÕ%´ŠÓ¦F@ÒI¢M€¶RtŠ R€É½†™Bgge„œFJ71LÚµÈo™À¤N Ï+Lí-BKÇ&(“êæ92T"#<'JTV`8›Î §03ª^WõH°Ä+gGbٙы ')0û• ÔFnxèÔõê*!G@½©p;¨O1د$B"|ÔR–%HÛ©GÙ!Õ_rþu0dð0ž¹ó¥"lÌÆÑ9£\”1eÂkMÆÑH.RG4pS©8º"#zHa #"T…­^–NÔ!!Àäž ¨Ž„Fa«Êßì}Š^PY êMAR®sÏ §Ä’cF[QTÑ"˜ O:Âé ïAw,q¸˜‰›mPÄ0¬-€9žìá@ E½¬d«6"bqЋëF8}mðÞJVPƒìÜ5à›}§Žãö¾Çà}¯³ø?F)0KíI~ŸŽçöž)µ`t>ÓÄpýE[.;ÝAÚ”Úç©¿ÙQã[÷Pä¬v¤æ ìC’’lÐ*'5BÝÄ”Oò­+ˆ®s@í†ÇbJÇd¨3ÙáØƒ“±%ag–€‹ˆ9("Å!ÈvC0vCb¸Š7Œn´‚”1!Ùu‡H†ÈÙ©äˆK™"¾IG²1\û1EÓ3z/@™.NÄ¢Y Å"Hº14ŒjÙÁtá5¡ 2²=Öˆ×(DK°‰€ ô*l¢»–äèáÑî8kÁ6ûOÇ´Kén>ËØüµöèÆüT"ŠŽ·24b¸) hL€}?Ïív¢ò0"b¤ŸƒR£€np”¸kk¡É¬Žðå’"àA¬ÒæÐ‹¤È[ÎËÇn]_ƒëÁª7ÎeÈÒ˜y„…†rPÄ k†¼o´ñÜ{3j@Ô‚üå—Ç2ßì½Àû_gð~×ÇsûOÚ°:iâ8{ÄMñ@×KNº ’Â[ÙQã[÷Tº½†¢(]7ŠQå± ‘*èëëïJÀ]ËK 0Óm¹6µXFéÀ³kRÌ-‘&rŒ’õ. ¥1´ IZš‰K¤LÈ5Å(¤¤E#FÛPI¤eªÚÑ¡SÀè/MÑÑ`]i’ L¶ÊÞíEŠA(–æZªD·Ž”‰èÈP2uh>ô½–#DÏ9©-!M%K¯4A•8( ÊD1S&g4È„„^±£!²EI¹}D¶›f˜û10ÈSöŒÒ ”Éø„]·ùÈPæv§0n”•ÔéjŸ§P,ܵ,`Ψ48§nì%Ee6 ßÐËœ§:LãfF@l&Ò‚ 4Uñ+@"R ÒŸ–­F¾ÚÒäx"`cm²Ò£Ë )põ©À››~¥åýj ,yÓÄ^FúâŒÀ÷W\«•KØNFàæQþHˆHè`6îåÕø>ÑÃ^ µ$‹³¥¼+»“eµ\‹šR¸h0ôû/Çí}Àû_gð~×ÇsûOÚ°:iâ8~×ÙQã[÷Ü # æT³s33¬Ð\T£Ýì™&ó4½)Ŕʲ¾oÐðí¨ÿ½çË«ð}£†¼nÉd·áDɃ3ö>;Úûö¾Ïàý¯Žçöž)µ`t>ÓÄpý¯²£Æ·ûOÚûÞ|º¿Ú8kÁ6¥ŽD¤s!Rø3i˜b) $NPÊ•B %_±ñÜ~רüµöí|w?´ñM«¡öž#‡í}•ÅkSSSSSSSSSSSSSSSSSSSKÃiCSSSSSSSSSSSSSSSSSSSN¹555555555555555555555榦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦•ú¿MMNóˆ‹v&Àä²Ô„ BBÛ­÷*jjjjjƒ L:1­YXL8õ¼ÞÕ555555554¶iøM*jjjjjjjjjjjjjjjjjjiøÛjjjjjjjjjjjjjjjjjjjjiz?¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦—§ðjjjjjjjjjjjjjjjjjjjjiøÛêjjjjjjjjjjjjjjjjjjiøM)XèTÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÓð6jjjjjjjjjjjjjjjjjjjjkÙPñLJä †nâ€Âú¤Ü°˜m›6 ¤d"H×ðÕü5 _ÃWðÕü5 _ÃWðÕü5 _ÃWðÕü5 _ÃWðÔâi6£ü5 _ÃWðÕü5 _ÃWðÕü5 _ÃWðÕü5 _ÃWðÕü5 _ÃWðÔ`/Á³u _ÃWðÕü5 _ÃWðÕü5 _ÃWðÕü5 _ÃWðÕü5 _ÃWðÕü5mÑ ×øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á«øjþ¿†§^âK-Ø9µ*k`Ä'ÚlÌ©,p ` o(fÀZš‘Po°^¿˜¯ã«øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¦ãR•ušÍ«øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¡H7i¶¿†¯á«øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á«øjþŒDi+6+øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á©Ål!fÍ _ÃWðÕü5 _ÃWðÕü5 _ÃWðÕü5 _ÃWðÕü5 _ÃWðÕü5*K»Mõü5 _ÃWðÕü5 _ÃWðÕü5 _ÃWðÕü5 _ÃWðÕü5 _ÃR˜i¤Ú fŠþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á¨Vß,6køjþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á«øjþ¿†¯á«øjþ“ »eY#_í)þ\Jn¥h¤´$˜4'£¥8y|N„.‚!µ‹Ô›¬`GÙxvÔÛ󚘒Y»åS.€¤,‚ä"@’’TÅÔÀœÄ¨…B “S^ú@2 ê5 Ñ „"‚ôqó) ±­N‰É¹a€%ä-1,K±pׂmöž;Úûö¾Ïàý¯Žçöž)µ`t>ÓÄpý™à2),ÒbD3ÌDÔQ’ )YÕkÆ·ûOÚû>s¬y•«ÕlÖÅ9°IH[}FD™p¦®œÅAyœ9¢hþ¸†É] €X¡b âpÝ6p¤ PåÊ% *Â)¢*ÒfQ@º^fuÍ8ä…,PVƒ‡Ù8kÁ6ûOÇí}Àû| —IJx@ØüÑ0’G¢vû?ƒö¾;Ÿu͚͊o˜ŒH ѼN j!JG‘ºîN“P¿³¢pÈ3©3H–{®„VÍ©3ôSA,¼LñÜñM«¡ô³¥â8~×ÙP„ 3=jêÇ£4”ì%*€vR²°êÅDM9MªêÄášJv¤YÚI¬¬:±QNSjº±8f“€^¤YÚI¬¬:±INSjPœ3IÁ/Ò,‡ &°±ÕŠ2ÓU6Å(MÆjÆKÂ5 !ÂI«)X¤‰!ºmBJq«/Ô,øMac«‘$ ÖÔ$‚n5c%á»åMac«‘$ ÖÔA7°’ð]µÂI« P¤’ºÒˆ&±¢ÂODjïI5a#ª’@7Z ›ªÂODjí®MXÈyB„ Ö† Žéµ`g£5wÈ$š±ò…( Ö† Žéµag£4+ÂI«(P’€åŠŠ;¦ÕeÏj+“IÁ(P²À劊r›Vz3B°Gi&’€P««–(b)ÊmYXôf…`i&’€”««¬RÇP@U°SÚ¯1v&à Ai/O?& ü$JdEj² 1c ³HX©‘.£R†„Aàf˜½\#!^¤Ð!6)TÙJº°êÅZ¼å6¬¬z3B¤„¥PÊVVX«Wœ¦Õ•FhTØJUì¥eaÕŠµyÊmWV=¤ a)T²••‡V*"ó”Ú®¬z3W@,`J+Å$â²°êÅDM9MªêÄášJxF‘di&²°êÅ$M9MªêÄáš±’ð"È6I5•‡V)"iÊmBJ†jÆKÂ4‹!ÂI¬,ub’$†éµ (MƬd¼#X»E$ãNÚ:±ICtÚ„”!¨ÕŒ—„jï”I5…ެRIÝmBH&ãV2^«¶8I5ލRIÝmA$q« /Õß ’jÂGT)$€n´A7°“Ñ»k„“P,[$4)$€n´1MÆÕa'£5v× &¬d<¡BHëCGtÚ¬¤ôf¥`8I5c!å ¸ Ú|ÊD@LÊPÔ½.a$“d­;5ŠÜZI‚áu¦Á˜†“ H#Ï«Mœ§õ_*¤Yˆ¾(BÂV`Ô C¸C#D™Úà0‹+{ÁÖ’é©õö¤±j6i@“Õ¥ecÑš€]¤šJvP««–*b)ÊmYXôf…`a&’€”««¬U«ÎSjÊÇ£4*AØJJì¥FF½‹°V¸–@=V½Ãh?uÛð­¾z[,B ¨1(æ`ÁÍ€Õ™Z$ôà ô|T^$ÖçÅQqæOq{Tè,€"W¨PÂ!e!*;6Š(ƒwUE…¶&Å©[]’ÊÙÀžô@ ©fQ³qÂðݤî~T™&°Ÿ¯ö¶}±¾A ÂAá….ìÝ'ËÁžÍïQ–y)Eï@KÈ4ô¯7Éõmc Ù0‘„Ñ.QüÞà6lMä´4ñÂÇ H8£€ë2ŸKÃÈò¡ë€xtS|Š`M¿šMæOCÖg¦„Š#¹å‹:&š»}Û‹;h+D~fäzh lÅyB©†F’ZW-¬–Wt µ^BÁL²ÀÁ*ïEÃñ°(ǦéOq+CUy¥¾m3<6t@žjòê蚆Ý—ôy'Ÿ–Bûã$„¯}'Ó‡8±È ˲ì"¯AÇE ÔJH’ëjœd±’ð]ò‰&°±ÕŠH’ëj ›XIxF®Úá$ՄލTÀ]•Ða:Eô)È<@¸€¹Q½gØ‹Y¸µwÈ$š°‘Õ I ­MÆÕa'¢5v× &¬d<¡BHëCGtÚ ¶ЈˆÚI3V –`Šg’4ÃJó¨Ú*Š'œ É1³©ÙÄHi1mE%C¶§q.,£KÏޱ½ä¡ X¶Âsµi.f攦9Z5³Jš” î¶bÑW@€P2uŒÓ˜6-(bT;éx¥¦m¸$2»™ˆ£P$)¹¾1j`Àk9eh£AµÄb\¦mB° †,{‰çMPpŒmE€1*n”—Y0ŸZ¿ lša#±ô á@˜•7JK¬˜Lõ«ò†àüÓ ˆG¥ bTàŠädÌ&zÕùCp~iDh„zPp… NJœñ\Œ™„ÏZ¾)È4Dh‚RB ‹òœñ^c&=iÉSh8ÈÑ Q éWå8â ‡Y0ŸZbTäš4A(„†À#Ò¯Ê;ñPC¬˜O­1*n†ƒ„ J!!°ô«ô†Àø !,¦ëLI7CAˆ%6•=Òm²a>´Ä“t4Thƒ@À °G¥kÁ˜ÅŠÜ õ¦$›¡¢ ¸?4 ¤G¥kÁ˜ÅŠÜ õ¤e›¡hÀ¤Òh*G, %á"a¡8–(ÁBg.ôÕÃ$¢“k•Â0ÀGc„Ê¢ò%`€MڂàG¥BÌ!wLçÆ(²€8)fèZµ@lšÑ"=+Z ÀŒ6à #-¹ ZPlšÑ"=*Õ!˜⑆ܤ&·!kJ ƒóZ#¤G¥Z‚[ñHË7Ò[µ¥ ?5,ƒ¤G¥Z€;ñD•‘ !ÖR'§ ?4¬ƒ¤G¥ÀŒ“p¢kr'Ö´àØ?4¬ÙzPPÈLI7J&·"}kN ƒóJȈG¥9)‰Sp¤­”Â}jý°~i„ŽÄ#Òƒ„€1*n‘=á0œu§¥`üÓ¢éA¢@˜•7RK¬˜O­_”7æ‘Dv!”!hS§|W#&a3Ö¯ÊrÍ ž”ahS’§|W˜É„ÏZrTä24A((‚Ðô«òœñ\Œ™„ÏZQR2Bƒ„JØzQÈ8-àX–‚'J$„ËÀ'¬Ó§!ù áD–d 2éQsX‡%¤M•£Ñ“ÐM'¤—­ ÕVdI‹ ¡¤Õ…¥¨ÚK-¬0 U…³ $ó¥åŽè4,$ÐDA²-éX;  J#p^Sq£Ø&ŽÕáhGµj»˜Œ~ãî3ïI¡¼N1j ÈV7"$±ÏåD‘±hÔ3 fŠÜ ¤e›¡jÔÜš†Ò#Ò¡//Joeü²-%$ÑEçû)û¯Y‚UF„˜Xh{¥H­X¼`RrÆà¼£º X[¨šÆÚ¶ZV–ÀRÐà - î…an¢kIp‹zU”¥±´¸.wBk CpkIp‹zU”¥±¼#¸.wBkuS"Þ•e ìE/îK¤7!5…¡¸4䆊-Š6ˆ¥äŽà»&Ü„Öê–&8E½*ÐìE]x—f› šÀÐÜX˜vE¨X@èKÉÀ)v|ˆ&®eÔ ,L;"Ô, lMJYÌ6_Þ…x °3ÍDD”SH%‰Ò‹Lì.•¼à1ª°0e¨€ˆþk¡‘)`[¼SâëaÈA(Ù á @œ•8â¹3 žµ|Sh(ˆÑ ¢@#Ò‡æ@<ÛJÅyŒ˜Lõ§%NA ã#DD€G¥_”àŠ‚dÂ}i‰S~h8HÑ¢JpS@exÖý³ÚÛ"ƒEùRžuŠ_(¾º•d?*:P@!«bà^ÁÅ™RRÌ„ æb p‚l¾jhUêÝ–ÇJDæ! àâ—LÍ^%!cÓì+cBpqOA* MF´/!žS™ó „ jô(†é&ÌgΣ9²s bL1W®[]‚fÝkö£þ—žW.‘`pJùTÌ" aA»@Lh$Iܦ„\¤„À™•E?‘x’)~ ,b`è©‘e,Ùpˆ-.Ë]tÄÂQ.‹J·Ö„,i»zÅ séá9 –wŠ0ÙÀÛÈ´Ø•b± C~ÐŒ × ¦]_ƒí5à›}—œV((  DuÉ^;t&Å"DœLèhöb{˜ÜX=è”7€“ÒiV‰È*»i'†.Œ¢<7§áoGóEÍZ‰ì´£"v‰ÀÑp­ËåN’Û BEH~ííE¡vA\ؤÀ™r•2§U• ¢—"–ì°âvSÖšÃØ¾Ô'0Ñî˜æ°K\«„l2¶”@Z,ƒêû‹Þ{•LEÀFT$½>f»¦éÝV€®•X–‘%£=Þ¼}B…&•Ø€!ä. h2"K0Œ}Ëí§‹åöž5µ:}§„áú ä¸'0‡Rkyž4HTÁ%G*Ð7%ä_4ÊcQ£BÌé Š Ä#¤„?ñ >KG¦™M:Éï{*ÓÂpýk4„D±®@†0N,ÉibXž÷²£Æ·ûOÚ¡ŠjIzÌȱÜöVI&Ä—»A@¢#qG²¨ÄúW#Ò’ÈúU“ÔOPq ùPóL&† >¡ïS( -l¬é‡p§ yܸMÀ¶¦mD Ñ  ¤Ha£jÑü[U *J1m^¼ƒbìB”¹[P™¥Dpœñ̇‘DDhÓ‘\Uw•àÒÕ(OM7¹3JÒ\f+Éuµ¡çlX¬Õ÷®¯’¦p®˜)ØïK=©t½°œ¢\Zø8~(úIzðf•¾îõ?õL.ä®úÖT75õJ»É½ËÖãÒ… Ý­4‘+•Õ}êlê^atŠv‰KÀ¨"Ú¥â›EL!å OZ`J\ˆæ0´Bœ–—%¢wRªDÒ=Þ€V‰„"Œ^$ †Tì3°~Í÷¬#mú õWRå˯PJù½Ä\ƒ«Hˆ‚Âì+—WàûG x&ßB7€ù¤cÓÏX‰b‰ƒD_™Ñ9â¬a“A[6~‹áö¾Çâ}¯±ø¿kâù}§mFŸiá8~ŒN*#4ƒÇ P²šÑ¢z"tSàQ½.‹ &¢¥zßQ/Hp㠭дÇÙQã[ý§‡mGg D>R*Ìõ‡âh•ÓJOŸ¨4i³¯ñHA'Mжêù+Öæ/cù¬pÖÐIk{&€Z›h°ÉtZ(¤à7°ÂDÓ|äÒj·ÉQ´¦N(0á`’:Á“øW½jïD\'Šuº¹KOºä«Œ}Z’æ]Så«Å9KU&°YH}iAHÄPé4§è£ äS„,@¬*‘AëPa"P %·-0ÐN1ÄÅ2eâ‚.fºÅâ% ”d+“áVKRØÍ7gªǬæ°!ÛÜ&¬c°…>aëJTz"›:¤ÝWLW+ª»¿VDâ¬or_šÃõþGì«*|׳Ó~h\'™B8hU`a›íM‰q½ ÍÀí)ÒßJ—ùEÙD¤ÉgR”e`ð2j` $&ìÑ*¨‹ xYu~´pׂmØ ¦òƒÞè)~“5aGƒö=Õá¼èVZ:¼‡¤è©q’öR^¾&Gë*úш:~Š0`GÒñ|>רüOµö?í|_.ü9 T;>•©¥C˜µ"dNµº1;Mp>± ]*Å»¾5µ:}§„áú2|b`¤JË2&É aAI9Y¢hÖj0I!Í£Ÿ¥ì¨ñ­þ¼¯½Ze™PE]Uè rК}aòSgg­ ËäPZ…Ì0€ÀÁÅ%ìñX½‰Å˜¤íœ{„” y{^' ØCÒ¥,5?aà9}Ä~I¸‘@—Uƒ-E,¯$¦RÚŸ QÂÕ¼ÕÀ°4ÑÎ ²pÑüU˜¼Næ­¦u¾JK…›ýï@±91ó s=?ñFÀÜ Å³df’ õ_¸{TCœúÅ 0l1ÜË«ð}. .%f–dºQª¢ÁM”= ¬&HE• ‚ÛoÊwL¼RÐVìÌ{éŸÀ¥\ý‡‹áö¾Çâ}¯±ø¿kâùwÄ{<˜y£è8eç¾â¢f7ÝD]2ݹ£E,GÖù£@S€ò,36-4 ålu¹Š ‘#ÉR„ßlqWjìxKe†5Å>—;¾5µ:}§„áú*Çj*ÜŠ“ ´Íâ(úA bÍãϲIºù~²£Æ·ú˜Í"g°%—®H XµB 2J$h˜¡BM3ë i·a¬j&E0Š:$%%¶ÈS‰žUëJ(x6Åð©P¤ËK¿Ùx_q l€GÉ«ïC“Èj·l…ú‚¨ä¯ò- DœLSÌ‚†ï!äŠÔj1j¤§¥>€$¤Á5‰«I{üF£Á>P¼–=ô9 ¡1*ª UX+ŸBƒÕ§B;¯ £]ºF1í*¶¼²{¹u~게ŸÖR²'Á¾‡º¤aôÈzö7ª#æY  ®Ä•ÍJM’`oDtè¦ûwÀªÑÙü” –¡l-‰‰»¥ˆÖµÝ!Å1È(Š8£ú+دhOáN+ǶûOÃí}Äû_cñ~×Åòï…u? …FH̹£°Güб±:®Œ¶Q$™uµ6…@"íºÍt¡È9DÄýc²Áf¬ )pQš¦Ò5¤’VAË^çmFŸiá8~„8¨f5¥¤À3P‘´‹ê¤,‰€t”å«4,·E¼)’ ŽüýeGoõ9¹B4bÀc¨%pëØ [ Óµçü) à2õ½ê¼7®jWD^N›9Ô~¢Ãèôº‹‚\pý—€å÷rÔHX 2<† P HÀá,›'Ö›u?.(|ºZ\vMåH15p0…ãØ ÌÄ‘´Bs&S"]†ÐjhÔ›’H IZPñF“.÷K¾”+„ô®ƒ­¨éV½”ÞäûUɇ"$ÚöÜíDàV5Z 1„Ðͽ% f?À)W=¦œ =F¥sØak$EK‘ôBøõL%­DŽ® ø=¦ï²=7³W‰cV¤·žXµz;‚ô#Kl› ò-$ëº/D·Ëa¦?ŠlËziâø}¯±øŸkì~/Úø¾]õ`E@ pDFWYš_íÓ‚!–Ž4 Û.èLX R ¦¬X1G´5ÛÀY7Ï4-ÙÜ–”„ƒ§• %ŠªD6[·¢±âa¡26£@•ÞçmFŸiá8{ú±Þ‘Ue.& Џ‰Ûly9ÅÝŸÍX»EÌzçÝRÝ °¾¿GÙQã[ýwÙÃh›^ø/—©NÝíZ÷êgÙù¥Ó2Zàs³¹=ëò÷âT€ Q²0i›™—Ì/J|â„D²5*Î¯Øø_{lâŸ8>õ Á½9½Aµ\úÚ¾b ²:?ÍOŒoìôù÷ÆzÒrâ!Fc‘N¯òöU5ÑÝü¢ˆ”«Ü÷ ‡ s¢…·„íWR Èú¾„v¸!xævSI4º °¬N‚Ç«ÙeÿŠ÷÷à¬ötT·Œ» àu| ’„КƒCåL夷$®tØêÁZ¢á}›Ú¼V}¿%„J"$õ"§ljþA‚pRlãøP6‰°‚!H`U¢H!0¥xößiâø}¯±øŸkì~/Úø¾_iã[Qƒ§ÚxNüÑÜÂ:ɦ7R&)‰U¼„DÍu>(2³ kjDÏÓöTxÖÿb ÈAÈà ¼MJ`4l¸JŽ…€-åeE/W…mGÚx_ñcd\sHH;ÜõŠøT~„=«Ùt_}÷¯ÃòµÉ\ ħ¦†(L;(E9©­ü"ú*ž44zš÷•è ùûïY?Ð| ÷ÈüB¼wÆôñ¿}n_Ú·üÉ­ÄêMFÂaÍþƒŠñí¾ÓÅðû_cñ>רü_µñ|¾Óƶ£O´ðœ?Eò¾@Gɧø>$н!# E÷òŽ„ìJ³gÙC ó·(ÔKP( ‰"aÿ² PŒ€ÝGÉ¡· x)§3ÆsI~$øiz³'Éùk[}×ÃW 8F¤d~–J Mà6,Ó‰~Opòh‘¤’q²×µhG-š–Õaà9Ê™u¾ ”ÇÙ¸¯Ûí<_µö?í}Åû_Ë톦ÀsëðkØ®oš†|ª'g¯µzÒíóª ǃõ¢0[…„"UíìÜ NÉw¬ z•‘SñS³Á|RÝj¤ªÈvÒ>Úx&¾/½ezЩ’ðæº'¡~Ú åÖÕulU« ~eeC`ýåVµzâ–UQO1­ÔÓÂpý¯†ïôo400S¶û_ËþTË­ð}£Šñí¾ÓÅðû_cñ>רü_µñ|¾Óƶ£O´ðœ?ká»ý­þµI¬° ßwJbAˆÖú¥#n£äo›cì¼*Š”Zѯ$Y@@äóíD&­(‡d hùP"¸6‰ß(Ã/OÙÈ,L*„[7p•‰³1¬ÔbHT6DÈý“ŠŽ Ka` ªÁ!Í nhÂÈðý—‹áI„!IoöžÇâR¶ÀÕÏ©Yâ¤D ¾ÎGB/£P¢¡hT LéD L2 ¹öfBä,šž!)¼[­ÌÞͲ?eã[UÚ+›_jañ¥&D$[Ÿeá8jØÌص¹ó¥‘Ê#v³íCUDÛýû/ ßíoð­ª)˜öZ‰Sp¶ãgÄR«å†Sdâ'ì¼*’E¡:£ø êŒ‹ ëFü4dfÒóÙ äe&luºú¡ ÂÖn¿fk@I$‘“jNŒ r:šÓÞLÁ»}“Š%¦;€Ø‡Ê‚ I10—Òhq<¾ËÅð¡Óƒ—ÇRº}Ÿ±ø”å’$BâÔmÃBFm-óÙ›DŸ&¤—g‰)›íÒ„D „,g"@nE²Ú©Qˆ+dee³6ƒZŒœ¥Wueû/Ú¢Ö#Õ&ðŒ‰Ý†ôÔÁH Îû/ ÃW<ŸªHu—ÏΙ ܉o¶ßeá»ÖnÕIGo´{Þ÷½ï{Þ÷½ï{ÞP¥Vßj÷½ï{Þ÷½ï{Þ÷½ìQ¢mû¾Õï{Þ÷½ï{Þ÷½ï{ú Ìâ~Ô÷½ï{Þ÷½ï{Þ÷½äey%ûh÷½ï{Þ÷½ï{Þ÷½†¸Á%¥íö{Þ÷½ï{Þ÷½ï{ÛÞϵ{Þ÷½ï{Þ÷½ï{Þõ¹Nýµ{Þ÷½ï{Þ÷½ï{Þð»FýŸµ{Þ÷½ï{Þ÷½ï{ÞøW»í÷½ï{Þ÷½ï{Þ÷œmOj!6þÒ÷½ï{Þ÷½ï{Þ÷½¶4#~ÏÚ½ï{Þ÷½ï{Þ÷½ïyÎ\ã¤×ÿÚ?`DÁ\*áW ¸U®p«…\*áW ¸U®p«…\*áW •q«\jáW ¸U®p«…\*áW ¸U®p«…\*áS²®5q¨}®p«…\*áW ¸U®p«…\*áW ¸U®q(ÛW ¸U®p«…\*áW ¸U®p«…\*áW ¸UÆ®5*áW ¸U®p«…\*áW ¸U®p«…\*áW ¸ÕÄ®p«…\*áW ¸U®p«…\*áW ¸U®p©ÙS¶®p«…\*áW ¸U®p«…\*áW ¸U®p«…\*ãQ¶®p«…\*áW ¸U®p«…\*áW ¸U®p«…\j6Õ®p«…\*áW ¸U®p«…\*áW ¸U®q«Bè®p«…\*áW ¸U®p«…\*áW ¸U®;*E'¢¸•®p«…\*áW ¸U®p«…\*áW ¸U§e\jãQ¶®p«…\*áW ¸U®p«…\*áW ¸U®Lþ+ÛhÓIQöMEEÚìâj(>Î*(û&“íbƒìâj(>Î&¢(#ìœRM$Ô}“QزüŸŠöÇÚ?jý®¿j}©öýóí}«ö¿“ñ^ØûGí_µ×¼±šmv„$OZM8”¡¿J‰”MÆjÛ ÉÊ93rœÏP¬øz%(SnÓèŸ,:±R$ka:•ލ‡­ ù”C,éFdòšLï(zP ËÊ;•¡ýO¥c¢4÷Yduý(1%…·ÍßìûçÚ8ûWí'â½±öÚ¿g¡*™„ûÄRYD«w°½é¸¤âe‘<ÃVƒz1óQ`®çä) ¢#ÝB‰¦"póýÐHÖ·k§‰¨¬¤tR§Â·sC6žÆ¥úƒò4ܯ•B)w/Va%‡šp,ù ô( ÖÎ@¤ðÅŸÖ£É*ŸWê°ÛÕ¥.¢xÞt¬²tF»rÝ2Üï_³%5¸Ô4¢Ä2~ŽÁLSr‡›Ý†glÿŠGUIzªãÇëW‘ì~R­0ÖQo*9G²Z–è1 zÚ‹¶ &¬@õ*n35–Sª 0J–YF¦ÌŸAúÂÒ:øðU¹›“ñC“6Å#yßÛ4´ [™ùñ¥) îx†=¯ ¯§%$œ©£Q=¸â¯ÓØÞŽT'’D·FçÙhãí_µüŸŠöÇÚ?jýaíÁ¿qJ-¦)™=Jö/Ô©Âäª(C³JùT"Á*“СR&€ýäiÈ}!Kµí?8!¤5:~í,wt½M®¡º– #äy¡Â¼U™ðÔæ•?¹%ö(U¨´"Ü9*ùÀ–¢‰âߊi.-GÂ^¿g• ¤¥¹g­EÛ½‡—íAÜ6  ¯RLyþjn7¹Aµe«MÛ¾iiã,V;ľ¯Ñ~¹fºÕ9—‚*Ô^&ih'©®jÀ9I:…8 o†˜ßzH‚l Ú— ½»ÝÞÓÛPV<Ê<Ô=Д yt›Å(IßjΈ}ä)Ê–A<¼1Í‘½DY”žº3 êÅgCÑšP/p,KÍAA$8fìÜz*8$‹&üUíÆV´)†Üb†Í/ šW‘ÍM“CH©<å¤I)ÇÈ‘C0<U «CâZ¡#m‡¤¾¿FíK^&Ó|î[蜓ֱèɳÑõ½ ðk $Q9 '¦ñaPÎ,Î’jrCa˜vúÎ>Õû_Éø¯l}£ö¯tIAÕ &A䨲=Ç ÆF†ïêÆ_ ü\ŠŠ0œr&M†[°j´T‡BT@ò|Ò·Qc­íE!:È~ñÈšD• 3D©‚ðó$Ó!¨²ªwÚ¾CñEÂn“ÓþVÔöìôØ%ï?ŠŸŽÇ.–¦ÈÐ>õ?=Ï@hœO›oE¡–;v£åõ*—!jÉuƒóDyݦYnö¿k—xI@ê°Jd.º4$æ}ªH™r8Bq¨î}G¾±B³]d¤–ÊGs[BGá«Ø%±ª÷³a&Â7…ó/ŠL@¢$$ˆš™Ó R0…d2D¼E¦^‘j,[¤'ÑByÍ"ƒ«|Q–Ä õ½ÏZ[8Ý!´À®Wƒ>þÌ>ß2àå{›íYb´È8Ãõ˰`4Ä&)e™³·pTª»õDÐ}97-)äý(K ”ÇÉúú%CšwPÏFh >Ë ò¨Á™’,ZÆ-›5jÓ‘“÷TV›%ÈõWÍ ±!-:·†§:8€ŸZq[‹œÝ[JVÀG«ƒæ×“AÓé8ûWí'â½±öÚÙPŠ;RJÀæþ”äI…ènz/K ®fþØ¡#ÈñEMF‚ÃÔ×µeŽ&ÍiºÞþõ ½hT{ FÔÄÔ2F}é&ÍYéè‘EW(0•¤ hù¥¦{žÕ0‹Xe ãZnS2ÏF¢…™,£°Ô`s³yc¤EIkµXz¾*#!«.3Å%+(‘¦c­OêÌÖCÒ«…&«<œÐ°Q“ª; <õÆ•sA¸()âî½íïXÓÀæ˜Yô;¯ÚåÝ(^'¬éAPl½ã⳽IJ.ßÚ‚€µçHÒ>£ôr…'p莢m Lbƒ*g*Â|Ö£ny$ µ ¦ë6Õ.Î*êúª®«zYÔn`l.j)V3ò”€»Â®Ç5 ^’<"wJ–µýä}yÑ„ò <Ú;’ +ŠÅ„)‹@Ó DT°¼¬û¿ŠEÙCÕM0Ö”‰V̼ét-)´‘ø}jý¯äüW¶>Ñû6Šp)è0náïø¥DÛÈ4ÌM¶&dìÑùƒ6–½(›D¯éVƒ&Á§¬­IŽR‡8ŽýD"u#O:̃ªT†‰êSïžjt‰Ž¨ 0æd»Òôþf‘‚:*ÖHÕEC±s'ä¢ÁÜ“*NÀX4ÇoF?u(ƶâÒ˜Lï&h3ÑD[÷Z@äfš;@~h6!™[жnבGJ%Wã­qkœN½Ñ º©I,쨡B»gî•È8õ«$"Ù[÷jý`ÖJz•ù¹4¬ä~ƒR Ddm†ØÊÔi»d2yõòƵtLä›cU~»‹³Ã¤C­ñ´­0s{`Ñkr怛ÍöÑloQôÞBã×K»Q.5ø(ö;™wa†=Ÿ†’CgJß %œêQF&ÿQúHlÓ±ºï“ލ«HÕ“ ÍÓ‹Ïq.JhÒÝYYÇ@ -7¿Û£Ün zÒ¸S`¾Au¥ A†|¦Jáðu¥ã—ˆø)Eò|Ÿ•K>{ÿƒLRd î¿Ei8p«$tLÎHYçܶ†nM÷¢’e=hYÁ` žGÍ+uªͦ¿XMøh@ŒÎ  áb+|”…Æ©è6qHÂá$€ÙbÞæÔMQrHõÛ1ÙiÖîò¥r””W ™/O*beäC∋úþŠ(™3‹ä(î´Óê ”;ÉŽã¦\}$$w7¤öŒ‚ŽÃÖ³HÄ"ô_UG K3×ò~+Ûhý–:¡YP¬¨P.)¿˜@íÒ–V[ÞQåŠ,ÈÚÁ _ÍùüS+?œ~*ÿ›7eócÊ‚ƒ¥+PŒ­aŸY¦rsxWÝ¥(«•ñb‹†òòšÃDÖ3rÓÅI6’_˜«ŸD€ iH$4Iw“%)º$<Ô1m{¦”Ýê­ G€U¼¾÷ݦÎ?‚ÔØdnËc÷R¬†×=qJÁöüÕÁ€SëD楀MÕ‰á«&žáß~ž”¬ß1¡¦Û~iibš‘Ç¢…cyöFÜ*BîàÖgåõ®cyùiiŸchø¤BèGŽ­û™wOYØü­™äûÅYÕËÊñMÉ Aîòsõª¢‡ÚÚ>ä“÷,§Õ`k1zA,º“ï˜&¬ÝKÞi¨p’Ï¢kjÀ£òkÚ0~¨0#§ÒâbÂ[ñóÙ5¿¯âÃïF–etÝÓΈ"¤ŒÆOŠCªLz¢çb‰,‚þánù 3w×÷VY<ߺL™íŠA’$6Oܨà DN©rm8ê÷àX „f$OR„ºáz½è“^%A”näTß±dB¨cG0ùv8úxy!¾h€ ŒF‘LÍ Bd[ØgÎüÐ2<Ç∠$ à±LÚÁ…ô¢½â‰”œ£Aû<ÓIÚ't‹úÅ K%âc…§À"5&ýgñF©œ(<ÆþŸiù?í´~ÈÄA27w8¨¨£QÃi·½\Jâ~ nlÙ¢ÑÞoãCq;æÔ“)6’jg°Y”Çž¯6BÌŸ&£ –&VènU¥³ŒO€åŠ æÕcÃ}j4ÕÂíX È[æ$«ÿ¶ ‡­%™é$¦Îµ-¼‰rõG—¦"ĉ=J˜ Í»à%¤ˆKÊ"§Y¬°yŸºQ7=©UÙ˜Qä>Ut1q ihœÑt¡¥&{@Á¼äÚ¤)3tOñš^U~¨]Nd‰¾ö¤I2å]êÔ3ffåC7ud£¼"`Ã7™ËÏf~ŸH&Å"Ž/˜Q襖ë(ŸaÓÕXæy“Õ%õ*HK‰e=U§°ó >tcÂ!¡0ÉkÅ2[Õ¢‹Ÿ0‰ëjÔzÚo©hŽ‹49$~-i&Ú?ºDGfm$‡›M‘[%>µ8¨J­`áËé4f›‘åÚj“›çŸ.â ‘²8iæàw(x:µ‹qØÂDºçI&Åä×{o‘Ú­¯^_b¬„6(ýª;_µüŸŠ*f }¤+° óöoØÃyrÝè6¢RƒEýLˆ ¿JšÕ’ŠüPeÛ¢ü­JûÎg²A͇9¥–j_îe+×JùS)ŠÁ-§F&´/K&æ¥_ÌþyT6ŽŽ”(é6ô֬Û/35heÈÃïo,ÐÊ™‹‘Ô8h0_RNñYÚª›5¥Y„œ†D‡>ÀóÏaS|ŸÚ3DÕÌ!ç±I’&Óçµc¼Æ¦ðRÈZ.Ÿ’”Ë›6ôÅa‚¬5Z-czÕ‹ýµf!lçÐK´è$AßÚÀŽÍŸ°sP3Ì<‹ÃíÒˆ(#hÞ§À$—Yëµ]rƒãý¤6b3Äýwþ# ³y€FrMNM,‘z1Ç­;Q dÚ…h“t4n>ß@ùë€ù£KªGbž¾èXTl8%ƒœÿié2i.£÷šzrˆ %3j3©6X^â Nô*ÎÄ k&­ì,'R{¦HzZlîSrCO¥ƒÍ¨÷yEŸ[ú“»Ÿ§ÚdTTUõ:ø¬Œ|¦ç’Q6’f†€q|‡BD(³3–nb þ¸èg^Ç  òð¼ÂEÀ:á½ASq#Ö®Ëåv JÒɸ³yÅNŸ DÔX0‚ïÍ(¿4Š÷£G”o‘ð¡Q@ùºA˜3\Þ´J0=‹Å@ŠeRzý§äüPêSA ‚4â‰K“›Ê*}*eltéÉ/‰¦ÈeœÀdœb¢¹‘> ZäM’}S/X¤·$$uæ) ƒŠ.§4Õ0ƒÖ³ÇÜ¢p¡ïFÊ|2{I@°®òÖAV°ø©aUÀ^÷¯p üÔ‹Š~~‚€™kàǬU¥Êäz»T™ÜÊÃ˲̄¹¹²Uèù«º4n”T>µÜ‹1Ö‚ÙòU U|ø”€50ú÷_\‰ˆÞ:Öènš* X°Ï¨ºe©ãJ«ê¶a’/W‰…“ÙÅLT©nÏL£XAü4Ý7YXöÍKw£àRyõŸªzÑ…ŸGf‘‚$P I1.JD&cxì§Õ¤CRµ‡dÁ½ô]èb!Ë6ˆÍEÄV ÄŸžùÚ‚C+M¼Îå="5§§íB“§ŒÝL3è××"4×´LN1©ïEJÊ9‘­Xq‰Èi<óYà±0z%B8,q%˜¿F5wQʯT½×ê£U2­‚š‘ÆÐCZdÔDÄA8–`×]ª;–‰ƒêP³þ(·IiNvÌ.PC$BÄØ¡eû ü¦W«Ê¦%µÏ)OjS/F ˜d¸$=“ë$’c) òÜó¦9êÜù–Ó@,¢ŠHëï¿X¨Z0‰uÖF¡Ù™Fj; 0H,Á6Óè¢DÈšÔ!–C ³X¼”ñ[šÛ{ÖH:)ñWÐù¿5jޤ rÀKÎ{# Nñ¬Qv™ Ãr_õÝÏÓí3;Ë4mavK Ýù >•͆ ‡ç/d²‘‚XôÇ`QMfæî˜úºT"„ƒG†X:6÷P øik±2ÒŸ³ü¬u‡ä¡À*Ażõ«%F;åžqDòY\CH^]å*;±)A^æsYEÐ?š„ìŽÏÉø¨Ä‚Á$ƒ¥õÚ‡˜œƒgU ‰$ˆ$m'¼MsôZƒ#½Â´³CÝ£ƒ¯lS|Ò”–«|qPD<£Ê€Áê*Â|¬}1_< ø­uñEŒYêõŠ á!òyÞ—;šB(ÈXØ`ô"•nÐFDÂSuÚÅ4„,ijÇZ¹µ‡ûOIêPpØÕ?Tÿ¹økÛis¢T0êÀ2Í+Ȅϊ”Îô½eš†¬ þÅO"ÑPwh09L`Ô(†¯S÷R# Õ‰Ž) ËLÉ•k*K'xœ»oJ»s>f”%Òþ±EPœ£Öˆ*èUqfä¨é!˜ÉYey)í¸0e]г¨Yÿ{˜¬O­R é þ)³á{c‹Þ’Y¿]§*€’““hzSR#À­†”tÊ$.òëPJKDöÇIÄHèS—DÓÈ™£—ê;§qs2ÆOò¥ãFàó^‰Ý€äó;[êÇfUi Кù0GÍ^лêÓy{·ù Œ}wé–†07‚pO ÞÔ£8Öñï zQø[«-A™Y–1js…ÊH¾ˆ«\G €HÚ8¼¹¡‹kz„£›Ry8õˆ¨JP.C&6†ô™+@¨o—˜#ÍËb…P¸CV×`¨2’ÐF ÚŸÔ&ßœÍÝYøQcÎü_µ\å1`XGœÕƒp±oCÔÅ"*àÀq)^íJ0¡7dH,<ÀhÜI›Ý–}1GE)&c“ƒØ€Æh/«!ÂE#2LšL©9D›LvŽ™›BH›#šU¨.a¬é¼éeÊp¢G^àý…•1á±ðÔœG’ä8GÃØPR´Õ¦6:Ò¼- –k³PwÇ©¾ô ù¹‚Ù&õfV> ¢Ÿ–z«óØp@DǤ¹nºµb!=×rBa,”\{'`f¶Ž®–ž !šÈÈ«k_Ë^.Î;Ú3xO˜ä·‹{·‚=Ò}¡Ÿ§Úf}®¾Ÿ@cêûjšª®%qóHd‘xdSŽÛaAÓ ¥¬¥Ú^Q¼é6ãÊNFêð•¬Iè~•ç#z|ªpKƒÞ»Ð£76`×(ÇNµdv/*þ’TRd…fEí4V.ý¤¤„‘‹ÈÅí˜Í(ÊL ½ŒÎ`²v"­â‚EÚÔ¸À¹ k®ŸQ}¨êAbÀ@* 5• –·j`@Zëq¶[4·sÙ4}c葯y¼/Ј”ÁC· ó÷£py'‡Ê‘Cf›8¼Cc­&jqpǦÚñCPy½0îÌú¸¨ŽØìVN—ä`éOO |±åCä'p[ æ~)¿4”ñãÓWµ=›¡,*bWðûRìÑ!ÄÎ(ÇHó&oÅ$åä«óz;ˆYÄ@—ɬUÈeÍýŸ™¨8CÓBþV3ÑÊÁ–#hipêÙïLBïꊌ«Êö %±[Y`òReÓÍEÂ(êCNVA"C“fcj(m€6EÛ…’ #L€Ä_ áš®3/{s¤4¬4Ö¤-Áfv)‘“suÈÙú/ÑSZ²úð‘{ïz/v%Üêk–…"¢®VUêµ G`€²nMM,Q¤ ¸„¥¸TIDѳÛj0ŽÙ1&ÓÈnBNÂnµÝd=!æ‰:æ^`2Ž4 éBK‚8´Uûˆ¸šN}U&|eÀüÓ*¹„„4m1™˜lõšÇ%¸]r®O­#(Ý? R^óû¾‹ƒ¼$€‡†ôiÀ<½©¢„Ì“T,iI¶ÀZûV~ž~Ÿi™öºú}¯¶©,IÀXŠƒóAcÒ~ªfE¸gJ€.q7T1Pâ‚^ìyTÔÔm«Bvšp…²4iÝÖ:þ欵f!'Tl»åLŠt`›à÷¥éŠL*S²gäIÄÅ6[²ì0²b$15…¦àeï ‹T¤Ì-b0#¤Ä˜\²’^vMi2R nBI˜È³ª ¶b ÀÍЛ!-¥u©ÄÐÜP–)‰6þj"¢‚j*)"¢‚j*)"¢‚j()"¢‚j*)"¢‚j*)"¢‚j*)"¢‚j*()Iñú¦ 矊n¸kÚ–QB´Äââ®Áî\ý”än54ö uò¡—{è0FDÈ5èÒVh –›N»RÔ$PME ¤µ± .ßí*B†!OZü0Ç¥MM MMOa±”®Âøh’€+‘à8Ò‚j)"¢¢‚jåÕ†L6½ŽºÔ¤æ©¢ TöMM MMY¬#ÕÝêÃry#)RêXEì dP™DFî'Î"õ4·ÙòÀ— Y”%°l¸»x›Ç7è–d™MØ))†¨K”Õ»–§²jjj{&–¦¦¦¦¦¦–É–jÁÔdûQLÉt÷Öˆ¤ÏZ¸t@¦2ÖÂæ+‘xéGˆ‹W ì¤nµ¹.$ґˤ&:qØ,ÙuÄôÅ!JÐ8µ£°ÙCÑšXèØ}b¬³‘èÐ’ˆt{c‹"JLêíÜ;ÍAà±­öãº% pp®º†)[ˆBMޱôëD"ª\·«•ÝßîöUÝ.Ÿ¥G¤ä{õšö=ÁôÞêðÁv `b¡$Á!i$ÅŠÄàt¬W°2:A3@f¥·÷iR¾Ë^0K Îb®Y†\†ı:£ÖÕØ{Rí¿…p év¬ÁÅ×\éINV”=Á§Z˜AhßXb“1·ò¯˜Ÿ““dц˜*Æ[¢D »r9;YàL&JŠ"ˆ¸/¬LÒçQ•YW—ë¸>×?O´Ìû]}>ÍX*æÄØËm)‚„“ôðá«m–›bO&½±ô]דRÒüTÒÉ8·q1ô’„‡¬S„a6{“ì´5555=“Ù5=“SMd&Œ Ž´|ËkùºökÝEa¾ô*{ÀPÔ3¸i:u¯”ù£¡¥aÆfžºµe'¾b¥@É"N¥@6J™G½N±Ÿª — R#ø£#0Ýó{NòRi }QÍ$Yív —<¨E–IŒÇÑ>º#L_#js hY$X›j÷ãÍS0Ó)-é Ô²$œä‹ÛjIò¡‚œSâmCͬQ±¢zè/8©É„FÂWIµ»X !8¤´7ô¢„ Èì&;šÉi¾&ô‘ 0`V­å9_c€gKŸ3öް*0Ù‰¼Üa<Æ´ª§ ˆÙ×*ä]+5ÖÒaóCJ—¤±$(±Ï á2- ­‰r¨c„Ë´K&-««Ì¡ ¦Hˆ—CæÓ–䑈KfèÛtÔÊ[-ú pØf}®¾Ø´0T°Ï &v»ðP¥LÉóŸJ"4n ÛWy”J’óg­Dõ „-þ”“ ø좼'ÕÁçQƒ;ÅïSávP{'Þ—si^«vˆŽ¤ Ö Of ³ÌM{cº§?b ˜°’Gj;B[@ ]›ËHrJt©®Vù¤~ar,“0%Ò®T ¢7€Ù5ÏyÉ$™Ru" ÈQÚ‡DB°iÎÚ](„¨¡¡ûÔƒë·6´ªÍñIhƒ²-¦Ôæbj° %ÄÑÃA $ºaœ˜Š€¢è©Ÿj?K‘‘ëŸ*’™< 0åŠsÃË„Ô{~™ö*F õhÏœê·{uúƒì@yÚþu3±ONäz-‚Èé)íE&ÀÝž(ÆE2×gdF›sþPW‘Âvôš(™°‡?º˜‚µAJ Fçz0 tXuô¬(ˆýýì#¶% ºm¨ žÖL%»žà(: Ähml%©[„‚z’Æój‹E ¦·q½4¡+’ÙÓió4ø«2Â7à’NØ2¸CÙhHò·u€“¦g­,/oݫȓ±+À榉³ºZw«ë<š”Ã`·ÿ+$'¥ªwC2‰Å¡K½l'ðªý«ƒì -i…,â!:h4d”F·1ºjÉÄÚŒ‘+…bÂÎ,uJŽÄõ/Ï8”ïDæÄ ‘nØž¹]áh$œ®Ðæ®{@“—]‘)…ˆFrJ aŠ  Ågßì3>×WNЛPöYéƒÕ ¢†Yv‚Ò ®W…·f–°m pЫIæ a>­‘u@ëR<¶uÃÞµ“÷Xøåá=3R6‹bÚd™vÒ“š ,²®3ÝüŸŠ~‘SSSSSSSSRTÔ”¤ Ì†DÎèÒhð6U¿;r sIªĕԂe5¤ 2ÊÎöë\ ØU૾FBo»ËJ€u°ÀÛH€l% ™!MßÒÅMTÔÔÐ-aëN,â`$îÆhÛG<;~J„!¼ óÞç4˜ ŒG&¼Í&Àà[= ¹¤P—©TªU*•J¥R jU*•J¥R©TªU*•J¥R©TªK04öX ŸÍyJš›ÔÔÔÔÔÔÔÕßA+–ƒµJ¥R©TªÔ$›çÉ ¤C¥¼f¤£>Þ¸¢f¬ë¡EYo€sµZššššššššššš ÅMMMMMMMMM MMMMMMMMMMMMMMM„)7v¤@¢‹8ø¨çTl·Fu @D)jjjiR@€¦e.‡'¨š†„ˆâ-J@VÔgœ$<„0u¿^ÁFñÇ\Ûq•5 Šï*ÍpW8ØQf]Sbõ" ¡½îT´¦xjj 5 Æ‚™6åÄ„³¹Ù«6A %‹‘rN2ÒŒc‘uàœèvâ™Ê©@邊6©íSÚ§µOjžÕ=ª{Tö©íSÚ§µOjžÕ=ª{Tö©íSÚ§µOj\=ª{Tö©íSÚ§µOjžÕ=ª{Tö©íSÚ§µOjžÕ=ª{Tö©íK5=ª{Tö©íSÚ§µOjžÕ=ª{Tö©íSÚ§µOjžÕ=ª{Tö¥œTö©íSÚ§µOjžÕ=ª{Tö©íSÚ§µOjžÔ¬®½dÝ?SÝâ¼óFߥ-,6×í5ð@~JJ®"–€¡•ÈÁ+>*‰w¨ÄT¼/U,¶-º—¿ê¦¦®¼‡]œ¾TÜ=™6«¯¥rzë oNèV6 `œÁ–¦¦¦¦¦¦¦¦¦¦¿7â”tŠžñ’·/%‡'|”%T 5jNÄA"˸w‚jP‚€Ü1sÎ6 ä&æGIJz…'pÕˆ1Òh–mGˆ @ýÞ‰ êdNô$.±vÎõ$2ä&ïË{R%Ÿ }°'¥ Ñ¡0>”£èÄ×ëu-®PR‰Ã?@É4E„ÛýweƒMîTˆ¨!°$ c¶Dí@UH3äõØ}©õ‚!C¼M49 r)3&‘˜a‹l»S*e%¤XŽ6âôDq0›¢ò•{´JAÝœÔLF±ž_Õtm®õ b#¶bñ™%èÕïðoC ÏC¯€j¸?ÂíZôÓpÖJÜÍâzÓð`NR$¼ãÎ"È iUFi¬E["#8¢A8]*÷ Ò0ÃÍ~ÕÁö¹ú}¦gÖ4€—5ò>ÅÐú§BRBl¹m‚’ò}‰SÒe:‰>êGÜ\z”4 ÀmVŒ¹ †#P¶¤ûÖhŒh5\%C‰0™:›ðß°Šãmµ¥"Ö„¥Xs&HÔ«>à Îwcz&Xf4a(ÌÊ’ÊR`pIæÌÆ‚Q’b7†Ã¤âš¤ LÀô­#çþ*É.%½h˜È$ß{ˆ†íö6ÓxŠ‚e–‹Éf>T×Z k‰$Ï6˜‹c”ä™ ¦GWIŠ‚ÙÓF‰v‚rÒLºM~ÑÁV,cxcÖ´ÖR$œ„Q§>J¸OXiîZs×=ËíQæ¤õ„…Ñ!ÙiX,2î¦Ü‘hElYeËÙä õvR¦3FbãôsôûLϧ!°É8t ë:X3T++è«‚…‚Ðí”ôš{‰ù Â\©¨‚XŽ…ë¬“g¸÷%¿ØV“RºŒv3ëc­@ yO¸ù)[‘ƒ”—.ݧL†É„놜ˆ©§ò}Éø¯lwƒ4)˜Í…!KÊ‹–fÃM.µ•KÜ Ì¸ØŒ  }'ê)&íCòëHX•™ž¶³½HÈ‘tNüœP,¸XwçŽñöºwT9±åx¥£ ³·Ñ¢¸‰RÛ÷ú€S2$56¯! råiG›XGaÓ£û¢„_²>šA»ðÔdÅ'ÄÏ,1£øtI©SafKF~NÀH/ì:'zév\^I0Ã{éC X¢»˜û  ,]—ƒZÛ¹WüÇ•C”Ã[Ô ¬~¨æåæ&btƒqâ&úRÆ+ngÎÖy 0X0TfbûöF6(—Ô\„Íâ’õž½ˆÆÉ«ø_• eçÛ±äƒC2ˆOyAa‘½7e¡C=kM -5Š%í EDp¦%Ž5ˆ¶/—€Í*TaE•¡v nT±QyÒ[쵟³pQF®dƒ†YйÚX.X-~ü—ëî ä GlE¡ s´õQh6= »,%/1™9‹qJWÓ&‰ªÑ>†~Ÿi™DN Jn®'K+œD±yù„zö®:ËeƒiUµYj7 :j7>„«L©q\Áw­7»Eàh]œù£n' \º`ebáÇTð-§LäͶŽãÝDD>eýÆ–à\ ƒYiä€-É‹ÄOÑ6 Á/¥žÄ}çäüT!%ˆ…ÍLìšS¡ÿªB ˆ¹0r—йº–YCsC°nB'PÔqZ0¼ä»¢TDéhø õ_®Ý$B4ó¢v!¡3æ½Óítî„HT”zñ¡´ôÒ¥O0~ « ¹-¬Z^~«CKŽFͺq—=iÁ3ƒ¯ÙUXåci¢Ú4A-™ôM[H<›ÌCÈzÒBØ Nl »Õ²;¨ÇÄrE7c‚‚ò¶iˆ.hŠM‰:iYúüR†,± ¶–±|TUlj°{â™ÝD‘°³y5|Õ¡kF“ŒÅñEΊ»3u’4×4à ¾3‹Å_&!Y†„#|q¨Ôb³wü©|ÛQ¥)[k§â€#†MjgS7:‰ŽÅškûÅ\›SB@ºúÔÉ4¨s‚õÓ„(؆¢/õˆü¨P ¶’Ó¼RòKèEý&¢îŠ˜&E ¿®b¶´$F¤C­4„,©¨!‚Ø4Í'É‘y ’aÜf‘,ý%²®,y° ]¢øÉI¨nâ Ô]10DÝsFÁm ~ñ{Ò?ÎF*&Œ^í ƒ ÕW‚Øè´ °4bfûS€¼‚½é#„‘+ˆ—Š|ÝÀ†äH!F‰X°<ŽÐ¢ºë\HÜ&Í&M´Ø¨ôu¥çCµ›&*g>‘4 ©¦XaÖ¢ˆp¹\7ÇÕ‡(´Q°Àº¹jú¤o[gζB’oœ›iOßm‰Šì'¦´Ô`UØ Ó´!,ŽDÓ±, ºG‚¨q®=Ð.ø¡¹Ý„¤¨ù%÷ Y.¶+ÁÖPÈAkdY¼’2p­{{¹<ã7ìŒÄ‰‰¥–õ“x³ù]aÒ³KÙ#ÀÑV±×ê—º‰† ²°å‚sg=ÄD8:ð åH縕3@n•+m]€·‹Æx¥© í¯¥Y¦ ˜Ô_¥(¤ ég»¹Ø?•¼ÏÑ ²‡Q½^u+ȳ…Æi[a¸÷<˜{jÅèZÐBAÖŽ™ÅgØÀ@¥ˆDÛ•~Ãò~+Ûhý‚Ò$Žg%zN/VëhK+YEÎí*2q†éŠñ‡â®~OÒ¼aø¯~+ÆŠñ‡â¼aø¯~+ÆŠþ…?¡OèSúþ…?¡OèSúþ…n-’"OS Ùf³æà޶ºRð©Ó>aبº–<·¨¢+¼¾µ”ó©Ž¿H™8¯T+`n´|ÉŒõÿhLa‰Tnp󓚊ªg1zÁjKØS‡9u0zvJ(5 ÓAéO'¶2çY~Äû†{ š”„Ì®žTêÈA#”kF‚G‹æ–nö¬Žë(ª“Úž¤ïö.Ö2ß4ždª³ÚujR±pôÉ­"l_¶© $@‰£3 ¾•{.&ÁŒ:-kË"=‹Ç\ô¬\Ç—Å x±dï Ý5¡d¡0I¸9"Ç›Úhéè¥ÒkªåóÐf²<éûx¢ Ey\£aÞÓ23ÆfYݯ:ç¹%1–<„äg3né'«Ø9 Öºš÷ÒÔåc_èMLgSˌ跣ᬮN‚9Y¥v5òDϤ}‚Mš,™(õBNµ~Id”50¯ Ø)CÄÞKÈ j#Í9eîEG|¢aO:+º—œËæVèp>bW ­¡ÛåóZnR1:t0yv@‚u(ù„;UËûòrPË=âÈn9G¨Ú€†0Ô$êS‰nÇSà,ðÛ±U°KíSä’o·q™)P¡ó(/N‰Ú“•mÙax›EdÐQ?#}{^ÔÎ$Õ] jüÉY‚Ú¶}äüW¶>Ñû) º'‡é1S4™,±¼~Í]>Í–MháÜ ô0›³"¢ m3Ñ $Ѭ7GùÑ`Y“y8í) é:3ŒÓ D|žO£/VÔL‘…Á¶_«È7©Ñ” Æð$oÐ Æ5ù¨6ÛôÈ7„ñ8©ÃÒÂU§Ø*½Î®)K¾‘A¤E\ÂéQ¢•´/ÓJŒˆe –†»Ñ«V¹Ÿ°>Æ*Iš-qWt`9if;”v!˜‹šâ–L{šö w#¯Bƒæå`ê‰i`4²q Má©ïM0A”òñëH„2²ñh¢Z6بy$—›DK\ÉkR8$Êó1+‚Íæ)hÁ‘†ã,9˜Ðò¢L ¹\“éšP‹2LlN7Wm` Èx÷"ÕÔ±×yh ®¡C 5$¦YE*eðÙ²4v¢›Š"ÈÉc‹íCÎæÐ¾x=*Ì»;`9†“&<©\TņKZF:GabrÄëÐÍO‚…ñ”ÃÆ%Í $‚nìž- å3_šº,€€o”Úh¶ Üs›x9©%º—¦þ]°N¢æpͽÕl#mÕã36,lZ‘!Ñ×LkD I–5|þ½‹£0MÉb 9¥ © h9H'´ÚÝ‚@  Ç•DBˆ 7-8tiÑ¢0ŽDÈ÷W8@ê ¦P9 ÁdØ&.XK±D a¾†æØú¸¢š‘ˆ[Y6h¦æ‘%gÓ^àhâ“·ÐaˆVE± ‰M‚¡Þc'ÐB#íÑ 6€y=žz‰qˆê%Îú!„ƲC¸”ÜÂá³Ø}éÐÁB“‰X&]mŸ^ÙPÚQçýv)2£ûUv³31PÃÜ{µ¸ÄWì^œ²’Êâ|°[o²üŸŠöÇÚ?e7âo”ÜóŽÝ]>Òß¼„Ë©WÑ, 9 KÖÇ4ù—ŠJÒšÕ°Tg(°ºð"Ýjb3“Ò?4ñP ·å¥kË›³wÞ¤® ¹pn¥Y:R†<‚ T!&Ò¢.†µ5*b0ᕱùÁMUЕ1z*Pô»H»/ùq<éL¤pçϰCfýVÎ*6(bt Š½ŸÏò–/RDЉ:}CìÀvu:48À¡åØ "b=ŠŠ1~¼sJÏÓñ,[¡Kºð!ÀRÙ»|[5J]ès7¼•j75‚mÊOÍK‘†³”ØïC1¢£s"¨2°,úRq‡ ´-Òs}Z˜0ˆ0­!¥‘R|-"c‡4U„c…¥õ$¶…2  WdM‹Ìv( Ã9Þ Œm®—ŸšrŸh·¸ÐmYK¡ ›‹gŠÃR‚F ‰:¸&ƒ­á[¦4ýPq“äØáAuPðôM·©ÍŠ˜:ˆ˜b®¦b̈pË%‰ƒŠSÉÞ%¤GK·qYE17\‹ÌÚ•ž5}‚ÏJP··ùLÆórŸ íP7X=ë_C¾ÍJ’B\9êͱê\ðŽväúÉÖ&5Ž” $°6BÀ‡‚‹w&èºûìå=Iî›]€KWõC !["ú‘H#` ê+Ò&t«^Ãd¹¡É!µ.äå„L˜.šUDL$°œGyØEÁ~(üc1"t±7[LAR³®¡ò(òJšË&¼›3“†Ô³ôR–è.ªÐ5…ö˜žÐòGP“MWÎÝ•;“”4 $rt SQB.FXÊGi™5lQ%Iî­åFVáÃBZZY@*X2â]ß³üŸŠöÇÚ?eoñ.Ý]>×ò¦Õy2@,¹Œca`%7ˆ‹iRˆd(«.ÎJ›–’=Ï÷´iPë*VIä óø ºÇŸ„æOxæ¼sVåI Xðæ‡ÉPàÝpe¢CÒR„V$ê]—ÔëzKRä^uò¨áI#.Ã÷FäòÁèT8±b€µû‚˜ IüþZœD”í/Š3€+Pø½4™Èž´#Í'×ê³ãôûPË=×H fÌ–/®œŽÓyzM zE¢{žeYHBI‘LJ½¹&ia¼2¾Š˜Ä79ÓI5‚jŽD™9(é^‚ ˜k6—y¥e[7ÞåóG¸ªJ환DŠkÞ+£}U©çRj#m¡½áòÅMABþª4ÅC †ñ¡Ù+%ìCR¶(›Rq–À§ZÐhï3ÌÔ8¨´ÚYÌæ¦”" «ÖAÈÙNéu¾`BZ÷k¯“‹ $ÆÝ-1˜’w¥b|AL¤yA’*a;ëØ¦Ë }©¹ëcòÕ»Ãwʺö’S‹Á!²,z\oõ`˜pK3 7®EÛ6™Å'B¥2¼÷d¤¥%O‹Y'ðhPïÛÉdÚÄÕsFÌ$:û–Húk†™D¡ <Ë^TE=Â=zP BŃýwY]ig°ÏÐ"[y¶&ñI!k-^T å 3‚5V€ªe\XLº¸ãÜmµXìÅËrZ®DLÑÜ.OÅK'šÏ3שéR2€efÛ“²ñš~!¨?Á*e .<ÈdõqFŒÀXµüŸŠöÇÚ?dà‚:‡>oºº}¯åØ¥ %HK´æ©*ʹW+BX rC‹ÒP“ñ±CÖõ¬ä°ˆzzqnë,žIo*™‹PC̉÷¥Yz^†ÅÏJÅ*‘ñŸjQMè‰$»(ôℸ¹¹@æ“z/dÒÕP„Åæx¥Ð¼¯mg·¸Q¥DÂ0ú”„Õ®W‡˜dhx?u|¬øGž –² I=]Ê…Nè’Q|’á)eÆ ³Ÿ%  &Ø&mÒ¥Ðô*¼Ø+d›tß¡/ éž»õ¥\NW©Ô³°’ó.{ÔŒ6Î&6Ÿ2…",Ì´!kéš72I8›Ð .8ì0Fƒ ±6d$ÅHI!['ƒnÇ.ƒB£Æ™¤®e‚‰6w:ÐE‚4šî¬âFÑ„ÎP3¬,‰fÜö¤`\PùÞP–/D¿H0&Ì‹Í56X$’Z2]j¢·ªPâYä|ϵ raI¶•§­4³ Á, Î;ïr4À’ Sp`3枇£Ñèò ÷$¬oš~Šj'qŽ.àPI¸1ëH@+°3@‹pÛˆ·êˆ¶¥ÑŒK, ‰]¥ÀwLöÈlz(XqH0‡©I6_©Pf)2á6‚”¼Oãš‚Åï E¤XÖœ€Œ+ò6ŽI£—ü¼ûÐÔâÈ• ¬ÑeºÎ‚(¸È$' úUÜÝ€ O»¡FKF ƒN¹,ÛÒjÔ/–býÚþOÅ{cí²EÊ;‘ì_·WO¢Æ" ù‘Ò™¥Øƒ4XŸR†,Òe d®q¦´õ±ÂÉn ”b5ŠŒÛI:…Yf+ušÚ¶dÝxÉ'BäV$Rn &x¬M¸¬°YëQg–¬À•ÖÖ¡À„l±RËÆ-\´ÉÛôE¿l‹DQ¸‘rÊï2ü»$$’T’jQVÉv» ×¶•&.1ð hc—“eØ{‡‰KJ¼ÚO0=ˆjˆ$»0]åJI"Å µ=³©ß>Ôúʼn"FÌMÙ“ ^6îü ì¦÷S†ëc•§¯sOšu”!µÒÔ€™º×Q|2 ݪF • Lb¢ø:$ÖSÖMV‡ úÖD™c-äúUœä!9ºþ”i^ ó®â¡cHááô©ÏD&¢ñŒïNb\¤%vR,±½Z Á!Âg-ì•)t”¡A´âå:15½#6ë0°üT"©ç÷@HžrúÓ¼ Èޏÿj1R\DDíÁP¥<Ûæ¡@IT+:\:µ™o2zM” ‹ÅnÆׂÑ>ûäjì½'5ª*6æ±±æáD‰ì›‹RV 8 žmÍ\{2²oÃS­¢ÔLBªAS‘rüf¢«²DÃn˼’.ý¥9'å*"V—!~¼þÔ¹Š™ÞЉˆK%8Y‘CxœqY’D# Fûâ¡ÓaTÌÁ…ÍdÞÕ#e&VCƒSKN)Ò`ºI›Î6D$ŽlÝÛˆÒk³°iA¸“âŒ:çC‡.t*ûE²+b4ËAß{ÃÞ‰ˆLè2yÐÖó][裑¢!º¸ T«ñÑ/¦/9s\ï­r¾´³w¼g²*úÓ1®ÕÏWͪʶ/;yzRJRJm1&uѤç âÛM2_n÷ª ³æ†‚bðXÇ,Ä‘!epd,‘„F,œ”ªH ˜´ç+óÜ~×ò~+Ûhý“Ф÷+'‘í=ºº}•`".©L‘8 âÙ§Gh|F/ZzA 8 ?4 8{€'t"eÃ{bh“r¤™,àø¨;2Øéx#·Ñ B DàDq­Ê ÂF6ˆ,7+@ˆÅ„ã;ÏÕÅ $/¼Zu†@LRÓS$B"n½È`‘y›R°ˆH±`‚4CWåÝŽè‚B-o|Z¹SÈr)‘µ+";Ý}U~‰öƒ4}X¸H’)` ìûW/@; á;0HÒ‰ghW"ž Àf ©Vñ• ñbôÛ#`È+«BALßç-©„†!£~×µ†$ÂHÑQ¬FKC{.¶¼FìéÚH)p&Ÿ…Äþ@3P‚@oAØ•Å/9@@<á‰õ¥˜Ç„TÄ’h¾ù‰Á<Ò( •×5L~è)ÂÞhLÞŽ$ê¬1kïá íÉ2Hëv:å 1âò¦EàÒ³K5Âú4™[±Bäm,Th …ŽÇ]…ºÏèjË óÐy¾™e©5[˜/—$u¡®‘3hÞq=èhYY…åcˆ¥±›¬…¡7ñÓJZ-Žsu«å"f>h8Vî†(qI2Ãøòí&€šzˆÀÍc&¬¬\–‡È ñ,䤑®Ðæ ñjÒdá²Æ„AP“$ž±iÇ›Õý»’:P`` :ÒÎ~ƒöƒÉWBŽÛðM%¸Ç½OŠ»Œ4¶oJLSˆôQ˜4§`MãCn½õ 6¨+B,ÈJku×]iV58¯ì,œÒ;AVëH˜ÍzR¥¢2ûÓN)(`ë×éå n†Ý)¾|R`¼õ-Ìv€Ò€)›¡ d“d)E“‰Š [dzñB@P`YÄÅv°"†NÖ¬ȼÅáuŽÄ¢3q¹Cȸm­µÛΕC¨o¦"g°˜$¥íEXÞÓ¿î¦@2ü‹ø(‰CZÚ6u¼•RZ뿚ƒÂ'Qdëœùv†‘®·§#Sôï‰Ã­.ø,”€/ Ô¦ Xat(q‘…**ª[¡Š&#…Òöf•J¥UW*åzöÇÑ1ÅEr$ÒÒKhð)¨N¥ÏZ”˜Âä ˜¾å©ÕØ•„ZË,\C$#…y¨AÆA˺j8•%*2~C ­¥#4/âˆÅ$YúOÙÙ)±.ÓJ¡u2?à왑À¹ù¥¥B„–×Û±;‚ÖE¡"˜G}/5aËù£“èy×"gZ%N&6ëÝ~×ò~+Ûhýœ3 „Jy殌¶Í‚cšÕÓí/£áHâ7½ ØÍ$% S0LûS°]È;0úÓéq™©æN$Ì¿ÍÛ„ÍpÏX¨ø|&G†ô 7X¡?S÷Q99±¼2ºYºš»†Æ _œ¯a 5lWƒ¿5À¹ÕŠ Òe¾'«ð’@eÕËÖ€+«j”ÄÑ"Z%[B bŽ`ùA©~zM©ºefH¯fìë б‚!@åf‹¨‘i.丆™ƒ–…gìãÊ^BÆ‘· ÜŒZ®~2EówŠtÄ2ƒ©r†={]‹µ ˰^‘$£ $8i+ñë^j>Óˆ276:LM@Ø\E Y$¦lÂQ‡²Ù¢êø©ÀyOzÈ&Í(:±P50"ã$bÝi.3dKš±¥ªvŽ=FG̨2$§“!òŽ”qã½'’JHì62€«ŠBÁ}FPõéNs[²_B‘ÄùÑÆ# ,6- .·+V- €d¸57uD§)†7¯Eël ¹_#–²™¯«§4¡BYðý'¿RH A¦ Rÿ¡R—FUlþ¨Â ¡i-Å[²Ä_fJ:zÒêÑf urüûÑY À–ÞVÜÆ¥Êädãg¸™Qn‰Ö8%¥Ñ‚z3êŽ Í0ì!xÕ†yÍ52|‰ŸHã²áÓÝŠpPÂ<‚f£,càoêRiû_Éø¯l}£öŽGAäk½+WOµü¾‰L¯-˜\5½*eBjK’!LûSìq ܲ'J0åEÞ×\ËF \ÅãÐæ“@«` ZTÁ0ef⢛j#ò½Ž+†„£Òj RP™BÉÝÁìHz~ŽÈ¨9;ž4 «T `7ê{ Ì_[2UÌ‘xáy½©ÆÊ˜%ãæ*m¢fÄ™¢ÌŒh•€ù§Îev( ¨ÅX©dòÚ¤Bö›>šhÙzÈ ¿4'4úÐTQ acr“µc¬ÚA"&[ÎÒ1ÀX„CF“˜{Už¶|zzñòj;ƒŠ‘½Ë5gä¶æ%3j …¾,&Và N%Wch;ª48á´ºÉ.cHæjîÌÖ.4*à¹|³Pê ŒÃ« ³@$€A¹f“¾÷ÑFñ¬ž=èÌœ|%tÅßLU¯„èšÚ¡¨üõýÐöÆü%:@HVj¶¡ð“KQéèš8„¼&뙀Þoرv‘†›—úßó™„s/Kž…èdcS²»”; @¨|Ôˆ.…âÎ]äovMü‘£%‰"õ‡öSѽó¯ôQ€)û_Éø¯l}£ö„‘²8J›±a؆ 0Â=œž¿iù} BX^%˜2ÐÆµ¬ä"bgs;KVƒVUºý3íO®[´feå1ùÌf²è§Å–#Vؾy§´‘Qgb'ueëñÝf[ë|ˆ+œ™ËÈĤ¨YÃÞÞT5ìz˜ÊOÂO¿ïº\;ƒW°:Oî„Ç0qžVMÿå+r:_Xâ¢Ì¥.ùúTB›,¶ *'ê_k›Ãs€+‚¡2˜a °Ž«ù ŠŠvô`BJñ&àÛé?àe°(…$¼Ç})i ‘Øø—%MH@Bh9)«åj*ÄÅ@ٛ܆f²ŸD_t{Vfµ¸üôèAQ—jX•€"Hqg4 ‰¤ÌµA`Û¯}ï€±Š ÄþÞµmSyóˆøúfLCGz” ~NábðZ”[|ÜØ´^°Y¶`œÄ3ÊÓ"D@”[IÒ’d!†ëD/!Á7êrQ)Á¤-9dž“‚iEÍɉXœ”]½oy®Öl-O½Y¤Fg Äû J•á7IK+œSòª]ŒÁ wŸµüŸŠöÇÚ?j5ÀÉt›ji“ÒŠÙÉß Üpó©L&Ð'2Q œï½Ñåü)ö©²>GèØþ]ø©sƒÜo±­b& Ç=]\¿D‰@sjmGLÇ[Rz•£}¡ô' ±¡—¥fè0#ÊÄwo? àø*g ¨l·ŒØ[› !mZé¡wŠ@—{-§m¯Ó°g!8€Ðbì{RˆBH›$‰Ý¤Sb6 7ÔÚ†§!0Qâ Ò'ܤbç÷«Óˆöâ•”“dû♄=òaXôëø¦/ñ¥é²"ÃM¡k¨uµR+Mt´L*AºÒ9a‘iÊ Œ^k0[a cLòÝ¡ËÒÍÜDØÔŽ[Ì`BÎQøHÞÊ8 2=ñR‡‰nfAq¬ÝCE# Œsx³󥻫¶ —di§l¹"n|…逖ŒÈgœÔ£9ÀarJ:A4Â@_4ƒÐa !©"¦%ÒÉH†‹õ*ÎE/×59Ìf–f/:ÎôÖF¤]رjÁ"œ); &ýWÒ–ély™læU Ü¥Éâ&Ë;{…À‚"$9åqÕRÙB’bÛqß~ÐÍ0L&Ä;÷ —XUºÆãB«)nkØ`nB8¨¹ŽlX&³™7£b–DVÜŽ‡¼UÔ"À¼5žA½dV윓?˜¥ÌL«/øp[²qß?kù?í´~Õì;ÅüT"V6Fbô0bÚµÒ=ÊšÃo6‘ÀèÔnà?p¼xóE¬@!1I±`KŠ-ž@aÎWI'r¯ ¼§$•þŸåÞ\; aÁÃ{Keú`A;0ùRM}#ußS`œéQc?Àü}¡õ‚rD —Ý£-œ#£w’©^äf`k¿¥ad0–˜&º6qzJbAbRVÉÊÒ³Š"k‹*î³=à-O„ÂpÚŒ@C*:Äñ5.,l~wíJàBí³Ì|š3*MÊ—ñºHdóìd«°¼ïóK"@5<ƒÍXæð÷%ñFÏKéšÉÆÙô%¯)ª%§ThB¼%lÉš¨ CMŒDížmS?Ì\3¬NóZué®`–œ.R÷TP€hO{ùUu S‘.tOZ&Ôgøðß´3è~ L„Û¦GÐÀó§#h€µ†EֱܰÐ1±o™£9ÜK±Œ§rþ4"/çGÍyVëËØ…{}ÁU­.$,‰ Ô{ïÚ¦[W£†¤‚Õ„p.½év—-¨ ÝJ¤W? 9ŒK˜¨ý¯äüW¶>ÑûWµs0¨¾ìì9QØd„JZgU0e”Ý$gÔ,we ›°ÊʹHù™ÜpygfFåE7(Èk `b¡“g 'A¯ Né1}zy†±j|8þT‚H“ŠE²Å¶EÐpyª ân¦#€sçWǦT˜Å(ÅÆ%”j-غ@êì Ùð.>³õϵ>‹€H·¾ ùî*xWcV¥Îm,Âæ÷½F•] µo à™ à6˜(]ôeIz+%²À‚Š·VêòöeëñØjÉ[Á½W)â5©x$2&dV]H¡q¦óbôºß={Z|PóÓ ™;JgØZXÝÄßÛZœ6Rž¾Ú«¯×•KQ¨õvã´Šõ™Yȱ–éK4V×nö!ðÃv¬ew°})ʆj jõ£†‡%ãx¢ÄvÉ>¦‡Ã“J‘02øan»2Ðä K‹ãzZ…M”ÒëtÖLUœüô»1˜Û¿çDÜ‘‰—CŠA…(Ò'ëF² \ˆ\G½îe‰‚qçÅYÅ{#£Ý»ÜSÄX$´žf3AŠ]uøÆzÔd9 ú™\=Ö§¶qcéöi‹+¡>õ å¥&Ø—UîÚȴ::I¦GxÛ¾ÄË=çí'⟤T …àÍ õ„ù555555555555555554µ55555555555555555554´…)u¹~™©¬ŠœH‘BÃÄmDaOY²Ù¾Ö« ›ƒYôxRP·‘¤É¥uÔWQ]EG* ufZÂNµh ÄËvUUV¥/ZlõöÂÓ¥-‡DÁÀ½u”}¡§+–œ!Ð<³Óc­À`;ẛo‡ _z8„ð&L¢È^¦¦¦¦„°PË CK%õ§e˜ËoÚ·F¦¦¦¦¦¦¦¦¦¦¦¦¦¦†¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦†¦¦¦¦¦¦¦žƒÜPSuN[üT”ÊÄ^ìZK.ä÷4tõ„ŸLÔ§%ù6áëµ+$d .Ɇ'xzPÉ‹ä=ÉÕO¥«¬®²ºÊë*þM~+¬©a‹Jåz´™ók¬®² "oÈSî•c#Cêi«¬¿ŠÆcØ5sZâz¡_6ºŠê(±½ueD+ç®_](åÈzÌhŽÞ[;ÆÔT'™A@Þq&§®Ib :V²€ÆøZ‰¼|_æŒK@Q.bD@¤îÞ— :h¤õ¨À2ĉ>¥u•ÖWYM¥ÍijN(DÈ^ÂP¶·ÒÐÍÄÙ¦ôÜ!†¨zÚ¥™Š$D™ø%V6®–ºZék¥¡±u„[™ÚËf¥èèo)$u¹{\×4"U¶ó„1N¡5jšNbžÖ“a°ÃGZ^‡šqmn¯ÃIËŒ!Ì#,Á-&úÄUì™Ùe`5á¸ëRQ"o̲t!«3LV ƒ³"w7¦ =Ò‘ÐîlÔÔÔÔÓ˜»/Æí‹´&ûÔÔÔÔÔÔÔÔÔÔÔÔÒÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔ׿üW²*hß쟵{PjcÊQtvÙîsOÁpVÜÔÔ ªä…Ž´¥QEÈ7€\Ž‘ñŠ‘äÕG†;€Eî¼;º÷eaHZð”›H™ÅeLU%$}_ÒŒ6S]¹Å ›—ú‡ÚŸATj–o~'TéˆYH²îÐB ƒ—W»0èé)UÝ•ù¯á¿º^`0 |LMM+êrŸž;¹züwü'´x þ^”’½ì»6nqsŒo¥!%®]ví„ LàŸ¤4 ™é¿—ÑÐitlÑL)3 üFz… Ø`]›ýH&Ô¯tÉF¾•×€}ç>CNFC ²3']{0<û [ œ,¶Î[±Ø=‰ ¨"2á›uј ˆ «ò§‚@CÊqåW`RÅ'¨8½8öŒh™«!„ZK |ÁëJP0 -Ø3QM6 Œ ʳÊRÆ—ÅBäÅ,(Ô,;ÚW¢ ¢™32d =è ‚ù]Dû"7¢£@c[‰H²à_’i€O¸%»,3-€±NbdˆAÕ.ºßë£×,nýAÜxòë¥MwË ,e¥7sô$x÷ýÒ‚oþùzæÑ‡ºý¤Å~oÅ é±)^&Úž}‡³8oÇ`Ž>£ö¯t_É ¤Ldá©xgƒY:CÞ°e…‹ +*‘ èÄmï 2S2‹›š¢]f 2@Àhìwuî€G[RDQdÝ[ÌËš”ˆ@œÄ¾õv&1ltÚ›¥”¦&³Mâ•Ñþ³DìŽ}¯AvïŸj}ŒÖh¦¶müö¦È®«š ´˜ ]ºm_ÂÑà“)‰£¬‚ ?Ç’õffW³«ž.sKZP‚On^¿÷À ¥ʧ3 %óÚ„T{ÑLšmw†ô†OT†DØ[£øŽï¶ìÓ›“ `„Åé€X„¹}ÂwIsÚ¢Úý·}mçI6i£ÑÇO¢!˜qo ðZ£goöó@‹äßjÊ@ŠPoµHñ ¬s Ò·?Gü•dœ'úoÁ,çJ# ¡PÈIâ°ËÒ’;’FDÝä÷µFã¸ý+þ‰a·™N•g™ºÙR° v³jóc+m2!éj×öŸº¹S“$Ì€:FNÇaµ&•¨ÄÊxD:]YÒ>±!•`­ òêÐÏheãJEhÂrNce-9ˆì–•(¿zÔ!Ý?icÕø¯l}`A+æç²{±˜Ö]«×^Œ3íš§í^ìÂÔOZò mò/Å„•ˆÀÀYœ¨[PU ä¬“XK‡s½~á <­Ý×»(½í2m@Nâ Çç^ËáÐ…œ´;U¬ aÀªc1ˆÆ:¸¬Q'D‡Ò MÄÕØÿ/ø«°±øâ²M ÀЛëížÓíO Ñ¸¤¤Ié§Z &áÝñšÑo,¶åñÒ™1‚˜½ãÚ{–Ò‚<»Åtœ¨òdu* ‹×G£³/_ŽøÁ’ „ߘ¡ï’;!G"ÆÍ2GO£í»q‡µÙa.<˜¢)-{©aȹH@‡¹©8‚ôNhô˜»¬Ãy£ÉBoXB"ÁÇ•€Õ3±5#Œó×õLSÓžœsA¶BÎzÿµ}ÕhLÓ•8€ (EÐ"Yy¤êD)>™‹\’db4ß4Ó2F\ø¶hr óÇÁRŠÔÆø˜–ÔØÏy£"ËJ_DS(Ø•ëØ;‰.3vtÊsi¨ä#$›11pÑÉo«*˜Åô1Óz±%– Oå\T(/rUý+-&K ¼u¯/Ž©­2]¦Ö>Èíí4Û(¶jQ|÷„” Žëö¿“ñ^ØìŠŠŽØ¨ìJK /Lv]l‰´mÏ5D¡8¤R:¼ÐïM‹OÔT#%¦Ýjt)Ç`ÔÔÍMb§±jjji¦§°©íj**;b¯ðvr½¢njTR1ɲhÖZ;ûCÃ_ZeV´§UÔïEè „p ż"¯Ôç1$[ ¸-fõ vû_²*;u¨¨ìœ¦®<¨4ÁåûiXóoŠ ŒGœÖw4„‡PÙ](“ˆÏ*|°vÁa'ÜÅK`bJñóIHW»YcÝ¥¤+Á;ç´*;‘QÜŠŽäTTvÅr*;€/už©©‘D”d bik‹$ªÃë Q QÍpAì³Qfˆ»ÐÏ’ÛšÒ´äÉX!ŠÍ2¥¹©ÄPŒQE”'‘R ‹/ßÌhHóùh*NˆaÈ¿ø¡W KÁ6±zÕG‡——¥7ožž´³SC4ý*̪UÐÅ,Ðñ•`ƒÅªS"±ÿŒo@È#¨Ú±¹.~NÑŽÀöÀL {ŸªÀY4§ÎDÔ ¾DiD`UÌXÃ+çvoRd'0/zˆ’ûn3#¨Òä~|mAˆ­s6%ÝiºÈ™ÄÐû0¬Úð´^zÓ*Â(…¤4ʰŠ!A) -,SW$ÅJ’Me2y|óHÚS2ÒnIcŽB¥ pSÎB#­/·`%±/ MI‹–Ót¨:±I]ð<>[P(DQ"ÀˆµH4pâ–qNC.ÔM–mdm¹6uÒ¥¤Ã„5+idëƒä{!#Ô.ê†ÂÒ(©¡Ššr¬9wÿæ!҆ų9Z(B??U=Ü…ú·¨îEGr*;‘QÜŠŠŽØ¨¨íŠŠŽØ¨¨íüߊöÇÕ#ÖOWŒÅã«yõ©œ ‚%p(HqžãÙ•ˆ¨¨¤¨¨ì¨í~‹ÒÁ€8†_E¸a§å”u)/Ù:¹šÈ°¦øƒÈ›ÐI\Tà"hbY`X4^é²\„•”4£5ŠBg“në$¤ ›í((­Ôä°Ò­+ ÁŸ"ƒHjê/»z2…˼bÑQPœg¤[Ε‚E‡”ÇV¬—¢kÉEEµÈVTWYmj`l®NžšÑô‘–"|© Jä=k®C׸}©ô$B”„’ùü«‹ÃÒ‰HÁ¸Ü FýØ·‡¤¯Ïr 93Éet o/™Á¡-„rwcZ—Žß-ý­FÀô î!X2ãö·b•q¹·…¼Z‘ƒéëÛ3K2píeüѨrÆ›36¯~*\jx žÞ‡ê–ÞD€XOÝI2Côѣùô¨Z^fÁƒõ*(ä ÌŒáâ³ðKÆÁÀ±Þð’&‰ã–¢!²=Pœu0Ù¨Ò6 ®‚ÿê.P“d‘Õí;šD,N]ŸœPÙ6;ŠñÂQ V{±ÆÏvÇXØ…ó—Šc$Du"YQß\S9<«[K‹™ ‚ñz½¤T ^•»—$¨L†P:ºÑ9$ÈʼnoeE¤Ót))DáÂG‰©S,‰Ã„Ab÷MëZDitÈl ÆŒi6¡Ë`U‡ödlÐU„Ù.4¹ñŠ%ÒÀ‘qœ–IÖÈÕ“ÎiÄRp[Wjn#¢~jû4㊋LÊNè`«²ùý›ö¿“ñ^Øúî® ÈSr3¹ïARV×í^ò2îQ mà,ž}çÞéžOG¯qBÊdzÝJ[ŠÊòÔ6„õ¡"ÁwTÊëØÐ’‰)#z2ÈÆ3ë’œ^Ù|6©3æQ-çú©„‘°¯îœÓ‡“T°‡‘Z½A(Nµf>Õë}†qÜ>ÔíI…(kÉë\ç­ ÜìI¦Š‰†øAºä fshŽÔ(èLyÔ$7$»ÅM_ãjt<©Hõ’¥–òþ ò®'g©ú"Q¢îY†€„‘ç5’”bF-CÖ˜KЦ'ÞÛ¾ƒ-è"ÝÔÈ)–О¸Çp7*äå·„•œå¦yµ ˜@zÚwEÖ§µû9v¦ÿí"ÀŸDIPu³ð} =1]^l½SjíF%¿& g¾48FfXS‚Þ³äNÕu`gIçHkBBÛ­~#B11¢`6Ivƒ:`áÚ±v¦cJJÁ-&¾X$iIWWŃC°£(H›Aº€¶™Mêqr’Ó(˜òì‰â[$¹*^”®}Êž_;ÒCBÈ%YSp¶-[ä,ÔÍT JÌ ïVèG§ËÐÕèUüv¥²æÚÐÏjMš2ÀbœV(’ i´Úÿ`ŒéB° üvŒE"̵1¸ˆù¤Ò%¥`™ý}$¸5ú?“ñ^Øû I âb(;_µ{Zˆ‚NŽ|û„‘"d46fóÜI¤F9H·Ó„¦’+UGáÜ@HFGdÅDzpïAó'¥zG쉤£•ý×ö¬&ZÙå° Vº¼PbQ-ͤ›Áp­ðT""6t{+BNÕN¯^Ç€)½‹f3J.«=™8–[L2=(m'£°"a¬Z)DÄ”Ì:‘ö›S%í_wt£Ü¡d, ×™ÅöBöüS‰¢¹”äœ}©ÚÉ#3íºUŽ^T»6Ó]Ž” »`þwk¢‘!7 &tÆ´œÎ¹¿<¾´úmíWãWóùvªYì)Òa(ATá&oFDo„µôdÈ ”%Àm…÷Köø(“÷ÞÃÏøîÜè)êjyÔÇêjuìA0õÓº4 sCYòº‡D®¤®õŠl ˜o)}#µ€ ƒ \Øœ–ÅGÒ†VR7„O–<Îú%\M! Í¥¢†3rÝõ!ίUdöìk‚ ‹¶CI슔6À‰Žy©)-›È.Æ#¡ŠN|Z™ ëfËm誡‚Zõ_g£ìQG¥@°‚Ãv]xŽóŠ´k•2°vs1(˜–#.Ð*˜Ö>ÂAÅš‘AäÃJ÷­SŨQ¬þh>j!?E eÍ~äüW¶;÷U›}Wí_¨Ó%$yÐeÉéPåž‹özRCQ‘<žëÁEfØô#åAŒt Ê2‰Sä[Á‚ŠÚ¶±­; èšÐhHÙ3žÔ‰¾ô¦s´ÌG4DaªVÿjîÆ¶üwÞëÓ¸Zˆ’XgÉP%yBFˆ!\a;LiFÈv—ì<ÿŽÒMñA.Ÿ[ù5Š0ÏS¸â[ [-§Ë­P ¤äÜsoò‡&yÃI™qp1Òj[øzT ŸV¬“-ŸÃŠ&yÀnÔ—OÐWéXÒu§ÉźÐr¼‡È}©+nX–ɶQb TGÓˆ7.ˆ¾È{ÈÞWʰ‹Cî|Ž4éÚi˯žÇËΰ»¡WW!`|ÓƧZÎB‘8BvÔᨉ`xU3mÌTrYI%ÐŽÑQo3do`ÕÍBØd$^H‰ÔéEùäj YŠ@L9rMÖ“~Ù¤L‰7©'qÅA9ïÝú´a(<ÑÔ=Š!ŠÓEðMDþšÅ9É–fêë{b…Jµ­&×mßÚKus>u:ûßì¿'â½±öÚ¿\ø°<õóš–Ž4}W⌠ÞqðeštfšÄ%úЄä9#«t¡W#¨k˜¥‘FiEË=!ƒÖí2rMÁyuQú‹hžÊ ž¶ Æt÷'êÄÓZ!³÷«‚ÑgÎÛúÐö‡h2€æQ2 1äÅ@ àÏjÅ ò´Ð¸M7ÕCN) PÉ3. ÞÌí¦Õ ÂBºn7`¤ìHHNŒL Hµ@3¾BF›ñ^'û¦~<Ô¶å}{a©D[} fÅ)¶Š2\ð"Ƭ÷ì|Q³Ý±¬ u®¯ŒmÙö¼Õ¨Hœóûo¯/2Ç¥üêtðž¤Q@Ú‘QRÄûÑO¯éÚ5 M(f‹Ænm¬þ¦-®å¡$/ºýl¥€êiç:\¤óýaç¹S.1Lj·†,®Èšùº·W¯xí|"ÁØòj=<ð:³Íõ:…)–f¬*Ä ±•Û•䱘ˆËÌ+ô©“/x‚b2ÁniAÈú­Ñ?ÆÕ}fio ŸJ™LÔ`Dh ß9‹ÔlˆÆj`m0É£O«‚i5œÐGs'J¹Ì¹iÄ‚“% ‚ൔœ¾GeáçÞ…ã‘üP Š>?u >›¡žšÒ²,$aÑÚþOÅ{cíµ~Êá‡R4²ñÞ&ñY¡ëDÅÌ ƒªpÃ"/Ò†snéIÃ]&™`+6»Q ÀÄ /+»MŠœVìÍï`Ö4(CŒt¿¾8ú&‹ ¶ýÆÅŠS¾5~u^&=Ô}©Û15)›¼¸í̱þRµJÒ7¥L€Ù†ž´%‡x uB&öë@DkzäTNÇyR€eX­Hê‰z7û*d^ëKÆb8£Px‘¨¢çøì@àªÀ«‚ƒL}ËmC‹º¨½Å‹µ šñäYYç½ô<…Á3{œz¦ê7³Q”¤3'ò~RÃ>Ÿ4 ݱq”âsТ÷ÎK+’’-ÃBÌ@›(±„` ŽfzTU‰ˆm²" ‹™šD»(Ùawεy§,ò°ÎbxdÀ“‚ ‰±±ál†Ã­“Xm„fKž¨¾'z2À’w JFÈÄû§p£À¶`ÌXÕXÓ50ê7ÍÙíWëät>L”¬”Õk|ãzQ™Œ¸¨á*Hp~jßGBí2¦ëJÂé”ô¨l^P\}bC:§Øf†nv˜úË«±é/3¾v²YÄͼ‘ÍF$ñwó=‰nÀ½g¢•X;ÉÑC°…•l—Å4‚uÂEá7m;µ•ßBXö«°]x 0»+ïvÓ1çBFêØ–2‰ ²¡é#R8zËР…ñúŠ$ ~ëŠÄê_0Ú'1‹Ó1`ÄÞØµ^u¢³Âb-г:ZuµEá”FÇ3y»¿Õ8Nµ¤tWÄàl]z´HÀ¾•´£ö¿“ñ^ØûGí_µ×³[›­t‹••;™.‹27R 锾æ&XÁÎJ&±P =‘k0K»¥FB¡B'ÛžWF Uéu¯W¸Yo;ŸÁS€&F¦à ZP9Qhƒ3=i`¨…âLê5< fò½`èÕ€‰ÁãJ>Ôï,†å%¡ŸÝ2O–kBˆSRðø½Os®4c*àœQ/YK‚&÷½¼ ´Ë®ÑÕúB‰[tÅ8¹ z§ÏΞÂã°JÄ!ƒ†I êÊ"àÍíc{o@àð¹¥È“tÖ øM…ñ™pÄ1§‹S™™dÀÁRt¡ rG;fzå) €.FK›Z—o?.é1‹T†NéÚî¼RèQ<@’óÜÙÑ]· VÒEœ2YEìøŠ_$Á:ü›:”¢ðÆÛÒ“¨­I+àf•L:ÈžëŽjÈG0=K§u¨_„£La¢a`‚§ê47[æ§L‰®â[¥ô“lÐÍHÝ&Z’‰n¶·m¨d¶$cÒ^ùEKf´¬“,òêÚ‚,vJ[&Ì98JÉuu„Éj¢ŒúÖl xØè :RM¨ÚCi¨æÚͪ«±H¢37&ô„D¢óv•/Jbê‹¡ú©l`Ä/¿´Ç=ùE‰p­‚ —%dµ±úòõ§%„‹¶FÔMô©Í”ØÂá66É(Q®ÀBuhŒŸPc:VåQ•–óïzÚœG–ÔýRÍÇH;Oä6gæ˜H&sÇ5g—3@I–KL[_N ³¥2^}÷“ÚÔt™ôüRñÙù?í´~Õû]{ Ásåý¦%ÀĬe' ‰M" ÄN"BäT³9¥$Dcbúš4jÙÉJñxüÍö£y 7øìÐ"zÝW¡‚UóV¬¨žDûæŽÞ·ìŠ– ÇÖñNåËQuñ’¬ÐœÄB{¶¨ïLˆ—?¯ÕIÒ*îó?dwŒÃzá*ÜA~`¤ÈF¢ìUÌõšqIb*0Hx3|Ä 2œ@@d˜LäM)éUÞ§6­bYÎgóM2ßàTÛ'ƒ1<wbذ¤3, œ‰._Šh‚6GähTC…4Dä|Öêð$zÑ»ÀY´‘B×,Nµn"KK1Ù=§BWÐ5Wb“‡Xkæ³íS¬Z#ß¹p‡ÌærÝÖ†:DÌÔDÒ\Çs¸v‚ß !ˆÈ¿¬Î”Š[Ô;Ÿ4Ya ³6ZÁCš(I%Trù—V;˜¦è3Ñ83P˜@[ä]^Õf¤g®þt ­Át’-ÖÖT¬ñb=¢o`ŠØ%ô’|˜|»d„-Ëwæ'¾RI`v°D HÙ.åÃ~)|9 NպׅÆm“ÏgnÄ@bD¹¤KZ-¦ 5ØåkÍ6ZÙ–^3[%˜-vœBó'!§Åu(.É¥D…ÏœR’JP»AGý&ÜØ4v¢«*Ý]Ú¿8 0ÃÒS](F@bA®ñfЦeZ ‰ÅÔÈ9ŒR$–ffLŒÅû¢eBÞux Pg!bsô¦7¬S ¹`å¢eILÐ$Œch¡0$˜ljÞÓ4z6÷Ÿu³‹vþOÅ{cíµ~×^áQ›"MÃÌ8AÅ6YŒŽgYË${³KAj@ÄùïS˜ÂpÔ:Γ±¹£"÷â˜:­Õe^Z4è—ÈðҾ䗜ð”RÏjÔÒö$Xó¦ ƒŸ#šŸj}¢& uã÷Q_׿ŠEdo¨-ŒiÍJшŒ&cÎàrpÙ‹Ú""Øœµœ%ÂÜËø©Ð¾ÜTäÎØW"FÄœ¡=X Xía'•,ö²“10ã=)$oÆdlbѬb4U¼„‚Û‰zÄγ:öS D BHMJGÐÏ;=¡n@/ ÊñKCKbص;QW+uìµ–]"4_1Hyãg‘ÃåÚv b ‹°ƒ{ZˆRDÃP …Û¶ ŽÐº`à["7$9oz4ja²\0§¥"a$z%YŸ¶~‡À¨}vG`’)Í@D!«7'\ßÕ7MÈ8¾iÛ±h{ÏA/€‡Î•%¦<è‚÷cåŸjÔ.Ÿ7öí; Nð+È÷±“µ¤ØBÀŒ>«R€NûÕGÂv¥ É#D±­«®'&ÝkðiÜE‰ùҌ級,(&´ )µîR.•ºÝ[«ºêöFg@_b¥™,BÅì@Ã`ÖβY‹V ['?@Ó» £ ¦¦ÞU¿>Š<úkÒí gοH˜­û§$…ˆàßw³~â"mÖ7¢Î•þÕä³Ûù?í´~Õû]{®c “µ¹£ÜÀIÁ:¦9©äN«/¿h˜.xÁ0˜èžtÍèüÓ!oÈìšíY1™³fãÎnX‘Α6gjyôØË¬]¾âô‘þsúš&x|d}šÑ\4(I6˜j†Í³E†ìîÌ»#µ¬·Œ!­#lÚˆ#Æ,ÒYE§ñBÀmVÞÃ]Ü{QV¢`BrÈ §H×ZßPÒaŽÉ•4”ݰ.Æ® æ’. GYWÇ›0ί=’ŠB\ Nå†.ÚnRá‘(ØÅ‡íWÓ)I}#ˆÔÖ›Ù!šÑ¦¾d•ƒq&R$,˜¿§• j³øªþr%×è{М…y‰ (Ù’ô²°É³s°ìE%ªÎtp?7lÍÆ,"‰²YúKä(«-¢‚)èØ WèŽ<òy}ð °®geÆ÷Ûº¬´ ñLÀ§n;Ç`0¤Ã1‰âtéIûÌi<Æ{qØF&M‘Ïâ®s#l±ê†…HhðÁ;Jª"æ0÷åVu_Z³ž;.Ü0~ø¢Š_&º‚oóZ^ 1S¡ ä- 6«XÜÍ$ C‚D¾fh Ù”5ý_ÒŽEZÈ–l³î¤»*yLÈ`pw®&Äõ_:=’’oл‚[˜‹#û'“$éÇdX”¸ÛÇcÖfïÈjìwÈìÞ¦Ôp™Õ]W-aó {%M–4¾ïäüW¶>ÑûWíuúoj@lK•L¸½ÂÖϤÐÒz\ÇͨÛK_¥Ï^ɱåÇXdžsDlLÉ`XAuôîŽl52ÐÉ0Y›ëK‘j'‹4™d¸\ˆÔаum†±—{U³Ðû¨ã`H"üñšŸþZ{±ØZ¦}X#¢3ÈŸúÚÔð¸!$"M˜ 2¡- Š€À‚¢¬DVVΜ¯ÄT¬ce‚؇Ê/ëA6*ôçõFÁ’"àÒć¢ô[a†ŠèÍ“vø¦þÔ†Á`0£DÕæÔ› {óÇŸ=ü´¥q( º-é"íå–qgÅ.ÝÌæ0sÁëE …åq蛵úQr„¬3¸3åÒ”0ÄØR˜½â–˜'‡t:Æxµ=˜,\¼I‘ g𛡬`o¤Î‘sdì;f)‰¤¢" ˆ…)I’vïZ›Ã±M $¶††Ç×Å‚Á‚¢ž÷qÓæçƒ¿Øˆ %Š9d \ µCa$÷É„bÛT'C¼v«Œ‚ÐP÷ <šŸa–’húFÈ`C¢û©Û¢0&(BöÉt1j“ÛÂÍÞØ¯T{Te6CÖ´9õP„ß HkRœ»ùV­÷mãÖ¥Y蠟ü¦ˆÑ@fóó‘`!&Æç•ªQ).4Ö]£($“MšHÅáoH*. ƒ‚UŽ«Øs¤¤a$®]«¹…ͧ˹jQl¿Qí€Å¸³¡»ö³½L ν³X& ½)„!I¸ílDEç~"Ê%É}É[™Ý_žïäüW¶>©3 M­¶ÜÖ,ÜæÞ-Þ~Õû]~Š(è"ÇͰѓ s£·3C )ë&­:ÈÙØëœö‹À/ÙÉê‘çÚ)3¹,:EïPHR¨mf¤Ø%H@×e˜§Obe*pÌ®LZÓLœo&®íŠ”&±"ˆ$äׯçÂk‰óš.“Jß'´2+©gΰÚºËîïECyvëO)ãéŸYˆ˜F¢S&BÍ®8¡ ÎuõíZZ{UoA8Ö‡™1Ñ9Õ߃±ö¿½†) ««5  Uó³Q k}aÓ¥- ÷Ù7æÐ´o¥X’qˆ¬ôR\çz÷|Šk‚¨ !Fâ˜Ö˜†„D|mA+:ò”¾o4D‰S¬ŒfhÈÇyVUêÐ$#„rT²Y‘.$¸^ŒÍfYáìÕ­4Ñw¤fÃ6 ðï›~Ée2 9çæ€{“M´/1vŠ€§/‘›Q˜MžÅ’g?δ¢,–hìûR¨{ð> öKûƒÚ €é×>OÅ ‹²ÆËóïDÁN¬pö›S\Xo>×<©3þއ±üÒ‡/;”` ¶WëÖ¤–falY­š…†%úBBIBm$Ë!L’€B„‰-•v<â‚J" DÎ à;L8‰1‰‘¹VnÙ#q8 Æ ¡±#A­ŽÙVÏëGYçsG‰ÏÖ‹`f,ÎÏÏÚ=‘"”•iMZ‰k‘¬ÂLL³ð.½(\‚]Ôâ„ùz¾Ô)Ko‹)Û&‚Ð=jTåSåИ,ò˜6åУc3`î³ÌãBŠ’ JNËlúâÁÎPÉŽ AËDG¤Š^/Š"ÿÊk¡±{œïuÕw]_³¸›´G4—ÃÎßPîÇ3$΋éB„±è웄$Ú'ÒŠ'Œ§ ´ÄÀ©0’Ð鬳i *æ~u®Oò=¨¢Œ‹0tS¨Je2D›PI}=X leÖ)R dp<ãÊ‹M·Ê¹X¶Ç—dh "&ß>”Æ’rJIpƒz,ÌrCo:?Ö¡É„e“ðö[…Yƒ°5¦\0?Yû 8ˆ¼gÊ/R&(¤R]Z2øGñ_“ñ^Øìˆ…¤ÎÔ¤™C;óØbZJoŠHM\ ãΠIÜp‚ÌßC¯^ûö¯Ú\SˆìÂ`ÕS@^¡ms×¼ïKJ"8©å¼ÿÚ.fW6ÕPcDŒ¡ˆ6%²îï˜u©~Ù+PÈôc„wõõïAä]‘ˆÊÛ±™ SrÛp&3„ŠYLÙN,PàýCíø%ÅEåw‚ÑLb Q}š%tІô2"Eô¾sn3G¼)ÀN®ÏP£³ª—g >@¯‚yL}õ¢ —;sL Hï à´MËKÀ÷}{f¦•Þ PA¾Ó}Fí_ð±öŒd² Ä>À¹ˆÞ¢>™Ý„Ã>f]0otO&òô=–”£Ô3W‘ˆÒ )¶knÙ»;[^½ë†RïFË ÓBo)*®üT0v4/—¥#4,ŠÞaQ(…($¿Á vÖ`Zj:ð:•8‹õ!H-ÂÜëÐî‚,ky¢/z ¸7;'â½±W«Õêô¦(çíÖšwÉùšÂ&WùÒ±W«Õêõz½^¯W«Ó5z½^¯W«Õêõz½^¯W«Õêõz½^¯M^¯W©«Õêõz½^¯W«Õêõz½^¯JÀâdw&‰¿(% ë:=*õz½^¯B$ðk)Á,Ù4DÃZ"¼é´oI`/‘·Z:uH„YGP,Ó)™B¢( )néƒ ÒÔ1,–‹V¼N¥<Š<Ðy+É^JòW’¼•›¯%y+É^JòP{jˆÂDVlKh„R $–lQǶˈY]$‹,DÓÌAh’Ð!$²0£Fн^¯W«Õêõz½W«Õêõz½^¯W«Õêõz½^¯W«Õé«Õêõz½^§jI4¾Gä¢Ñt&ìÂêN•‘ L €,hKD„(´oÖ>ËÃ3+ÏZÒz•ä©–Œ5N1¤Æm½F‰°dc(no#vÊÅLfÐÆ‡eØxf5¥O‚Q&$½ X¨¶7– ¸fïA5P¶ò}bÄs¥9*¤‰Ù„è<‹ÐI°L³ žÜUÊDÈ„âÈ›TCy&å’ù¦5ƒ@Ã{ŽÕe~MäÓÉæw–ä—qÈPEн^¯W«Õêõz½^¯W«Õêõz½^¯W¦Y ½^¯W«Õêõz½^¯E^¯W«Õè¾as¡ÃD7äÝŸÝZ ’¤Ñ Óò¡ 7çŽ*õ¢’K óßZ—$’~ªÕjµ‚S‘ÈÓNá: öC/ އdæÂj9z5‰zÅY9¨gzÉqõ^ Œ°p… 0T|‰¤äÚÝt¢–V,Ç•hUÉ-¸ ÝÒ“K3”xu¿d\Ä“J\áâ‚1W0’džj /h¾½jZŽ)t'5t`ÞïlÒGg±>h쨠ƒ‰žÀ\@ÖT¬Äáÿ(‰f!:ŽžU0›‡ ŽÕD^wâ>·äüW¶>ÂYQ³¯^ãÞhÛ•båí>ߺŽÞôŠÙï¤Ò ëì‡Ó~×^ô0^`@¾ª-ôÄÒwÎT-vìXÖˆx¦„Ï++ÏÑ××ì-äzƒóB‚Ð Ûd‡¬÷´~Œ±­NÊ/c{s\:?¬£0È‹‚LfþŸE\—`S¡yöÌ&Ð&öÙ-„JÅÈ=fYغœ—™ÑŒaó&´Ñ@/»bÉt«Ø $bØ©&(µuǪÙ>Ç?¶¯ï3߈"fQÈ™fAˆó(…  èTËô¦h—]mJr&o{»ô¤Ó}5òñÅ]§}o¯î€rýw¡''‘’(³C:Ò( ~MºQÄUÄXÓ­CÅ"bKÉJ„¬oþR•¢0ï7¨A Í0´ÄRi™ˆæùôŠxÑï/Q)$Ýq\¥iAAúoÚëß à´8GÙÏ`@ L]"u¾r](̪\êcŒT‰¦À¹^¢ØGékëÝ•EGÒëâw¥,b[ǯd€é?Šå=hMJBxÍpý7xá§’Ô%“žÂ&ˆW“ÛlðÓ2 ’}Ú™âef#Z"tM©7-é ØÁ1‰×ºBbêÇÂ(ÙO^Øí—ùdž™óúl!C¼OÓv bðĺÅJÊFÊ6¼ŒÓ¢5šœ˜×v*j3wˆ¿‹Ô¸â~ô‰ IlQ:36 &SzÙIüR ¹—î(X!´µdM.¯#7jM¶†ôq‡ÏNÆ•3>[ö½+B˜€y¦qWXT RÍε)d‰\-þT¢&tŠy¨LÙÑÄÚ˜âHz5nÇÏ^ÀT 6ÖôÒp›Nb’š; Qj*@:øÚžâ@› B)UÚÚ¢¼èbñ[Pa4JÝÅný¹š$3„i–¶é¥þ” ¡ù?íŽìD¢äÚdÔÿi›Ëäiô^œ‘ cn;¯y&£P@N#XÐÚ.µpb(«~¾1õOÚëß „v|Y¨EQ r”“Ó]Su{Ü!û¨~˜™I¹ŠK±+€ÝãèkëÜ+y7»—Nå]VýH%y]g(UMft~-ô@Kb”™F,o8–4{‡Ò3N{ Ý ý¢LÓ)—6òšD«3ÝÇèãX&hN!´¦;]ÊW›ö ¡dŠ`äIÖ î?Èã‚ó”Š(ô0ÚHS%¿–»»sU¢%9ó|ï5cIÔŽäߟ_ÕÜtðh#Èáû4²õŠƒèõŠ#4;úT 8ͤ—¡‹­AX¬(¦¸}Jr-2be©þqS Q›ƒ©Š TÃÕuýRè²1,AšWÄ@ pÓq¥hpÒÑëV@š©è›iÛ0.¥6Ýšcc”–wEi[Z…ÃÂítâ"óÆÔ†¦ÑÅ( ràBÐ6ŠtÍÍèº.Ý?TRÁØÄêÒMš kÇ4æt­imÊ%Š …0&±¥ {7G‡Î¬ã0¼,rUÒ1)IÞ l´¬ùÓ§~´…†Ì2zûýOÉø¯lw’éÚ‘‰»§hHÎ-ò¢(†.f<û¬‚†,ìÐBõ9Í:õïÂg]ê9Ö^Ÿ8m â3cŸÔ~×_ dH&ç9æ~ËÜhЩLŠ sFžÀË®`ÛJÁ̧P@ôˆ9jaˆÌ.ô‚œ^d"¢ìw5õî´*[™[ñ0M8‘ÁD‘¥¢Äˆ&‚#’a¸“‰·ÑÊÚCPê°Å„ëÝ>ÑúEðn2<™òîŠ2W›SÞCÝ{’$\‰&èò1­`ùvg€n6£ Þ¹™$ÞÅ»ˆÄÐÚ%g~Ôãæ™‹ñV¥´Òœ»(µL:™·>Š)]ôÞô-ÏJf$ÓÚ©éNüƒ¿äÓqÜh!Ú9çhã4鈾N”1w2Ù>ènÐ< nžÅK Ï“jœns3ùòŠŠË'ë|ˆÖwî c^䈢$î9éÚ€†§£¥\Kôi‰d5&ýmù nGI¡’NâÌO•K¤<¦§æÌ¶L-¤tí\V]Ê>‡äüW¶;Þ°ö­¡2QŠOZ”_è¡,ŒA¶þ´ý«öºý i"è"ÆÏ2Êù2w2DxK•/Ì(«Ôycƒ:S²«ºËl]천S C®LíèÒ’&£ŽÝ}~ºî âŽQ ‚éöÒ^%ƒ+‰…ë•èõø¿J^CØ4ÆP!sa3G1´ʆ©ÁPà8&ˆÒå#3šfI‘%±'$@ ÕânåìS ‚  êšö8_(“X g7ô/OL&$öB-A!2Èô¦ûiGËI¨‰rN¶´ÖŒé-M‚†J lBÝM<ñCdYbg„TZ•ÄzUŽÁ@$Ã÷‡qb€`|¾~»Žú"%Иž&ˆ —iÙ¡êÌÙÒ¬š)ÐVE8› š¼¥”–îæXœkLض ÒoXM0°ò5YQÔ¶ÝˤDCP>I@$ÃÜji&‹D4oü¢Ý¨ YÌ;Å1oM?Ôx¨LëØ“P¨Å¯.7Ã%䀿3Vëù?íŽÕ 9ç§dN2‡W´@†Ü~è„ß1¥oϨÓR m·Oö€e7?4©Ld&a×N”uƒÖóJg¯ëóõŸµ×èœÖUÔr: =Óp€yOÉXí”s‡ñ†˜:CØQZU`E= V¾¿X¬ÂE"aÇP©4QèlDxûGè¬ì œVù±K¢«ªöLL*Ήö¥±3hula7Ú¥LH7K›éŠj`‰6ÌN=j[l%Ð<Î{^5žï(b9^U'@¬,,„o¯b¶7a‚ˆ›Ì£¤oh1}¡š{ [žÛòi1^Œ¬B1¯NÁÁ bè|vNÔee[§:tìc„„uǵ™î GO¼;Óàœ³"Åôâ)4Û&Xjr#mÅ#fÞ³¦ã¾°—Š@uj Ï&2“€€zÑkv—@dYÊJnÞNÙŠ!¥´,™FN¹4½ªù¨Ëd$ó yw© #~•4dïgäüW¶*: :XÝýv¡Í.%ܱB…ðƒÛÅQ 2€&ÉÆhI‚Ó#{éQ¤ÁˆD¹ºh‹0ÄñÙ5H†lÙáúïÚëôoI´–jÅ»=ÿMNâl.]#t¹ã°'°‘Ø'T›¦pÅkëõ§]ŸÞ?=óí£Âǹ{Í{ÏÁØ­(–@#/VÒ(´KQc 5Ø›yÚ£g:¢0)äÉÌMFd@vJÕQøcB ”4|A)¹3z‰9h¦'¡BÆ]• /Ä;ö’ ¤h „ƒˆ!¤—îAçõ­+uÁXæ€ æÉ÷‡z&gË÷R߸¨ˆmýOÅg—Ž7¦ã´¶ <äýù3Ñ<Í=Ü*#@ÆÙæ°z܈Xë-Á*UœÆÅ,°/¥Ó¹ó¸`u4qL󋘑ˆA¤ö©™D<Ê\©#Ôa¢?@etE„LÁ­©ðÚÂ:›NÎ×ê4%ÓR ¹`l5,4«Ñm}ƒý©.Gƒ÷NØ‚ŽŸçì¨k_“ñKÒ<§Ö’èvI#fÌîkõ¤q„óÍ,Wæ±"ÛÞš9oG¢.F¿7¤ ‚_Ô.*ÙAy–H^uéSÂ5oíHÆ,æh™:·mYû]~’#¾Èäy*M]F·Øèaë\M ¯~Õˆ•/=_‹§`ÅM,ö"%ý*:2b`@ÅõÞL¥.Cf}SG`êŒùwÏ´~„ƒ°ÓxC+ì‡y¤4$9á‘äNË38&m¬LY&½)*ÐÁªthÿ´=J=}WÄ݆ö (ÛÖ¤g.I¥c#Ú>¬f¬Zg³³â®@¢ 9|’R‹B”/ÀÍíN¥Ùžq>Dh*Å»fÌ"êìPA¹äq(Wd&cƒ° Q[¼tûü”³ëç@³ZU–‚A:{}7†U°]iy“H#lÆÐ›Ú‡òJ½ `È^7ì@V“F1=%ŠÈa{ƒ(@ÁiX/d<Ô ÎæÆLÖ}ò?5”F:¥üöOlz¤Oßkõ¢¦N”’» wI70Úå !¥ô*ÜAA†·Dÿ–ö¨ðZbÓ‰‹ÆÕù?DÄnËÙ,u¨‰6Õ–QniÄsôµ‹æ8ïŽeÔÌyæ”v0C–€Rpg>|öEf®Ö bØéM(¸Þ$ 9æ5 ó3–í$˜sItJœmõ_µ×êYÁÑ'ãRÔ¹ÀbÒú<¯Q_/=,"hÔÙAЩ,DÌ[عõATˆ$âYÆbýóí d ŸŠa"ä‰ê³ç'‘vðóvíà‡Šñ¿¥'ä¿%%²Ч[Ö :ò4;:Àz¬Vò¥¥¦ãÉ¢,nh¨ë6x½œ”6“Å.Œ Ae-nN(ÒÀÙt ]Ô­Z>U4C¨ž’hT°±máw?ÔR+ı:¯·Iup@`,ȲØó2shŽÙ÷P#µ/ž;Ò̯Çub¡½Gr„qرK“õÏ´qØÄ\c6)-˜¾ø¥rª>¥. ¬ÇÒ”AÌ¢z˜|é[3¾ÂÙÊ’¬®¤[€ou×\cµ R!¢VŠ“ÑÑäÏcõ@Ì- ºô¤žê7ôº:Éo#:u©u$‘s²|? Ä=KS,li¤7ŸÆ­Ø‚MÄé¨Úý±à‰ ¡s×S´À –qå¬÷¶ÉçOt€ñ}»Ïxa!éP"aåc婊*„’"t5ýS‚™Ä棘é94±š@KG{j;ïÚëõW&Q¥i6"1ª˜ˆGGº9!Õ«‘{©J8•›H»Õª¥¼Žùöѳ—< Ÿžôk0 ‚ ¤Þ‚Á“lÀdÕýb› ‚¬]5ëÍK#dvi@±io¶‹â­¡–\ 3¿ª-x¡ËÆAÇ–ÍܰÎíRÊ,‰FøÔ’ÅM„"+h½$ s1`cfºP22²øó®gÛ¹j‡§úí\Á­ âé݉å4˦¿ø«x<¡?”±šP æ²]·¥¢˜ëÖÇΖ¥×i¬ýcíRÆ UØüíST\Òá«åö ü[ H¢6ss4iÄIânSßV °eàëNJB“qÙºñ1¬oÜ´‹ ÅÚÛ1݉l½ìÄ3c  (튿!ÞtìXH¤ ¸Ö>”ÌÙjY:SZdnÎ]ÛöCfíñ5 &‡t_Ç☠­þ*Gúö$Ô4–5n÷ò‹=]‘»&ýµt£ ¹Æw)b´æ™_oÌ÷ßµ×ꨊÖo`‚TLÒ°²¥»$CÜuÈØvidÞš3,@‰ê>£7µg+O`ÓvưãÅûÏMã¡¿• ¹3öÑh2ióßløwZˆ’úéXÖ&²ŠÄbR1æQ‡°² ]c}€Y»LL‰˜`õ¥'xHåËÑûhÑŸEü4–Úë±øT¶–¿ð£ŸM7Ÿ×q‘ nP)]ÙÅJ¢ý{oÞ$lÆ/ögب…bA:Jì ¯LцÚ¹‚ñ»ŠÎ\ðx§hE‹ƒ™eòŽ(¸Ìœº û$š”ð†ràÐÌ#Å #„úW6í!(LÇ7¥˜ÌØ.’™A½Ðlœ]€*.$I1*zt"Hm­ßüŸŠöÇlukjšÁÝzÔÊV §¿¤œæ1Ò’(K(¢e¨3}œ'¥DÌ`²í±ž)ØÅëxÏ÷¦â†¡”Öv§8¬œÕÐ’ ìæNòÅ ÔöÃíuú¬EëŸG9=¢·Yê¯I<ŠŠãcúºfÑy—D‹=h|4LÛ˜-N\³ e¢û~hl—Ž|-F€ßWolMJ%‰1ˆóâ&o±š0â$‰qGè¤P¾2¸ Z“ ’6ëô´~•¾ ÷u>;˜NfmGXÐíN¼Ï%Ñż©$à"òeäC¨)MÐ^!¸ä ãRÔ $.@u–Þm¦¦l倵è:=jY‚¢lÝ› ·v¨–&%‡!3 F¸ÜÐ\ºôT*T£)æ1§aÃ@<ÖOS'—» ´ÒµñÁ±¡N&ŽÑ>?§uþZ|M*ÿ1éÚqiYÄ“ ™Í©!40çí°Uh?•€p_$f¸'ZXÌåø6 Çf¿jñ@QÑ‘Ñ(àZº!†,ÍÌ~”m^ L7,ôš&Û Ê¶ovÍ%¡ª9’L¬ŒºGq¢…û'⽱ݕ´©ó3hòïg1n·üQ '¢O­§‰iB Ǻþ¨S%=ïa¡dÇÅ( St¤D&ØÞ˜”…1·?1†ÑÛèZ´œM,]5{³ãZ\’âý?$Ž¢gÒ¶9×Òv£¯H¡E¿DQ‹¹ùûM~ªY-‡ÄN“4) E» ¼Zõ8¤€adã fÛÒ¼·d"×P‡„DvLRt• µ:AÊ Žœš:%©+&³™B5µ*•{ Ø*« ཨ#ˆHŒ‰¸÷‘(°‹ "3i—Ó·ñxÌóAlŒT›ˆ¼ã!ôO´~Ÿj;|o.Äšâ= ŽÇ¥šR•‰vƒô¦lxò¹ìÐ*ö¶>c´\çðý÷L«[R¹£nšv9AëF'Ë~* }ª;|Ÿ™«Ü}>Øû¼†/‘øšY»Ûƒ¯Ñ `¥•ɸY%õ[¥+"r9šjÜ?E\"ð„IÐFæãÕåÈdO¢m¾”3ßXÐ$¥FÖ¨v¼E¶âs+êsô­Íý*4†æøó§}éÄ×&8ïþOÅ{cê!(MDÂW) ãØ87zýA°Ÿ'š@ σÚ<¦1Ç·köºý\k³y\<Ð(Ì9½+qs "o .ç4Š\½ÃN‰q2RyÌ?CÍaeÝÕß3Ø)‡Iaò #qÕ'ܽÄD‡a‹¾ ×Ê[R:l|³ÔŸ*>™öÒ6xDõ®H'£Ûlû„¼H“å7â€8‰"a5 AºêÐÇ„dz%“ ’ÁëíBb @€œòàÏjÅ^ º– DªÕ §{a¥šåu{ÇDÅ)«šEúQQÈI´º[?|}„Ÿ»H8“è÷ÜÁ×è«€^\@‹ìPŸ‰ŠB‰aÒ&®6‹¼´4Û5ƒ)Y!€ºÂ,Ò€% CÉ›Kjãè§_Jè„wo™$@NqÎ'5p!ÌM§©? ”IÏ+6·©i¦zûpІôÞüŸŠöEMMMMOdÔÔÔöMMOlÔÔÒÛ²jh%ùå·µ*Vuø½3ÆSëSSÛ55555555=“SKSSSSSØéLéý©©©©ìšššžÉ½MOlÔÔrÁ‚ò ÏÕOdÔÔÔOeè/ÌT"i­$ЋœZ”Pš ¨Ò˜SÒ®8Y&:š8—j waò² 8ç3ÎBÞ”’v!‘´«Ú¦¦¦¦¦¦¦¦¦¦¦¦†§¶jjj{&¦§¶jjjjjijj{&¦–¦¦¦¦“ª>U}­ b©au—'¶*ùÀ5+­©½ç´ÑŠ¥ž¨=(ƒÈX'x|û‚`ÆFø ,PB@ÇQͤcN-žyT‘ˆßÙŠžÉ©©¨6ieØ*jjj{f¦§²jjjj{&¦¦¦¦¦¦§¹5545555555555555554ÌÞ Cæ¤<>ô¥ ½#ŠHÒ¥”6ôŽ*S£4³48š/b˜Ó ÑdÅ+ŠÅ(¥™©ÄÐÜP›RÒ”Íùiz‹˜ºÁ555555=“SKSSSSSÚþ_ŠöÇlTö&¦Ô´ÔÇbêÀµ6¦—²h£±AmßCÊ‚œ$4“QõžüÌÒu3E¾®¿VMÐu}…%·Ž© yz Œ¦@©ƒcc½£¥(Y„å/žíI¸|ȃÉ8æ…—Q¤ìÒyyT/¨bÀœv™lׂ³C©'·Õ>ÑúhG)`k¡ò¡PÏŸ;ñQßZL»âƆ9ÒƒÄúÓàh·üSì{²™Û®(¿n¿k¯§d6Ä}*À,‹Iåö_“ñ^ب‹Å9)£“¶;ÑÞŽÔûWíuú¢V¼Eº G¾´èV\fPž;ú:vEœ€AÈævƒu耹‰2öN‡ ÎßXûGêOK!¾¾´³fIú£l¾«/뺌À*ðm»±H€H0¤ðNHe£zOZ`Ô¡…æîÿÈ>Á†‹}ù_³Á;puû]};DÖhy#Ïåô†ÜgYé·ÐüŸŠöÅ"`3r'B3öoÚ¿k¯Ô4m ‰V$JË€,pQ(Í’L/(þŽ”º Áå5ï†q­IO©†m*&³ @v·i (®"€$KQ<¢®ëŸó¬}£õ$5dëH¡ì ìG`I•ê´MümRY%!tˆœ‡†…8dh“}_ù'ØÒ,¶—ˆ¸:ý®¾6¨Bô²üŸŠQÒ)ÁYÊóèiåQQQQQQQQQQQQQQQQQQIQQQQQQQQQQQQQQQQQQQQIQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQI4Í(Ø!ÑHi!z–k¢º+¢º+¢º+´¨è3ízºFÚ…™:²ûk¥L–ÓŒƒ\†'ÞhÒ +Táȱ á¤H‰èEÌŽe"hl`ä×W^…EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE%EEEEEEEEEEEE Kö TÔö‚Z#-Öxਨ¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨ ¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨«‘!yŸŠqD²ê£~+¤«…Šé+¤®’‰Zz°ÜjvC¤z ^ÔA)Öò•k0|‡ÁI5…ί$éDF$I²§IÍt•ÒTv+¤®’ºJé+¤®’ºJÏcÒP‚=–ðóQQQQJ*«>Ý************;?7â½±B=(#쟵~×^ñ@ÍÐÝø©,^”$'S¸$/[zíBÆÀAMî¹Ñ8ã²Å'#a/ùPl )l6]VšJD„³œ±¢÷µ.Á¼–l4# ®¥:wze¦³ÕZ:SòO’{EÊ VlŠq³å,9–$DÆ ]#š˜b%]%¦2 ;8ósÊÚ}öÕm§u‹/J¸Xç>”4y_ùgØØÀv~µýØ Ùƒ­Yú«{Dù wôa=ͧᾸòŠžèP&²²«±â9/½Kà%ÿ[Q1Oi6Bì?C_Jp¡q;òZæ2ÂVê§±¡o¬fF{‘5ù¿í´~Õû]~¤ $NÐ /šô¬ì‡ šÊ‰!±`à[±y‹Áå4ÑW€¸ Þ$´ÀT‰‰¦«¸‰³›,h±X²˜_ }¤7Š`E’p›*àKÇZÀÚšsg¤Äó‡©*$D ÎúŽGÊ„5Œx6šN­½¸Üõ~ÄûGê‡1K…žô,ËçEÀ ÅÊ€Acþiö>ø`:&‰¥,‰ªP¿K4ma„ ó/•F:vLëwšÇ¥5 õ± èGÐTH‰3 7E/ … ¦–&&ÀµkD‘ƒb¹@ÒúúTðÚ€ùZxsõØ1ܳ¼õÞž ȵö=ÏÉø¯l}£ö¯ÚëôÖ/V.'&§©R…À02Iü/ÓèhéLÉ$›šžeB—»ìÏ´ïŸhãê æ ;KacD¥ËL…"Ñ´1žú >iöp `E m4áùõ¢@„ ®…¯Ûù?í´~Õû]~ÒPÁ0&ÜAfµŠ×‹è_‘ô:PQ(ºÑC~²áÁƒìϪ*¦¹ Œî $«SBDk1åš$§þ³H°§¹ú'Ú8úÈ ¤Ž$ÔܦlCgC|úïÛ=ƒÀăMá¼lëSÛ*ÄŒ‰Î~š Ô7¨oPí3+šjæ0Ù»Mªxɲ3C0ˆY]µŠüŸŠöÇÚ?jý®¿O3È÷„óì<#Z"é;ÁÍÄÈ&na1µH¼~ j5†Ðj_zkÔ‡©—í«"x®¿jÜê1ЊS—Ú¢°æ=™¦Œ87ó¨#bŸú×$°bZpú'Ú8úü- zuìÔ†Ÿ$­XZ߉o‹ÓˆFÒÒiR.c^(Æ[ÔCl6N"Ÿ¦Lmù ÊzTþdikGˆ¡«yí†\Œ°ÿiZµgÖŒ¯•$¡ÒCnµù?í´~Õû]~™i¹—æ>B  ð\jA}J¼¬½ËÙŽ$:eè^”…[$>o(Ĉ#w¦;µ>ª ØïA ÷ŸûçÚ8û­ßUxšè‚0Ù6ÃAÖ0z+ ¹îx“OØÍ«¡±ÊÒœDॱö¢àL1. t¶Çêþ;?'â½±öÚ¿k¯Ó;§NnÆ„S•õÆFO‰ÓpYewå2#G$2ÑA}ï@@¡c°ûSíûçÚ8û — Ýus[´¢ŒFPè`ò¬•ËÐ+Ö)úæìN‡]”…®ûlyQ#‘vKÆíêw¿i´ïÙù?íŽÃt,& ùP‰Ðçì_µ~×_¥2 KùÚÙ*S1r™±J¶mª„ à ±•ÈI.Õ†ŽåÂ`£ #|м-ß7W´ûSêÈ&ÍChžq¥ R×üM ©“ç4_µÿ¾}£²÷ÛÑ45e›[sÍ”¬d‹Ø#ž´ýReË-¨ÕËú©±1ƒC¥ A­ËJÄX·NçäüW¶;e!Ë×ì_µ~×_¤¨À…ä¦4²7á0NC5LJ— ÙodŠE8ƒ X]³š:‹”וYÑ€ŽÒj`Yfñ˜ò½aI _åÃíO¬¡@†ÐwûçÚ8ûWê9òWb´T6ÞÁŠühY  NïäüW¶>ÑûWíuúVƒš>s7¿•<:¸`5[¡An¦jdà [cjH¢Æ%fÜvÐ`7<èÊ DiÂh›wOµ>Ñû Ku  ƒsè9ˆnDÏU¡äÄn7û³í}«õ o#ާ4ªŠ7›(â¤Ð™Àtßʧ¢Ë*ïÓN÷äüW¶;ªÞŸš÷Ëú¡ïóf2=ûŽÎ‡æµÙoÅ>7ÒöŸšÂ´‘m{^çÎWÊþ;sXj;pR»ËÒÕÄ•a°Ÿ_åD8ù¿y[ÓòÖ‡Çf.¿†°u{ËñJ‹?]–æ¿0þ¶¿I Bàf"é¤^¢… 2r°n\Þæ´tŠ°Ã„ô[A¯rÍfÀ›y¢óiYT \¬]c¸}©öÝ Ù ø‡Ú8ûWê96F‘·4+r¬«—ÏŒ@¡1¼ ‹Z öÇwOÍáƒZ$–Õ{B+AÚiæÑ}iž¾Žõƒ¡ù¥ËñJSv¦DÚŒ¤‚úÓBƒu§ZÌmËðP…wjn£Zãzµ)'rm_)_;øì˜l·­X®“íW×gÕþW´5ëD¾ŠAfÜãʬ¦ÜcÌ “]i‚jÔ Úu"±tüµÑÁëL¬Û¬U6ñ­`êö~OÅFnžêTê¶Ò[Òí|ïš Eq½Z{TÎé{Tü±¼ÖUæ5yrYïëô¬ mˆ¢ó©eÊFÊ\ˤº «Ø TívÑLÀ—×Y€4à uŸjfmà­«Ú}â…Ö€SOÜ{ÑB ϵD_°>Ñÿ ¼[ LyS¥dÚ<öúÇÚ8ûWí#«¥Xeø¯lwqôüÑ.M.¾‚h¿ôüömî¬ÍgòüV~¿ŠøÏÍa_Xù¿ [SRf7Þ„Õ¤Ëmf¾R¾wñÙóô®£ùø«†ïùJAÅÏÉROI}k_ÅFÏÍBöÕø?5ò?šÔ¬=?4ŸCâ°uk_ÃX:½Ÿ“ñ^Pc¨Ô ÒçJ˜?-|ïšB:ñEÁoH)NמÁ—’¡sÅë'翯Ҁ Â×I3 C.WµH& Æ›[ëC1(À(ÊÌÛ°_%–‘v7mcz¸j‰;¬gº9øÇ꺵ÎsG=töf†¶˜ó¨·Øhÿß>ÑÇÚ¿Rö‰D¸ú'äüW¶;®0iù£±&ÕŸ¦‰ðÐ=iÖ®‡æ” Ó÷AXqø¦YßñF HŠ ÄÃNÌa2\¬Ü”EÝšlIé¤QüMCÍ´i,îS\ßõØ“juƒš‰ØR Ïò™–¯Å|+­"‘nqXŸ-æÕƒ\´ä‘ËùìqƒOÍG½Zi‡´žU»Øh¡ÞŰoø¢“x½8ÝAo5ðåóFN(Ö†ãQ°ÆÃAIÆf°ï ù®U—¿¯Òg …`ƒË…3€ÌP«+оD‚*Cèh­‹ŒêâTï1¿…+b6M³š\!äîC³Ý@³ ÿª‚h2ç‰}a ¢YQešû‚àý¡öýóí}«õ†E$ºkò~+Ûa=ÅìšžìýUïÏdÔÒÌ4Ü 1ôuúI4ª™t‰2´±‹¹¤¬¹e$T¬Ë¡LÒ,ˆ¤î0È8±ïGU À‹é®i.Þ¬%¬êò£"HÜL'túç Æ* ‡ÓíúsÝ/"¡šÆ­×«ôÏ´qö¯Öw.½ÿÉø¯lwI£9!‹—ò)fÌÚþ”´‰¹¡…ä¡Ho|𔄥MÅ0ØÚþ•ªƒH×I6«§­¢üTŠÛÒ?ÚB)%Ò¤À‘5ðÔ­fSXëF œoV"–ã°Ae˜ò«c»ê¤ÅÙõUí²|é%6'Ê€‹4Ý ŸZ2nU°³r)IJìgÊ ¡Ë{;Q‘6’í>ܦ`Z‚”˜ b›†q8pѼƒÆœsPIX.M/ žH¤á™Ø&¡æox†c¨áÌâ ¤`›Úæ»}ž¿Pë LN M¤³E1Y•É)` YÃv&Èfð›`FlÄó¥5Á vŒ_F6µéÁÓϬ1K#¥K7´]›XÏ4s;[0!°žÓë—œ“@ÝÓ<ðóÝ>ÑíÐEÛß7ÿ¸}£µ{âVcCåÏÖüŸŠöÇw81½l*aKÐŒg\´8&³2G¥L±vDiE Ê<è‘7 fÔ¼©ƒ€ë¡E³4T••(LÉ!–<ñKâ:ÍèÉFVôÍ[ X4ÁW1ºørÖé39~*%MÖ˜ÍEHEé7¢!¹'¥HR EÇ•«¨zÐVuN»P‰¦A…:Ðå© Ëè fTŠŒ ‰ æm¥M›&ÉŠh9%õü´‘:€x¢H2 Œ‡Í "Ð37ñ­D¤©Rj{4ÍDazaJ“„¨H” I"Rk¢ž)nGêœýž¿Y&…,\²rÎYqW•UVU»Ý>Ô­L _dP³£&£³ÏÙ?÷Ï´qö¯ÚþOÅ{c¾“ØØŒŠÇÐ~Õû]~ÔûS°]!$…»8~Éÿ¾}£µ~×ò~+Ûhý«öºý©ö¥@Y&X¶¯•:™&,ØãìŸûçÚ8ûWí'â½±öÚ¿k¯Ú¡Œ2fu+*\ÜF6ãžëDÊ|£éŸhÿß>ÑÇÚ¿kù?äT* ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠJŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠJŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹ÔTTTTTTTTTTTTTTTTTTTÿ#ÕµKÈny5Þ˜ 4¥ µ•••jüߊ™8*f(#쟵~×_²&X±DbCޏ'JI;æÅø«²\Ø:Òâ”,‡u”‡%SaƒÞ´Q±Ëb%éöGÚ?÷Ï´qö¯Ú¿7â½±öÚ¿k¯Ø:x «W)”‰é:sGЛ? Å 4Rèr‘cj4!%—Â|èlTBkÀâ¥Á…º$¨öëÕû3íø«¤5=~äï‘2Ì@*za:Ù¨"Œó½Š„C²Iî8ûWí'â½±öÚ¿k¯ÖX¤C5»€2αG;°¨DkÊÓå ÀÆÖüTÒ‹ˆùnºÒÀàn™¶–«å–c¥j}£ÿ½KÄúÒ rÅùÔ¤HŠ’×ñJe}Z‹Q6N6i!ˆjéÒ±3ѰXìÉ!æVy)à4†üñB g@ÏSŠÒvÝ?è‚ãä~êèO6) _2h)„eõ¢ †ùÿºAÊoç}jòú­eû ¡>‰¬1ù'æ”_=Gâ°ÿ„}”äLº­ Ïï6¥&4hHÕ$õ jþ=>ô¶Â?Êk/AkTz¨0£Uøwh>hf¤^Ÿ"ln¾tÄR‰»wFž"Æ‚*íH”„ iM>Šù(Ø· 4DùRƒÍi<ߺïPýRq¨•‰ô=j4y( 'côa—ËäÇ­@j M ãé~OÅ{c²*oSSCS6©©¡šZš)µ=dTw"£»Øí{"£¹ȨîEGn½‘ÝŽäTTS™aÌ1ñM#(™[=jF©“‘Ÿ§LXyÖ)Á7/§eˉÛZ¥ȨîEr*;‘QÜŠŽäRwc»بîE'i “QQÚô ̨òïLniÛy‹ðPò?/ökY»çY&óiY=Ʀ¦§¹5=É©îMOfU55=­û'¹5=Æô‡ðU$>Ôf)Êl{R4 €Kmj)«@et¡Î#„ÇlwA®VîÛTTw"£·ó~+Û±QQQQQÙ‘QQOÚ¿k¯Ñ3. G$ê'ÉC$”Sl8dÒxjm0wˆz3+òу¼Õlò[¯6ÐÛ°u¡‹Mr±:¿UfEM‹‰’µ>Éc43Oy(dêQ2‡¥ûf”%¨°&òEa½b†qÝmX•Õ+Lyê°¯@ýÓŸÐ¥Ž««š°èýÖmJ Öþ™ Gx’Zoõ^ðIÎwW¥C vZÁç@Ñïú©v.3Kˆ-+~~‹ö†~Ôì÷?tl訦LÍÉô¦u…ø[¥aµ™¶}êV<ÿÑ«öçSJ©ù?í´~ÕúÍ«Ÿ{_£yžÓÉ‹yR€1üš#ð¥-e$Çdíþ¡ÅùÅ;FÉg´Š3åR¸Ö £ ÊûQö§Ô"Pu¥‹†š Úx€ºXÑÙo€×õP’Ýô–®G½V# 2zPû ‹°žµ—^e#)<ßå xùÖÀói‡‡4ÙDvëK7h㑳ø¢!÷>))ˆÒÁþÖ´½Z¸²l’ÐÂ%´ìÁ‹_ó@GÄ¥’¿.#â³cÍ¥œÑö¯xï I?Ê'¢”‚I-t3¼4¦ÿQûC?j}e VoÕPá nFÃÑB̼¿Úe4BúÖYyÃèÐw¿'â½±öÚ¿HIAÕŠ›M,Ü€â(ø„/¤”TÀÞÞÂÔQµšý ?ª‰>"š%#Š À’d¨¡:íNÒ½8I15 ;€qEËŸLÐaò¡ žuõ*TWLŠiW1#EßjI™û¤®Oê”§*Wàjvò\‹ëÛ2bzÑ ©4< ¿3GÚŸHŒ´Ô#ßõONUŸÀüÔÍ;%ʆ–žäUŸj}«ö¯ÚûSë)X YùLþž]ïÉø¯l}£ô–.Ñ™CÓ²Ê ÷b¢š†ºÅXdt[¬¥.Z)?4.e©tu©];šJ\q5,¸Ïl=“4JÐX¢¦Ù·ÍRl^¤ZY[Ê{DzE. yÒO7ó_ -ñPˆ-ešÂz­XÛUóWF»Uk(NŠVÒ<þsH«“gÖ²aê(¬ oby4H@­Å-î-ÓŸT4‚“±g¬×ú? Iq++ 'g¡ûWøõYj¦An³ÝF…2µ>Œ‡@9×Ò†°žcñ} rÙË;óôOµ>ÕûWí ý©ôÝ@1›…81:1ç@ÌË•ý1Sh•Õw§ÿÉø¯l}£ÚFPu¬hòQrùø¡µz©¨nx~éxÝ!©siz¼×¹"ÑŠͽ1LÍüRi‰OÅO¢¬¶ô ¢dmõ¥ÿ})ûZWöªÜýÖ~½U¥ÁN”¦WÕ¥šUH’MiB -NËt4%¨38}J$–4Už€H ¬kÈŠÿüSÄMŸà÷§eKßÓ¾7éCcÐhOÜýVCÐifGÓº–ÖfyGͳyò(ÚÑfáÒk„Qö§mÈ©_/ñZÁóTÂOhU¯‡¤¾­7,õi]_V–ný#íOµ~Õú‘QQÞ3ö§Ñ*S¨þ9«$=B®æÜÄ|ŸKò~+Û\e™&å`—T¡±è¨™DÜf„qK-r{Q„dÒÊ4XyÃðÒu‰Ìrb£ìOµÓ´vu©Ö£‡î°Ç©ýRÆCX—ßZ2ÀëÅ+*Ãú•'ëV^cS~‚”ýOÕŽ#â°ÓË÷X`<ŽéöjîÑÈ/¡+éGX#æ’¨;@û´´»Í¤É_±>ÔûWí^ø*Í!S§îŸšŒ@±…d¢5Ô4ãÁ‹|«šÂÓ™¡É·¼©hÚâ×j j}ÀwLý©ôY"'{Lñû¢'y}?Éø¯l}u‰ Æå˜¤ÉìR‡†É£äCaB<-ZÅç M ÎB’æýj~Èû];F1X‰ç?5™Gä£1®O™Ob]‡²TeçÉêÑ]A­çþ©yGO…á;1{ªSPM°iÁùwNè™X9¯Œ üUŸ£÷_Ðkú@.èJµú‘)¦ ºCñ@f®,þ»,6²RYÖ³Cô«Ê^¬öeö‡ÚŸjý«ß’Œ¤³ËñWŸö5žæš„†š­ëL¾w§O½{WÊ’4.^/CdíÕën韵>ŒÑn©ö¥„WÝH 9†PüG_£ù?í¨jm¾ÞôË×¶V&ôì=_Ý]=Z#ÏýV9}Jï*ò±¹g·ÙŸk§x¡[=úæš@ã6·­ …醃ƒJÈlâÙÒ˜À8(YÙ>Z.: vÅE$uB…c×¹9åøuK“Æ—qÜŒ5<-ii[¤OGµ4”€z™UäVwÊ#â–ÿM^ýXº·4¥!}¡ö§Ú¿j÷ó ˜³ØŽ&,P –]JÉn!¶F–™rŒ4¥em‹ÆÓµKqŒb›5¯¥ÉÌmA\ ¶˜¥žáŸµ;䤎ċ²èPSñoÕatV<覧èþOÅ{cë°ž+”n¥' Ìiy—ûBéê46Õ S—/üToNá#¡7~Äû]>ŠòÇE+= ÷Íh/ñF²õ(Õõ Ç”üV‚:‰Y…æPvS–>W/ÏA{Õù©×ªOæ‡cÛ>ÖV“4k|7¯õB.:+éú f~åkGª§¤ ¬_×±*@jÖ^_ºGT)xެÓ2ý = Àz`½Ue¨…6Y+/´>ÔûWí_´3ö§ bÂâ¼i|¹ÄÙ|³õ?'â½±ö*J ˜¤ÍƒPGâ’ÀÄO•eö§ÚéöY@èµò¡ŸšÉ©ú¡ez”»9ÊŸj&O1ŠÇI°Ÿ|R0¯ÁXHtý«`ô ÿýVKÖ¯ê«û*þ‹÷_Õ~èÈaÊ¿?G/´>ÔûWí_´3ö§ÑE#K q-ÎŽÕ«^¥[ñÇ•7.}Éø£& ·)™©IþâýOÝX$ó(L'¯Ô]%™[ÎiòžÔ•‡Q¤NÉ©ûítûWírûCíOµ~ÕûC?j}Y¬_©[ï¨5š¥'éýŠ |^Tì/F²Hêª.æ«&kÜþبì1Õ¬ y¿t HÔÖ÷­×Ô+!/*W©ZƒáÒƒ_•Þ¡Oú?ìï=J7Þ¥#ÑüÒµ<ÝJ:þ RÙðs]‡JŽÞçj,y?ˆ¢Ä“ù_ª}®Ÿjý®_h}©ö¯Ú¿hgíOµËËù¯sø>‚Î~¦_j}®Ÿjý®_h}©ö¯Ú¿hgíOµËËù¯sø>Ó* ­ßf*-=†e§Ÿa#ìâ{ f’ý ¢_hv}œOa#ìâ{ df‚h—ÙŠ‹M0¦e——ó^çð}¦T1D>ÌTÚ¡¥/ÙiÙ_³ì,ýœÃWRÏÙ} ì/ÙŒGe~Îa¦T³öFhb„>ÌTö²eåüÓðE³mŽk“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺ&ÏÛ÷\ßoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöý×'Û÷F÷Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöý×'Û÷V³öý×'Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöýÓ½öý×'Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöý×'Û÷\ŸoÝr}¿uÉöýÓ6~ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·îï·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·îï·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·îï·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·îï·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû£{íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû£{íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû®O·î¹>ߺäû~ë“íû¥ ‰ˆÇ;WÿÚ?I©Ôêu:N§S©Ôêu:N§S©Ôêu:•J¥S©Ôêu:N§S©Ôêu:N§S©ÔªU*N§S©Ôêu:N§S©Ôêu:N¥R©Ôêu:N§S©Ôêu:N§S©ÔêU*•N§S©Ôêu:N§S©Ôêu:N§R©Têu:N§S©Ôêu:N§S©Ôêu:•N§S©Ôêu:N§S©Ôêu:N§S©ÔªU:N§S©Ôêu:N§S©Ôêu:J¥R©Ôêu:N§S©Ôêu:N§S©ÔêU*•N§S©Ôêu:N§S©Ôêu:N§R©Tªu:N§S©Ôêu:N§S©Ôêu:•J¥S©Ôêu:N§S©Ôêu:N§S¤µ—Ú“ÿ&~Ö~Ö~ÒåOÚÏÚm?iËíü£ÿ“ïòûCþ”5tAÕ ô?t0“ÌÙA¢ôÜ!‘¥ŒÔT5Qbí7"z•cy?uöõǽ¹IŒW þb7Á3@.:Áû«–õŠ^S¢?ªñ-°ô4ûZÀê¿ÚÒ8jü{ÿ—Úò^DɘHô"±„ê'âµGA«ú¦`z?šŒÈ:J‰æ*Êü?º w»Ù¨Ì1Ò'ºšH>ˆ)Iãè=—ŽR ¿>–©“%Hc ”|¼è¬‹‚$ŒÜ^”£?öy}¡÷M®Õä©FÊ&ã=Èâjèf7ñš1¸ÛÀÖùþÕuB>2~(³¥”'ø£¡õGìPðµ–†”›Ì¨kç³øiDqJBQ’èßY_Šdv_9´>´ ά~èømÓz_ÔÔÉÔ€}NÃdÇåŠ^@s‚œÈÖ}踀À¥¤ô'Û€eÀ¡%ô•~“Ö‡»Ãv€"öú<ŒúÔèWÆ6û†]å\Å݉:gÚ°¬¼í:KŸh£ºâ÷éó­þÐ ôÄ>ôŽf†­”’š2L5u’Õ§=ªWÊÊZ9û%%²Ÿ–:•î K˜úƒ3¢>õ‘¼Ÿg÷ôZ–?$ßñÔ%?Э9ô$ußÁó#Íú¬ ïç@|Q)—u´-$üªvÁ˜=*`’-6M prùT`‡ui4µ³¢çøóÿ«Ëíµ)¸ e½&aŸÚ%Æ~û0ûSräš>ä~*úޤÃÛbKzgS®:Se—*TbHÀ[Þ¤µGÜGÍ´_D3Ù%󺋑ïHƒÌ9¥7GŸàÕܧrÏ©ù¤/U¡Ä}ï¼ähéVnmvoéPÅå2úEýiYäóI³ååP&IdÔ ÎÛÒ°e‰yeêÓð„øa¤?E ÊkÃ=3DÔ#¹úÃÚÑsŒžÀ‡¥]˜Ý~j%ëeü­XcÂæ˜Yû¦]Ѐ¡x"ï3¥ ¢Þ°ü%YÕi)¿Í3>Zh3g#ƒÄÔb–W–žEN27+åh¤¬G—={¦Â&É43it~mS¬³øšróHö«‡†3jÜôªWQ?u†‡÷`7“øú  A Ã& Í&È5‰ÒþXÖ¡.É’IcÇ}Ì]Ä,ÃLjÌц ˜ÌDy´í›Ê ƒIÆüñEhÈ]´XFÆù¨rˆK].íšž¶By€îeݰÞzÙ=(k>Z.Xxè¸PAŸn9QÞw'îY]Z0ò=Û ‹u%·´W‹þ(,ùî’ |Ï‚a:ýï¢(DeU qôgã1È|¥!q :Ëáܽ§%Ý͵1z~ʾ'o‰8õ*UE’ niË%Äš™‡DuJq0³vþÎKTÁ°õ2|Ocòæ. _ÏPqA°7ƒÏ΄‹¼Ö¥<Ó“Dæí¥,™êè,ýpšt–Mš—uj òœùU7’SÊ ò©övm×o:^7,ùGZ›XmI€…‡¥è:Iž—_¶åö‡Ù,f³aäýÖ<ÏßeÌÓR—š† 4Êzíz²_»ÜžÔÜžù)õŒ²ß–<¯µúüªA]ÑY§‘ß+ûµrûÝŸU)›b Ã×Å&Íë¤E\7Å_×°z¾Ø¼Ðn98£+ÄÚ÷Je¬åOÛA’ôB=bk_3Þ‘e6°ô‘I+’“é0síD¹z2zÍ'‘ùQ@RÁ.ì_ëLPª Ȗ퉫x¾Ìûdó;å‚àŒ{êqRWÌhiµ­ÌgZvOaÓn)‘pqÇ™(Ï0°É@!Ô¸7NŸ¢mZuÛv™“~Ó†Ôˆ]ü¯š¯s.î~ÐÔ}ÒÔ¨1‹ƒa#Š3ts7ÖƒÆßôí=£Ñ6¢Í?8™ö¥å€Ç²dµŽ\cÎÓëY¦t²ûª4ßéM‰[“&›P‘v§„O&) ø$‘Ö—V]3+Ô “õXAèPáÜ2$EÄaÇÆÍI Bf4¶ƒÏ§8)›X1çLÇô‡Õ7ö¨=L$£yùذS8çê›Ì2kzE.u¢0(’Ž<½)™?GóJ²Ì¶/Ví4°xO•"]z¡ñ4Á'㊙%¶ªtfÞ”¥h‹HO@¢d]pô¤€©/-ëö¼¾Ðû!C*yr鯵:4lÜ÷šRm/Ši¾Ÿ±A xu¡wǤƵŠa0˜¥–ƒÊ8¨´‰òNÁ2ƒ­ª)Òƒ}oKb¦_ŠÅ,±“^mjy]È?/—æ²ý9üV´u ñN‰Ø´¼¢Þ~”Óz›?oµHÓËU¤Ë) ø2õ±G™Lºf´]Zמ“WïÖ™-´dn]§÷R5ìê}h¨sHBQþße—d›¤L0~/@ Ã3¸é“¥ªú#£2yÿ•'Aˆ™Žxò¡’O×ý0š)R7¸N±o*˜á£ÌXâiæË…Èg0£IZVW/ÐBl‰óB`})6¬ 8}}ªÝÖÎ_x©I lÉäÒ¢Ü[%ׂôO7"s¨o ýЈcˆVÚå(Ëè~”<¬EèØ ÆÅöšBU¢xã;1úKhœõòš~€(ó]L¥]ÑeæŽCÄ<Ô§ŸE{‰ËÉÓÊ«à²ä‡­çŸÔýÒ4U;¤=Bb‘a›«õB˜=_ª-w¼0¾Tt Ï”|QðÇv“MóJm•eñS~\ toÜE%š†Í5‚^®¾wì„Þ‚µ ‘Úé}£zKò-îÕç¶òsÖßu.Ñ8¡”n õ³>ÆûX—Êäu œf¦c‘¿¤ö3/¤‰àLË~˜«JW êY|^˜]uçǦ’ÔpçœÒ–\Ñ—Us×þOÕ2Q›{±št`jxøµÅ©¹ãÑ«ÂIQhMšM:zUÂßI Ñ¾CgÊmå3OBÔy–Ó_Zë_‘ÌüÐY­âIožý±=Ô9ߎ §Ëô½q0†…ícšH·aD¨h1>{oX !±™Òî›Õ¹ÅÇÎÑæS@ÉÓGÚ¬!T!·µ,LY›ÖŠá$ú}E¤ˆÎÇ Úí- ‚G’×ͧÒ›?a•? « éÒ"FÑý¬1pfuØò«‡) êýÓ•ˆ¹rÓ ÛÓþš± ²]µŠÁãN AæùQ.8£™^/«d÷>ƒðW ¿2XÎáðÎÔ—%#ÀlKÇŒQ¶5 ÁªüèZ$•MŠˆÉí$$ ìÁxZ&ž){“u/” ”«^E†¯µc5rl“%„90“ЩI(ž>“ÝÇí0í³ÔšË3ßõYól¬HR-% Ûs3ˆ À7xqsDT Ë¸Ó°Ê“C娿85½ó>eª+°KéCØúOÅ aœD^zPŒ)†±m¨ŒêÉËÊÇÅ;-êÅ+úߪ “ÀÃìü¨Ëæ4Æ2âp¯üT¢N€/ò°æ–¶H}µŽ$EÙfÒúÒ¤!Ì~AúA6(9¾kd§óŠD˜¸ýŸÕ9!-îÿ)Aдõs”ƒa·ÅM-·±Hhõ(ÔZKCÖ³éÅ /¤Ö _&€”:Ð3$VÅ .N(û®è]ôÛ­J ÜÔêvM\H£ÄøìŽ—'f'J•E8ÖùÍè>Gö×§ùAOžßâ‰P¦^êÄBd¼&ôûÙö¬%;6ô~¨X½«5/&FL*F ˜Y=–Ô1r¹`¿RÏ¿d·¸îVIæCïû¤Ù9XÃbô†Ëòb‘ýÊ ˆÙ“„ÈÓ°Q䊎œK—JPtô~žÁÖ¦¥Cã}šu(ĸoºïQ8ZÞÇ˽+c¦çŒ})§n”?'â¢Ã¦ð>I H-ÙçsãëÜ´ºc=+ HÖ$ùÌúÐ$‰æËËž”_ c[T²{ߥ¦h ‹¯)pµÉ¹ô‘Ëúøû0¬M#*O5Ö¢)ö¦`Š3gÞå$©úÚD-Ài«£Ò˜Â,¡n/VQOÊF¬2t±åb+N "ô3-ÏbF{ƒb º¯=©ÂQêT§K‚&í¦mQØ™I6EãKíW zƒVï@«ºXà™ƒŽÉKT[­2$S){ˆÇ^î?i…Њ@r"&ãšJx=ïïPߪ÷Æ;ÈK¬ëؽ–‹wÑO‰Ý ë¤Ê¢|®{S¥íþ«>ºÄû~ëEðÓ–LžÅ9:S€“,Ï if¯Í£YÄqžðf–RÝ+>D¥Š“¹ͦ,S[i½% ÔYèãè²qp¬°u?©¢KšÇO£‡lTR!Añz)yxmZ~wèþë>§QüMc¾šFû~“B†.ç³Ëâ¦ÇFG½Z6,öŠE-êmÂjRŽº-XšE,Ýlë~j@¡·í–†Í.=OÍ#9ÑŸ˜ù­ì©:'á  €»w¯åR“Î’ñjG ’@W‹Ö7ÒOš g÷|0 Ðsi—ÛŠ 7ÅçO€~kOEzF:´ØBÐ$Ì£3­5é ›ïŒ^Œç(Éó0P0’íñÖ)*ðe=LV)¹MµóéX`Ù 3//ö¼H1ª¤Ø å\ÈÙ;C+d«Z!tò7`ìe L‚)×nãRПZ¸ÉpÉé/’Œ‘±‘4ì\¸X—°¼uŸL¤‚×ù‘ 8§ $¦8Š:ÌA Gy"Õu ßE½f†€¨§Œ*ÌËähD ,wLÀ‘Ѥ+€vB`ç^†W¥©&Qã›zRQ[d°oØøZp0ô\f•bò²>_¡ÚaGÞ~¯½ kêU-«**á„pßÑÿjk4š2,&½xpñö¤ ,Aê,'¯^ÇWí9ô*õsíû¡™£óDl§æ*a.— cGGYñÛe10OV"’ÃÓøTh)E›\ôn=+T˜¹ÅÔ4)pkÁù¥Íõ’ó#ÖJk©Õ•ûîx¶ê1õ~‰Ò)„8ÑnèBôZÑMü©œXìÚ®¶í¡‹ãV§û$7ž½g¥0Y6 s~yzÔ*$MÙ¿;ÊÀ‹/ÀðÖWä‰-å@+‚Ön̾ÛÒß1~jДòÇJIhÊžeBˆ»|ì–+)K8÷©qÒòf÷á¨O›/¯v8æSi^¨þjô*à¾Ìy­BÀms€>„ÚÌfø£%|íó@Bê=³íK"q¨?/êž5çµî2ÈKÒ~jKJÉfþQ0eœñRhefRÐlu’׫0ØOK|ÕÖ} ·œÿ”ç)öcËóC.1<ÏÅ‚Õ=°¢&“QO&¦ì—ÑÖ¢ ;=£NV3MãNÇì4Z|—õµ?1ÒÅÝfô¤·³„δÜç(}˜ù§GÑ(ß(7ŽcëTJ=J9Kº¿WÃe áź所ƒó@@|Ÿ¯¢eï+N¤Iê'µO›~hI"ð¾t0¨Mú°Úúxý¦É{ÚŽX:¥k*NŒRº¾­,&û°îO¾ôÔ.Aoe¤Q _3ùöEe"vbŒ uY÷V¨|ŸÕ1[(úu¡7¶=3ñFêX©¨‰Ñ“©IÉ=%ŒhèÅX¡¿/k²gEŸ–ƒd¹)¤LošÄÝœ½}8«E6ÍïéyTÒÅM9ð×P‚¥A5‘H•* ¨¤ŠD©P-ÐRŠ•ÐME"T¨‚j)"¥Bh&¢’*T&¢’”—òóÓÖš#Z~,UùÂ-ˆ—0G¥8”jÖûßZV^€þ5n+ä“÷êR—ÆÁ0±Ïùš üå IcœÒD´^ë[@ßjBmA5P®K52ê‘}­óOg!„síJÎ÷£sµî܅ئpºa]"Š`ÉÉ6½§PN*)"”Pš ¡!%¸ä¥Çæƒ:[¾÷!Dý{ŒuS-[FE™÷e÷h –EîΘ榆!ÜÈäzQ*f=—Í#P/ó¬­<ÏÏØ¥L¶µÖ3ÌñK¡²Lâç½`EáÛQÍï'ùNïHéø¢f¦÷¿•f§Nú ¿ç53ÌÙÓ‰R¥à!*Ï ŽÛÐRjjÛ“B¨¥”@VgñF’V¤rÆç¥8 17zâzŘ¾£òs@j‚–;j‚˜vÔ)L5º€Ô),TëD¨ ¦:Ñ*H)…n¢TS Vš 4‚˜TëD¨ ¦:Ñ*H)…N´3@i‚•¦€Ð… ì 5 AØ%@j‚¦‰P… ¥ŠS@j‚–(f€Ô),PͨR aC4¡OP¦èö)`bÞ ò…¢d`—î‘çVíf³3V]â…%ÌflúkåÛ6‹¸ºÓøWÚÞô¤“Y/ô0î´çÃY ”zh-4KŒP F 9¨G•7Å%±P‰£ ý“¹,˜jÄ2}©¡›ùy”$à4¬ :ñWb›Ã Äþ¢.:—|:µ’M[ûcÚg,ulNOж11n:vÁ!,ƒË Æ´ÃÒÐa„òžØQ<êtt¦¬Ã‘óþ4|¨7F;P§d”ÀN¾ZSôgr,kM"û÷QA–€zyÞÞõrIHe·‘ôè€ æ¿ßù¯Ð0'¹ëYÙê¯Ø,v  8C ­ü¢›‚;‡l’çA—üæ­Ä÷?¯jüÝxö«àD]‹Ï§•I)wi0dƒ}A§Z–Ah½Üª>ðbåå×%(©fWÅYsqY&q¦a$i9†öPô˜ `,\Ëö¸}¦Ç (4»hŠGúrd¬â$ô×βú&¬“yaŽ() -},HuJ3&;¾-º•MM TöKSSSسSK4köG™AwÚ‰[?µ?Býî¿€÷£çbÏŒNúV£zÃòP0½ýÕè‡ËŸH«-Þ-<è6LšôjÄ7§äž ÞãHoù“ð”¬NEw»ü ‹})×Q`ui¥"0ö d:”4¤,CoÚcè¿\؉” ù—£Ì€:™—Wè IoÙ*•Ùm~Óc$!Ö-H¥eç²_{Yõ;’¨‹î¿J[p ‹wmiwÁFÎ4.ïÏO(ûC/Ø5!‰¾&-9P$\G;¥^È á› b²úü°g§ZŽ`·“ o§*r'¯ªÞù…´ŠL'ï0\ßCô¤”"fY›%ÏaG„3™+âÙÞt¨ uuû (û•ùBÁ³à¡·'Pü´Å `³>s´Ä–`YcGÏ5¥2X:Æ*SC\×GœRCrþ^ìh_ƒ/•YîÿGÚ„Ì û“Ú§Å— TÄ—ÍOFJIyD±hÁYwKn4²ÇqQ ¹ÁÍúf€*@le½­ù«BB|=肦$ŒúëIÌØÄ¼½(HjçÕ§ É—™Ð¨#ÎFçWoš&uKSŸjŸYu‡>n/íI1âœhbû‘ù¦A.ˆܽ)}ÈRK¨ñ¯`/Ó5ûX­ «»ÏÓ)¤Ê'­G:)è÷"Wl¥’L ÖM)Y µçºíØç ìP™R8M~ÅM˜“f“›ºªz~è-y3éM‚a[þŸ6¢x‚¢ýù‰bv5|Šm`±uXÕ:åŒwnâýŠ¥Ð°mˆÞúméÚÛ6`f×9ýÒH‚oèoW$ÉwÊZ´ÈIæ–èW”|Ô™&Ë8˜ Eîô|Ôe·ä9å¿JYؗȵ2ýƒIÑ9-Ædžw)yOG8l@˜½f»p[F}™©NÌ1˜#jC"Y¸^Ù•‰ŽJØŽ7–(M²žjˬ\vXtPBð†nÌô°Æ]!Í C(FÈíöQöå‹´©ÌÝM_jd¦[ôNÔ‚ ÐFs}ˆ ¦K¦¾³ò–„*h·¶´ÁíRˆá èI@¼Nu†‰ö)ÐõŸÕgƒ/ä:ßÊ¥—X„#Ö¤l¡N÷/¨@'A-œE©ÑañÍ!a5Ž¿Ÿn êÅB€JØ¥ÓxÒ]ÿTùŽ ƒËµ>^š]âÕ™,bhìAÈ‘6ü覆Ô'Öh'4ü14_Të9«(q+ æ„D©TªU*•J¥R¡TªU*•J¥R©TªU*•J¥R©TXEé­=•*•J¥R©²âvIö£A܆¥3;O‚*ë"”8a]öÍ'cö¯ØN,³®”P¢¤„ÜÎ:Ò«IGcmÚÇu…z±Fæ¤À–@çŠ AP¨QŒ¯e¤âPB[æ;JѲqÓŠJ%ôž”:ùƒs¶þ´†“ƒaӚؖX‚ ®[µ¤ÍGzŽõê;Ôw¨ïQÞ£½GzŽõê;Ôw¨ïQÞ£½GzŽõê;Ð&£½GzŽõê;Ôw¨ïQÞ£½GzŽõê;Ôw¨ïQÞ£½GzŽõè"£½GzŽõê;Ôw¨ïQÞ£½GzŽõê;Ôw¨ïQÞ£½GzŽôQÞ£½GzŽõê;Ôw¨ïQÞ£½GzŽõê;Ð’ ÍaLº½º×-þ…DõQY!ù?†‹. ß9¨VVK`8ÿU:EüÙ|è˜O0ýרÑ2ÞÐ~ûaÂ^·ƒÎ(É÷7—éëJžÕà5jÌ>mù}­QôùwÔŠ–IúíßU5¬óÔ𨴶?mbRp-ícÒ¿+ pôVëšE-Ú>•¬qìF´ I7¾ÎJ&l¤Mi7ºòÄÛ¢* kô€½/Bhï7¡0>Ž™ô¥‡o=¾ƒS2çmO/ÌR >“÷J\âÿ@Ì 6oPEd19¯u€C’zTàÌ&Ä$6-ÚèM/KZGŸnýq%X‚NŒu,– 3Í~Z4\åßùµ*-ÛDØ‹Q¦W-ÄÊlmGgjNieÅgp󩈆ÐÙµ«Öy¨óÚh-å†þRÅu΂'6ך¶h´ ¨ K˜…ñçD®ÍIêŸò¬Ü@ÃÉ4äûS/Úáö˜}f“'d`yÀò–ŠB^ûý3JlÐ òÚ•”ê~‘B `ìHƒÓJv›Œ0hOÒŽÎU=åp0}ží€XšCÙ ¶¹ÍlŒÔÙé,EI5°ÎäÇNÓéMk tâêZF•aÝ(W•CQ¸ø¶Š ; äˆÚÉ~;æ¿Bk¤Žµmiøeß™¡›Š¶Hþˆ« ³ç›ú~~„¢*(K“¢”4iÑ›?~~µø¡ø÷£2„ážáS#H“ÕyØÅJ/šÎÆCFû})Ã3:yW©‘ÈíöÏØ#À°üŒsÅÜ`8Î})>J¢˜!hùili·–c,q5*•,ö©Û ±8©Zõ°H*¾QSß5›/Øõ£8­–ÝBôà3Y.yˆ¤[ÎO^Ì©šÅÐEͧhÙÍ`˜þ*Ay”lŽ-³HeÖø k¥Š’c2D$ÌíÔ¯‘7;\ôŠ×@íw¹D÷“ÜvMí ¿kÚaGÓ@à[‰6 ¸œ›Ò%n·~œ@ÕV* Ø»ÊëÜ`ð2øÖ‡$lƒ¾°hN¥=œ¾Å“}=qM´Í÷¥KÃT¯êz#¶bH|Ñ×—7žµ"{ÇÔ ÝàaÇ8Ñ«¨›Úr‹¸ .Ã*ÛÒ8ïšý¥£‹mA\Yœ¹µýB’‘OWL|µ’Vmžgæý~–³2K1Z‡ÙüR€ Ø™]Ï>ÈÍa6X£àa·íŸ©/,äI)ŒD{…KH7q;`ûGê¬]£Q¢h.‰÷Ít6ò¦ª¸¿.ÁLÉØ-:Šë¤{V,u¥w#N¸îôŠk…ð%F.‚ o"› 0ˆúŸåkÌ©¿– ¨DåZb厯î‘ H-ë@ü:P1'ä’J$Iú½0’VbÆ-¥¯ÄLÔà Þm 2$Í -)) t»:¸ÈÀc@Ÿ (¤ÎJX"R:¬:6×:mö†Z¹q;Mêd->‹@Ë韊V£Ä¡y„ÆÕlC©{'ÅJž4°y,µ.‰éEÈVCafbc‰«åÂIôì Èì¿ ¿­ 8)£–ΟG´Â¤¸´Y‰‡¦´²˜¥¬=Ú·/"oýŠ. ¸“â‚¡¢y– …ú¶¬©¨~ƒ™Ô>ŒÖ6{ «‚¬2\zÃá –C9™ÈhoÛ øñÖ‰ž>.ðM.â!,Û˯Æ#ozƒ—ÄLúqo×ÑI"µç«yÞ}iiŽíGò£f uÝïý®ÒO('Ëæiº/×J ³\x§¿Ô*‰ !©µ-èÍýS²¯7î§…“£ûõ¡„Ÿdý5%DEžÆ¹Xà·YÒÀÍòZÚÒ…wA:9qØiVe¶v¬yäžÌ{Í+°ìȸGDÝâîjZ#.¼×7dö3¤¶™ö÷âžô½YìU„Þ®,´kº|ŸJ ²ò[ŽËJ’ºà×'†"æ(#0ÅÖHÒÒGj¿,¦u6ñü¤±$DHÅíÐÖ•º!ds:¤¼£ìÌ´ìJˆæPœ\Ó%;”¯7ïé$“Î¥Á&÷ øt¥›´@ ÄØÄÑ6‘õ“Gècö˜T5žè]×x1F]e_HŠ ­>aØêÅY0“a§Ð‹ 7¸#a±éA§#MÀ'á;³¥´q!™ç’…ã3Cëo9ú$MZ¼™=’€©Á®‘@˜¤”"H›LýE6Ø—ÒŒƒ ì÷nœÎ›Gç°fAIAf®†tAÍG„æê\Ð…¼êy1ž6t¡£nü(G7ú§×‹­¬ÏSzR:>jþ»¦¿k§t^Sǽ0úÌþÅ9œO 7æ¤Pù¡‰yúÃåGJ‰ØPèúP÷BÝ1­þÉúª—9 3z *=œûšC<34}×biõÀåmMAêgÞ°¦Ô½ÀQt…´Òy êj0»Oæ´—Â)†K<áó¨4•åõ©‘@×]LŠPÒ*"òuc±ê#ÜP2 õÆCÖõ B¶æƒKïíRâ(È`?”é{Õ(†8t fj= ôdõÛ⥖ œ¤ol•4iÐajC"õ÷žO¹ç@$¿ÒN [Í1X6ó`çóVf,—®ÑçF@­gvzÐÍJB[7ÇZ"PœSRJ×…Ë”â[]sŸ©ÝÛxÜäÓÒ‚@0DÁ~´K•=Ü{®ž-b!ßÍl¨sZåàÀ>iH¶—ë‡Éú˜ÐqÂ@îeh¸ëÜS8é"üµ ý + vgˆÏûVâ$«Ô'9:RÉIlìë>³ô@šè´ú£®^•5ä†Ðf:¾ßN/=È Ããâ²ûCì,S`:Ìw¢C¯ÀëÃPÖe ˜tõ<_´×ítï€L†AÓ£˜ãè`—^›Ñ³ôt%—©þ«Ä?ª?½ú Ö "<Ú½)Îß`ëQE æ9o»ÎŽ<Ÿdý„àV'¦mš› û' •ÝŽ™¡k@\I:£ òKëø+%åAðTVs²«åïR_ ‹‘b”cg Ø3Š’ˆÞÒI¯;Ñ$àÙ¹ÙrS+€ÃDq8«Àà¤ôµ €\dÚë7ÍR•"èÁ Nj\mt,ᓪ§³^þÁ»N c›ïÝCjDQ&Ì-1 <9«}fÏ(>hÃ/Q;HEGðuî¡F\] ßJ€»³Š5¶dÉH‘W+wµ¢ÃF§( ØÈ¨ E°}Õ*|2=FÍ…( s!š|Y€ÿ~ªE,¡²æƒ„6™=Š"Âa˜Ž‘€`¶Žó…ïÖ™0Õ`õ£"HÜib±‰_g¯>KsY2tšb¼É|”éE½¡¤aŽükš8]Dù vå:IáÜ[U¦½{Y„Dy=Ê·KÊüCå­9å·ä+½I씦 ]WIž;–Ë–_£– A¼ˆ§•½¦‘ofáTGž()#mèkš#,»À/=Ðüòú-Šm.‹çÏñW„M÷ê>qD`ž¡&ždŸA\m“ìþka(Yù¶ û_h}€Å% ¦W'×¼ÓbÒã ¬òZƒ Ñc̽x¿ª³Áù¯õ^/ê¼_Õx¿ªñUâþ«ÅýTüºŸƒ÷Sð~ê~ÝOÁû©ø?u?î§àýÔüºÁ ˜2Y™ÈÞ“†&áÛ§@¸ÜìLviHN5/Æïµr¬üÅK4ŒŽIç_Ÿ¤\ qzÛ~Wš²êÅ!ƒ`üÐ'—%9x¨ç'ž)f#܃Õ>]¤Àãü—°ÒÄd7CïYŽS§“®ÿbýˆ)!`‚j`DcY§Rd±×M¹¨W}§Ö&”¥»Û2ˆÔwÍ1RN©ŸSJ(dà¶“×ã¶cÆp›5:PÊèçç­2h¯u­Í9uæ‘i–Z<ýéûl"*Žú8hëQ("!Tb¸—iªËâ*ó’¹6&ÿ®Õ!!°¿”º„ùìjHžå䈛†}¤‹u-Öüú±ß¡OŒTj8šµçìoN6±èµ PÈ“F3ò¡ÌbKóZw"£¾f•c1´Ÿ!±ëåz_õ|Eaß^Â$ÁâMúTU¢Qé5—{–”¢®×&.\ J€HÒ¾êÔ÷;(U€ójéê% ;[¸Œ dŒ–èÓ" ê•>”Ðä²”oDÝ4ò㽉{Âûµ2cU¼]—vÿeËí²„ß"„"[ß­»³IÍ' Ô Ò²Ì"ÆÁòѷرNÍ9ÀøâšŸGG“èÁˆæÓÓØ¤ ¤I7rêþ‚¢’è”C­ÎbÃóN#h@õSÙ 5z_¥é`.YRì¾ÕYÄDë Ðâ’NMùLE,•9‡®÷ÞˆŠX%æ/öØÅ±6k¯÷‹Û`lR:¾ÎãÚò¤áÔYydæ ˜ì!thš=*jˆÞQFÞ¸¨AOì\*èI±-¹O!t Þ#NèÅY„ô¸Ú‘»ÔJæ4Ä1ÙÁHdí[ æ§z—/®ütPh!èÅ0¢80Ì"öl6ômWŠXz;CNt£2\{¢ËA}*BAl´é3ë>lN4ý­ „2ôµÒ÷ÝbjD˜!ÁþýædÃa½óiRD…ŸˆqV@:!äó§á è÷ÉÂ!Â9¥ª7º<–*#6Õ$­±EFÑ\_=ª, j»œ9×NÁ©HÄbâó†ÞtšìDBIï@-)–ã0f=lÔ«mö|¾Ðû#)Ð_>×í,Œóg‘¥XØ´õ¨¡A’²Ÿi{GDY}ÕøÐü4«¤óCÙ®¨1;wò€³±ªô+ =)h+0l"¾EMY„»…M‡æ—ÕÃ⌗Êgâhqg ¹M/i§¶*ÖF/r{#¶>£öbÀêÓ£’:Ø€DÄEõ i½ê@1~¼mI‚“|:ñDØËwBXBÐ6@À¸¼¶„ÍñR=Q%mŒ¤…¡e…¶K_Œ|TLìL&Í'©A77IÄÐN2ò-ü£Ðu]çGÛŠƒ2&é¥÷©1•_h);arN¤·òÑìnÃ9ß­GbÒjâó¯>Š:Íï¢îQ0Ãx‹‘nÔI!œ‚hµŠI,Е–Š&–ÆPâÍúQ H"俛߼™ ~iÄÕFp¶ú§u Ã±À½¡5Ƕ¾•1-;‡ëE_š{,÷Í c¸¹¡=b€=;¥@Ùø¥Š[c„”»N5?ØëQ¡r‘l‹dÌMi‘ÉŸ¼GR˜W0O§zòƒ­?"°,å£T£puÒ”€”—Y¥k)*ê¿A"Ÿ9$.Ô=ÞÕŠTƒƒ}uí1¯Ô2yÍMð‘%˜-8ÏhK*! ³#Ë+±( ¹]ãC‹õ¥€¸±ÁÇÚrûCìŒùß'köºÓSöA@Ð&©ÐhMþ"§SеCåÓåCÃC%ìyµ6$Ñ!ü¾ÕÃõý«‡ëûQ À‚ ÆÓŸzA€ñ½Ä¶ƒ÷ Pž,]¥ nˆ&\/àŠZtfû úûV\W/àŠ6¡2‡¥û;R»õGìã»ô¢IÞ{®Áí«ƒÁ×Jnê rAÖ•/_ÙòjDDb<î¢@H‹Bñ¡ÍF“j0é—DìÂk@€³e)„Mô6â)[ Æé{2Y¶+ !ì4i^ÙÜñzM0ɇ÷Ø ‚ëBIUNÃstˆÌœSA,²ªUí(MKu ÃhÓÊ¢µÈ¹=($p˜î¢+7„³L^>vì–Õ=Ÿ + ¼íèSóï²–šƒo$UÚ“ž'êÙ :9Bò5¬gzcÚ¤œ¯tÒ¢B%’ƒÌ‡ä¢â/QDEÔBW;ž”¤$w¦ ÄË[¦MªJ­î{ R•«¤ÙÀc·/ £aH¤‹ ʆ "s|·÷¨‹™`œ¹oNš•Ø)µS3CƒÏ¹u3\%£Š®`Ÿ9*´Ô·ül<œÒ5nº¿mËí²zÙÐÏ¿köºöD¤JcW¯ùš"Häâ¥,Ž[Ÿê(k¦åŸZ&%¾Ó<õ¦ØýÕ«@Ïr V ©9SƇCµËîcí_­{@|Pö*}ØH“4Æ#†F=¦%‡$úM1‚c"f „µU(ÁCÜ®°ôu0æ`‹?-`æ3 ÅMé ÇÙò£aÑ$¢1ŽÑ€dãr<%@ÏÙzñó™‚á…×ÁÌ4±ËøµŠá ÀO6$陡fË›rZÔf®Nà"6Í",oÇ=iøBÖ :îúNê'£§•[…]™ó½f€šê$™»KSe…ÑRXo½/˜rI£[r a ÊED[³ÏÓ¸{=ò~&#~”â¥$/£Ò€1§SÇJщÓ12aÝM‹2?¯~Ð[‡#Î2Ñ™!{e‰éXwV¤ÅËEé:Oô)>D'õïSua=‰kDNU1ý4 ÷\¾Jµ­ )ò~ëüŸº:HáŽâF)ÚP‰¤¿1ƒz@XzåîåÛ-¶5Þƒœ3ihC|­~ÏÝEÜ‘Ìt§áÏ_ÝCQA‘Þñ‡X£Ž–&D<Ô‰6àÒMH¡ yl޶éL†Ã.->NzKVOHÎYi™ƒ±çyÞ†"¡°É§)$me9 `âƒËŠFðì.ϳVw ºƒ×JTX×WR2M br[‡¥2X µ;Cn° ‡96¨êÉ \°„޹âºàd1Œ®“€OV·NÃÊ„S÷LÓ`¹ o.®OÕEEO+ãóNÃÈ? àî_Õø©Ÿ&¿êò)ù'yOÍ&6”|Ö{Ùv"UŦcXÅH§~m@ŒVn¶&e¶…¹µ‹…8‰Ê‡]Öœ¡¤ÔÁai:ç¼5©›mØ}×/´>É)i¼þ{_¢!ˆ"w²YñZÑ®ú¿›éQÊs} ˜ÅªQsÑsÑ4Í$±/Ò¥2.8@ÆiI`K”‡Z-…§8çd¥®Ï‚Ï­G/‘7ÈÆ`J €M-³éMÞ:JžÕ¯Ò•)±T„[Û\Öuûçê’©ËÕéz \^'^t© »ØÀšE+‚¢AKh'´Ð¦Q6J`PL¡¼–¸i{LÞÄkÚ´¤ÚTQßdÆ0íÕ¤Ó¹ÙfÀs½hÕ, 1•’š*NGõú¦f-ƒ1(ô8¨%åºìj’ÿ9¨²‡~ šN¨yºÎ=è+rHÆ/©¥ît‚ŒoŽŒ °RÎk^•ÊôilHìXÀÚZ‹´±ØîÀEâóú›!nEô:MôºËD8 I[1 ùÍE ³ÌëV ’›zRÑ9ò¤¢IgÓ±dFÝ7è _Ói熯Ä31ϯ4¨˜Š:Z† ¬,›N$ÃÚªiR 2 žÌìQ c¢;D‚ÊfëX†ÕPg€@ô4¥ñwbˆ³¸àè~_joy`ù6ã~”ó\`¡½ßÅ ¡EÚÌ»fdØF'Λ2Ia™ÈùÿoF­öwÊJ³Pº}ß/´>ÉHäGÒ„µžÇíuú.‹ âCðûSº•åÖIôµ"RW/ß‹Ý邂Ɔ^“IÎÒõÉñ?Rh^­ÌôzVlÁh0ïù{¥™1GtÉ +<‘V§ ËIØÒ{[f…’ª^¾õÅ !Á[Îg8žÄRZ£N‹0¹ºyÒ 3ƒ÷âkHNŒú‘qŽb’$RûYàsdä{ v*uÈü.þœ)<àáÄË+/mÝ)=ý*$y.P8‚ÈA¤ü·¨qdÄìöØDòLzëYôÊTÓ”èC¬ÒS.»k?º„‘Çê­¤ÙLT ’Ozr ëÕçJVTkº%ªMH¿ÃFÔ‘|å®iôPÜ6ò¬¦j'(G‰Æ•2`W)'¬ÓCd² ë”HûÔ¯´HLK´ûÔ`Ý^¯dÊ‹l{³Ÿ%«H#ïN´¤–Õ"[ílÍ"‡¾±zÚ¢ýß/´>Ï$Kf’3,íŸSöºýÛ#nN7¦! ƒþØ’E ‚^«î~˜Ô€ŸŠ•Ým©,h&¥Y̓°½ ¥$#´^„7;yÔrfŒ~qW_j6'Š{d3l‰´ÒdÁ €ìÙóBЉˆe0ܱÁK7{Fp7HYÎ&/ü¨:VFÊà ¯{Y“z!enscžjT/ œ—ÀèÚõf¹1¬æ S&!”£(®Pǯb+±v¥vŒuÚ’– NÓG¡!”:Ô·® y¸(©ÂÝiâ f è±ëJÀ–8}[fÍ“^Åx’*+ùCò~©ÐBf‚qJ‹S*3äXùéCÈ<¹zïY3ð0'âÐbå;8Ø—ègʱ’í‡Ó?i¯Ð³Iv?;R¢bRMýxâ§D¿ð®LÂ-ŸÀü#šÀ©5}o$6_cÇ5W ‰´“=ð´Ù4uÕü¼MÁ¶ßÚd\ÿKI€ŠSº€†ô¤ÐŠÇaÙœHõ1çR¢vã¥(†gh§™½ìîoã9¦Rpêõ¥Ú iê_Ѭ,5åzöÆ“dyßÊ>‘)xM¸§wTôØ©îâKTµuÒt+ç=$ŸjD jèÖœ@N÷©å/·¥0'.ËwF6㟬 IŠ âñ>h(SyóˆŸB>žƒé:”BÌLŽ{€ •ˆüßñRè ›LúÒÜ´±V Õ¤¨ù¦ÍS/Šš *#ï9}¡ö¨3QÅB2Zþ¿Š‹BɼÕø¥c|ÍN¦Ÿc¯~s®h~ÚXª¹ú ¸ìUE]Ihýs Ãg»,|Ž þÒäHäœ] hlihÕƒÕ±C0ma¼|Û°@L¦8„cÖ|ªMcº“R,†y£ø}wBBø(¨±°¾ U8OÔÒñàͽèGÅ`gbÿ„ Ù·Ï|jT9¡a˜•2y½@0kúßÊ‚¨Hr3¨i4Œ®öf ëAqc~Mšµ ;XùoÝ%€1<ÓRQ÷,üv¢„°Á¿¬¹ZŽTt{mXiC+ÅfÅ ­ÁmŒßžÀE­XpM÷<Nô‡­!9væxçÎ¥2…$ží•=ä â÷÷…­šE½'&”a{öc°ûÞ_h}ºµÇS?ºl ‘2jDÒíóO9ŸASÔ˜ƒ„ÒHh¼+ÍÅÓQòe(-å–ŠH®ÌÎXëJ ɹÉôõïJ(áΟ¿§)(’<œÜ³öÖ scy´EFÜJg¨ü…AŸ"qtÛÖ¤’ ¹ćMræ™&V gSÊCṨ;ÄLJMªH¶Ãtò ôÚ;ùö , ì˜ýU‰^gç²AÃ<}Kv"’¯·õñV\ä“i¹‚F¹«ùPúB”ޏ%d~X=[T@:³_'­2BO(¡¥5¢1!ç’#敌h³›0KŸJ:m,\C~µvpÛѨ±Žçä¥ DÑíRru§r LN¿CZa–M–ÖîF©œFôÀ’šÚ*iµB°Ømôî^„™–Lz•Q4~í•"ا|\ô-d Ýuï"’„þ/´>à ùºb3I$0:1ˆÅ–òÊ>vf³£ªßöjZœE‰dÙñê^„¡VÁŽ®6‹ä"hÿ9-Ý!øëµ '¬Q2 mJDk{ñš—sÊá^}ÊM:2çzŒŸ5¥ØŠ¸¸øâ?U\.¶ ÚÀ( oQ²elS1.pN“%úS*Ź]I9@AÜ!6Õwé4Ŭͼ³ù V†Ý#ö=²Ð‡õCf#:c´àoeè)G÷q—¶µ%±OF¬jw1å·•B@Å×Ïn;B[Õ¢à½?9ì èb6;B)“moåA¯Dô.zãΠM1Š{¾"ŠÈ"0S¼´,2"w´/ŸlÍÙQ»££Ó°ð_Š&EœÜ–Ôé†m:r êÞ~À¤,lM(*'1½wâ4Þ˜¼hØý÷ ¬6K. è,r,ÀŒ§¥84 Ì#¼žÔ Þ-ëöS6QÑïþU°€%´qw*÷Bk÷ï9}¡öТ3QÙ7£“1„ú¦/ML>tÜó¾Eúaa^ÍArù‹õ³­tWEtWE);¨cgGÌjêeX•ç:1„ ­ŸÓ‡Žb£âøM“#Ó±* ñŠnçËjYÍEOÈNb›T³ž{®‹ÁüyQCÍþZž6ÿˆý `- ê©Ö‚¡¦]'}]ŒÔ‚Û‹Ìâ#3¥ ªæ-·û»å¥30_|S‡xg–šoQ¦êÁ:óYInþ U=À» 9+ƒØ¢Ø;EŸF;Ckwš"\ÓWs· ÐV.Æuü÷* ¯¢‡µtWE7WE26Ìq>¥/6CÍ£(íE”`¿]ü覦¦¦¬ æ° èþ憠H-ÇRe!Ó ¾µ%EHƒm#éðÐúž¦µ”ONàÒ(&ÐŽ§ìüÓˆ  µbÐ ±´Ìû%‰/ñ½G,güÇ•Mº éVë4ÎõøÒ@Ž7!ßy«‡nc¨x(ßT ‰6žh'(g0Ù¡ bFXf?ÅyÓâQ‘=iƒ †zT!…iwÐt¤‚‹u~Öš í–»þʳ0]Å™Ô÷¢h‹¼ºá­ç4@‘n%áµ®ÊhæT7M8îÅGü>_h}É€EË®îpñD\o-²çG:dvrŠ€m"Ò-™õžù¸JÞæt8¡š"[XúOž •×%,$zҫȧ¥+môóØç3ÿú„‡Z Îm¤,Õ•¨ âÚ¼/ÑW30Nb¥îju> E£ó#ßµµcó@¸{ù_6¡‡Ú=ÿã^Ì\g(c>šÐ}'ï´ÅdàGôÉK!¡ÑÓ×GÒk©Ve÷mÃKyf‰måú[óz‰Ò¬× ¿çœQ äo¶ý†{4¤@æe¿9¿¯g¶½cõAâ}h@._4ÓhhÛVŸ”|ÿùp­ÆjM²Ì¬æz·¨Äa±È$7dDÍ8Ìi HN& qA/ÂÒ‹‰O*Ûã„X ­HbËõýZ€[tA×NOzdØ"ðñ²Ztcýó¢e· "ôŠÖýlÌÆèy†5!7¶ŒjT^Íî†5Ž 4I1˜Öް&eƺÎó~ò )W°$mÄÆÑL°ÌBL1n~™ e¸>/\Ÿ×d0ØX6Ÿ‚yÏjJ’Ÿ¼~”ï29ýSdWUñn”í[1hÚvk“Ô~¨:HÝ#µHMJ†=7äýzTùÔúBLu;fßm¢€/Š5ÆM6»Ç3Í9“_yâ˜H› kgñ}ß ËÙÀÈ]Q‘“åj ‚DlÛ`† vÚ¯?>]…#“ž|kôS†a—x6£7JˆÒ/ú¢Ef ÑJ yQN ¿£Î’„6ò­±ÔUººêC xaˆ^¦"³ds$þé„oošÝÝ/@ ƒs°+7éôÄb¡§°TŠD²ß¡±Š}E/šžÝ›…KÀPE p ­Š„kƒ»W¦ßX†UðùQ¼!áó¨ŽÑúGÞrúñÙ‡Ý 3J 7RêÜÓ§q"ÉɵdDbb<5‚#RçìÙïsú¥.¿MÕÍó{zqýìtEÌyäèêRT³|oøñ½’©v%Rû“ô4™ .´ R)ÍÓËÖ—³ye™M×ãÎ-Ja¡ÜºB"àíEÍÎ y˜~vj¾®áòŸ¢‘¸!‹4å*hx’«#Sèø^^×'i•\lÒ¥£nUý•ñ‡¥ þ:¹ÐMp=i6›{ãÖ¸çLÐÏfL¾´“õEJ¤À–Þcâ¥ㆳ}]*C¿ÌR Ëܦ®–¡§ãÞ‹"±FJPÚ~™‰w¢9²²ooƒ4”ÀÁ¾‹áßCÊ‚Eƒ–Æoi}±šPæ Pà6å~;‰¯xfÑc­”!œtú®¤‹ƒ{!ΆûÑ–¢`Ÿ6†¡ Ÿâ†§ºÒ­·:³Ö­ÿ#—Òç»gÂÑ÷…1k—6o‹f<û¢ 'ŒqPe…¶Î=(…^BèÅàæÎƒjmàÈ"d Mü&¢r1Œ|6²Þå©Ûø,}( Ü×ôU£ ø '¨7 Oci7çoŠRûƦ‘©ù6hc5î¤)Ÿ:I7*U*•J¥ÿD©M‡íø©¢ÑXãJ8ü÷%Ù#ê•"”fÎN¯Ãë¥ >cS©Q5QI54@5jIõ߃òúRȺÄ3ÔŠ5* Mù>µr bYƾmO§2料ꖎ®Ž)f¦†*b½·å¢ ‘)()j@«¡O%¸6 ›Šܦ=8N?ÊRMEÖWd‹V„žût¨ù4öEi1W1Ñ¥!ÈEbä'éòk<~iòU•;•c)Û]zßjµ –¯¹Þ sÕÄ•ø¹çH)e2išY™u¤ 'S4KV PÞY eAYPõóø(}½?g­HšL1¸²_Þ™QR AL »¥ý¤à$ê¸ò¡—xÓ nƧ!êtîL鯵BÀEOeÓwØÐ:v ‡dEÓ“B‹Úµróv®IØZiŒ aqçA@8õ›R(FD¹fñ¸8L4ýØ{b¢£¸1=³˜øâ¡ã˳2n¿ÆŒCþŽå/«ôûPÂM(ÕÊrÝÁÇÏx…¥ïkëÜPdÑ$©η‘ÉïOÜS®äKELî 6ÀŽÓØ7ìÖMº?Tä~™PsVNïŠEÌ«ëÙèÍ;Çh<ü•/âCÓ¹5Ú·cC(PË1?íDÆ$R{¼-jI¼Ö„¤V"ihìIiÐÌ­ì¨`´B§XÓ«B¢•~”‰\´• =^ ÚÓÕX:—Ïè?`0ìØ(Õ‡ÙòûŒ2Mï¿pû”†;]-xßNèQ8Q¿"ü9£¥ÜI>™în F†‚GOÁŽ´ŸÁG¬Å/‡/k{ÓÑë´èÓæŽI]pRèKÀ`󉕶¢\øñ­Gh ïØHÊûêà«.Ø}Æš¶X1„ÔzP¤hµ ®²êi.i¤)·‰»7Å÷ŠŠ:™Ž—#˜hê$™èñ4躤="hLÐâÜGkV¨§í^ÒˆSÞ†+š¥½sW[Éê=¦ {›ÉI€À“¾=ˆÓ4†tšÎ—«=¢igmãdþ?=ïaò;†;®fÍÍOJ‘Ò{œ&ƒ¬.u×»¡ÄëÐÍfÓ`ƒÕ¡²KtÔܧšÞZv˜_éâwÂk_žãÚ$ksËÝ™Œ@» ÌG—h•$’]ïLƒQˆ·/Z¢«Æm¬k9´æÔÉåDû=>öS$ß3?ºSÄ[ë:mß Ú?`Tñˆ š•Gaû>]õª„0âþƒ©Ãë@K>ÖûãÞ¦Ž£'t¢$^C#ψuF±«ã²+R*/Ú # #¢I@AÎ5uñÓ±E,ÑqÚQÉPË´Å^’>Ñí.@ª%ÒþÍ–3=ÔĬZv’};ÒæÎùÃê|÷…ítO6·{À7;DÉ"ãzf£êÏ’Mb¼3q’à€»iŽ:ÔD2NMIÊö*T‚m³å@”‚Ë®ÌkÏJˆ%Þçµ$^tÈñûEBw°wwèf¤¡<@hpW3Ѭ:”ï5[«œY¡ñB}A+¼EuíMCÐÿi`š%0±M‡%8½î8 ä­]Øà4(+³AsÂcfبÛ“Âþ»ÔRáÿûÚ±‹ôï+”~£”1Ü‹?gËíøŽÌC‘ºòüÔF¾ö=óM©Âe×ð"§²²:Ÿ†Ý*j·SGR3Àiâ‚Caœî÷¡fÀ¢v*@Z] ÒL½)Dä•_B£HXÙ«¯Å†ÎÙôu¤­h#¶{f)Å 8“#Ó ß»<;_Ï«C6KE惘œxh¨ ë°³Î}xÕÌ-ø¢•XaêT¢’Mx)û¯?uà z¢ZoFÈh:=èeðewÛ¿Š–Çïj:%ç°J%Ãfò†KÃŽ; Æ÷Š{P¥Ñ™»†;¼‘»Ì³Òôòâz‘Açƒd²S*t|=®€ 0T‰Úƒ“6ZÚÎWÚ…a%Ÿ^bh€Ð=__¬“j‘s)Åc¼öÇÀžþ´l­°üïQâ{Oâ”yÓ?ª ëÁÒœEhJ|¡/ñJG0tnCm=k҆瑯t§WÒÔžÂYHÉH“-~ã—Úò J´âmâ-5«Eñ>⧤8 xŠbõÌ0™’oÖ€·læÓÐßJ`Ø.«È|ªÊÀY™Cc«ŽZ%¶>€M$w*x¨‰2Äí·Iÿ>ÙízÔv„Ô»’H’Ï•Kă#,ÒE,JŒDÉ­¨a5¿½‹°yDzPGtòý¯C-fÁh Þ¨¶–^u2Hm‚oiù§d„ÍÍw;lRƒ –_ªW {¥%åJ:mêCÞ2fítÆ{¦;¬‰C ÞœTÀVØ[¨ß¥ ³ÌÉ犰zg/î9£#¡¥þ;T„è?8¡–™:oCÁunú´ö“·qO|ö½XÆóh÷¡Ö9ü9;a(»%yÒzoúIG¬Ïµ¾<‰™ixLáÛ|n¸‡D´LÝÔsñØaÞX½B';Q|}{rckeÚifïØ0»7vô¶ßC—Úñò¤GaT¤èÅ w†Ù¦I`„šK0õ(ó$.86Ú¥ª)FËÇMB„=çp¸ VvHk°þ‡eªÕjº·Ž"4Îzé@Rf÷Ôv\ 0C¹H–ÎIªƒNÄœ,ÚÉ4µ%bÈ&/h¶õJBÚâÌ1˜pÇiÔί’ôV3z K£Ôoæ×)_©¨ˆ*O‘8(Š w"¿KÏM(œBuG¾=þ´DîDO}ì_#óOdÎc_M++U álµ³@¢—µ:ˆ}éD ÈK+ ¿ªBn˶Ó}q>T`¦ãd e®꟯~Ë.óšP)K2¯©PXŸª@ <ýÉËíù™`2lÔ|fô£ Aì$‹±š: æa“&;Óü³Å $˜æ7Í#`D²a°¯Y‹ÚJNa¤âÍ ­BJ ¤‡!ÆI’†§JÜ ÉL™*>Ùï íŒÅB†!S±±«B,²òÍ*µ RW¼Aù*Ζ›\´mšB’ \žD<˜Ú¡·SMärS>¢-R D³…ůwkÒ™ðúQ7‘³Ê[RXÅr×/Œ¬ÜaŒTd¢é˜¾—(½¢Õ›æs4e‰$è÷ž¥[\Á-óÒ¤M~Wqb¢‘ j¹0žéŽÖ W`–€r/[Ÿžâ éã4€–² '?5bNÁe\‡NÌáš±õJNÁ;P„wÚ*Åç´$’/C>Ô°á°u]õšAØàÈajFwµK Z)°QyІòDó   ¿óº±øï…øÅ@&ž÷§ê T¾°!Kñüîš‚ §·—ÚóÚÚ¯Î)‡Xt¥„3ãZ°“R;5@ǃv}ª@ÀACÎªÏ >ž”Îõ[£~ÇD˜Ä“@ãÐ*]ÁþQ‚±‰½[š8ò¤G!¾ƒyüw:ÏÙ=ንMJ‹2Œ%š¶B}œ:4y¦B‚’¨õCݱ!KÈÙÈá²6¢aƒj^¡ÀˆzWÆ«¤"&pS¹ÜÌp ïDœ,ˆ‡NæÒŠšå#™=ø©&Dõ§Q+-¯øUؘïvGh$Ê_t««!Ôà=.Ò`ç!ÞyÛ¹nqË»c·šÙ(Š!ÉÒ°šdàyLw vÊbàj¸tˆüPEaEn WMÜϽ%]7°ƒ­C}ÏÕXin{‘>¼ö@ŽÕS¯ž;ïcÛ"Jã~~j; $À ;~óŠ! Ã~M#›Rl°´b??C=“¡nWÿ*i;¡=„¸&‚h«ç/´?çGŠÝh¸Áy*äDÙ²{¾ô8q`2ù¸ñ- DÃ:ËñLŠùãØÚÞôh@0 @9FÀ‘%¬ÙÞ‰‚숛•dµæøÔïI‘ðÒ4˜Å;DŒûËØY:'|CÕÈí_§!\Cñ'¹FÌï 52HH$Õy6«ÄÌB™ò¦Ó8,ŽP/fŽ+ Kòü,ó·žµ(FdŠJ˜$ÚÔЗ0/æ:”T P’H“Þ‚;ØH‰‰×ƒ›gsyÒs7«ø#%ö±é1DiØÂšP/ =(¢\K%#s÷0¼ï@%¡°ÅÅß)͉*¬ºçž(Ììq&k`aa¶› 2{Lv2¸¹£bÛÁË¿l÷.£çµ_¤âÏz¹ ¢üR(jGÙÊÙ VÑ’:·ŸƒÊ õ–êÿsBh)¯g^áhSÚ Aƒ”‹ÔŠŸƒ¡Ü•Kµ;€Ÿ•hÒHwF;+?D§ëŒTé—s—Úóç%$™'Zä _ž{.îM…3‰&&£ˆvÆÅ C…dÖGË[þÔPdÛö9NòE Ž-1†w,Ô¸BÍÛ˜Ödõ¶õg¥ÿ3Š…û'éJ¦‚ù)uÞ½ºèt¦3£ptÖu\·ýQ+8´ÅúP0· VÆ^Á¬LÐ0ÇGóPü›ïú}¨¿DÜ`óÒšmd>©¤“¸h5qz»hRÅaBÑ–ÖÅg±›`4² |ÈŒ®qÖ‡¹¤3]bU››í½L0sÌèéY®ä‰é—F³pZÌÉ >:8I(¹ùªÒ0PÓ,óAñUp`h²6oOàÔŽÃŽåC¹D\–ÿo€Iô Wè|—ʲÔ8ûYB%¢Ìâó»®{OkSºƒi’<óÞ{ {QcHBgXó¢Çéz”åØeñ­%(Ll›œv¡ÍD6©‘RÞòM&؂ܾ~ Gq!g]÷c¾“ûÛ¥U¬'™üRvØTõ{ܾÐÿ¦Bå„»±v£Xme½ñë‹­Äü^€e‚úÞÝlúv,2&·’Cb¡îXí‹2Ë‚Ü÷på0‚ÝÕîÚÚ\‹déòQ Y°ëNë’,XåÖ­€˜ýû4²`c óú é#âû}ƒõlæ‘=À€zºïiéH!`ˆãÞŸ%í¹ßd¤ŒÏ`gD—Ö€ Ú±zºžO±ïš•Ë>µ"eæ¤ ¡u-‘ßyÎýŸÆ0´ùI•|`Ò Õc*”q¯*hwžå®w =?ý/š•®üÎù¢UÀŽÅ•°sH´$%"у‰¡ˆ2 #?>t¯p½ú=sRý;u‹RýËC=ÄQŒ›þ¼»;ܾ¬PÏûÞ?éÜi°¾÷>)å‚«Î:cjxT\r¶t¬7'mÏŽÖ@×/UŸIžƒØ‘dźÒÖ&÷¿•$`‰É4’Sæ¡X!475æ®p¦Pe 2Ý©@œ3{ÑÄœšõŸGÂk8BM÷Vh.t•ï}øÏZ rŠBš,ý7ë„K01=hÞ^ÒDPÁ8í!K4ÌÆÈð–«W+ÞÞÔÑ©œ=S«1‘v-·J…[¹‡'JN»&üÐqhßO=iâ #S嵇ˆ«u úSÊ "‚ߥµ¦$„dy¯ÏùjU!%) su{ TëäôiäÅG‰h²Ï¥‹…”>7ò¯$|Ó°€÷ïìÍE",€Zrc Máèlù8õõ(̇ “Ê¥l.¶:·ò)Ý¡¨þï:T¿,Þ6ÁM|Òò¾-û¤h ƒìfBPH:,-4S&Æc‰â¢¢£è=Ì 8/N74¢–ÀþJ ýMG’°z‘‘'¡3BžD‘5&m õ¨;˜ôì,öÅ•ê?ƒš_1HDxF©1FA²´âæÑDˆÀR6Å™#Zg¶ Ç߸[>“¯“«пx—ceK´H©Tê_\#èrûCþ‘a^W… x¢Ž—yä‡¤š“¤š˜¤ah·¥¼û¨ nÞqc¶(P/3/’4ÄdùÔdB´ÜM§a©XÁóO+ÓtbÀ¦` ¾ñ4Ö°—àÐùh ­xõÇ¿×~ØíÞÀØ–Ì%upJ¿kº¯ÕQz&ÃR­<"Í€þ¾t3sÐý¿E¨±sÍýÕ›VØ7¨Ñ2\¶ÊoÇ¥~—ÜÏbÐaOvV5ìlk¡Öû)Ô+à‚ÅkÍÔpô³P¨RŸ ÷BÜÇž§\T@±^MÝ£}ªA¢jQ$XÅÚÛn•b@~MZm„z†cÏ °£²’ L]ÉҼϫ•Ïãj{òx³2#n{ Ò6Âoáz;Z€+Ú€&·ìŸŠj{ÑcšT«5š›}xñØܹ}r7\DOª7éÝ?è¹9v—½ Rßpr˜ÕÆãé ›0·qF¢„˜h"FIÎÝhúMÀ[þ7¤WiÓhíp³ ‘Üyæ¦fаÛU6UVUn»÷Ûëë÷;Ø¥¢—[}>*D3xÙ7æ•(ÔkbÄ_Ìþ*Ü…ýÑlžò¢DÀ¦}þM|»ÐórÊümDÉI´¨(Ylëýt¬òuóÖ–*p:¹âv«ëDG¹?åcàìÕûFÖ‘†Óœù_ê=Ð6Ž,‹ÊbvŠ4M¡¾ŸdiÁ‹äÒ‹"„'A«1:cjB"Âä–†n¿쵕…ä.<1V5R×·yu(z ¢Ú?…d¾ïn{Ñ@ÌÏ/dâ ‘ñúô¨$èäëR¢]‰ì¡E$è͸m_ÆTÐÙ2µíÀ{ ÆžTHÆ€‘?Š˜[1xÄë|e>_ìî\»NÀ¨¨¤ú‡ü˜‘ÙÏ~­Æç%&İM¥óÏç¼. ƒmÖ5áS†F¥¼þ/Kš¦:È„â’UGIÂlw£, ?ç—l”¹^ŽçÕ7Ó5EGÔ~ìAÖçž= 6†ÍîN6*1<5ݪ¤ÇTçÎÇ&ž†FzPÙ·3½°–9¿Ð?Oò¨ ¤”C·ˆ¢ë`9_ǧczL_.}jú"hú Ô~Ô4*‘Ü“–sÓê=ä’ÄôdsHÝÌ­ÏÞbĺ''çLÒƒO±"wÊ6í†r³²ÍGtvyg4QÔ2›çÚ™pÆaéWK1ùzÐ`1Ùpe¡bÂyZÆÍƒO?š Ð{F|MBN ¯X4¢a'_Æh¶>íhg¹ËºvÌ©úgü–åFËtÖÓÏIã¼¥å‚Û]Šp‚Y²h‰0æ®p¯:rkŠ£ 3m§zT³º598«ìÀË“¡Â“ˆ½f„ÊÁÖ‰:¶ÙŸ€ú "–&jtÓaF£é¿vD3cøö¤ÁwŸ§‚oË£—ª‡¥g¨GWõ/•^®º0¿˜ì³µt3û ÕH´xŠaàâžFõ‘šÐ–Þ„d‡S«‘þÕÎÙq‘Îj÷0”Yuó:&õo0lºóø>KRÕêõ¦Ncm'¿á=æh¿GÏ—5"3k§ø¿•Z 6X,àCLiÍN$fw¾š.™¶h.–SËOjµZ­Z y6óh 09 *·¸~x¦€ƒrãòQÂTä“ÐÁ´|Q¥"ûŒf Í"àF —Kϵ0¡Ø²×nôˆó¢¬fU”M/tOäSÂPëìÒråãšÈ'YÔ¬"îCÊmäR© ·QÆÞõ$¤ñéÙ ¾*DÔÕÞ¦"Õ@÷¯-½"Lë·O©%B’ç{÷9SögÜ¿nÊ ´“Ðs»Þj]%–3°~ix<Ÿ#øíL`Ðè`ò¨\Æ2%äÇc1h#z •X]ýu¬@~šfMT`Î ±“×ñØn!˜°¶oâ*U“£¤GS4h‰R³†Ýv÷í/˜L…£×â— q%âvñ½67qÇá£dr ž¤dæ %dI虵OI 1¡µJÐ ú§‡ò›¯_ŽÅdGqŠJHÜf*òá³gõY0žàÅÊ•7N¸j!#kçÂÔ!‚ôµþŒ÷£·—uãêvŸô,! Ý#Ù ˆ“£']dÝÒ”tBz™ýw‡ oçµtÍ×ñ‰åD½=¾ØñHíì~óÕQ¹C×Vü¼Q€ —fÝ)rÆ?—1Ú¸PŠÙ,ßµš“0Ë-]®ñ ´qo>8­×V¾-Z¿»=¬„ ‚^œcK&ÆcéÞ|_óm¨'´HïÇÕ~”$rT½ÜùýŽ}{"{¾ØXM4Ê–„¶/púÓr†劋M¦—ùïFTš¼ì|b’Â%çr¾¥9—§XÔã­ûõ&›ç¹Ìnkc‹ÕÑt2Ÿ³Þ®AWåI{=¦“9ÁXÌ®ûGÅj±±¤Åž)`#ª,˵$Ãrû0&ƒôHicf•O&fÔú‰öÍCAÊL½*aždŽ‰ÚŒOî ¤…ÐI½×Æ”l¢n\­;±QR÷=Óø©ßûXÍ-¬#I™àÒ&Ns4mQ` ŽÀIYÀeŒÓ„±9Ži"_zehSŽ7¤%5˜Ë> ÂDšÊé,Ûsº€$l”4ë’ ˜—33³jD¼[S_-:>´"wFЙ¨(ú»Ë½'Ö;ñ’7×’1´Mô¥º]Yô'ÿÉ4˜j!gS²in¾»:4©±Îôõ@Ÿ”†~hÛÆÇÒq}{ÒÇaæ_«0”%Á:¼R¶Ìïv“:v L!ž‘J[wœo^9«t^[Ñ$(׃«'œœ`SÚÏLžM`îA¸XtÿNà pP.?i5?Eú þ|ÔvEGaðß÷š-è³óQˆšÝ2ýqB ¨¬ÜŸ)Ššx!&â¾a–9(RÊ iÖ‘H"mÉ·­˜äv=ªú̾ìúÖ˜‚߀¼mWäå]>µé† ÿ'ŽÀе¸ŒPq2KX†J«uùh´Pà”Û‡m(KCÔ«ã_ t{eÎNþ©Ü]Ó !y?¡P“oì•6$:ØxŸÝ Gb“âHø¦&ÝF®§oS0U¹ðŸIæ¤ ƒÆÝg¬éCõy}¡Ÿ¡‚+y¼ï¡´iû¡œÛÇç?òKLÃÃ~áédLÔ†«ºb¢ˆË¡ÕÒµè(,Tgåøú@¤‡è±wuûçWÉ£™sÝ p ¤q{w$ó~sØêàˆôi Á~XWá>ÝÃÄ"‰=É®‘ïØv$w·~šË²³ß†Rx¹³L‰®wx3·d€„Ý}u¢Ö­×}¼Š)Š"¡ô/,º'MÎàXà ßóDÈM¬!ëú©ZÛk.Å;Œ½>{¨/ŽjP:/ËÞ™FLššzt·nx·Œý.]àš•$SôÏúNÁ£5;zšpÌ 6hÀC ¾¯Ùé­F íÏ`äÊY8ܤ²;wŸ¾d*r|öUÆõ:|Ú‚áìxá• nµ!ÀapÒ§NEÏ3W@IAŸX‹˜dô¢“v}(ÞK§`ehbågã­ëÙa}ÂÑÄO4õ!M¬—˜áP+Vugç²pêÓ­BNv¾3)ˆæ¦§ïžèÏÛ΢|É¿•¸xºzm튛€/lÇMèÔFœ«¸Ì­-½å–._Áêçô!ÎÃÌ}¸î4Ù51äÈî>KSÛL~;äSvrî£.žt1ÙYú‡eš±Vj µ?ó 0RóýwU“1nº{Ñ0v™ågÔ¦œÏ»ÉýÐPFéo®JôÇyûÙDveœ¥Ý(¨À@qØ- ÝQïAfD^M_8e1çKÌ"x¾~F¹ì DÝÕ½±a1»³µñ`eøéQÀÍç„q`™oÒ§$’ó¸DOœþéP;2§²h#±È†å‹E-y²co->ñï@Ô ‡Ù!®…1(ÐØ<|TvÍÙœÚÑׯqM#ËËèÁùg쳣ʎè gÕÖˆŸ?ªT´ ±/¦ô­&öcxjéD}žl³åS‚É‹Æ'Xã±'þn¼H}*du5:÷&6tÝýv[I2Åb:¹éû£•¢#ÜŸó±GQöÃ÷½7}ï¿ó?/b¢Â1î‡ÄúV×WBke]=Õ!– ±z2Òóg±û¨8¸-û¢XPƒBau«ƒ§ ·Ý¥=ÁevÕ„YmŒ ÷(%h2ççïú]ˆ¬NÂdÅ …×Vˆ6†ñ2y¦æÊ$‰cÌêƒA`I­çæ;T3BI©F †5¬‚VÃõÖ<郸üÐOnöGo*Ÿ°ŽÈNè˜âŽÄÔH!Ÿ'F:ç$˜0tc_óή&Lj¤«ÿÍ“p”zf<Í()bpÏ`³-mo‡™¯dfÊ‹úí¢¦áœö½Dõ§mêR„ÏÕ2;ïÞ@—^øæ€ú¿‹ôìnDé¯?¶‚ÆíõεÜ7qýõiëeÄ6Ú,t© ›_п˜©³\–z8k?ÉÕ$+š\ ´gâ;uݲÚ(Âe›án"oHªcAfȰe.¶}­x÷Ò©}`M- ™–"Ÿd.v÷žÄ¬¹(wjvëoÓO/JŽÐüôìaÌ¡ëFg@=;%.ÃáúâóCµŸ&œ«±')$KÔËX©©{J=œª>”}øšn|Þþ\úv³ûéØ6¿h[EßÍG7|ÿÏ èè›56W×Qãz”Û‰6ý˜Æ×ï”R,¦Óøú¬_¾ýášDÄØE­+LoÜ…›@¿Zþ§õIú¿r!ò$9ˆ-Xbó|Š´‹¡ö/Vùp°á '¹¦Êˆ…Î!㘦a$B2bnýTeº:’ óŠÜ«®¯ã…%ÊŠ®‘|S8)M3šÅã»çŽä\ •ýxý²÷1'Y©¬W:ùŸH˜©Bb„<Ý9ù­a3½¨ FÍ")õã$Û>Ô÷FƒËjáØÓµJ{Çy½F‹Öǽײ~ПùNäaâ„%QÖÍI×»‰Aù¨r½mëz‹1ÀKÛWñõqtï¿{'Eô?¶¼õï0&W‹é­*×÷­ØVz™¡Há*@…˜-2J^×¶h™$0÷=>WÖ‘m8S;,a’6)Ϧ̖!}.¹ˆ†í;’ŠÍ¾jÎî4êömóªF¨ýûP0Xî¨j…=Š*ÅB‘§Ö~ÔQJØ¢HL/á§Ø´%ؼ1­"¡úXmÎÔ!ŽÈ׸LQ%ò–m¶/Ý)ìåE¯Û íô EÎL÷ ;"“¿g±EFšM/ü’œ«H°± ng¸Éɇ&¾“Ò”·ì3¥ºCcg™õ%ΟAûÔ#dâ‘ òÓÛ»ò?ÂluØû±~b’iÉØ‚‚q6IiB4÷­ì¿eßÁA½G¢þT uØü=íQ±dÍÜúó©wPS ÜëAê:¢<µî‡Æ¿fý‹ؚɌR—!½žÙjö«¼Çµ# ;ëëör²›jé»QJ+7&b@Av>‰â”,ƒT¸ W÷QiYÍÉ=ª3eâ'XÚvú»#ç>u*Hï¤öìšššžø²Î4ÿ|èB7 XÍ·Ží¿çFQR®¶u3íA2.0uÀÜÑ* Ë<ðùMJÈ ,;]&œÐu¶I,S"a-Ûø u­)¾®&ߤùÔ©¹ÝÓIœ^sšÓöŸºHú( ¨ýï¶ô¿?·Ã±©r136öÖ‚±¥Û¯i¨’_ éÀ<«%›¬Äf?Te)-Õ!fyÄyæ‘3ô™C<˜e—唳1“sXÜ©rÔ"[AÔz§º÷9wJ²ïì5)á}e IRT”¥M?ðV (KSšZ¾§V\½Tè$HNÐýòË{Ý®ƒ~ëjIL›/çO:Wµ*ö%.Ùè§µ$wš÷ÅGyô_¾÷φŒvE|¯ÇlöE9rñBàdQ7ãbœÔ¢CûöíÅëÞÚŒŠF   ÁŽË/·?aÐúÛ‡ÑX „wÝü¨KßW-3òz•³â( [BÇ‹ñô $ëFEaÓo-¾”è*ŸÐåõ&¦¥©©îe(¿·üWÒ¦‡ÈÕa†ÜP1Ü@b$#q9*GË.ÿ/J-%¾#s±$%‡Ê„@=î'E5?Mûè›5êOG¶ÊÀt½$Y œR%šLGûRŠUÊþ»Ñ”pÔ7i\Ú€AÞœ)A1‘¿Ó¸ñz Ne¶3‰ûçì™t×ñÜÃ貌Ëâ&‚aŒOF‰´`׌“Õaˆ&ók·I 3¢šÝ21çQNàû}˜¸iàyòýT„¦6ú2Kåö‡vÂ’O¸²K2Eýyï8]„qƒÅÍüšH)…n¢TM˜ÏÍ:ÐMÒAàrvµDP=Î~Åûöå¿’¢¢²>‰„½u÷ ‡09gXÒ³‰¹iëQªû¬ÇJ©¥†¬ØóÅÀqR‹`úçè¸NÀ}”}ìôñ††¦–)Z††{ 443SK44K°±SSC54±SK43HsQ”bXû>]Ȩ¨í;#¶>ƒÙy£íÒHû·`¬8åleüÔ1«Ï{U3enൎ "Qtpž/I£25·Åž P‘``?={…¾«÷è9/øø¤†PaÏaŸj†`ïkP²%BœÑ/ A]y©Tÿã?`…©‘híÃíÑ"¤@ž©?gËíúéh¢WàYú$ÊLYnNÜœ4³w¹®úÏüP/X0IyŸš|‚mgÞ‚qæûlw@n¸5_Ú¤€d±OR_ÎÅ(õ³iœL´l¾ïò‘å|Gü‡ìl;ŸßnpBè³Êx/³åö‡ýtÚèå/"0/‘Ÿ ”°Î ˆe9ÐÛ:TúBìÞOÝ3HíxšˆúÏü1¦y5£w‡²ÚÓgXÄ~éðëÓ÷BÀ9áü=è)2‹jlVQdÃãG_Ýkàh9w¶«ôƒv€Ìÿ¯d&<üE d“ꫪº«ªº««°‰Œø6)(/m7ò¨5ÌY†N–ßJW"HÜá‹N•öÿ‚­‚…¨­Enw«{óÓõJËgÒ>)|ϯŒiQSÊw¥I[ 5ÿ”ý‹ŽašŒƒëQBÕ‘HCë‰UáœR!Ê¿ªF!åþѶ«‹ŸºQ¸4mQQQQQQQQQQB¢¤[<ÿÎìý~_hÐX¦£ÉÑ!ó;…,ÞŸ3l÷!Öòì¿È’ÍÔ~c ;¯tRj0 `Œ!™^tÉc]¹94h ˜p5Ç]5­T.7@|Ô¤öÓj›9ž»> Ÿ¬±ºúR4}ƒÿ n_©ÏNÜè:ÑÞ3IÀ³ñVÃ-ð~Ú–Rütÿ–ýŒB—à¨M€vaPjVš„.¬4*ÍkbÇŽ½ãI ††6Ã'äñj– sHŽn t¿Ñ­=Ý)¦¾š>Ó—Úõ¢TÙÕµhF]Ýeó{ ºYL,j£ª¬;!eH-Þ0†bb‰FÜE1|Âjã&'g+÷Úhì"ÈîÔ5‘BV^Sù#(˜âçî‡_Fý+` —Ÿ±àʈhñFÆmæµ³úø§eWv¯):RÍßù¯Ø‡XOE5²ž¥M ;Õ¢x˯Ç`ŒRÏк¸„bµÚì0¼kBy@Þ/~3ÏÑmv\ìòùéõåÍ!æ‹?ï—w—Úõ¥cj“ÒÉÕ9¿8  (0ó¥[RÌŽWmöoþMÿƒ´\C1L`‘s´wÅÍ}=éºý€#”¸>šúв9mîæ{œ¾ÐÿªÒ»“ÈÙô·¯Ñ+X¯-´~Î_³~¬TTTTÙ/Ñá Ñ7*wùjuížÅ-s0MOмF³öIÕ¨5ºÁÓh &' 7TÊ4ã³—ÚõRHì^ ªm&KrÞŽFa†Ò8Ε Þ“õVÏy{ÛÚ‚6˜=>Ñú£œ#²Ê™Sÿ]'ÇÏÑá©)ËS’zÍYÅлúò¦¡ç+ìч#^›sIÔPSHR#µHŽZèêøëH‹*¾µ¯ÌÞÖ  jLao^;9}¡ÿWÏ(0d:,P– ¡nå¼IÎzéçDÊ&ã%@bbÌÁ{í§_·~¬èCiOsÿ ÿÅt¦¢z‘S,É-›qšv»4 pè;ÆØ›xRx6:ºRYtµü¨HâÛæž…‡~_nÞ_hÕBÈj:ˆ>GØîˆŒÊ‰ Ï•KnSâ•Rý»ÿ“ãNÒY—~¿4 K­ ‰Ç,ÌyÇØ+Ð-Ë¡çZ‘Ò44*'HbF_6NmçQbþ[Æ'¹Ë²í{P‘I¹™ö'ýEþ9¨"’’ö›Îòdîµ±‘-ÀÉs_Š‘ŸŒ}ÃõNÎ_øCÿ;Âx¿%pÅÈ[íŠI@ 鬱š[]~\RwXÀ`ñ»G`—Ü+†(Ñ@Þ_hÔ*ÖîtÂËE"ôé…­ÇWµz ›í8÷£PH9ƒýŸ¹~´ÔÔÿàßú¯RÕv9­¨•Õöì79°N¿ãæ*{ܾÐÿ¨ÓÓ5óNê‡Y "Àóc^ÇîDÊY¤"¼ø¿_ºç,Q¢ÏÝ¿õQ"Æ‘'^µða÷·½@Œu_Á¯Å^K¬º¯Ç—z‚H»˜Ò†œÖ„§s*˱IM¨ÁB4¥ïcOfu—lIØ%¬>áb êñ­3ŒÚ¤öiÏpq™0·öäQ"KÆßrÿäßúÆ·ñðqôYw¹Q¿dõ£¹XÖtá¥4-aR4¥ Ñ• @VUŸc£zqj„´¥ìI‚iXÐKJPZ˶7JÖE4Š¡BP!  )ö9M1vÔØ]zÆ(ä‰bU•ÿÈíx "'ujPò¬««Ü §°—a£öþ©fâ}µ©úÏýþ]ëÈËXö4Ƴ¬ëÌ;9ÑZ¯`"Z²¬û4.R½]K¬ë*Ï·CXÖU•gYvÖ)#OJi%(¤8ísû+b}©ë„ãÒ¡É–D6Åû…†7›ÆôåÚt0pqÝì½Ä~ÁÿÉ¿õI){{ôú\»®²í½@©lRµgYÖ… +TM!HÖ…Bˆ ʲíRÒµ9¨B€v"·bµi Yv:Τ)ËKj‚’Ð;s@RgìbqDáõñš‰óht'•ôÍ ¸^|SÛƒ¸Åä²n³O=×óɽ+ϵJ2VІ4$…©|”]fΞÉËöþMÿ¯?C—Úð2¤?ig4Á “PX¦šŽÒÔéÃkÚ‘~ÐÙ‹ÔÉÔî¿ør‘'ErP‚>sôßûü»¡5*•(ìN²£}ЊѨ«±@6¢‚;#±*%QQx­š.¦ÝR©(TJ‡Gd‰P¨t³ïÒlÒtZ$Õ÷¦›BM꫊ õõëQá¹u‘ZÒžÒË™~È7¨ÃvCȳ„ÜéÝ~Ü#þãÿC%™´mÏ9úü»ªˆ(pUŠL´ö =(qYR޵$E@Å3Ù“°­¥ P"­½1šj© Q«Ò5 RÚ ­Š1VH«$S)¨O—ýÖŸü“ÿ—ÚùG²q¯?ü‹ÿ—ÚùF™Ò‚5ÿ8ÿÈ¿÷ù}¡ÿ‚$¦Þ<ègãÇï¼ý7ÿ&ÿßåö‡ýéñ ¡>­z·EÊ#ÒKíÖ€B—xo´òoýú½OÙŸ÷)#+‰Ž(Ñq8Ï⥒] óZm`"×”ÌDÚ9в9‰LLi7KQ:ý“ÿ“îÏg/´?í(% üøi’nfÂ1ïj¬„Ê|Ñ6)9սǑûšž„£èFšf‘ÄymCBfÿÙ ¨~åïÄ—á~)*;ÑP˜›í¯§üþ_hÙuyŒC2ëåP áqˆnþ)$±¡ë‹y✤ÏgÁoí½@‰ƒtÂN!ŨÑÀ`•ùûgþ¬T°ƒ_Šžbo6óÚ’¢ž®i)gÎB—Êú´N ¿ †¤eÍ"âý/QöI’CªR€Š¾X7j)k¸Hùiõ«/üÅ?ÌýÒóhé)Ò¿åEŠty‰ü”Â^·¢ã¬nùh{Ô›‘:·¾µr}hø©?sEB ú‘AÈõ?- ø‡ãâ­N9Ϧi¡5Ý,ye¬’ójU"k­E w˜(§ÙðßÞÔU‰Ãw½% ž%øšùý*W“Ÿjµ>ˆÿbŒ°|¿JÃü—™cѧµ6D=Jh6Ex z¤I,rÍI­jz(Øè™£ ²ö·µlz¿Å;¢?ªÙ]Gý«|íó{Z@–ÆÿTçêrîÇÑŠŽÃ´ÿ°%Xsà¥,ÙqæÒ’•e÷ÜÅš{p*ø$n<íB0'©Úý«ÿ1# K‚nô;¹D:¥=Zè<ðùQ²¼¨ö?t,ûŸºB‹«»†€‹'`*jjjjf›Öd ÄzÊÕoP~Jàz?Už——é(‚þ³GËzŸºœx>)¥Ê;[nõ çbïj&ÐüÔR†¬ÝéŠÊzÏÝdó옩ŠW©©©©ŠšššššššššššššššÊ–¦¦¦*i¿dÔÔÔÔÔÔÔö,Ô8¹·¦+=Ï×µZ©GûŠ ›g°ÊZ%ÇS¾xî¾oÓåßžÙîÏlöõ¤:Û±Tæd…:k5|"8,CóB‹xl_…ô©”¸Vܳ^WÀШ¢0ÉÄÓ@Kƒ#ÑÓ£RaI?'Û?eô[¹.Âä” Gv*胪5’õ+U=ü Ó¿è~ê[ªëã(hÙòßç­m£þи.§ö¬›‘$ìM7»õ\ès&ŽWÁ@ ±Ù€Ž¨R²¤Ž{õbB–·_ÇÞ²ûw÷º&Hr+L㉛“/­3þ˜÷ù¬foïjJæïlÔ’úܾÐÿ‰^U3iv´#¯êQ À1|z²ô§á|Ò³{’æ4{.véçW™f¶Ïi 3;U€XBÖƒYë¦`ûgêÃJî…Øß§‚“‚›>â¥W]Z7aÊÞHèUøöŸº ° =bí,:Ÿ›Ð`#;{P<£æ˜Ù«ú¥ðzÝ }i´üƒãÓrìï)íKfYêå ™÷ÝXl›f€ ±´±éÙ†^mk¿8~J _B”×Ь‹òƒà¬Ëy¿t››Ñµpw¤ÀìIŽš•þû?4ªL¶Ñê0Q»?ð}éIImYR‚EÞUãí>ƒû©°qIO=ªòODþÔwù}¡ÿ‘pY„“ÉŸŠ½ùr|Ã+rIë%|è7rŸi¨0y…^‰‘ÚLÐj}I‰¿dGr)c°ø#Ö¶“>iýÑŠ(0À]=ª-Eh‡œü ,ʬ/&›¼ˆM¢ø{fL*p´˜œØù»?lý5 OZ|W¬yõ¥œÓ°Y.Pˆ_OÇr*Ößjcí\òYÃ)_”¢Ùr¼»pwù}¡ô¢£±# zý3a»b±‰ÐþJ†•—@”ëµJ)>8M‚X—4fAK±îGF€A¥, ²“Í-’ð‘8•a½4…nJ|Ò+§E¯‘LüÍj3¤$29e–ƒý•3.¦o}ï*Y¾ªÖX­²÷õÍKÍ|ÍþhÙ——é( çôt4IФ;ûS0OV‹d>ÕŸÊý¥­ õ?TR%ÄÅñ|ÈùÖ<§å¥hôR?Éú¤$.{¯ek'ÌûgèDБ±ë>ÔLšy?WÑçS Lµçï†>ÕÁÿ%—ü…–̱šŒK:€Wõ@ØzÍÿ^Ô ånúíÇÐåö‡s,2²>°üPŸçô«Qzl{þ«CðÕüØÄ¼¿¢³{ÏðSr‡o3åÐ~f§ÈzþŠ _Bµ@è?Ú7¾‡ê‹ˆŸš[ü?T¿ì¥?cRþåf§ªµ™'F)\¯«K7iöEó$â‘bÄY øôÅo™2]êE¨´Œhª4”6‰Í#¤ŸºÖ¾Iú«Õ°QùÚ”•^{úwÆÀú4.=õB~çê²^£õId}ŽÄŽÅ°¡X¿:ß1B0ÕÄcÞ²v¶ßnöE6Íb×™ø¡iôU4JØÕb+÷öö¤çÕG±JêõiU—ïÆ>ÕÁ÷Èí‡5 ÞË#NI´Õ°Î~KÅ O—×cR²pê‡ÍûÝ‹ÔU’~d>j2 2uGå>hÈä¥Ãæ”<Ô}‰¯ÚéÚ;+í»å@n½÷gâ·ÇUE/D`þÕêY>JöV=¼Ÿº6§î}餺”QX™Ð*cî` ZW¹üf°êô?qVÐ$ºÍ~}'±Yz³ÿcí\x5™·åTÓi{§æ¢÷EbÈÉŠXÏLG¶:«^Ö^^,èHÖ!ÀÍð@;¹}ñ†p/ÕÐxÞ”MËŸS—×-1¤–ó*Á4™‚=ŠO1õ{#BH … NAæÓšú‡ê••è0ô)ïKÖô³öF¿k§h¥ÊÅ?;üÍ`ÞD>ߪQ3àø«Ö¢]HCÕnÑüV©óÿTô£§éz–¨¬:¡NDWi/a¨²k¹£§^gâµOA«ú©lðé\Wõ@‘J\¿§aÈ ¯B¼"ø¦ùc¨>J‹ut¿ë±xÊÎuÔÅl~€ÿ}êè—ª½™ÉûWÞ%òH³Íø«ÓoY´Ð|Ò„°˜‹ È|éÔÈ!ÎôIK×ßíô!€¹1kÐYF]ÞÝܾù4›ªcËóD#掯¥ËêÊÉê üž³ÛrnW€uñ“ß÷E©çþ©&Í䟺wýO꯬ü·Ùšý®â€øk×4€àˇU‰£ ºKᢠº©ž{JTÚh$-MË-cÏV‰‚î¨Kn´fP›—îXWã4ÍèØ·£÷Üw$&¥=¶&iñ7Ñ´¦ÈjvMOhäR¿ŠSý?tÔá”ι©„"ø4ÿ’1ö®¼eW!a`ù§ÀÞBÇ?Wµ ­¢õ–\%…ÇùRÒä;`ÿjBpŒ^ 'j6 Áb#óNVÌèI9‡JNÄ ˆÅ,÷2ûÅÂÃ…—-š¸O 5b Ž…€úœ¾ÃŸô°PË0=^Aä?šF£ÍXãç'â…ý”˜§$O’ûM"l¶>F%û_µÓèEd£¢Ö€ê/êEk/ñGò(ÛôºÉ#©úšÃùÖùŠÃDj3ä§Å)•ñhЋtÏö¦fo©ùüQ«@AÛ0”’$³äÓ%½"¹~‡î‰‘è~eçþPZ>ªÇÇBþ¹ìH ­k¢üÅ è~h?kú?4ÝÐmc÷ïX/Yû ?er¥ÝËþHÇÚ¸?ä²ûÆl“ž¦(š`‹$ùŠ"ÀãùŸ«ËìA [e=±íCY XÅ5€5ˆšËíM~×O±Â‹XWçÉYIyGÂV´ù/ûVfè³ðÑ2zÅd‹Ç€÷£>Uø?tüÉ4OE+üŸªUЯì5ýaAC •~Ž_òF>ÕÁÿ%—ßȣŠÊtfÏ\”kûUic–LÐ .8~ð'dOÍ)xF¡½r¥s¥⡨úcÂ.³†|mLfô†”Yy©FG²j~Ä×ítÿ”ËþHÇÚ¸?ä²ÿ„•jgñYtz‡â(]ýÖµòOÑF§¬V`CðÖÉê?ªžŒçó܈¨¢Õ‚_V°³÷DÓi0Þ ß–µAê?Ê>E䟚Ò>KúhÔ^(ÔõŠ5=ÝxÃ÷^ý׎(ÿx¥fGá£CÙþÔõ³ªÃå£Ö§³âæ§³áÉQsî?4ß§âþµt<ùüPƒ¢+õM~×OùL¿äŒ}«ƒþK/ùO×è­ߩ—Úšý®Ÿò™ÉûWü–_òž#¯ÚÆTJ±Ÿ³šÖÙaöZyÔé‡Ù¤öR>Í%jt‘öbhc°Ãìâc²ÃìÒ{)e•¢T}„Ócö^#¯ÚÆT1P׿ìjm )}–ž}…´gg§eOÙÌ54³³ß´1VâŸf1et>ÎÆ™æ”¿e• P}6ŽË,kö^#­"”[5â?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¥Qâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¼GûEGˆÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?ÚñíZ£Äµâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxöšþ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Ú•Gˆÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?ÞÄx÷¶#ĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkÄ´Eàù¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úb£Äµâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíx÷±#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿjU#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkÄ´Txö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þ׈ÿkĵâ?Úñíxö¼Gû^#ý¯þÓÓ%>uÿÙjung-jung-2.1.1/jung-samples/src/main/resources/images/russia.gif000066400000000000000000000015301276402340000250100ustar00rootroot00000000000000GIF89a÷üþüüüüP4¨À|  D€Ä>£9V°¢CC°æõwf¨¢S!çw$Ðìýœ£8$¤7Éä¶¥KõwfE÷wP$ð¤Õöw$ÿ¤ÿÿÿrærìNýõwç$xÿÿ&õÿw²£õwXµKÐæÈ°õ,wEPx¤$¤¦Ð0°­÷wØÀ¢,Ð@A9õwÿ lÿ¥[ÿçÿw÷wðÐÕöwÿÐÿÿÿkæõwXxõw²öæõwwçwìýHP£È(çwx¡l[çw<ùPÿÿ÷ÿwÿÈÕ)öçwwÿhÐÿ£°ÿÿjd/õèwwàp¤çw®KP¤ÐsPrNl„¤¤d˜/èçww4¥§N³<å<¥ÿõNÿwø¦hУ°0­|€Ð£û°Oo5çws¯sG$iø¦a.ø¦i!ù,5H° Áƒ(\Ȱ¡Ã‡#JŒ( ¢Å‹3jÜȱ#Ç CŠI²¤É“(O"\ɲ`@;jung-jung-2.1.1/jung-samples/src/main/resources/images/spain.gif000066400000000000000000000015411276402340000246160ustar00rootroot00000000000000GIF89a÷‚‚„„üþÄÂÄü„‚„‚„üÀ|  D€Ä>£9V°¢CC°æõwf¨¢S!çw"Ðìýœ£8"¤7Éä¶¥KõwfE÷wP"ð¤Õöw$ÿ¤ÿÿÿrærìNýõwè"xÿÿ$õÿw²£õwXµKÐæÈ°õ,wEPx¤$¤¦Ð0°­÷wØÀ¢,Ð@A9õwÿ lÿ¥[ÿçÿw÷wðÐÕöwÿÐÿÿÿkæõwXxõw²öæõwwçwìýHP£È(çwx¡l[çw<ùPÿÿ÷ÿwÿÈÕ)öçwwÿhÐÿ£°ÿÿjd/õèwwàp¤çw®KP¤ÐsPrNl„¤¤d˜/èçww4¥§N³<æ<¥ÿõNÿwø¦hУ°0­z€Ð£û°Oo5çwa¯iG"nø¦.iø¦c!ù,>H° Áƒ4À°¡Ã‡#JœØ€Å‹3b`à€ HPdÆ 0ÉÒ$Å—0c2TH“f@;jung-jung-2.1.1/jung-samples/src/main/resources/images/topicapple.gif000066400000000000000000000027041276402340000256460ustar00rootroot00000000000000GIF89a4@Äbbbòóô‰ŠŠÛÛÛýýýÉÉɤ¤¤ãããÔÔÔÁÁÁµµµêêì»»»õõöåæçíîïçèéààáïðòÞÞßùùùÎÎÏ÷÷÷ÇÇÇ××ׯ¯¯ÑÑÑðððøøøÌÌÌ!ù,4@@ÿà'Ždižæ!T•€¾p, ±@Ç%$ÜÀp!Æ€ H " ¨(@£A!¦…Ó@,‰šhoßË…‚=d¹Èd2è2&Ogil y{}€ ( – …:{|~€žŸ£¥ À ÂÄ (Ñ‚ºÕ¡¤¾ÀÛÂÃÅÅ¥†/ ë럟젽¥ÁÄá?û']W<Øà‚ŸÁ8sfÀÁÁ³ÈGŽÅ q0Âys¡Ã…2ÜÑä§Ï?ÿø#p ‘£G8Hp3§ÃA–2áʵ ÔaNPà BKh$4è@¬eŽZ¸ñÂ6›‚Ä^|0Óº’rI½Fõƒnݾ©eA@¨«fÍûP¢F•õeö¬7q[ €¿sphÀ¡C†IÀ0E@Œ˜ xÈyA ØÐ¹³€ Wd]s ôC 8 @À+€ ®Oxç60#h` oDÜÒ@z_ Œt˜NÝãÅpÁØF‹œ "º!®È4ˆÙ}ã–ïk.øóDÏ­M¹ÒŸ'Ñ*ÀK41±1“ïa€xåaÿøE³ËOÍ•0Nx94ÓLqP@'ä´à-"IõÎ(¡°ŒPìa”…iÐV¿áƒèâI<ÙÌcA,€‚c,‚”P€JE}"O#æŒ7 P `Æx W`‰b'¼ÈcÕ_ß$À£™%D@\XKˆÒôt6O¦¥–Z`0Z ™QãI'€Ö%JŽtމg`ãr¢ <°“—^NúU¢î÷ƒD@Š:ñøÔâ(àA€PŠt@¹}u E¬¥9°„‰!®ûh¹€Æ°2dÖÀ¶ž1%²2è™&@­ÿ +- à^w- ^¹µeßþC¸]{§h0Àá)Ôn ˜€€L`àÀX`û8à@Tgü4î= ˜‘9dÐî *pEF[§E[¸Çt0ÛH~L%PÀÃDoô| }¶€!š~@Až¤¡Fw»—²Ý¡ÓNѤ§€ UÚðˆ>Hà{sHbt&÷Õ8 'ÄuÁ%/=ÂÁØ-Ó±À­µî(R n7°â+gÝÆ °A‡2Ò'˜¼xðSkBYPÔQ¯Ä„!¨Ó&P…5^Øl@Y( ²G…Ž$(PÉÿjyDE ŽT±ÕÖ™c ‡bk8\NXo2–^tÂ&Ú¯$¸Maç3QÌI3ÞÇ$޹³A=žÙ-!A„%$¶Ÿ>gUvƒ@.a õyrxÔlúdUå—w2À<õ’™#,0—]òt#îÜ#Ó ?øhUº€c–ñø¯IÙüît'À½C—4â&Ô -ÄSzP‚*j‚Ô]è¾:‘ QM …]Ðå†Ä¡¶ñ¤oÀЀÏxcÄ#/ Ô_˜è #2ŒÔ£¦¸ŽAEÐR—â—ò!d sÁá5"e9‚‚g)ùDƒ(„(c}`›Bf ¯šÀ0‚¼Ê5 ¨MeòtÀLÀm½) Ëõ­;Polonus jung-jung-2.1.1/jung-samples/src/main/resources/images/topicgamespcgames.gif000066400000000000000000000041711276402340000272010ustar00rootroot00000000000000GIF89a<NÕÿ&klnµ¶¸j†¹\s›¡ÙOa‚#(1,2=DSk;GY4úõáumR-(QOJ šŠo·¦‰åÏ®ÒÑÐÀÀÀýýýûûûøøøóóóîîîèèèÅÅŦ¦¦!ù",<N@ÿ@‘pH,G£Ò)”B™FƨtJ5*+©æQ,Óp*¨´Ên•!UƒqyXî‡Å$P>ŸJnm$&q }‚“D$[  ¤ |Nj”&*c\µc   ÃÆ e y ž›*!k‰¡« Ÿ æç §L» ¾éÑw¤Œ` *)ž`àƒ¯Þ W a»…¼z™› •‚`Ih€ <Üc&Dš!„P„ ÂAû0¤‹POÆa8;3–óA„ÿ-c8pàãà$Ü$1q‚Â@–\6³©ê>¢ª"ÄÑI)U1ÂÁs¢}I +6XbãŠPB!µ 6@xÁ¦>&É¥”r¥ ,¬‚ ˜L™‘žd"EÔ6XЙF&˜4w²` —€f£ìT`ÁŽ1 `p°¯À?’¨èkÀÀ‚ríb Lä>¥c0ÏT4·¥]_!ú¶þ Ÿ £PÑ©#LÈÐ!D^ñ¨Ë7e½Ùw_ÀbçÕÄ£‰`‚JPipW<÷ÅãËDÖ]wFÅàt›0Pc 5D p RQ$Ñÿ-MµI”hâ‰%Þ£&Ü•!d¯¬1Ù$”À (8UWTà '˜@ 2šÆF –0ÁQÐÊ Ai¤ )90 …ñ4ÀÕQEN9!†¤Ð@^Œ“2áE&f `†D@mø¢@O¼YÅ,!¦Ø*üÄAöéç˜W¤‚\@Ðü@"É¢žÐãwØQè&¨@A@Áa:ô¦‹qìu Ý-†Á¢F¦i–p@Ý)õlÂÀfœ•H“{ØS¡o‘€% ˆ5ÒÀ× 9 tp18 ƒ8ÀA€%"8°¡²Q˜àÿ@ìs^t”À;ðý"ÊNÆ4D—Ààáo¹[)Ä д@qÇÑ7ϮՑÂ%‚ b÷€v-&©)˜% (`GBÌ!w_qô¾#-~y*È ußýâÚ(ß .%:‚JŒñI< éâÐÐE´àt5]4s3Ÿœ¢Ø|ðÁTáH l¢`Ð ½g4( ZT"¾ÅØ&Áhp!I‰&å!!öÌÁD»ÒSÑEc߯SN:u`A‹Ba؇xèá¡!P5p¥"‹¬!ßÄ”mA»·´¸¢0"uk‡6¢0PˆTm2»‹Äø]—#ª!6F£Míè¨F¥ÿÕV\uå#CšJ„ ;ÊFÖæ¾ÿnÙ#ä”Áˬ—š¼I„À*°5AwßÑêÖ¥Ó£Tˆ]\ fæ$`ÆoÒûN‚¦)ã(³"˜„ü¢# ¸égzQ#0ðK &|"è_¤’€: D´2¾Ás*@"à)||'­C` $€ ê”ÂPINbAº¤ %Ôb"p¨ªÇ‚‡[I '%« ¹É‚"(L <€Æý fH/È„-hFÈP`–(qe%Ò‰ê¤ßÄ(ðÖ¦ò™½XÀÀL¡ॷ´oJ'PM-4yTÿ+À¬"Њ/Ši!¸’ÍÌS¬bЦD¤F)¼e‡ 8à7¥™Œ%d2ÙxÂŒñˆbð‘Y«D0¹ü($í¨X˜< ó! 'ÕÊÉF\¢qýD*à]$¥€$Ì`š|ÈYî­o ÐÅZé%æÐð/׎d±…ZüÀļv!‘{E[8 º:*Æ;›(É[Š` \ÀŒÈ<ꄈs¬Œ:È©IMHᑸ‹6û%‚ @`a0@=:E^ŒpM+DQ½m$ÜñN¥é¦Àqõðˆ*å#Eƒf(/TFŽz„Mcÿ’²æåŒ2žÄð;`€¤@€†l­8³—ÐXJ7zÌìXE'ö1µª=!ÙÜÃqL†S…tí= t£Uµ0 ƒ@=ñÿ8UðÀHŠÓp´ghê9¢ãÒD2-f¢H*Ú¤ö>$JuñÖ¬ªª‹·6¤k_«NÒ°—<@ÉÆ8 ’¬`…&ì U  = qV6ŠjD'Á8$HB"8š~-1ÖÖƒ²ˆHl°vv"!œ-mkó+Œ`A—•¸dmç@GÝê13ß“oø[à·¾·È(%psÉ•¸«}¦i·ßŒ¶†áŽ(4 –á–‚¸–(‰ `D0| 9Îð$ºµ@„åЛ¹7R¯¸/t‰’2ã)Æ º±ý0!”Ë%s»”ÂRTà©(©g¬ï0 “;x+ýÝŸ„;·#¨H%tÓnUú¡ºïN,¯S˜P;©Xøe( ît§KS"Æ'ÈÑŽ˜?Úå.^qE†äã)Ř)PвŽgš ;jung-jung-2.1.1/jung-samples/src/main/resources/images/topicgraphics3.gif000066400000000000000000000032031276402340000264230ustar00rootroot00000000000000GIF89aE>„&.,[^^vvt––”Öæ$âV4¦¦¤†ÊL²²´ÞælvVœáâު΄6BD¾¼ÊÒ¤Šr¬RF”ÞštJRTŠŠ„ÒÎÄîîìÚÚØÉÉÉfjlžŽ¼?JKÎÑÕÚÖ̲ªÄÿÿÿ!ù,E>@þàÒq$wKº\êb½©å~tmßx®çç™5“à$@,„“rÓh:ŸO ’2 Ånû©œ:Ê` “˜ÍäÌ1¨„º€ö‘¨× ø|ƒ!õµ9^qMK‡ˆoNq y|}$39'dDlMR«¬­ ‘³ %%|˜D™|À$¾.ƒV¨ÎÏÎÒÖ×Ö %˜áÛ}™˜™fÃ;2ÆÐòÐ$Ú üÛá Ôp‹Ã€2®„bÇ¥áàQ8sFMštž@]xÁEÐ…@vèÑ¢¤Œ'þ]ÈxÁ’eŒ¢ây"À†!cÈPpJHEPLPÁ¡Ãˆsæbîp𥠜8múüù†‘#ìÜÑã …2W8ºAꂃB@ÓªŠP”[·´àÂ¥«œ],~Æ·¯ß¿•8HÀV@ƒLÞæb"‘Ø.R( û›Ȭ|Âle€ Cƒ¾RA…}ùôi蓜pB𠡯z¹´ÜÍ;Ì&pðàACìqæ‰ìÉA–Ü2£·Kј/ãÒ?ÐüÒƒdÉïàÛÌ> E´j]©_jÁï…äitÿ!fLÎûk’PM¿Ê‚cbŧÝRñRÆNjäþ·„ZªLpt$ ‡Q$`§STÐÀM8¥ááS1TT0IVZI¨Ça}€vËX6`ÈAK<…Ä!# @‚ÇVxeÄS8‚)¥ ȼ(à 5Q_N§ä§ŸÎqàö_XÓ:ôR¡rº¨°±ßˆt ZYÛm$ÓvNFOV‹˜^)<ˆÉl_ÒnMÓ`qÍX‘!š­ˆlÂyžÀb"|L†Ô,uö=”t bŠg((†cxÁ+BAtþ7êíF˜(aŠ*fÅbñp:­Œ4rhcÞaàˆZ ަÜêÜ´KÆ~HÞW5Á³T^‡\Xrð•ïR ZN"R|PPÀAÉ' $šX5’…8˜`z:Õ7µTÉÁŠê— Е…úéÑý&T–ùþžH‘ߥÄ" ûµ"úÃ÷hq¦ø‘_Ÿcøe²Â|äSºr-`Ë  Ó™~@Ö8 _óÓ¡æÄB¿\¢4×k“¥LTëy dÄ¢¨‚y"Q@ "ªªÂÐð.¹À”9´”Œbë0Ä€‰*a‰*fÊR´„b)ÆàÂÄ1þ€†ÃäøNi¿ˆ°R­g¨ÊÀªà‚Ká0—9À»„LF ïúMg~ª“ÈsxT c]ð°ŠU¹àÇ?"ì$Ö g€ÈljƉ€8Pà ?xÍZ ´2`IÃ}’ÈÊf  6«2lš5V=O¿“‰ þô™ &*‚ÐÀª–‰X5k6Qs¤õ¯´Å(i£Î HpSÞ¼DŽ Š”¯æ ŒbÓáÀšÄÂè#ä ËWbNW¢ó‘ˆd R‚{)#_·Å9ïé|~ŠaeB FÀ¸ô!Ñd¢0s°8ô¡îJ‰wXŸ;jung-jung-2.1.1/jung-samples/src/main/resources/images/topichumor.gif000066400000000000000000000024221276402340000256740ustar00rootroot00000000000000GIF89a:'¥; IR%d<<|9)™U9ªgJÁ‡kµxWÕ¤„ß´¢®‚d⻪ܫ“¥¥¦¸¸¸ÆÆÇééée-"É”pÒœvhhh†††ÙÙÙN <—G/GGGÁ{Q;;ñÕbÆz[% 0¡rÇ8¯°L0prÏl!{xͰ"Å"‹¶äžÅŠ¡:$a„…è}z`Œb°…:t˜WÖÏ÷Õ[i×^Ë^¤Ëë¹à Cˆ Œ–ÜB+” P|ÏDó [8`G |$…)ÈÐMºþ$ÇŸ# Ã8"ˆðB(¾@‚3°€3HÀ8*”XÎa‰åÁŽ;Re•gõ[m±£ZHB¥;²5Y`rѵ@BöØ×@4eœ“O>&×P¸¡uª< ‡±qi”f€¦È‹b#0¦f—q @]pBâˆè·åœ!@d€x%š˜"Š(âˆ%B"T?oÐçbQåU/džù '‚µ#!Y`e¸ãžÖ²FF0º»÷‚Öèµ_;ðÁ3©¼:A;jung-jung-2.1.1/jung-samples/src/main/resources/images/topicinputdevices.gif000066400000000000000000000025741276402340000272540ustar00rootroot00000000000000GIF89aFDÕvtp}y^][éçã¶µ³­¬ªñðîýøðäáܦ¤¡lie‘ŽŠeZL71*ˆ…‚˜•’ÅÃÁÛÙ×ÒÑÐÊÉÈ KHFŸœš¾¼»RQQìëëÿÿÿüüüúúú÷÷÷ôôô!ù,FDÿ@pH,Ȥr‰Ül:a™L$sËíj6if0I,Ç㑘 6Þ¸ü9ED.‹@^ø r„LtT|i k€…—Fa |‘ ˜¬ ¢~~»€ Z­„› ¶¡¤€¥ k ·Ãr Ó¸‘ 䦾 ƒ×]Ùeµàá»÷f¨ ÂìL´%€‡¡Â4f¥v% ž`éßwN 9ä 0 @¡‚…U•Û¶@€ 62[°Ä ´9ç`B‡ÿ”I^I ÐíeL™\Р…¦Ä*@ F^ݱ@@Á£< „u¨ð V#nƒWÁàF Ô€`€ß¿ˆøù–ȆbC‘y­ ÏÖ-šüªí;‚)U…‹p0k ‚‚Œo1hð˜@„É}ÙªY !³aÄǺ‚Æk³mÖ§ûJpÀk×CèL- Z¨ 0LÈMÔƒŒ Pÿâ!' ë ¨‚DOçŒ} ÞÚt!›!û ZÀ‚ PiàÞ“þ¦?5!6ûºâÝsY}TÅ׿G³$³cjX@`¾¡" ÇÔâÿUA„Àˆ#*€„ D!zÕUAv |ˆŒœá@ âŠºhÆ.}¢q'’U9­ñXœxr†{8ÉslcUneÈAR%B/kØøˆ õWŸ\¥àKV1C\#9kHÈV¢©¦~`ˆ1€D Á ÌiC‚J¸À+~áDQe ivHPh8.'7XÕ(Np *Õ!"'U™ êÀ.i6 ×£KZšã=ˆNp„¹Z\Ú‘È=8B€§Jå§«°š ì{±Ç~"sˉX\°N´DLKŽpÁ¦$P(´Çòÿ-…õUY}L°-·ÒNa)UáVÉÕ“-  l®Ñ>Ê!4e ÉXJñ¿jC–3WrSÊýN€î§-ögÁاí8ÁžºöXèÎŽ,À ~Ý¡-·}R! -z¨ñ\_XË94·špò#‰Dö§Z±\ «lb £q«F0öd…®¬+.¯÷M^HvZ œø4÷~ÃÅâbOv'¸Û#|¼}(ž‘Íwœóòhïع„@™ä‚âr† ÷·Þæ8ðT䀯b£sk% q.™ù¦ Täê–°0î ˆ„Cuû'­¥½/&³9cïE([ xzìŦ+¢¦9QÒ£ ø6§ÅŽAŸñײ7¨y<“8ÂG«ôíIãw¯Šc£§‡;G™È@þÒÕ£_]†J|ñ€ñB¶¡MxÉZàÑgXjPnË€tW1€ùi’2À;Ø„-EÁàÑ‚;jung-jung-2.1.1/jung-samples/src/main/resources/images/topiclinux.gif000066400000000000000000000037041276402340000257050ustar00rootroot00000000000000GIF89a<Fƪ¨ÜUMy8JIi{{±ØØÙ»·Ïéé霜¡''&''MÈÈËUToiit:.<§§¨¹¸¸667^\u6( GD=ŒŒŽxxz”””„„†'µ¨’ yפĤ¼–°zÙ¸ñ¼èËèÄ®šh»ƒ 㪠çÍT\F ;;s…eÑ• Õœ sO …Y ºŽD•gwkn¦‹eûûùnYW¬—§Z=Æ‹ ¸•hJ0¡k€hLŽv„I9I!þMade with GIMP!ù @,<F@þ€@‚ƒ„…††‘’“”‡‹ ‹‹› •¬•,-(#$$)$%$",›+¤ ©­Æƒ+ 5Í5ªÎÌÒš½ ¥ÄªÇ’¡ÌÑÎÍÒã㘘›+ÂÚ«Æ‹áõÏåâøÍüýŒÀ2$–êÝ #*,¡°!¯E ÆÙ«Ç C,16!bÆ… ¨2ˆ€Á}z‘€À·Qº«C†ò.” 'î5#P¨°Ùá Ú tÃCB† 7rlÁ!tì §7„°®}v† HÏg ˜Yáó”þTñL`T`ý a!M +\ÉNš˜çQÈ­u“ðÓ÷+ Öc£ZA¯FÖŠ `úР¬•&ÚØ)¬++6A€ $°SÞ™”4°4*Rá†{’°uZEœ&~Jˆ5`@cÔ»~ýN‡“ˆ½òä¯8?LÓªJp+~ ‹N6h€Ü ÓºW”HE¨FƒÖqApŠ È€<Ï9"Ÿt ‹aý§Q•%Ä&¶k½°3AÞè…ÜÈoîþµƒED ŒƒÒâ¨É†Ð%2È ¶˜óläû³d$3ÊLvK!€O[Þ¡EsqË]0pÀŸa& TcOJ‰ËXk€Ôa‰K$™í  0¨@Ö6±ƒZ6#-èAÔ&öÌË`k4p€ 2‚–È„ ƒÀ Ð xâaD¡@€°H32 €ÇÈÉÁ ¦d(K@ @à?è˜ÄŠ‚Ÿûìg? }(D÷%±Š Dd»$DɨB3úè¢öqÙËR&°ÜÀ¤'-PRöT -h4&« RF ˜]+X© Káó•Ÿ9hOIÅûLT°Ôh%cêTŒJÀJ¼ChOÝÆQE*4¡5Hr`-D ;jung-jung-2.1.1/jung-samples/src/main/resources/images/topicmusic.gif000066400000000000000000000022131276402340000256600ustar00rootroot00000000000000GIF89a<1¥òëäêÛÇű¦È‘A{U+cE!á͵´€6R@*¥w4Ý¿—ׯvÒ¡[“g1…abR8þþü¡‘Œ¸Ÿ–655uqqƒƒcbbg85®žŸB$".!%~E?›}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!þMade with GIMP!ù ,<1@þ@pHŤrÉl …bÁ8 B¡€@<¤sL6$€…šzph¹ˆ-‰ €AÁXedy„yTnaK‘•–•€c_¨ª«ª¨¢_³’ŸB¤¥¦¿§¨¨°ÊË˺ÐÑczR  ÙnÇÒÞ „T× dz ŒßexPS×X[[_~žNuX &BêFÄ… 5 ©f`U°pÃp`øMÁ¬ˆ¨PAœeñ"³—£`Œ—€ ^Eªõë%þ3(gEK—4½Œ¢fÀ€YŒzy@2© =®YÖN—.‚Z]¢pšk ¨Ã·eÀ–#ô;$@Â#|®%ضv‹ƒj¹CW`QT§ ˆ-“爞([±¤Û²Øˆ€Æêpº0AÄ 0TÀ`!ÐCƒ¦”óPDž½0{Ð% „äú6AbVªrú*ÅrÖ«Q¾”½3a®. , @€!v$d­´Ê`k„oЧôàBy¤››NLf>8‡f`B[¾xþJÉÿ½² òÑ%Ôw»I€¼‘ €ºPÏ5Qu„െ^{þIå†c@± $•Q´}ñˆd‰ˆQH5U °¨„câôqÍ ¼³Em0ˆƒPˆ‰kyÇQqô1$]weµ!êÄá4J Á! üacZ£×ppA@ø €å À€D-Ç7 ᵕirÑ øæ]©M@Á<9 jR8õÔ6?0pôYhE0†á5yÀùÔš8º„8™¢“&–ª•¤ZK¡Ö©=P‘Å$£ÔÂÁ)\TÀ™`b±) Gˆ³X}íWÑÎ^įqP´fÄáô«ü¶JFÈ™‚ßN™Êa’àúª.PʮؗOî!÷0°á&ÍüšÛ.q¾¥Â w°ì:‘³°¼ˆ³´GÌDºâ K0)åÔÝ2B@q¾9€*­¾ïí ‰¶N@²o¾aöÉàöçŸrdºD8`‹$æ²ÄßÐɤ¬\IIH`»È·_Ñ0Ð1‹<ûl1Óÿ5gc†lËC;jung-jung-2.1.1/jung-samples/src/main/resources/images/topicos.gif000066400000000000000000000031021276402340000251570ustar00rootroot00000000000000GIF89aB1Äÿa`cZVcb^n¼»ÀDBMfcsli{¤£©°¯µ|zˆPOWЉ𙠒‘˜&10:rq{‚‚‡jjmzz|ßßàÕÕÖÉÉÊÀÀÀýýýûûûúúú÷÷÷ôôôïïïèèè!ù,B1@ÿà%Ždižh¹¥lë’X7Tµø‘J üÀ_i ¢Bép4˜T1 "pˆ,Âðt:Æ"ñ Îè4ºÀ.Ûìßd1*+žçÆ3h &E    7gj ‘ ›…? t‹M\ 7 =s© H‰pA?qǦ ©¼u­+,ÙÙ%5\ À ‹¨ âÏssJxQ/ü'6làÀá‹Á/jȰ¯ŸÃ /TÀAÁÃ1$©´H‰’|ù>láÀ ÿ\P¨€š4ʸi3é™'~¼è!kÌEØa A8H€êA€—CŒ&‰ GÊøPvê™*jMöT@°„6”:*  €Á‚I"ª´È"°nr U€©KíêØ£ÀaD·±*X@9 ñb&÷™P'œxøxã&€K¨èԂЃž˜Vy2ŒD‘a` Å(¯h<’ê×**÷˜8Ù zµï fäRÁ›"ûæ3ƒ‚ÆçI% s(‰/ù"*çÖmbƒ +ß=}™Œ¦™.ŽÞÒ„Cï‘E`ð@EáÎÃ4"Xtyò4è†L¨@ ATà…ÿ¼qÀEcW$aR ð•ßü™AÞ œ%SP= 5 ÎØ‘`ðaÒN%0ÀLñàɇ àˆ$”¬QNìUHˆ¢•2¡¥òI5R1‰uÀ!:´²áuLÀH@Õ(>ø L*LSXUüDË d¥óÉæH€“µåqÄ„^Lm0“@m˜EÀÀ y\€Á+(F£he^ôSc}R‹'pnÇ¥40‰Që\´ÀR7Ü“ TÐ^ì¶A@°TÑ:éFÑe sÄIí½§Ü+¯ã–eeF3À8óÐóŒ'áaÿvÛùóJA±PPl‹…ûdØñ–\¶üÄ ¡‹îvŠQ Ðw÷P‘bB­d#‡Ø‘C ü ×ö›mk‡RP08,` K1ÁŸë¸•Šš4<á°oPÙHÅk%’ T@à•*D€°ÊµK7*0Ɇ &Cù±˜5#ÐÁÛÁ,‚ÕÒ†xÎä’Ôȉ5^,XB¼ªT‹Ÿ "‰K+ÈçžB´Sã™ uSÅF:w+ÐiöÎPá—.lóÃ/³ÖS“fk]2ãg1…Še ýÒŸ…‘‹x4áKÙ`u€ÿ™äe1`”KtÚhG$|눉ê¢l¢ÈðULOý™N¨aÕ@8ó^Æ‚„'|‚Õζ&#Hî¹?0{(£è D‘eŸIÁŸ1[3[H,à€Oʃ#NEO‰¬C}æ(à£ö>`îkèP ›À\tÁq,¯ XËÎ,wžMMë; DV´â§Tü) üKÔ’>±Ÿ„ÉêLÒEžô¦±!j#B_ÞñŽ”4 ›€²îoôÑ0ĈAÂeÌS|Ç­’a©¥P~ñ¬2÷1Béc ‰bÚ80ƒ±^8ê'‚JŠRv§§"vîE¢Š4(¿ÉÇQLS~‚›o¡D#é  ³L±•ò¸ˆ?‡0A-U¦ip#«CB~«‘1H,¸°Ž'•i–i ‡ˆ+@•‰*Ϻ¡ˆ†‚l”J «ÒΡ^óª˜ÏÃ/%šTð€õ ŠŠö‚y}#R}ĘªBŽ\Ü"cŒU~"ªkùëPÜ’Ešfe™X]Lc©üDµ*bL‘ †\ „²ƒ,à&7Ô|¶¬ÉR*2…Lg:é„ì-;jung-jung-2.1.1/jung-samples/src/main/resources/images/topicprivacy.gif000066400000000000000000000025751276402340000262300ustar00rootroot00000000000000GIF89aG5Äÿ"&-25:BEOX[ktw‘WadaknGOQx€‚áââÖ××·¸¸ ¡¡ÀÀÀÇÇÇ!ù,G5@ÿà#ŽdIBN Gá0¦¹4†Š¸p¬ïüx$ƒ p €HtŠtº ‹Þ®‘ x ‡ë”J`q} ÌMhÂÖ6VN–ç8„Å7‚moRPM^kD]dAMN7^g‡ 9# _  ƒvwy]¡¢AR¯`a „†–x{^¡­£u¸P‹ž- ½Y% ( cÁŸµµÔKo š"Ï Ã£6€ «ßZZϭ؉kE^´™›êñ`‰Ú¢µ 0ò³ï¤töx4h†…›#¦tqãÆO—€%]fÝŠ£ áýR‘m ÄŠÿhˆˆ#Œ#1à‚&C”¢Ä¡³ ê_…(L@d䡹õD€¦b^kDD.iAŒ´¾ˆÂ!ï€0î’Á„ÌÀ ÛÊNêdjÉÉLbBÓ¥°§©j½ÏB1œ0Æ”AÆX±Z2¸`’%¦$mŠ!;jung-jung-2.1.1/jung-samples/src/main/resources/images/topicsample.gif000066400000000000000000000015011276402340000260200ustar00rootroot00000000000000GIF89a ÷€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿ3f™Ìÿ3333f3™3Ì3ÿff3fff™fÌfÿ™™3™f™™™Ì™ÿÌÌ3ÌfÌ™ÌÌÌÿÿÿ3ÿfÿ™ÿÌÿÿ3333f3™3Ì3ÿ3333333f33™33Ì33ÿ3f3f33ff3f™3fÌ3fÿ3™3™33™f3™™3™Ì3™ÿ3Ì3Ì33Ìf3Ì™3ÌÌ3Ìÿ3ÿ3ÿ33ÿf3ÿ™3ÿÌ3ÿÿff3fff™fÌfÿf3f33f3ff3™f3Ìf3ÿffff3fffff™ffÌffÿf™f™3f™ff™™f™Ìf™ÿfÌfÌ3fÌffÌ™fÌÌfÌÿfÿfÿ3fÿffÿ™fÿÌfÿÿ™™3™f™™™Ì™ÿ™3™33™3f™3™™3Ì™3ÿ™f™f3™ff™f™™fÌ™fÿ™™™™3™™f™™™™™Ì™™ÿ™Ì™Ì3™Ìf™Ì™™ÌÌ™Ìÿ™ÿ™ÿ3™ÿf™ÿ™™ÿÌ™ÿÿÌÌ3ÌfÌ™ÌÌÌÿÌ3Ì33Ì3fÌ3™Ì3ÌÌ3ÿÌfÌf3ÌffÌf™ÌfÌÌfÿ̙̙3Ì™fÌ™™Ì™ÌÌ™ÿÌÌÌÌ3ÌÌfÌÌ™ÌÌÌÌÌÿÌÿÌÿ3ÌÿfÌÿ™ÌÿÌÌÿÿÿÿ3ÿfÿ™ÿÌÿÿÿ3ÿ33ÿ3fÿ3™ÿ3Ìÿ3ÿÿfÿf3ÿffÿf™ÿfÌÿfÿÿ™ÿ™3ÿ™fÿ™™ÿ™Ìÿ™ÿÿÌÿÌ3ÿÌfÿÌ™ÿÌÌÿÌÿÿÿÿÿ3ÿÿfÿÿ™ÿÿÌÿÿÿ, &Hp €T˜Ð Àƒ!6$aD‡2TX°£Ç;jung-jung-2.1.1/jung-samples/src/main/resources/images/topicsample2.gif000066400000000000000000000014731276402340000261120ustar00rootroot00000000000000GIF89a ÷€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿ3f™Ìÿ3333f3™3Ì3ÿff3fff™fÌfÿ™™3™f™™™Ì™ÿÌÌ3ÌfÌ™ÌÌÌÿÿÿ3ÿfÿ™ÿÌÿÿ3333f3™3Ì3ÿ3333333f33™33Ì33ÿ3f3f33ff3f™3fÌ3fÿ3™3™33™f3™™3™Ì3™ÿ3Ì3Ì33Ìf3Ì™3ÌÌ3Ìÿ3ÿ3ÿ33ÿf3ÿ™3ÿÌ3ÿÿff3fff™fÌfÿf3f33f3ff3™f3Ìf3ÿffff3fffff™ffÌffÿf™f™3f™ff™™f™Ìf™ÿfÌfÌ3fÌffÌ™fÌÌfÌÿfÿfÿ3fÿffÿ™fÿÌfÿÿ™™3™f™™™Ì™ÿ™3™33™3f™3™™3Ì™3ÿ™f™f3™ff™f™™fÌ™fÿ™™™™3™™f™™™™™Ì™™ÿ™Ì™Ì3™Ìf™Ì™™ÌÌ™Ìÿ™ÿ™ÿ3™ÿf™ÿ™™ÿÌ™ÿÿÌÌ3ÌfÌ™ÌÌÌÿÌ3Ì33Ì3fÌ3™Ì3ÌÌ3ÿÌfÌf3ÌffÌf™ÌfÌÌfÿ̙̙3Ì™fÌ™™Ì™ÌÌ™ÿÌÌÌÌ3ÌÌfÌÌ™ÌÌÌÌÌÿÌÿÌÿ3ÌÿfÌÿ™ÌÿÌÌÿÿÿÿ3ÿfÿ™ÿÌÿÿÿ3ÿ33ÿ3fÿ3™ÿ3Ìÿ3ÿÿfÿf3ÿffÿf™ÿfÌÿfÿÿ™ÿ™3ÿ™fÿ™™ÿ™Ìÿ™ÿÿÌÿÌ3ÿÌfÿÌ™ÿÌÌÿÌÿÿÿÿÿ3ÿÿfÿÿ™ÿÿÌÿÿÿ!ù, ÿ H° Áƒ¢@¡¡Á… #J,;jung-jung-2.1.1/jung-samples/src/main/resources/images/topicwireless.gif000066400000000000000000000020251276402340000263760ustar00rootroot00000000000000GIF89a%M³ÿÄ ÀcnÃ_ÀÀÀòïyîê´Ú°ëêçÝÛ×ÄÃÁš[œ‰¤MH“ ¯¨¨!ù,%M@ÿpÈI«½¸‚::`Q8I‚8b¡Z[rß1t ØM,·>›NTë: !h˜DÏÂèƒv»%”d« åàÉ3CàX4Þ…ã#æ­ ÁÂáP7ö|   &6  Œ$ gg $%— Io s‰b,Œ%˜_—oˆu†Ožu³.¸¸¶tS4+.G¸OºA!/¾`4'—©&É@EÔUmVÝ“£êÏ¿^ÿò0˜¦ ³~È[9Æ—/S˜“DE®'O¶@vaа"·¢Šóˆ¬Ô€þqêGc@®)[¥Æ³+ž¬«”™ªF” `Ô#J¹&óZãçKlIð³m¹£Ó Ä@PjA&6,ÅÛS¬ÀP 8*EBíʘ< w¯š´H+eF±€‘– 18„_qó9Z2räA}å”®@ª^)FŒôUZÅOu'Ó¥¡á³/6a/Ù$­7Nz$ˆ$ˆJ†,FDÊ©7%âÌ"çózõ’ߟè-7ŽJí $„féfI¾…0®pfI«W¶ULÿL3ÑÔMNy Ø~c¸ŒUðXµƒ5V,” SwÙˆT3äà ÙTÅGIÙ¥&zõƒ æìµ•…%0%3Õ”Œ>ÝPÅU:ð :*£â3ª0•@ƒ9rÕƒ…’‘ b!ÀT:î'0ÁЂIá““3˜(Bá°Ã ]uYR^a¾ùTÄ*U0ÄKœ`FeÕ³—c÷$qÖÄzõ‰RJž¡\©ÉÑ›+Ð ;‹˜#–EÈ ¤|Êfö Û¨¨7’q(äF¢Øw:‰5ñHÆiÔ­z®Æ™e#-JÈ„ 7K¹IYÂ>ªZ֢΢Þ{Hbl¶–µ‘,°è@$‰ õ¼È*k ‰}¯˜±Èh4¢Z!–õDû‘¡+F¤H†!›4AÂÊR@AI°—‰¼ÆU’ ¿ëPW0RŒíz[A϶‡}œà+ )ü®Ò«7ªyóG£9ñp!ëä7ÉñqÉhÕ;ÐÉ(J%–ª¨"°Ì˜81¶²àìMÀÓÞÑÐ\tHM„‡éìì ,w„öÄȬþ‡,[‹UØD;jung-jung-2.1.1/jung-samples/src/main/resources/images/topicx.gif000066400000000000000000000023501276402340000250110ustar00rootroot00000000000000GIF89a<=ÄÿíîíçèçÀÀÀ‹Œ†||z““’ŽŽ‡‡†‚‚ßßßÖÖÖÉÉÉ¿¿¿¶¶¶¯¯¯©©©¢¢¢›››ŠŠŠqqqcccSSSBBB!ù,<=@ÿ  BpŽbë¾p,·JÆS…á÷…ˆ°QÌŽ/£á X*† ),ÇÃáÐr¹ í P2ËÄ‘˜R©ec±Ïï ÁQ×*P %o„3 L B;c…— “E0 ‰«ªR˜- ©i‹ ƒ—£¨=½¥‚1 É€°±H ÔÕÖÔ n ª¬¬Ý¦¡ÏG LÙ¹/V“X’X’ noq ‰Ÿ¸»`AƒƒÚaÂçE€u"xBÐ ¹7—8hÁA'u6"³`@Å‹„ ÿ:*¦B9J ;0ó€Tœ¡±€Ö[*R‰6Ào«h•ðj Œ¾yªðê$Ê<6ƒùðe@—NwŠE@Ú7¤ª¨!ȹo, ]8åt§ ‚Rá~EöÒêÎ]XV˜P¤m‹Qz#)†Ö\Áƒ<õ‹äУËYêuyÄ€ò ˆp’P̺ &ìÒù©Ÿ^¾˜`ÉA;¸ë < :‚"¸Ü«3‡ŽÁ0°³1IÍláI4BHóàŒ-“ÊAÿœN´  @øÑÕ«)ŠžÝPð ‚E‹Zgr@ó.ª£çYp`Ä­ƒ®ûÔöHnW…eŠ %00˜%ôpŠsFºÚ XµÀEMsà*>°ÒLWq¡%Bætp_”¸éVm\H ‡.ÄɉÀtõCT2Özpý#!‰ ü÷ 0À$„žÏÔÆÄ€ ßyìE=Y´WÀ™±XqÐtŸIHmøÜƒ…=™EÐ@ÊÑÄŒ ƒíawÝ_È3áSÄIdz´Y×Å`Ä®ÆÌá@ÿ'Ì%Öq©–ÐE¶°^’‹™—ns„q« (†üùšªn °kGH•¨`:Oä„®X[îËß­hP/åZ0#µYŽ €qÄ…áº1×LÄ2@”ÚOäëÅ6p–e ¢H6[lq¨¥q¬e£œ2ÃÑË{ã£vç°K=ðÃ]5)E1$ŽeÐx-E¡`À)E£² ÏnEºCˆAéÓ‚&¨àeõ*ß´³çGy’m¸&Ì2f‚gaý 7µD€ Ï•‘#8&5M $]YŽœX Fao4Š7K¥XæÆ˜Ð—ö' . :ªX^ÔH²À‡Øòðe39¸Ç£¨{õã±/cTˆkÛ{{]Š¢/>bêBFqú„§'§Ô“¡; ÌQ•ìóù*@ɽS…¨ì¸ƒŽG _ÊL@ÁGœ"þ2ÉnÕz ¨`ù¢ ´¯ÀûðÃ?H;jung-jung-2.1.1/jung-samples/src/main/resources/images/united-states.gif000066400000000000000000000015551276402340000263020ustar00rootroot00000000000000GIF89a÷üþüüüP4¨À|  D€Ä>£9V°¢CC°æõwf¨¢S!çwÐìýœ£8¤7Éä¶¥KõwfE÷wPð¤Õöw$ÿ¤ÿÿÿrærìNýõwëxÿÿõÿw²£õwXµKÐæÈ°õ,wEPx¤$¤¦Ð0°­÷wØÀ¢,Ð@A9õwÿ lÿ¥[ÿçÿw÷wðÐÕöwÿÐÿÿÿkæõwXxõw²öæõwwçwìýHP£È(çwx¡l[çw<ùPÿÿ÷ÿwÿÈÕ)öçwwÿhÐÿ£°ÿÿjd/õèwwàp¤çw®KP¤ÐsPrNl„¤¤d˜/èçww4 ¥§N³<é<¥ÿõNÿwø¦hУ°0­t€Ð£û°Oo5çw.¯iGcø¦o¨øs¦!ù,JH° Áƒ(\(`€Ã‡H1€Å‹NœØ"ÄŠ1bÔH±£G‡ 7ŠÌÈPáɇ+c^|Is€L™5_ÞŒ™ó$B„;jung-jung-2.1.1/jung-samples/src/site/000077500000000000000000000000001276402340000175555ustar00rootroot00000000000000jung-jung-2.1.1/jung-samples/src/site/site.xml000066400000000000000000000004441276402340000212450ustar00rootroot00000000000000 ${project.name} jung-jung-2.1.1/jung-visualization/000077500000000000000000000000001276402340000172575ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/assembly.xml000066400000000000000000000005161276402340000216220ustar00rootroot00000000000000 dependencies tar.gz false / false runtime jung-jung-2.1.1/jung-visualization/pom.xml000066400000000000000000000023511276402340000205750ustar00rootroot00000000000000 4.0.0 net.sf.jung jung-parent 2.1.1 jung-visualization JUNG - Visualization Support Core visualization support for the JUNG project net.sf.jung jung-api ${project.version} net.sf.jung jung-algorithms ${project.version} net.sf.jung jung-graph-impl ${project.version} test junit junit test jung-jung-2.1.1/jung-visualization/src/000077500000000000000000000000001276402340000200465ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/000077500000000000000000000000001276402340000207725ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/000077500000000000000000000000001276402340000217135ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/000077500000000000000000000000001276402340000224705ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/000077500000000000000000000000001276402340000232505ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/000077500000000000000000000000001276402340000240265ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/000077500000000000000000000000001276402340000247715ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/000077500000000000000000000000001276402340000276725ustar00rootroot00000000000000BasicTransformer.java000066400000000000000000000121301276402340000337170ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualizationpackage edu.uci.ics.jung.visualization; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import edu.uci.ics.jung.visualization.transform.MutableAffineTransformer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer; import edu.uci.ics.jung.visualization.util.ChangeEventSupport; import edu.uci.ics.jung.visualization.util.DefaultChangeEventSupport; /** * A basic implementation of the MultiLayerTransformer interface that * provides two Layers: VIEW and LAYOUT. It also provides ChangeEventSupport * @author Tom Nelson - tomnelson@dev.java.net * */ public class BasicTransformer implements MultiLayerTransformer, ShapeTransformer, ChangeListener, ChangeEventSupport { protected ChangeEventSupport changeSupport = new DefaultChangeEventSupport(this); protected MutableTransformer viewTransformer = new MutableAffineTransformer(new AffineTransform()); protected MutableTransformer layoutTransformer = new MutableAffineTransformer(new AffineTransform()); /** * Creates an instance and notifies the view and layout Functions to listen to * changes published by this instance. */ public BasicTransformer() { super(); viewTransformer.addChangeListener(this); layoutTransformer.addChangeListener(this); } protected void setViewTransformer(MutableTransformer Function) { this.viewTransformer.removeChangeListener(this); this.viewTransformer = Function; this.viewTransformer.addChangeListener(this); } protected void setLayoutTransformer(MutableTransformer Function) { this.layoutTransformer.removeChangeListener(this); this.layoutTransformer = Function; this.layoutTransformer.addChangeListener(this); } protected MutableTransformer getLayoutTransformer() { return layoutTransformer; } protected MutableTransformer getViewTransformer() { return viewTransformer; } public Point2D inverseTransform(Point2D p) { return inverseLayoutTransform(inverseViewTransform(p)); } protected Point2D inverseViewTransform(Point2D p) { return viewTransformer.inverseTransform(p); } protected Point2D inverseLayoutTransform(Point2D p) { return layoutTransformer.inverseTransform(p); } public Point2D transform(Point2D p) { return viewTransform(layoutTransform(p)); } protected Point2D viewTransform(Point2D p) { return viewTransformer.transform(p); } protected Point2D layoutTransform(Point2D p) { return layoutTransformer.transform(p); } public Shape inverseTransform(Shape shape) { return inverseLayoutTransform(inverseViewTransform(shape)); } protected Shape inverseViewTransform(Shape shape) { return viewTransformer.inverseTransform(shape); } protected Shape inverseLayoutTransform(Shape shape) { return layoutTransformer.inverseTransform(shape); } public Shape transform(Shape shape) { return viewTransform(layoutTransform(shape)); } protected Shape viewTransform(Shape shape) { return viewTransformer.transform(shape); } protected Shape layoutTransform(Shape shape) { return layoutTransformer.transform(shape); } public void setToIdentity() { layoutTransformer.setToIdentity(); viewTransformer.setToIdentity(); } public void addChangeListener(ChangeListener l) { changeSupport.addChangeListener(l); } public void removeChangeListener(ChangeListener l) { changeSupport.removeChangeListener(l); } public ChangeListener[] getChangeListeners() { return changeSupport.getChangeListeners(); } public void fireStateChanged() { changeSupport.fireStateChanged(); } public void stateChanged(ChangeEvent e) { fireStateChanged(); } public MutableTransformer getTransformer(Layer layer) { if(layer == Layer.LAYOUT) return layoutTransformer; if(layer == Layer.VIEW) return viewTransformer; return null; } public Point2D inverseTransform(Layer layer, Point2D p) { if(layer == Layer.LAYOUT) return inverseLayoutTransform(p); if(layer == Layer.VIEW) return inverseViewTransform(p); return null; } public void setTransformer(Layer layer, MutableTransformer Function) { if(layer == Layer.LAYOUT) setLayoutTransformer(Function); if(layer == Layer.VIEW) setViewTransformer(Function); } public Point2D transform(Layer layer, Point2D p) { if(layer == Layer.LAYOUT) return layoutTransform(p); if(layer == Layer.VIEW) return viewTransform(p); return null; } public Shape transform(Layer layer, Shape shape) { if(layer == Layer.LAYOUT) return layoutTransform(shape); if(layer == Layer.VIEW) return viewTransform(shape); return null; } public Shape inverseTransform(Layer layer, Shape shape) { if(layer == Layer.LAYOUT) return inverseLayoutTransform(shape); if(layer == Layer.VIEW) return inverseViewTransform(shape); return null; } } BasicVisualizationServer.java000066400000000000000000000354721276402340000354630ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/* \* Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.RenderingHints.Key; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.JPanel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer; import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer; import edu.uci.ics.jung.visualization.picking.MultiPickedState; import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.picking.ShapePickSupport; import edu.uci.ics.jung.visualization.renderers.BasicRenderer; import edu.uci.ics.jung.visualization.renderers.Renderer; import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator; import edu.uci.ics.jung.visualization.util.Caching; import edu.uci.ics.jung.visualization.util.ChangeEventSupport; import edu.uci.ics.jung.visualization.util.DefaultChangeEventSupport; /** * A class that maintains many of the details necessary for creating * visualizations of graphs. * This is the old VisualizationViewer without tooltips and mouse behaviors. Its purpose is * to be a base class that can also be used on the server side of a multi-tiered application. * * @author Joshua O'Madadhain * @author Tom Nelson * @author Danyel Fisher */ @SuppressWarnings("serial") public class BasicVisualizationServer extends JPanel implements ChangeListener, ChangeEventSupport, VisualizationServer{ protected ChangeEventSupport changeSupport = new DefaultChangeEventSupport(this); /** * holds the state of this View */ protected VisualizationModel model; /** * handles the actual drawing of graph elements */ protected Renderer renderer = new BasicRenderer(); /** * rendering hints used in drawing. Anti-aliasing is on * by default */ protected Map renderingHints = new HashMap(); /** * holds the state of which vertices of the graph are * currently 'picked' */ protected PickedState pickedVertexState; /** * holds the state of which edges of the graph are * currently 'picked' */ protected PickedState pickedEdgeState; /** * a listener used to cause pick events to result in * repaints, even if they come from another view */ protected ItemListener pickEventListener; /** * an offscreen image to render the graph * Used if doubleBuffered is set to true */ protected BufferedImage offscreen; /** * graphics context for the offscreen image * Used if doubleBuffered is set to true */ protected Graphics2D offscreenG2d; /** * user-settable choice to use the offscreen image * or not. 'false' by default */ protected boolean doubleBuffered; /** * a collection of user-implementable functions to render under * the topology (before the graph is rendered) */ protected List preRenderers = new ArrayList(); /** * a collection of user-implementable functions to render over the * topology (after the graph is rendered) */ protected List postRenderers = new ArrayList(); protected RenderContext renderContext; /** * Create an instance with the specified Layout. * * @param layout The Layout to apply, with its associated Graph */ public BasicVisualizationServer(Layout layout) { this(new DefaultVisualizationModel(layout)); } /** * Create an instance with the specified Layout and view dimension. * * @param layout The Layout to apply, with its associated Graph * @param preferredSize the preferred size of this View */ public BasicVisualizationServer(Layout layout, Dimension preferredSize) { this(new DefaultVisualizationModel(layout, preferredSize), preferredSize); } /** * Create an instance with the specified model and a default dimension (600x600). * * @param model the model to use */ public BasicVisualizationServer(VisualizationModel model) { this(model, new Dimension(600,600)); } /** * Create an instance with the specified model and view dimension. * * @param model the model to use * @param preferredSize initial preferred size of the view */ public BasicVisualizationServer(VisualizationModel model, Dimension preferredSize) { this.model = model; renderContext = new PluggableRenderContext(model.getGraphLayout().getGraph()); model.addChangeListener(this); setDoubleBuffered(false); this.addComponentListener(new VisualizationListener(this)); setPickSupport(new ShapePickSupport(this)); setPickedVertexState(new MultiPickedState()); setPickedEdgeState(new MultiPickedState()); renderContext.setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer(getPickedEdgeState(), Color.black, Color.cyan)); renderContext.setVertexFillPaintTransformer(new PickableVertexPaintTransformer(getPickedVertexState(), Color.red, Color.yellow)); setPreferredSize(preferredSize); renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); renderContext.getMultiLayerTransformer().addChangeListener(this); } @Override public void setDoubleBuffered(boolean doubleBuffered) { this.doubleBuffered = doubleBuffered; } @Override public boolean isDoubleBuffered() { return doubleBuffered; } /** * Always sanity-check getSize so that we don't use a * value that is improbable * @see java.awt.Component#getSize() */ @Override public Dimension getSize() { Dimension d = super.getSize(); if(d.width <= 0 || d.height <= 0) { d = getPreferredSize(); } return d; } /** * Ensure that, if doubleBuffering is enabled, the offscreen * image buffer exists and is the correct size. * @param d the expected Dimension of the offscreen buffer */ protected void checkOffscreenImage(Dimension d) { if(doubleBuffered) { if(offscreen == null || offscreen.getWidth() != d.width || offscreen.getHeight() != d.height) { offscreen = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB); offscreenG2d = offscreen.createGraphics(); } } } public VisualizationModel getModel() { return model; } public void setModel(VisualizationModel model) { this.model = model; } public void stateChanged(ChangeEvent e) { repaint(); fireStateChanged(); } public void setRenderer(Renderer r) { this.renderer = r; repaint(); } public Renderer getRenderer() { return renderer; } public void setGraphLayout(Layout layout) { Dimension viewSize = getPreferredSize(); if(this.isShowing()) { viewSize = getSize(); } model.setGraphLayout(layout, viewSize); } public void scaleToLayout(ScalingControl scaler) { Dimension vd = getPreferredSize(); if(this.isShowing()) { vd = getSize(); } Dimension ld = getGraphLayout().getSize(); if(vd.equals(ld) == false) { scaler.scale(this, (float)(vd.getWidth()/ld.getWidth()), new Point2D.Double()); } } public Layout getGraphLayout() { return model.getGraphLayout(); } @Override public void setVisible(boolean aFlag) { super.setVisible(aFlag); if(aFlag == true) { Dimension d = this.getSize(); if(d.width <= 0 || d.height <= 0) { d = this.getPreferredSize(); } model.getGraphLayout().setSize(d); } } public Map getRenderingHints() { return renderingHints; } public void setRenderingHints(Map renderingHints) { this.renderingHints = renderingHints; } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D)g; if(doubleBuffered) { checkOffscreenImage(getSize()); renderGraph(offscreenG2d); g2d.drawImage(offscreen, null, 0, 0); } else { renderGraph(g2d); } } protected void renderGraph(Graphics2D g2d) { if(renderContext.getGraphicsContext() == null) { renderContext.setGraphicsContext(new GraphicsDecorator(g2d)); } else { renderContext.getGraphicsContext().setDelegate(g2d); } renderContext.setScreenDevice(this); Layout layout = model.getGraphLayout(); g2d.setRenderingHints(renderingHints); // the size of the VisualizationViewer Dimension d = getSize(); // clear the offscreen image g2d.setColor(getBackground()); g2d.fillRect(0,0,d.width,d.height); AffineTransform oldXform = g2d.getTransform(); AffineTransform newXform = new AffineTransform(oldXform); newXform.concatenate( renderContext.getMultiLayerTransformer().getTransformer(Layer.VIEW).getTransform()); g2d.setTransform(newXform); // if there are preRenderers set, paint them for(Paintable paintable : preRenderers) { if(paintable.useTransform()) { paintable.paint(g2d); } else { g2d.setTransform(oldXform); paintable.paint(g2d); g2d.setTransform(newXform); } } if(layout instanceof Caching) { ((Caching)layout).clear(); } renderer.render(renderContext, layout); // if there are postRenderers set, do it for(Paintable paintable : postRenderers) { if(paintable.useTransform()) { paintable.paint(g2d); } else { g2d.setTransform(oldXform); paintable.paint(g2d); g2d.setTransform(newXform); } } g2d.setTransform(oldXform); } /** * VisualizationListener reacts to changes in the size of the * VisualizationViewer. When the size changes, it ensures * that the offscreen image is sized properly. * If the layout is locked to this view size, then the layout * is also resized to be the same as the view size. * * */ protected class VisualizationListener extends ComponentAdapter { protected BasicVisualizationServer vv; public VisualizationListener(BasicVisualizationServer vv) { this.vv = vv; } /** * create a new offscreen image for the graph * whenever the window is resied */ @Override public void componentResized(ComponentEvent e) { Dimension d = vv.getSize(); if(d.width <= 0 || d.height <= 0) return; checkOffscreenImage(d); repaint(); } } public void addPreRenderPaintable(Paintable paintable) { if(preRenderers == null) { preRenderers = new ArrayList(); } preRenderers.add(paintable); } public void prependPreRenderPaintable(Paintable paintable) { if(preRenderers == null) { preRenderers = new ArrayList(); } preRenderers.add(0,paintable); } public void removePreRenderPaintable(Paintable paintable) { if(preRenderers != null) { preRenderers.remove(paintable); } } public void addPostRenderPaintable(Paintable paintable) { if(postRenderers == null) { postRenderers = new ArrayList(); } postRenderers.add(paintable); } public void prependPostRenderPaintable(Paintable paintable) { if(postRenderers == null) { postRenderers = new ArrayList(); } postRenderers.add(0,paintable); } public void removePostRenderPaintable(Paintable paintable) { if(postRenderers != null) { postRenderers.remove(paintable); } } public void addChangeListener(ChangeListener l) { changeSupport.addChangeListener(l); } public void removeChangeListener(ChangeListener l) { changeSupport.removeChangeListener(l); } public ChangeListener[] getChangeListeners() { return changeSupport.getChangeListeners(); } public void fireStateChanged() { changeSupport.fireStateChanged(); } public PickedState getPickedVertexState() { return pickedVertexState; } public PickedState getPickedEdgeState() { return pickedEdgeState; } public void setPickedVertexState(PickedState pickedVertexState) { if(pickEventListener != null && this.pickedVertexState != null) { this.pickedVertexState.removeItemListener(pickEventListener); } this.pickedVertexState = pickedVertexState; this.renderContext.setPickedVertexState(pickedVertexState); if(pickEventListener == null) { pickEventListener = new ItemListener() { public void itemStateChanged(ItemEvent e) { repaint(); } }; } pickedVertexState.addItemListener(pickEventListener); } public void setPickedEdgeState(PickedState pickedEdgeState) { if(pickEventListener != null && this.pickedEdgeState != null) { this.pickedEdgeState.removeItemListener(pickEventListener); } this.pickedEdgeState = pickedEdgeState; this.renderContext.setPickedEdgeState(pickedEdgeState); if(pickEventListener == null) { pickEventListener = new ItemListener() { public void itemStateChanged(ItemEvent e) { repaint(); } }; } pickedEdgeState.addItemListener(pickEventListener); } public GraphElementAccessor getPickSupport() { return renderContext.getPickSupport(); } public void setPickSupport(GraphElementAccessor pickSupport) { renderContext.setPickSupport(pickSupport); } public Point2D getCenter() { Dimension d = getSize(); return new Point2D.Float(d.width/2, d.height/2); } public RenderContext getRenderContext() { return renderContext; } public void setRenderContext(RenderContext renderContext) { this.renderContext = renderContext; } } DefaultVisualizationModel.java000066400000000000000000000124501276402340000356070ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization; import java.awt.Dimension; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.util.Relaxer; import edu.uci.ics.jung.algorithms.layout.util.VisRunner; import edu.uci.ics.jung.algorithms.util.IterativeContext; import edu.uci.ics.jung.visualization.layout.ObservableCachingLayout; import edu.uci.ics.jung.visualization.util.ChangeEventSupport; import edu.uci.ics.jung.visualization.util.DefaultChangeEventSupport; /** * The model containing state values for * visualizations of graphs. * Refactored and extracted from the 1.6.0 version of VisualizationViewer * * @author Tom Nelson */ public class DefaultVisualizationModel implements VisualizationModel, ChangeEventSupport { ChangeEventSupport changeSupport = new DefaultChangeEventSupport(this); /** * manages the thread that applies the current layout algorithm */ protected Relaxer relaxer; /** * the layout algorithm currently in use */ protected Layout layout; /** * listens for changes in the layout, forwards to the viewer * */ protected ChangeListener changeListener; /** * * @param layout The Layout to apply, with its associated Graph */ public DefaultVisualizationModel(Layout layout) { this(layout, null); } /** * Create an instance with the specified layout and dimension. * @param layout the layout to use * @param d The preferred size of the View that will display this graph */ public DefaultVisualizationModel(Layout layout, Dimension d) { if(changeListener == null) { changeListener = new ChangeListener() { public void stateChanged(ChangeEvent e) { fireStateChanged(); } }; } setGraphLayout(layout, d); } /** * Removes the current graph layout, and adds a new one. * @param layout the new layout to use * @param viewSize the size of the View that will display this layout */ public void setGraphLayout(Layout layout, Dimension viewSize) { // remove listener from old layout if(this.layout != null && this.layout instanceof ChangeEventSupport) { ((ChangeEventSupport)this.layout).removeChangeListener(changeListener); } // set to new layout if(layout instanceof ChangeEventSupport) { this.layout = layout; } else { this.layout = new ObservableCachingLayout(layout); } ((ChangeEventSupport)this.layout).addChangeListener(changeListener); if(viewSize == null) { viewSize = new Dimension(600,600); } Dimension layoutSize = layout.getSize(); // if the layout has NOT been initialized yet, initialize its size // now to the size of the VisualizationViewer window if(layoutSize == null) { layout.setSize(viewSize); } if(relaxer != null) { relaxer.stop(); relaxer = null; } if(layout instanceof IterativeContext) { layout.initialize(); if(relaxer == null) { relaxer = new VisRunner((IterativeContext)this.layout); relaxer.prerelax(); relaxer.relax(); } } fireStateChanged(); } /** * set the graph Layout and if it is not already initialized, initialize * it to the default VisualizationViewer preferred size of 600x600 */ public void setGraphLayout(Layout layout) { setGraphLayout(layout, null); } /** * Returns the current graph layout. */ public Layout getGraphLayout() { return layout; } /** * @return the relaxer */ public Relaxer getRelaxer() { return relaxer; } /** * @param relaxer the relaxer to set */ public void setRelaxer(VisRunner relaxer) { this.relaxer = relaxer; } /** * Adds a ChangeListener. * @param l the listener to be added */ public void addChangeListener(ChangeListener l) { changeSupport.addChangeListener(l); } /** * Removes a ChangeListener. * @param l the listener to be removed */ public void removeChangeListener(ChangeListener l) { changeSupport.removeChangeListener(l); } /** * Returns an array of all the ChangeListeners added * with addChangeListener(). * * @return all of the ChangeListeners added or an empty * array if no listeners have been added */ public ChangeListener[] getChangeListeners() { return changeSupport.getChangeListeners(); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created. * The primary listeners will be views that need to be repainted * because of changes in this model instance * @see EventListenerList */ public void fireStateChanged() { changeSupport.fireStateChanged(); } } FourPassImageShaper.java000066400000000000000000000177131276402340000343370ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jun 17, 2005 */ package edu.uci.ics.jung.visualization; import java.awt.Shape; import java.awt.geom.Area; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; /** * Provides Supplier methods that, given a BufferedImage, an Image, * or the fileName of an image, will return a java.awt.Shape that * is the contiguous traced outline of the opaque part of the image. * This could be used to define an image for use in a Vertex, where * the shape used for picking and edge-arrow placement follows the * opaque part of an image that has a transparent background. * The methods try to detect lines in order to minimize points * in the path * * @author Tom Nelson * * */ public class FourPassImageShaper { public static Shape getShape(BufferedImage image) { Area area = new Area(leftEdge(image)); area.intersect(new Area(bottomEdge(image))); area.intersect(new Area(rightEdge(image))); area.intersect(new Area(topEdge(image))); return area; } /** * Checks to see if point p is on a line that passes thru * points p1 and p2. If p is on the line, extend the line * segment so that it is from p1 to the location of p. * If the point p is not on the line, update my shape * with a line extending to the old p2 location, make * the old p2 the new p1, and make p2 the old p * @param p1 * @param p2 * @param p * @param line * @param path * @return */ private static Point2D detectLine(Point2D p1, Point2D p2, Point2D p, Line2D line, GeneralPath path) { // check for line // if p is on the line that extends thru p1 and p2 if(line.ptLineDistSq(p) == 0) { // p is on the line p1,p2 // extend line so that p2 is at p p2.setLocation(p); } else { // its not on the current line // start a new line from p2 to p p1.setLocation(p2); p2.setLocation(p); line.setLine(p1,p2); // end the ongoing path line at the new p1 (the old p2) path.lineTo((float)p1.getX(), (float)p1.getY()); } return p2; } /** * trace the left side of the image * @param image * @param path * @return */ private static Shape leftEdge(BufferedImage image) { GeneralPath path = new GeneralPath(); Point2D p1 = null; Point2D p2 = null; Line2D line = new Line2D.Float(); Point2D p = new Point2D.Float(); int foundPointY = -1; for(int i=0; i= 0) { if(p2 == null) { // this is the first point found. project line to right edge p1 = new Point2D.Float(image.getWidth()-1, foundPointY); path.moveTo(p1.getX(), p1.getY()); p2 = new Point2D.Float(); p2.setLocation(p); } else { p2 = detectLine(p1, p2, p, line, path); } } } path.lineTo(p.getX(), p.getY()); if(foundPointY >= 0) { path.lineTo(image.getWidth()-1, foundPointY); } path.closePath(); return path; } /** * trace the bottom of the image * @param image * @param path * @param start * @return */ private static Shape bottomEdge(BufferedImage image) { GeneralPath path = new GeneralPath(); Point2D p1 = null; Point2D p2 = null; Line2D line = new Line2D.Float(); Point2D p = new Point2D.Float(); int foundPointX = -1; for(int i=0; i=0; j--) { if((image.getRGB(i,j) & 0xff000000) != 0) { // this is a point I want p.setLocation(i,j); foundPointX = i; break; } } if(foundPointX >= 0) { if(p2 == null) { // this is the first point found. project line to top edge p1 = new Point2D.Float(foundPointX, 0); // path starts here path.moveTo(p1.getX(), p1.getY()); p2 = new Point2D.Float(); p2.setLocation(p); } else { p2 = detectLine(p1, p2, p, line, path); } } } path.lineTo(p.getX(), p.getY()); if(foundPointX >= 0) { path.lineTo(foundPointX, 0); } path.closePath(); return path; } /** * trace the right side of the image * @param image * @param path * @param start * @return */ private static Shape rightEdge(BufferedImage image) { GeneralPath path = new GeneralPath(); Point2D p1 = null; Point2D p2 = null; Line2D line = new Line2D.Float(); Point2D p = new Point2D.Float(); int foundPointY = -1; for(int i=image.getHeight()-1; i>=0; i--) { for(int j=image.getWidth()-1; j>=0; j--) { if((image.getRGB(j,i) & 0xff000000) != 0) { // this is a point I want p.setLocation(j,i); foundPointY = i; break; } } if(foundPointY >= 0) { if(p2 == null) { // this is the first point found. project line to top edge p1 = new Point2D.Float(0, foundPointY); // path starts here path.moveTo(p1.getX(), p1.getY()); p2 = new Point2D.Float(); p2.setLocation(p); } else { p2 = detectLine(p1, p2, p, line, path); } } } path.lineTo(p.getX(), p.getY()); if(foundPointY >= 0) { path.lineTo(0, foundPointY); } path.closePath(); return path; } /** * trace the top of the image * @param image * @param path * @param start * @return */ private static Shape topEdge(BufferedImage image) { GeneralPath path = new GeneralPath(); Point2D p1 = null; Point2D p2 = null; Line2D line = new Line2D.Float(); Point2D p = new Point2D.Float(); int foundPointX = -1; for(int i=image.getWidth()-1; i>=0; i--) { for(int j=0; j= 0) { if(p2 == null) { // this is the first point found. project line to top edge p1 = new Point2D.Float(foundPointX, image.getHeight()-1); // path starts here path.moveTo(p1.getX(), p1.getY()); p2 = new Point2D.Float(); p2.setLocation(p); } else { p2 = detectLine(p1, p2, p, line, path); } } } path.lineTo(p.getX(), p.getY()); if(foundPointX >= 0) { path.lineTo(foundPointX, image.getHeight()-1); } path.closePath(); return path; } } GraphZoomScrollPane.java000066400000000000000000000276571276402340000343700ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Feb 2, 2005 * */ package edu.uci.ics.jung.visualization; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Set; import javax.swing.BoundedRangeModel; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JScrollBar; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer; import edu.uci.ics.jung.visualization.transform.shape.Intersector; /** * GraphZoomScrollPane is a Container for the Graph's VisualizationViewer * and includes custom horizontal and vertical scrollbars. * GraphZoomScrollPane listens for changes in the scale and * translation of the VisualizationViewer, and will update the * scrollbar positions and sizes accordingly. Changes in the * scrollbar positions will cause the corresponding change in * the translation component (offset) of the VisualizationViewer. * The scrollbars are modified so that they will allow panning * of the graph when the scale has been changed (e.g. zoomed-in * or zoomed-out). * * The lower-right corner of this component is available to * use as a small button or menu. * * samples.graph.GraphZoomScrollPaneDemo shows the use of this component. * * @author Tom Nelson * * */ @SuppressWarnings("serial") public class GraphZoomScrollPane extends JPanel { protected VisualizationViewer vv; protected JScrollBar horizontalScrollBar; protected JScrollBar verticalScrollBar; protected JComponent corner; protected boolean scrollBarsMayControlAdjusting = true; protected JPanel south; /** * Create an instance of the GraphZoomScrollPane to contain the * VisualizationViewer * @param vv the VisualizationViewer for which this instance is to be created */ public GraphZoomScrollPane(VisualizationViewer vv) { super(new BorderLayout()); this.vv = vv; addComponentListener(new ResizeListener()); Dimension d = vv.getGraphLayout().getSize(); verticalScrollBar = new JScrollBar(JScrollBar.VERTICAL, 0, d.height, 0, d.height); horizontalScrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 0, d.width, 0, d.width); verticalScrollBar.addAdjustmentListener(new VerticalAdjustmentListenerImpl()); horizontalScrollBar.addAdjustmentListener(new HorizontalAdjustmentListenerImpl()); verticalScrollBar.setUnitIncrement(20); horizontalScrollBar.setUnitIncrement(20); // respond to changes in the VisualizationViewer's transform // and set the scroll bar parameters appropriately vv.addChangeListener( new ChangeListener(){ public void stateChanged(ChangeEvent evt) { VisualizationViewer vv = (VisualizationViewer)evt.getSource(); setScrollBars(vv); } }); add(vv); add(verticalScrollBar, BorderLayout.EAST); south = new JPanel(new BorderLayout()); south.add(horizontalScrollBar); setCorner(new JPanel()); add(south, BorderLayout.SOUTH); } /** * listener for adjustment of the horizontal scroll bar. * Sets the translation of the VisualizationViewer */ class HorizontalAdjustmentListenerImpl implements AdjustmentListener { int previous = 0; public void adjustmentValueChanged(AdjustmentEvent e) { int hval = e.getValue(); float dh = previous - hval; previous = hval; if(dh != 0 && scrollBarsMayControlAdjusting) { // get the uniform scale of all transforms float layoutScale = (float) vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale(); dh *= layoutScale; AffineTransform at = AffineTransform.getTranslateInstance(dh, 0); vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).preConcatenate(at); } } } /** * Listener for adjustment of the vertical scroll bar. * Sets the translation of the VisualizationViewer */ class VerticalAdjustmentListenerImpl implements AdjustmentListener { int previous = 0; public void adjustmentValueChanged(AdjustmentEvent e) { JScrollBar sb = (JScrollBar)e.getSource(); BoundedRangeModel m = sb.getModel(); int vval = m.getValue(); float dv = previous - vval; previous = vval; if(dv != 0 && scrollBarsMayControlAdjusting) { // get the uniform scale of all transforms float layoutScale = (float) vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale(); dv *= layoutScale; AffineTransform at = AffineTransform.getTranslateInstance(0, dv); vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).preConcatenate(at); } } } /** * use the supplied vv characteristics to set the position and * dimensions of the scroll bars. Called in response to * a ChangeEvent from the VisualizationViewer * @param xform the transform of the VisualizationViewer */ private void setScrollBars(VisualizationViewer vv) { Dimension d = vv.getGraphLayout().getSize(); Rectangle2D vvBounds = vv.getBounds(); // a rectangle representing the layout Rectangle layoutRectangle = new Rectangle(0,0,d.width,d.height); //-d.width/2, -d.height/2, 2*d.width, 2*d.height); BidirectionalTransformer viewTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW); BidirectionalTransformer layoutTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); Point2D h0 = new Point2D.Double(vvBounds.getMinX(), vvBounds.getCenterY()); Point2D h1 = new Point2D.Double(vvBounds.getMaxX(), vvBounds.getCenterY()); Point2D v0 = new Point2D.Double(vvBounds.getCenterX(), vvBounds.getMinY()); Point2D v1 = new Point2D.Double(vvBounds.getCenterX(), vvBounds.getMaxY()); h0 = viewTransformer.inverseTransform(h0); h0 = layoutTransformer.inverseTransform(h0); h1 = viewTransformer.inverseTransform(h1); h1 = layoutTransformer.inverseTransform(h1); v0 = viewTransformer.inverseTransform(v0); v0 = layoutTransformer.inverseTransform(v0); v1 = viewTransformer.inverseTransform(v1); v1 = layoutTransformer.inverseTransform(v1); scrollBarsMayControlAdjusting = false; setScrollBarValues(layoutRectangle, h0, h1, v0, v1); scrollBarsMayControlAdjusting = true; } protected void setScrollBarValues(Rectangle rectangle, Point2D h0, Point2D h1, Point2D v0, Point2D v1) { boolean containsH0 = rectangle.contains(h0); boolean containsH1 = rectangle.contains(h1); boolean containsV0 = rectangle.contains(v0); boolean containsV1 = rectangle.contains(v1); // horizontal scrollbar: Intersector intersector = new Intersector(rectangle, new Line2D.Double(h0, h1)); int min = 0; int ext; int val = 0; int max; Set points = intersector.getPoints(); Point2D first = null; Point2D second = null; Point2D[] pointArray = (Point2D[])points.toArray(new Point2D[points.size()]); if(pointArray.length > 1) { first = pointArray[0]; second = pointArray[1]; } else if(pointArray.length > 0) { first = second = pointArray[0]; } if(first != null && second != null) { // correct direction of intersect points if((h0.getX() - h1.getX()) * (first.getX() - second.getX()) < 0) { // swap them Point2D temp = first; first = second; second = temp; } if(containsH0 && containsH1) { max = (int)first.distance(second); val = (int)first.distance(h0); ext = (int)h0.distance(h1); } else if(containsH0) { max = (int)first.distance(second); val = (int)first.distance(h0); ext = (int)h0.distance(second); } else if(containsH1) { max = (int) first.distance(second); val = 0; ext = (int) first.distance(h1); } else { max = ext = rectangle.width; val = min; } horizontalScrollBar.setValues(val, ext+1, min, max); } // vertical scroll bar min = val = 0; intersector.intersectLine(new Line2D.Double(v0, v1)); points = intersector.getPoints(); pointArray = (Point2D[])points.toArray(new Point2D[points.size()]); if(pointArray.length > 1) { first = pointArray[0]; second = pointArray[1]; } else if(pointArray.length > 0) { first = second = pointArray[0]; } if(first != null && second != null) { // arrange for direction if((v0.getY() - v1.getY()) * (first.getY() - second.getY()) < 0) { // swap them Point2D temp = first; first = second; second = temp; } if(containsV0 && containsV1) { max = (int)first.distance(second); val = (int)first.distance(v0); ext = (int)v0.distance(v1); } else if(containsV0) { max = (int)first.distance(second); val = (int)first.distance(v0); ext = (int)v0.distance(second); } else if(containsV1) { max = (int) first.distance(second); val = 0; ext = (int) first.distance(v1); } else { max = ext = rectangle.height; val = min; } verticalScrollBar.setValues(val, ext+1, min, max); } } /** * Listener to adjust the scroll bar parameters when the window * is resized */ protected class ResizeListener extends ComponentAdapter { public void componentHidden(ComponentEvent e) { } public void componentResized(ComponentEvent e) { setScrollBars(vv); } public void componentShown(ComponentEvent e) { } } /** * @return Returns the corner component. */ public JComponent getCorner() { return corner; } /** * @param corner The cornerButton to set. */ public void setCorner(JComponent corner) { this.corner = corner; corner.setPreferredSize(new Dimension(verticalScrollBar.getPreferredSize().width, horizontalScrollBar.getPreferredSize().height)); south.add(this.corner, BorderLayout.EAST); } public JScrollBar getHorizontalScrollBar() { return horizontalScrollBar; } public JScrollBar getVerticalScrollBar() { return verticalScrollBar; } } jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/Layer.java000066400000000000000000000001151276402340000316060ustar00rootroot00000000000000package edu.uci.ics.jung.visualization; public enum Layer { LAYOUT, VIEW } jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/LayeredIcon.java000066400000000000000000000021651276402340000327370ustar00rootroot00000000000000package edu.uci.ics.jung.visualization; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Image; import java.util.LinkedHashSet; import java.util.Set; import javax.swing.Icon; import javax.swing.ImageIcon; /** * An icon that is made up of a collection of Icons. * They are rendered in layers starting with the first * Icon added (from the constructor). * * @author Tom Nelson * */ @SuppressWarnings("serial") public class LayeredIcon extends ImageIcon { Set iconSet = new LinkedHashSet(); public LayeredIcon(Image image) { super(image); } public void paintIcon(Component c, Graphics g, int x, int y) { super.paintIcon(c, g, x, y); Dimension d = new Dimension(getIconWidth(), getIconHeight()); for (Icon icon : iconSet) { Dimension id = new Dimension(icon.getIconWidth(), icon.getIconHeight()); int dx = (d.width - id.width)/2; int dy = (d.height - id.height)/2; icon.paintIcon(c, g, x+dx, y+dy); } } public void add(Icon icon) { iconSet.add(icon); } public boolean remove(Icon icon) { return iconSet.remove(icon); } }MultiLayerTransformer.java000066400000000000000000000014671276402340000350000ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualizationpackage edu.uci.ics.jung.visualization; import java.awt.Shape; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer; import edu.uci.ics.jung.visualization.util.ChangeEventSupport; public interface MultiLayerTransformer extends BidirectionalTransformer, ShapeTransformer, ChangeEventSupport { void setTransformer(Layer layer, MutableTransformer Function); MutableTransformer getTransformer(Layer layer); Point2D inverseTransform(Layer layer, Point2D p); Point2D transform(Layer layer, Point2D p); Shape transform(Layer layer, Shape shape); Shape inverseTransform(Layer layer, Shape shape); void setToIdentity(); }PivotingImageShaper.java000066400000000000000000000147511276402340000343730ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jun 17, 2005 */ package edu.uci.ics.jung.visualization; import java.awt.Shape; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; /** * Provides Supplier methods that, given a BufferedImage, an Image, * or the fileName of an image, will return a java.awt.Shape that * is the contiguous traced outline of the opaque part of the image. * This could be used to define an image for use in a Vertex, where * the shape used for picking and edge-arrow placement follows the * opaque part of an image that has a transparent background. * The methods try to detect lines in order to minimize points * in the path * * @author Tom Nelson * * */ public class PivotingImageShaper { /** * the number of pixels to skip while sampling the * images edges */ static int sample = 1; /** * the first x coordinate of the shape. Used to discern * when we are done */ static int firstx = 0; /** * Given an image, possibly with a transparent background, return * the Shape of the opaque part of the image * @param image the image whose shape is being returned * @return the Shape */ public static Shape getShape(BufferedImage image) { firstx = 0; return leftEdge(image, new GeneralPath()); } private static Point2D detectLine(Point2D p1, Point2D p2, Point2D p, Line2D line, GeneralPath path) { if(p2 == null) { p2 = p; line.setLine(p1,p2); } // check for line else if(line.ptLineDistSq(p) < 1) { // its on the line // make it p2 p2.setLocation(p); } else { // its not on the current line p1.setLocation(p2); p2.setLocation(p); line.setLine(p1,p2); path.lineTo((float)p1.getX(), (float)p1.getY()); } return p2; } /** * trace the left side of the image * @param image * @param path * @return */ private static Shape leftEdge(BufferedImage image, GeneralPath path) { int lastj = 0; Point2D p1 = null; Point2D p2 = null; Line2D line = new Line2D.Float(); for(int i=0; i=0; j-=sample) { if((image.getRGB(i,j) & 0xff000000) != 0) { // this is a point I want Point2D p = new Point2D.Float(i,j); aPointExistsOnThisLine = true; p2 = detectLine(p1,p2,p,line,path); lastj = j; break; } } if(aPointExistsOnThisLine == false) { break; } } return rightEdge(image, path, lastj); } /** * trace the right side of the image * @param image * @param path * @param start * @return */ private static Shape rightEdge(BufferedImage image, GeneralPath path, int start) { int lastj = 0; Point2D p1 = path.getCurrentPoint(); Point2D p2 = null; Line2D line = new Line2D.Float(); for(int i=start; i>=0; i-=sample) { boolean aPointExistsOnThisLine = false; for(int j=image.getWidth()-1; j>=0; j-=sample) { if((image.getRGB(j,i) & 0xff000000) != 0) { // this is a point I want Point2D p = new Point2D.Float(j,i); aPointExistsOnThisLine = true; p2 = detectLine(p1,p2,p,line,path); lastj=j; break; } } if(aPointExistsOnThisLine == false) { break; } } return topEdge(image, path, lastj); } /** * trace the top of the image * @param image * @param path * @param start * @return */ private static Shape topEdge(BufferedImage image, GeneralPath path, int start) { Point2D p1 = path.getCurrentPoint(); Point2D p2 = null; Line2D line = new Line2D.Float(); for(int i=start; i>=firstx; i-=sample) { boolean aPointExistsOnThisLine = false; for(int j=0; j implements RenderContext { protected float arrowPlacementTolerance = 1; protected Predicate,V>> vertexIncludePredicate = Predicates.alwaysTrue(); protected Function vertexStrokeTransformer = Functions.constant(new BasicStroke(1.0f)); protected Function vertexShapeTransformer = Functions.constant( new Ellipse2D.Float(-10,-10,20,20)); protected Function vertexLabelTransformer = Functions.constant(null); protected Function vertexIconTransformer; protected Function vertexFontTransformer = Functions.constant(new Font("Helvetica", Font.PLAIN, 12)); protected Function vertexDrawPaintTransformer = Functions.constant(Color.BLACK); protected Function vertexFillPaintTransformer = Functions.constant(Color.RED); protected Function edgeLabelTransformer = Functions.constant(null); protected Function edgeStrokeTransformer = Functions.constant(new BasicStroke(1.0f)); protected Function edgeArrowStrokeTransformer = Functions.constant(new BasicStroke(1.0f)); protected Function,E>,Shape> edgeArrowTransformer = new DirectionalEdgeArrowTransformer(10, 8, 4); protected Predicate,E>> edgeArrowPredicate = new DirectedEdgeArrowPredicate(); protected Predicate,E>> edgeIncludePredicate = Predicates.alwaysTrue(); protected Function edgeFontTransformer = Functions.constant(new Font("Helvetica", Font.PLAIN, 12)); protected Function,E>,Number> edgeLabelClosenessTransformer = new ConstantDirectionalEdgeValueTransformer(0.5, 0.65); protected Function edgeShapeTransformer; protected Function edgeFillPaintTransformer = Functions.constant(null); protected Function edgeDrawPaintTransformer = Functions.constant(Color.black); protected Function arrowFillPaintTransformer = Functions.constant(Color.black); protected Function arrowDrawPaintTransformer = Functions.constant(Color.black); protected EdgeIndexFunction parallelEdgeIndexFunction = DefaultParallelEdgeIndexFunction.getInstance(); protected EdgeIndexFunction incidentEdgeIndexFunction = IncidentEdgeIndexFunction.getInstance(); protected MultiLayerTransformer multiLayerTransformer = new BasicTransformer(); /** * pluggable support for picking graph elements by * finding them based on their coordinates. */ protected GraphElementAccessor pickSupport; protected int labelOffset = LABEL_OFFSET; /** * the JComponent that this Renderer will display the graph on */ protected JComponent screenDevice; protected PickedState pickedVertexState; protected PickedState pickedEdgeState; /** * The CellRendererPane is used here just as it is in JTree * and JTable, to allow a pluggable JLabel-based renderer for * Vertex and Edge label strings and icons. */ protected CellRendererPane rendererPane = new CellRendererPane(); /** * A default GraphLabelRenderer - picked Vertex labels are * blue, picked edge labels are cyan */ protected VertexLabelRenderer vertexLabelRenderer = new DefaultVertexLabelRenderer(Color.blue); protected EdgeLabelRenderer edgeLabelRenderer = new DefaultEdgeLabelRenderer(Color.cyan); protected GraphicsDecorator graphicsContext; private EdgeShape edgeShape; PluggableRenderContext(Graph graph) { this.edgeShape = new EdgeShape(graph); this.edgeShapeTransformer = edgeShape.new QuadCurve(); } /** * @return the vertexShapeTransformer */ public Function getVertexShapeTransformer() { return vertexShapeTransformer; } /** * @param vertexShapeTransformer the vertexShapeTransformer to set */ public void setVertexShapeTransformer( Function vertexShapeTransformer) { this.vertexShapeTransformer = vertexShapeTransformer; } /** * @return the vertexStrokeTransformer */ public Function getVertexStrokeTransformer() { return vertexStrokeTransformer; } /** * @param vertexStrokeTransformer the vertexStrokeTransformer to set */ public void setVertexStrokeTransformer( Function vertexStrokeTransformer) { this.vertexStrokeTransformer = vertexStrokeTransformer; } public static float[] getDashing() { return dashing; } public static float[] getDotting() { return dotting; } public float getArrowPlacementTolerance() { return arrowPlacementTolerance; } public void setArrowPlacementTolerance(float arrow_placement_tolerance) { this.arrowPlacementTolerance = arrow_placement_tolerance; } public Function,E>,Shape> getEdgeArrowTransformer() { return edgeArrowTransformer; } public void setEdgeArrowTransformer(Function,E>,Shape> edgeArrowTransformer) { this.edgeArrowTransformer = edgeArrowTransformer; } public Predicate,E>> getEdgeArrowPredicate() { return edgeArrowPredicate; } public void setEdgeArrowPredicate(Predicate,E>> edgeArrowPredicate) { this.edgeArrowPredicate = edgeArrowPredicate; } public Function getEdgeFontTransformer() { return edgeFontTransformer; } public void setEdgeFontTransformer(Function edgeFontTransformer) { this.edgeFontTransformer = edgeFontTransformer; } public Predicate,E>> getEdgeIncludePredicate() { return edgeIncludePredicate; } public void setEdgeIncludePredicate(Predicate,E>> edgeIncludePredicate) { this.edgeIncludePredicate = edgeIncludePredicate; } public Function,E>,Number> getEdgeLabelClosenessTransformer() { return edgeLabelClosenessTransformer; } public void setEdgeLabelClosenessTransformer( Function,E>,Number> edgeLabelClosenessTransformer) { this.edgeLabelClosenessTransformer = edgeLabelClosenessTransformer; } public EdgeLabelRenderer getEdgeLabelRenderer() { return edgeLabelRenderer; } public void setEdgeLabelRenderer(EdgeLabelRenderer edgeLabelRenderer) { this.edgeLabelRenderer = edgeLabelRenderer; } public Function getEdgeFillPaintTransformer() { return edgeFillPaintTransformer; } public void setEdgeDrawPaintTransformer(Function edgeDrawPaintTransformer) { this.edgeDrawPaintTransformer = edgeDrawPaintTransformer; } public Function getEdgeDrawPaintTransformer() { return edgeDrawPaintTransformer; } public void setEdgeFillPaintTransformer(Function edgeFillPaintTransformer) { this.edgeFillPaintTransformer = edgeFillPaintTransformer; } public Function getEdgeShapeTransformer() { return edgeShapeTransformer; } public void setEdgeShapeTransformer(Function edgeShapeTransformer) { this.edgeShapeTransformer = edgeShapeTransformer; if (edgeShapeTransformer instanceof ParallelEdgeShapeTransformer) { @SuppressWarnings("unchecked") ParallelEdgeShapeTransformer transformer = (ParallelEdgeShapeTransformer)edgeShapeTransformer; if (transformer instanceof EdgeShape.Orthogonal) { transformer.setEdgeIndexFunction(this.incidentEdgeIndexFunction); } else { transformer.setEdgeIndexFunction(this.parallelEdgeIndexFunction); } } } public Function getEdgeLabelTransformer() { return edgeLabelTransformer; } public void setEdgeLabelTransformer(Function edgeLabelTransformer) { this.edgeLabelTransformer = edgeLabelTransformer; } public Function getEdgeStrokeTransformer() { return edgeStrokeTransformer; } public void setEdgeStrokeTransformer(Function edgeStrokeTransformer) { this.edgeStrokeTransformer = edgeStrokeTransformer; } public Function getEdgeArrowStrokeTransformer() { return edgeArrowStrokeTransformer; } public void setEdgeArrowStrokeTransformer(Function edgeArrowStrokeTransformer) { this.edgeArrowStrokeTransformer = edgeArrowStrokeTransformer; } public GraphicsDecorator getGraphicsContext() { return graphicsContext; } public void setGraphicsContext(GraphicsDecorator graphicsContext) { this.graphicsContext = graphicsContext; } public int getLabelOffset() { return labelOffset; } public void setLabelOffset(int labelOffset) { this.labelOffset = labelOffset; } public EdgeIndexFunction getParallelEdgeIndexFunction() { return parallelEdgeIndexFunction; } public void setParallelEdgeIndexFunction( EdgeIndexFunction parallelEdgeIndexFunction) { this.parallelEdgeIndexFunction = parallelEdgeIndexFunction; // reset the edge shape Function, as the parallel edge index function // is used by it this.setEdgeShapeTransformer(getEdgeShapeTransformer()); } public PickedState getPickedEdgeState() { return pickedEdgeState; } public void setPickedEdgeState(PickedState pickedEdgeState) { this.pickedEdgeState = pickedEdgeState; } public PickedState getPickedVertexState() { return pickedVertexState; } public void setPickedVertexState(PickedState pickedVertexState) { this.pickedVertexState = pickedVertexState; } public CellRendererPane getRendererPane() { return rendererPane; } public void setRendererPane(CellRendererPane rendererPane) { this.rendererPane = rendererPane; } public JComponent getScreenDevice() { return screenDevice; } public void setScreenDevice(JComponent screenDevice) { this.screenDevice = screenDevice; screenDevice.add(rendererPane); } public Function getVertexFontTransformer() { return vertexFontTransformer; } public void setVertexFontTransformer(Function vertexFontTransformer) { this.vertexFontTransformer = vertexFontTransformer; } public Function getVertexIconTransformer() { return vertexIconTransformer; } public void setVertexIconTransformer(Function vertexIconTransformer) { this.vertexIconTransformer = vertexIconTransformer; } public Predicate,V>> getVertexIncludePredicate() { return vertexIncludePredicate; } public void setVertexIncludePredicate(Predicate,V>> vertexIncludePredicate) { this.vertexIncludePredicate = vertexIncludePredicate; } public VertexLabelRenderer getVertexLabelRenderer() { return vertexLabelRenderer; } public void setVertexLabelRenderer(VertexLabelRenderer vertexLabelRenderer) { this.vertexLabelRenderer = vertexLabelRenderer; } public Function getVertexFillPaintTransformer() { return vertexFillPaintTransformer; } public void setVertexFillPaintTransformer(Function vertexFillPaintTransformer) { this.vertexFillPaintTransformer = vertexFillPaintTransformer; } public Function getVertexDrawPaintTransformer() { return vertexDrawPaintTransformer; } public void setVertexDrawPaintTransformer(Function vertexDrawPaintTransformer) { this.vertexDrawPaintTransformer = vertexDrawPaintTransformer; } public Function getVertexLabelTransformer() { return vertexLabelTransformer; } public void setVertexLabelTransformer(Function vertexLabelTransformer) { this.vertexLabelTransformer = vertexLabelTransformer; } public GraphElementAccessor getPickSupport() { return pickSupport; } public void setPickSupport(GraphElementAccessor pickSupport) { this.pickSupport = pickSupport; } public MultiLayerTransformer getMultiLayerTransformer() { return multiLayerTransformer; } public void setMultiLayerTransformer(MultiLayerTransformer basicTransformer) { this.multiLayerTransformer = basicTransformer; } public Function getArrowDrawPaintTransformer() { return arrowDrawPaintTransformer; } public Function getArrowFillPaintTransformer() { return arrowFillPaintTransformer; } public void setArrowDrawPaintTransformer(Function arrowDrawPaintTransformer) { this.arrowDrawPaintTransformer = arrowDrawPaintTransformer; } public void setArrowFillPaintTransformer(Function arrowFillPaintTransformer) { this.arrowFillPaintTransformer = arrowFillPaintTransformer; } } jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/RenderContext.java000066400000000000000000000152341276402340000333260ustar00rootroot00000000000000package edu.uci.ics.jung.visualization; import java.awt.BasicStroke; import java.awt.Font; import java.awt.Paint; import java.awt.Shape; import java.awt.Stroke; import javax.swing.CellRendererPane; import javax.swing.Icon; import javax.swing.JComponent; import com.google.common.base.Function; import com.google.common.base.Predicate; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.graph.util.EdgeIndexFunction; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.renderers.EdgeLabelRenderer; import edu.uci.ics.jung.visualization.renderers.VertexLabelRenderer; import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator; public interface RenderContext { float[] dotting = {1.0f, 3.0f}; float[] dashing = {5.0f}; /** * A stroke for a dotted line: 1 pixel width, round caps, round joins, and an * array of {1.0f, 3.0f}. */ Stroke DOTTED = new BasicStroke(1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f, dotting, 0f); /** * A stroke for a dashed line: 1 pixel width, square caps, beveled joins, and an * array of {5.0f}. */ Stroke DASHED = new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 1.0f, dashing, 0f); /** * Specifies the offset for the edge labels. */ int LABEL_OFFSET = 10; int getLabelOffset(); void setLabelOffset(int labelOffset); float getArrowPlacementTolerance(); void setArrowPlacementTolerance(float arrow_placement_tolerance); Function,E>,Shape> getEdgeArrowTransformer(); void setEdgeArrowTransformer(Function,E>,Shape> edgeArrowTransformer); Predicate,E>> getEdgeArrowPredicate() ; void setEdgeArrowPredicate(Predicate,E>> edgeArrowPredicate); Function getEdgeFontTransformer(); void setEdgeFontTransformer(Function edgeFontTransformer); Predicate,E>> getEdgeIncludePredicate(); void setEdgeIncludePredicate(Predicate,E>> edgeIncludePredicate); Function,E>,Number> getEdgeLabelClosenessTransformer(); void setEdgeLabelClosenessTransformer( Function,E>,Number> edgeLabelClosenessTransformer); EdgeLabelRenderer getEdgeLabelRenderer(); void setEdgeLabelRenderer(EdgeLabelRenderer edgeLabelRenderer); Function getEdgeFillPaintTransformer(); void setEdgeFillPaintTransformer(Function edgePaintTransformer); Function getEdgeDrawPaintTransformer(); void setEdgeDrawPaintTransformer(Function edgeDrawPaintTransformer); Function getArrowDrawPaintTransformer(); void setArrowDrawPaintTransformer(Function arrowDrawPaintTransformer); Function getArrowFillPaintTransformer(); void setArrowFillPaintTransformer(Function arrowFillPaintTransformer); Function getEdgeShapeTransformer(); void setEdgeShapeTransformer(Function edgeShapeTransformer); Function getEdgeLabelTransformer(); void setEdgeLabelTransformer(Function edgeStringer); Function getEdgeStrokeTransformer(); void setEdgeStrokeTransformer(Function edgeStrokeTransformer); Function getEdgeArrowStrokeTransformer(); void setEdgeArrowStrokeTransformer(Function edgeArrowStrokeTransformer); GraphicsDecorator getGraphicsContext(); void setGraphicsContext(GraphicsDecorator graphicsContext); EdgeIndexFunction getParallelEdgeIndexFunction(); void setParallelEdgeIndexFunction( EdgeIndexFunction parallelEdgeIndexFunction); PickedState getPickedEdgeState(); void setPickedEdgeState(PickedState pickedEdgeState); PickedState getPickedVertexState(); void setPickedVertexState(PickedState pickedVertexState); CellRendererPane getRendererPane(); void setRendererPane(CellRendererPane rendererPane); JComponent getScreenDevice(); void setScreenDevice(JComponent screenDevice); Function getVertexFontTransformer(); void setVertexFontTransformer(Function vertexFontTransformer); Function getVertexIconTransformer(); void setVertexIconTransformer(Function vertexIconTransformer); Predicate,V>> getVertexIncludePredicate(); void setVertexIncludePredicate(Predicate,V>> vertexIncludePredicate); VertexLabelRenderer getVertexLabelRenderer(); void setVertexLabelRenderer(VertexLabelRenderer vertexLabelRenderer); Function getVertexFillPaintTransformer(); void setVertexFillPaintTransformer(Function vertexFillPaintTransformer); Function getVertexDrawPaintTransformer(); void setVertexDrawPaintTransformer(Function vertexDrawPaintTransformer); Function getVertexShapeTransformer(); void setVertexShapeTransformer(Function vertexShapeTransformer); Function getVertexLabelTransformer(); void setVertexLabelTransformer(Function vertexStringer); Function getVertexStrokeTransformer(); void setVertexStrokeTransformer(Function vertexStrokeTransformer); class DirectedEdgeArrowPredicate implements Predicate,E>> { public boolean apply(Context,E> c) { return c.graph.getEdgeType(c.element) == EdgeType.DIRECTED; } } class UndirectedEdgeArrowPredicate implements Predicate,E>> { //extends AbstractGraphPredicate { public boolean apply(Context,E> c) { return c.graph.getEdgeType(c.element) == EdgeType.UNDIRECTED; } } MultiLayerTransformer getMultiLayerTransformer(); void setMultiLayerTransformer(MultiLayerTransformer basicTransformer); /** * @return the pickSupport */ GraphElementAccessor getPickSupport(); /** * @param pickSupport the pickSupport to set */ void setPickSupport(GraphElementAccessor pickSupport); }VisualizationImageServer.java000066400000000000000000000044071276402340000354560ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.util.HashMap; import java.util.Map; import edu.uci.ics.jung.algorithms.layout.Layout; /** * A class that could be used on the server side of a thin-client application. It creates the jung * visualization, then produces an image of it. * @author tom * * @param the vertex type * @param the edge type */ @SuppressWarnings("serial") public class VisualizationImageServer extends BasicVisualizationServer { Map renderingHints = new HashMap(); /** * Creates a new instance with the specified layout and preferred size. * * @param layout the Layout instance; provides the vertex locations * @param preferredSize the preferred size of the image */ public VisualizationImageServer(Layout layout, Dimension preferredSize) { super(layout, preferredSize); setSize(preferredSize); renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); addNotify(); } public Image getImage(Point2D center, Dimension d) { int width = getWidth(); int height = getHeight(); float scalex = (float)width/d.width; float scaley = (float)height/d.height; try { renderContext.getMultiLayerTransformer().getTransformer(Layer.VIEW).scale(scalex, scaley, center); BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D graphics = bi.createGraphics(); graphics.setRenderingHints(renderingHints); paint(graphics); graphics.dispose(); return bi; } finally { renderContext.getMultiLayerTransformer().getTransformer(Layer.VIEW).setToIdentity(); } } } VisualizationModel.java000066400000000000000000000040111276402340000342740ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on May 4, 2005 */ package edu.uci.ics.jung.visualization; import java.awt.Dimension; import javax.swing.event.ChangeListener; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.util.Relaxer; import edu.uci.ics.jung.visualization.util.ChangeEventSupport; /** * Interface for the state holding model of the VisualizationViewer. * Refactored and extracted from the 1.6.0 version of VisualizationViewer * * @author Tom Nelson */ public interface VisualizationModel extends ChangeEventSupport { Relaxer getRelaxer(); /** * set the graph Layout * @param layout the layout to use */ void setGraphLayout(Layout layout); /** * Sets the graph Layout and initialize the Layout size to * the passed dimensions. The passed Dimension will often be * the size of the View that will display the graph. * @param layout the layout to use * @param d the dimensions to use */ void setGraphLayout(Layout layout, Dimension d); /** * @return the current graph layout */ Layout getGraphLayout(); /** * Register l as a listeners to changes in the model. The View registers * in order to repaint itself when the model changes. * @param l the listener to add */ void addChangeListener(ChangeListener l); /** * Removes a ChangeListener. * @param l the listener to be removed */ void removeChangeListener(ChangeListener l); /** * Returns an array of all the ChangeListeners added * with addChangeListener(). * * @return all of the ChangeListeners added or an empty * array if no listeners have been added */ ChangeListener[] getChangeListeners(); }VisualizationServer.java000066400000000000000000000122041276402340000345050ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization; import java.awt.Graphics; import java.awt.RenderingHints.Key; import java.awt.geom.Point2D; import java.util.Map; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.renderers.Renderer; /** * @author tom * * @param the vertex type * @param the edge type */ public interface VisualizationServer { /** * Specify whether this class uses its offscreen image or not. * * @param doubleBuffered if true, then doubleBuffering in the superclass is set to 'false' */ void setDoubleBuffered(boolean doubleBuffered); /** * Returns whether this class uses double buffering. The superclass * will be the opposite state. * @return the double buffered state */ boolean isDoubleBuffered(); /** * @return the model. */ VisualizationModel getModel(); /** * @param model the model for this class to use */ void setModel(VisualizationModel model); /** * In response to changes from the model, repaint the * view, then fire an event to any listeners. * Examples of listeners are the GraphZoomScrollPane and * the BirdsEyeVisualizationViewer * @param e the change event */ void stateChanged(ChangeEvent e); /** * Sets the showing Renderer to be the input Renderer. Also * tells the Renderer to refer to this instance * as a PickedKey. (Because Renderers maintain a small * amount of state, such as the PickedKey, it is important * to create a separate instance for each VV instance.) * @param r the renderer to use */ void setRenderer(Renderer r); /** * @return the renderer used by this instance. */ Renderer getRenderer(); /** * Replaces the current graph layout with {@code layout}. * @param layout the new layout to set */ void setGraphLayout(Layout layout); /** * @return the current graph layout. */ Layout getGraphLayout(); /** * Makes the component visible if {@code aFlag} is true, or invisible if false. * @param aFlag true iff the component should be visible * @see javax.swing.JComponent#setVisible(boolean) */ void setVisible(boolean aFlag); /** * @return the renderingHints */ Map getRenderingHints(); /** * @param renderingHints The renderingHints to set. */ void setRenderingHints(Map renderingHints); /** * @param paintable The paintable to add. */ void addPreRenderPaintable(Paintable paintable); /** * @param paintable The paintable to remove. */ void removePreRenderPaintable(Paintable paintable); /** * @param paintable The paintable to add. */ void addPostRenderPaintable(Paintable paintable); /** * @param paintable The paintable to remove. */ void removePostRenderPaintable(Paintable paintable); /** * Adds a ChangeListener. * @param l the listener to be added */ void addChangeListener(ChangeListener l); /** * Removes a ChangeListener. * @param l the listener to be removed */ void removeChangeListener(ChangeListener l); /** * Returns an array of all the ChangeListeners added * with addChangeListener(). * * @return all of the ChangeListeners added or an empty * array if no listeners have been added */ ChangeListener[] getChangeListeners(); /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created. * @see EventListenerList */ void fireStateChanged(); /** * @return the vertex PickedState instance */ PickedState getPickedVertexState(); /** * @return the edge PickedState instance */ PickedState getPickedEdgeState(); void setPickedVertexState(PickedState pickedVertexState); void setPickedEdgeState(PickedState pickedEdgeState); /** * @return the GraphElementAccessor */ GraphElementAccessor getPickSupport(); /** * @param pickSupport The pickSupport to set. */ void setPickSupport(GraphElementAccessor pickSupport); Point2D getCenter(); RenderContext getRenderContext(); void setRenderContext(RenderContext renderContext); void repaint(); /** * an interface for the preRender and postRender */ interface Paintable { public void paint(Graphics g); public boolean useTransform(); } }VisualizationViewer.java000066400000000000000000000137521276402340000345110ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization; import java.awt.Dimension; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelListener; import java.awt.geom.Point2D; import javax.swing.ToolTipManager; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.visualization.control.GraphMouseListener; import edu.uci.ics.jung.visualization.control.MouseListenerTranslator; /** * Adds mouse behaviors and tooltips to the graph visualization base class * * @author Joshua O'Madadhain * @author Tom Nelson * @author Danyel Fisher */ @SuppressWarnings("serial") public class VisualizationViewer extends BasicVisualizationServer { protected Function vertexToolTipTransformer; protected Function edgeToolTipTransformer; protected Function mouseEventToolTipTransformer; /** * provides MouseListener, MouseMotionListener, and MouseWheelListener * events to the graph */ protected GraphMouse graphMouse; protected MouseListener requestFocusListener = new MouseAdapter() { public void mouseClicked(MouseEvent e) { requestFocusInWindow(); } }; public VisualizationViewer(Layout layout) { this(new DefaultVisualizationModel(layout)); } public VisualizationViewer(Layout layout, Dimension preferredSize) { this(new DefaultVisualizationModel(layout, preferredSize), preferredSize); } public VisualizationViewer(VisualizationModel model) { this(model, new Dimension(600,600)); } public VisualizationViewer(VisualizationModel model, Dimension preferredSize) { super(model, preferredSize); setFocusable(true); addMouseListener(requestFocusListener); } /** * a setter for the GraphMouse. This will remove any * previous GraphMouse (including the one that * is added in the initMouseClicker method. * @param graphMouse new value */ public void setGraphMouse(GraphMouse graphMouse) { this.graphMouse = graphMouse; MouseListener[] ml = getMouseListeners(); for(int i=0; iGraphMouse */ public GraphMouse getGraphMouse() { return graphMouse; } /** * This is the interface for adding a mouse listener. The GEL * will be called back with mouse clicks on vertices. * @param gel the mouse listener to add */ public void addGraphMouseListener( GraphMouseListener gel ) { addMouseListener( new MouseListenerTranslator( gel, this )); } /** * Override to request focus on mouse enter, if a key listener is added * @see java.awt.Component#addKeyListener(java.awt.event.KeyListener) */ @Override public synchronized void addKeyListener(KeyListener l) { super.addKeyListener(l); } /** * @param edgeToolTipTransformer the edgeToolTipTransformer to set */ public void setEdgeToolTipTransformer( Function edgeToolTipTransformer) { this.edgeToolTipTransformer = edgeToolTipTransformer; ToolTipManager.sharedInstance().registerComponent(this); } /** * @param mouseEventToolTipTransformer the mouseEventToolTipTransformer to set */ public void setMouseEventToolTipTransformer( Function mouseEventToolTipTransformer) { this.mouseEventToolTipTransformer = mouseEventToolTipTransformer; ToolTipManager.sharedInstance().registerComponent(this); } /** * @param vertexToolTipTransformer the vertexToolTipTransformer to set */ public void setVertexToolTipTransformer( Function vertexToolTipTransformer) { this.vertexToolTipTransformer = vertexToolTipTransformer; ToolTipManager.sharedInstance().registerComponent(this); } /** * called by the superclass to display tooltips */ public String getToolTipText(MouseEvent event) { Layout layout = getGraphLayout(); Point2D p = null; if(vertexToolTipTransformer != null) { p = event.getPoint(); //renderContext.getBasicTransformer().inverseViewTransform(event.getPoint()); V vertex = getPickSupport().getVertex(layout, p.getX(), p.getY()); if(vertex != null) { return vertexToolTipTransformer.apply(vertex); } } if(edgeToolTipTransformer != null) { if(p == null) p = renderContext.getMultiLayerTransformer().inverseTransform(Layer.VIEW, event.getPoint()); E edge = getPickSupport().getEdge(layout, p.getX(), p.getY()); if(edge != null) { return edgeToolTipTransformer.apply(edge); } } if(mouseEventToolTipTransformer != null) { return mouseEventToolTipTransformer.apply(event); } return super.getToolTipText(event); } /** * a convenience type to represent a class that * processes all types of mouse events for the graph */ public interface GraphMouse extends MouseListener, MouseMotionListener, MouseWheelListener {} } jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/000077500000000000000000000000001276402340000322275ustar00rootroot00000000000000AnnotatingGraphMousePlugin.java000066400000000000000000000211431276402340000402700ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.visualization.annotations; import java.awt.Color; import java.awt.Cursor; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RectangularShape; import javax.swing.JComponent; import javax.swing.JOptionPane; import edu.uci.ics.jung.visualization.MultiLayerTransformer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.VisualizationServer.Paintable; import edu.uci.ics.jung.visualization.control.AbstractGraphMousePlugin; /** * AnnotatingGraphMousePlugin can create Shape and Text annotations * in a layer of the graph visualization. * * @author Tom Nelson */ public class AnnotatingGraphMousePlugin extends AbstractGraphMousePlugin implements MouseListener, MouseMotionListener { /** * additional modifiers for the action of adding to an existing * selection */ protected int additionalModifiers; /** * used to draw a Shape annotation */ protected RectangularShape rectangularShape = new Rectangle2D.Float(); /** * the Paintable for the Shape annotation */ protected Paintable lensPaintable; /** * a Paintable to store all Annotations */ protected AnnotationManager annotationManager; /** * color for annotations */ protected Color annotationColor = Color.cyan; /** * layer for annotations */ protected Annotation.Layer layer = Annotation.Layer.LOWER; protected boolean fill; /** * holds rendering transforms */ protected MultiLayerTransformer basicTransformer; /** * holds rendering settings */ protected RenderContext rc; /** * set to true when the AnnotationPaintable has been * added to the view component */ protected boolean added = false; /** * Create an instance with defaults for primary (button 1) and secondary * (button 1 + shift) selection. * @param rc the RenderContext for which this plugin will be used */ public AnnotatingGraphMousePlugin(RenderContext rc) { this(rc, InputEvent.BUTTON1_MASK, InputEvent.BUTTON1_MASK | InputEvent.SHIFT_MASK); } /** * Create an instance with the specified primary and secondary selection * mechanisms. * @param rc the RenderContext for which this plugin will be used * @param selectionModifiers for primary selection * @param additionalModifiers for additional selection */ public AnnotatingGraphMousePlugin(RenderContext rc, int selectionModifiers, int additionalModifiers) { super(selectionModifiers); this.rc = rc; this.basicTransformer = rc.getMultiLayerTransformer(); this.additionalModifiers = additionalModifiers; this.lensPaintable = new LensPaintable(); this.annotationManager = new AnnotationManager(rc); this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); } /** * @return Returns the lensColor. */ public Color getAnnotationColor() { return annotationColor; } /** * @param lensColor The lensColor to set. */ public void setAnnotationColor(Color lensColor) { this.annotationColor = lensColor; } /** * the Paintable that draws a Shape annotation * only while it is being created * */ class LensPaintable implements Paintable { public void paint(Graphics g) { Color oldColor = g.getColor(); g.setColor(annotationColor); ((Graphics2D)g).draw(rectangularShape); g.setColor(oldColor); } public boolean useTransform() { return false; } } /** * Sets the location for an Annotation. * Will either pop up a dialog to prompt for text * input for a text annotation, or begin the process * of drawing a Shape annotation * * @param e the event */ @SuppressWarnings("unchecked") public void mousePressed(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); down = e.getPoint(); if(added == false) { vv.addPreRenderPaintable(annotationManager.getLowerAnnotationPaintable()); vv.addPostRenderPaintable(annotationManager.getUpperAnnotationPaintable()); added = true; } if(e.isPopupTrigger()) { String annotationString = JOptionPane.showInputDialog(vv,"Annotation:"); if(annotationString != null && annotationString.length() > 0) { Point2D p = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(down); Annotation annotation = new Annotation(annotationString, layer, annotationColor, fill, p); annotationManager.add(layer, annotation); } } else if(e.getModifiers() == additionalModifiers) { Annotation annotation = annotationManager.getAnnotation(down); annotationManager.remove(annotation); } else if(e.getModifiers() == modifiers) { rectangularShape.setFrameFromDiagonal(down,down); vv.addPostRenderPaintable(lensPaintable); } vv.repaint(); } /** * Completes the process of adding a Shape annotation * and removed the transient paintable * */ @SuppressWarnings("unchecked") public void mouseReleased(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); if(e.isPopupTrigger()) { String annotationString = JOptionPane.showInputDialog(vv,"Annotation:"); if(annotationString != null && annotationString.length() > 0) { Point2D p = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(down); Annotation annotation = new Annotation(annotationString, layer, annotationColor, fill, p); annotationManager.add(layer, annotation); } } else if(e.getModifiers() == modifiers) { if(down != null) { Point2D out = e.getPoint(); RectangularShape arect = (RectangularShape)rectangularShape.clone(); arect.setFrameFromDiagonal(down,out); Shape s = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(arect); Annotation annotation = new Annotation(s, layer, annotationColor, fill, out); annotationManager.add(layer, annotation); } } down = null; vv.removePostRenderPaintable(lensPaintable); vv.repaint(); } /** * Draws the transient Paintable that will become * a Shape annotation when the mouse button is * released * */ @SuppressWarnings("unchecked") public void mouseDragged(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); Point2D out = e.getPoint(); if(e.getModifiers() == additionalModifiers) { rectangularShape.setFrameFromDiagonal(down,out); } else if(e.getModifiers() == modifiers) { rectangularShape.setFrameFromDiagonal(down,out); } rectangularShape.setFrameFromDiagonal(down,out); vv.repaint(); } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { JComponent c = (JComponent)e.getSource(); c.setCursor(cursor); } public void mouseExited(MouseEvent e) { JComponent c = (JComponent)e.getSource(); c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } public void mouseMoved(MouseEvent e) { } /** * @return the rect */ public RectangularShape getRectangularShape() { return rectangularShape; } /** * @param rect the rect to set */ public void setRectangularShape(RectangularShape rect) { this.rectangularShape = rect; } /** * @return the layer */ public Annotation.Layer getLayer() { return layer; } /** * @param layer the layer to set */ public void setLayer(Annotation.Layer layer) { this.layer = layer; } /** * @return the fill */ public boolean isFill() { return fill; } /** * @param fill the fill to set */ public void setFill(boolean fill) { this.fill = fill; } } AnnotatingModalGraphMouse.java000066400000000000000000000177111276402340000400740ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * */ package edu.uci.ics.jung.visualization.annotations; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.ItemSelectable; import java.awt.event.InputEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import javax.swing.ButtonGroup; import javax.swing.Icon; import javax.swing.JComboBox; import javax.swing.JMenu; import javax.swing.JRadioButtonMenuItem; import javax.swing.plaf.basic.BasicIconFactory; import edu.uci.ics.jung.visualization.MultiLayerTransformer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.control.AbstractModalGraphMouse; import edu.uci.ics.jung.visualization.control.AnimatedPickingGraphMousePlugin; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.PickingGraphMousePlugin; import edu.uci.ics.jung.visualization.control.RotatingGraphMousePlugin; import edu.uci.ics.jung.visualization.control.ScalingGraphMousePlugin; import edu.uci.ics.jung.visualization.control.ShearingGraphMousePlugin; import edu.uci.ics.jung.visualization.control.TranslatingGraphMousePlugin; /** * a graph mouse that supplies an annotations mode * * @author Tom Nelson - tomnelson@dev.java.net * * @param the vertex type * @param the edge type */ public class AnnotatingModalGraphMouse extends AbstractModalGraphMouse implements ModalGraphMouse, ItemSelectable { protected AnnotatingGraphMousePlugin annotatingPlugin; protected MultiLayerTransformer basicTransformer; protected RenderContext rc; /** * Create an instance with default values for scale in (1.1) and scale out (1/1.1). * * @param rc the RenderContext for which this class will be used * @param annotatingPlugin the plugin used by this class for annotating */ public AnnotatingModalGraphMouse(RenderContext rc, AnnotatingGraphMousePlugin annotatingPlugin) { this(rc, annotatingPlugin, 1.1f, 1/1.1f); } /** * Create an instance with the specified scale in and scale out values. * * @param rc the RenderContext for which this class will be used * @param annotatingPlugin the plugin used by this class for annotating * @param in override value for scale in * @param out override value for scale out */ public AnnotatingModalGraphMouse(RenderContext rc, AnnotatingGraphMousePlugin annotatingPlugin, float in, float out) { super(in,out); this.rc = rc; this.basicTransformer = rc.getMultiLayerTransformer(); this.annotatingPlugin = annotatingPlugin; loadPlugins(); setModeKeyListener(new ModeKeyAdapter(this)); } /** * create the plugins, and load the plugins for TRANSFORMING mode * */ @Override protected void loadPlugins() { this.pickingPlugin = new PickingGraphMousePlugin(); this.animatedPickingPlugin = new AnimatedPickingGraphMousePlugin(); this.translatingPlugin = new TranslatingGraphMousePlugin(InputEvent.BUTTON1_MASK); this.scalingPlugin = new ScalingGraphMousePlugin(new CrossoverScalingControl(), 0, in, out); this.rotatingPlugin = new RotatingGraphMousePlugin(); this.shearingPlugin = new ShearingGraphMousePlugin(); add(scalingPlugin); setMode(Mode.TRANSFORMING); } /** * setter for the Mode. */ @Override public void setMode(Mode mode) { if(this.mode != mode) { fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, this.mode, ItemEvent.DESELECTED)); this.mode = mode; if(mode == Mode.TRANSFORMING) { setTransformingMode(); } else if(mode == Mode.PICKING) { setPickingMode(); } else if(mode == Mode.ANNOTATING) { setAnnotatingMode(); } if(modeBox != null) { modeBox.setSelectedItem(mode); } fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, mode, ItemEvent.SELECTED)); } } @Override protected void setPickingMode() { remove(translatingPlugin); remove(rotatingPlugin); remove(shearingPlugin); remove(annotatingPlugin); add(pickingPlugin); add(animatedPickingPlugin); } @Override protected void setTransformingMode() { remove(pickingPlugin); remove(animatedPickingPlugin); remove(annotatingPlugin); add(translatingPlugin); add(rotatingPlugin); add(shearingPlugin); } protected void setEditingMode() { remove(pickingPlugin); remove(animatedPickingPlugin); remove(translatingPlugin); remove(rotatingPlugin); remove(shearingPlugin); remove(annotatingPlugin); } protected void setAnnotatingMode() { remove(pickingPlugin); remove(animatedPickingPlugin); remove(translatingPlugin); remove(rotatingPlugin); remove(shearingPlugin); add(annotatingPlugin); } /** * @return Returns the modeBox. */ @Override public JComboBox getModeComboBox() { if(modeBox == null) { modeBox = new JComboBox( new Mode[]{Mode.TRANSFORMING, Mode.PICKING, Mode.ANNOTATING}); modeBox.addItemListener(getModeListener()); } modeBox.setSelectedItem(mode); return modeBox; } /** * create (if necessary) and return a menu that will change * the mode * @return the menu */ @Override public JMenu getModeMenu() { if(modeMenu == null) { modeMenu = new JMenu();// { Icon icon = BasicIconFactory.getMenuArrowIcon(); modeMenu.setIcon(BasicIconFactory.getMenuArrowIcon()); modeMenu.setPreferredSize(new Dimension(icon.getIconWidth()+10, icon.getIconHeight()+10)); final JRadioButtonMenuItem transformingButton = new JRadioButtonMenuItem(Mode.TRANSFORMING.toString()); transformingButton.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { setMode(Mode.TRANSFORMING); } }}); final JRadioButtonMenuItem pickingButton = new JRadioButtonMenuItem(Mode.PICKING.toString()); pickingButton.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { setMode(Mode.PICKING); } }}); ButtonGroup radio = new ButtonGroup(); radio.add(transformingButton); radio.add(pickingButton); transformingButton.setSelected(true); modeMenu.add(transformingButton); modeMenu.add(pickingButton); modeMenu.setToolTipText("Menu for setting Mouse Mode"); addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { if(e.getItem() == Mode.TRANSFORMING) { transformingButton.setSelected(true); } else if(e.getItem() == Mode.PICKING) { pickingButton.setSelected(true); } } }}); } return modeMenu; } public static class ModeKeyAdapter extends KeyAdapter { private char t = 't'; private char p = 'p'; private char a = 'a'; protected ModalGraphMouse graphMouse; public ModeKeyAdapter(ModalGraphMouse graphMouse) { this.graphMouse = graphMouse; } public ModeKeyAdapter(char t, char p, char a, ModalGraphMouse graphMouse) { this.t = t; this.p = p; this.a = a; this.graphMouse = graphMouse; } @Override public void keyTyped(KeyEvent event) { char keyChar = event.getKeyChar(); if(keyChar == t) { ((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); graphMouse.setMode(Mode.TRANSFORMING); } else if(keyChar == p) { ((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); graphMouse.setMode(Mode.PICKING); } else if(keyChar == a) { ((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); graphMouse.setMode(Mode.ANNOTATING); } } } } Annotation.java000066400000000000000000000035741276402340000351360ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * */ package edu.uci.ics.jung.visualization.annotations; import java.awt.Paint; import java.awt.geom.Point2D; /** * stores an annotation, either a shape or a string * * @author Tom Nelson - tomnelson@dev.java.net * * @param the type of the annotation object */ public class Annotation { protected T annotation; protected Paint paint; protected Point2D location; protected Layer layer; protected boolean fill; public static enum Layer { LOWER, UPPER } public Annotation(T annotation, Layer layer, Paint paint, boolean fill, Point2D location) { this.annotation = annotation; this.layer = layer; this.paint = paint; this.fill = fill; this.location = location; } /** * @return the annotation */ public T getAnnotation() { return annotation; } /** * @param annotation the annotation to set */ public void setAnnotation(T annotation) { this.annotation = annotation; } /** * @return the location */ public Point2D getLocation() { return location; } /** * @return the layer */ public Layer getLayer() { return layer; } /** * @param layer the layer to set */ public void setLayer(Layer layer) { this.layer = layer; } /** * @param location the location to set */ public void setLocation(Point2D location) { this.location = location; } /** * @return the paint */ public Paint getPaint() { return paint; } /** * @param paint the paint to set */ public void setPaint(Paint paint) { this.paint = paint; } /** * @return the fill */ public boolean isFill() { return fill; } /** * @param fill the fill to set */ public void setFill(boolean fill) { this.fill = fill; } } AnnotationControls.java000066400000000000000000000076221276402340000366600ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * */ package edu.uci.ics.jung.visualization.annotations; import java.awt.Color; import java.awt.Component; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RectangularShape; import java.awt.geom.RoundRectangle2D; import javax.swing.DefaultListCellRenderer; import javax.swing.JButton; import javax.swing.JColorChooser; import javax.swing.JComboBox; import javax.swing.JList; import javax.swing.JToggleButton; import javax.swing.JToolBar; /** * a collection of controls for annotations. * allows selection of colors, shapes, etc * @author Tom Nelson - tomnelson@dev.java.net * */ public class AnnotationControls { protected AnnotatingGraphMousePlugin annotatingPlugin; public AnnotationControls(AnnotatingGraphMousePlugin annotatingPlugin) { this.annotatingPlugin = annotatingPlugin; } @SuppressWarnings("serial") public JComboBox getShapeBox() { JComboBox shapeBox = new JComboBox( new Shape[] { new Rectangle2D.Double(), new RoundRectangle2D.Double(0,0,0,0,50,50), new Ellipse2D.Double() }); shapeBox.setRenderer(new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean hasFocus) { String valueString = value.toString(); valueString = valueString.substring(0,valueString.indexOf("2D")); valueString = valueString.substring(valueString.lastIndexOf('.')+1); return super.getListCellRendererComponent(list, valueString, index, isSelected, hasFocus); } }); shapeBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { annotatingPlugin.setRectangularShape((RectangularShape)e.getItem()); } }}); return shapeBox; } public JButton getColorChooserButton() { final JButton colorChooser = new JButton("Color"); colorChooser.setForeground(annotatingPlugin.getAnnotationColor()); colorChooser.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Color color = JColorChooser.showDialog(colorChooser, "Annotation Color", colorChooser.getForeground()); annotatingPlugin.setAnnotationColor(color); colorChooser.setForeground(color); }}); return colorChooser; } public JComboBox getLayerBox() { final JComboBox layerBox = new JComboBox( new Annotation.Layer[] { Annotation.Layer.LOWER, Annotation.Layer.UPPER }); layerBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { annotatingPlugin.setLayer((Annotation.Layer)e.getItem()); } }}); return layerBox; } public JToggleButton getFillButton() { JToggleButton fillButton = new JToggleButton("Fill"); fillButton.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { annotatingPlugin.setFill(e.getStateChange() == ItemEvent.SELECTED); }}); return fillButton; } public JToolBar getAnnotationsToolBar() { JToolBar toolBar = new JToolBar(); toolBar.add(this.getShapeBox()); toolBar.add(this.getColorChooserButton()); toolBar.add(this.getFillButton()); toolBar.add(this.getLayerBox()); return toolBar; } } AnnotationManager.java000066400000000000000000000122171276402340000364230ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * */ package edu.uci.ics.jung.visualization.annotations; import java.awt.Component; import java.awt.Dimension; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Collection; import java.util.HashSet; import java.util.Set; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.transform.AffineTransformer; import edu.uci.ics.jung.visualization.transform.LensTransformer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * handles the selection of annotations, and the support for the * tools to draw them at specific layers. * * @author Tom Nelson - tomnelson@dev.java.net * */ public class AnnotationManager { protected AnnotationRenderer annotationRenderer = new AnnotationRenderer(); protected AnnotationPaintable lowerAnnotationPaintable; protected AnnotationPaintable upperAnnotationPaintable; protected RenderContext rc; protected AffineTransformer transformer; public AnnotationManager(RenderContext rc) { this.rc = rc; this.lowerAnnotationPaintable = new AnnotationPaintable(rc, annotationRenderer); this.upperAnnotationPaintable = new AnnotationPaintable(rc, annotationRenderer); MutableTransformer mt = rc.getMultiLayerTransformer().getTransformer(Layer.LAYOUT); if(mt instanceof AffineTransformer) { transformer = (AffineTransformer)mt; } else if(mt instanceof LensTransformer) { transformer = (AffineTransformer)((LensTransformer)mt).getDelegate(); } } public AnnotationPaintable getAnnotationPaintable(Annotation.Layer layer) { if(layer == Annotation.Layer.LOWER) { return this.lowerAnnotationPaintable; } if(layer == Annotation.Layer.UPPER) { return this.upperAnnotationPaintable; } return null; } public void add(Annotation.Layer layer, Annotation annotation) { if(layer == Annotation.Layer.LOWER) { this.lowerAnnotationPaintable.add(annotation); } if(layer == Annotation.Layer.UPPER) { this.upperAnnotationPaintable.add(annotation); } } public void remove(Annotation annotation) { this.lowerAnnotationPaintable.remove(annotation); this.upperAnnotationPaintable.remove(annotation); } protected AnnotationPaintable getLowerAnnotationPaintable() { return lowerAnnotationPaintable; } protected AnnotationPaintable getUpperAnnotationPaintable() { return upperAnnotationPaintable; } public Annotation getAnnotation(Point2D p) { @SuppressWarnings("rawtypes") Set annotations = new HashSet(lowerAnnotationPaintable.getAnnotations()); annotations.addAll(upperAnnotationPaintable.getAnnotations()); return getAnnotation(p, annotations); } @SuppressWarnings("rawtypes") public Annotation getAnnotation(Point2D p, Collection annotations) { double closestDistance = Double.MAX_VALUE; Annotation closestAnnotation = null; for(Annotation annotation : annotations) { Object ann = annotation.getAnnotation(); if(ann instanceof Shape) { Point2D ip = rc.getMultiLayerTransformer().inverseTransform(p); Shape shape = (Shape)ann; if(shape.contains(ip)) { Rectangle2D shapeBounds = shape.getBounds2D(); Point2D shapeCenter = new Point2D.Double(shapeBounds.getCenterX(), shapeBounds.getCenterY()); double distanceSq = shapeCenter.distanceSq(ip); if(distanceSq < closestDistance) { closestDistance = distanceSq; closestAnnotation = annotation; } } } else if(ann instanceof String) { Point2D ip = rc.getMultiLayerTransformer().inverseTransform(Layer.VIEW, p); Point2D ap = annotation.getLocation(); String label = (String)ann; Component component = prepareRenderer(rc, annotationRenderer, label); AffineTransform base = new AffineTransform(transformer.getTransform()); double rotation = transformer.getRotation(); // unrotate the annotation AffineTransform unrotate = AffineTransform.getRotateInstance(-rotation, ap.getX(), ap.getY()); base.concatenate(unrotate); Dimension d = component.getPreferredSize(); Rectangle2D componentBounds = new Rectangle2D.Double(ap.getX(), ap.getY(), d.width, d.height); Shape componentBoundsShape = base.createTransformedShape(componentBounds); Point2D componentCenter = new Point2D.Double(componentBoundsShape.getBounds().getCenterX(), componentBoundsShape.getBounds().getCenterY()); if(componentBoundsShape.contains(ip)) { double distanceSq = componentCenter.distanceSq(ip); if(distanceSq < closestDistance) { closestDistance = distanceSq; closestAnnotation = annotation; } } } } return closestAnnotation; } public Component prepareRenderer(RenderContext rc, AnnotationRenderer annotationRenderer, Object value) { return annotationRenderer.getAnnotationRendererComponent(rc.getScreenDevice(), value); } } AnnotationPaintable.java000066400000000000000000000105741276402340000367540ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * */ package edu.uci.ics.jung.visualization.annotations; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.util.Collections; import java.util.HashSet; import java.util.Set; import javax.swing.JComponent; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.VisualizationServer.Paintable; import edu.uci.ics.jung.visualization.transform.AffineTransformer; import edu.uci.ics.jung.visualization.transform.LensTransformer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * handles the actual drawing of annotations * * @author Tom Nelson - tomnelson@dev.java.net * */ public class AnnotationPaintable implements Paintable { @SuppressWarnings("rawtypes") protected Set annotations = new HashSet(); protected AnnotationRenderer annotationRenderer; protected RenderContext rc; protected AffineTransformer transformer; public AnnotationPaintable(RenderContext rc, AnnotationRenderer annotationRenderer) { this.rc = rc; this.annotationRenderer = annotationRenderer; MutableTransformer mt = rc.getMultiLayerTransformer().getTransformer(Layer.LAYOUT); if(mt instanceof AffineTransformer) { transformer = (AffineTransformer)mt; } else if(mt instanceof LensTransformer) { transformer = (AffineTransformer)((LensTransformer)mt).getDelegate(); } } public void add(Annotation annotation) { annotations.add(annotation); } public void remove(Annotation annotation) { annotations.remove(annotation); } /** * @return the annotations */ @SuppressWarnings("rawtypes") public Set getAnnotations() { return Collections.unmodifiableSet(annotations); } public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; Color oldColor = g.getColor(); for(Annotation annotation : annotations) { Object ann = annotation.getAnnotation(); if(ann instanceof Shape) { Shape shape = (Shape)ann; Paint paint = annotation.getPaint(); Shape s = transformer.transform(shape); g2d.setPaint(paint); if(annotation.isFill()) { g2d.fill(s); } else { g2d.draw(s); } } else if(ann instanceof String) { Point2D p = annotation.getLocation(); String label = (String)ann; Component component = prepareRenderer(rc, annotationRenderer, label); component.setForeground((Color)annotation.getPaint()); if(annotation.isFill()) { ((JComponent)component).setOpaque(true); component.setBackground((Color)annotation.getPaint()); component.setForeground(Color.black); } Dimension d = component.getPreferredSize(); AffineTransform old = g2d.getTransform(); AffineTransform base = new AffineTransform(old); AffineTransform xform = transformer.getTransform(); double rotation = transformer.getRotation(); // unrotate the annotation AffineTransform unrotate = AffineTransform.getRotateInstance(-rotation, p.getX(), p.getY()); base.concatenate(xform); base.concatenate(unrotate); g2d.setTransform(base); rc.getRendererPane().paintComponent(g, component, rc.getScreenDevice(), (int)p.getX(), (int)p.getY(), d.width, d.height, true); g2d.setTransform(old); } } g.setColor(oldColor); } public Component prepareRenderer(RenderContext rc, AnnotationRenderer annotationRenderer, Object value) { return annotationRenderer.getAnnotationRendererComponent(rc.getScreenDevice(), value); } public boolean useTransform() { return true; } } AnnotationRenderer.java000066400000000000000000000123651276402340000366230ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * */ package edu.uci.ics.jung.visualization.annotations; import java.awt.Color; import java.awt.Component; import java.awt.Rectangle; import java.io.Serializable; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; /** * AnnotationRenderer is similar to the cell renderers * used by the JTable and JTree JFC classes. * * @author Tom Nelson * * */ @SuppressWarnings("serial") public class AnnotationRenderer extends JLabel implements Serializable { protected static Border noFocusBorder = new EmptyBorder(0,0,0,0); /** * Creates a default table cell renderer. */ public AnnotationRenderer() { setOpaque(true); setBorder(noFocusBorder); } /** * Overrides JComponent.setForeground to assign * the unselected-foreground color to the specified color. * * @param c set the foreground color to this value */ @Override public void setForeground(Color c) { super.setForeground(c); } /** * Overrides JComponent.setBackground to assign * the unselected-background color to the specified color. * * @param c set the background color to this value */ @Override public void setBackground(Color c) { super.setBackground(c); } /** * Notification from the UIManager that the look and feel * has changed. * Replaces the current UI object with the latest version from the * UIManager. * * @see JComponent#updateUI */ @Override public void updateUI() { super.updateUI(); setForeground(null); setBackground(null); } /** * Returns the default label renderer. * * @param vv the VisualizationViewer to render on * @param value the value to assign to the label * @return the default label renderer */ public Component getAnnotationRendererComponent(JComponent vv, Object value) { super.setForeground(vv.getForeground()); super.setBackground(vv.getBackground()); setFont(vv.getFont()); setIcon(null); setBorder(noFocusBorder); setValue(value); return this; } /* * The following methods are overridden as a performance measure to * to prune code-paths are often called in the case of renders * but which we know are unnecessary. Great care should be taken * when writing your own renderer to weigh the benefits and * drawbacks of overriding methods like these. */ /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public boolean isOpaque() { Color back = getBackground(); Component p = getParent(); if (p != null) { p = p.getParent(); } boolean colorMatch = (back != null) && (p != null) && back.equals(p.getBackground()) && p.isOpaque(); return !colorMatch && super.isOpaque(); } /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void validate() {} /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void revalidate() {} /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void repaint(long tm, int x, int y, int width, int height) {} /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void repaint(Rectangle r) { } /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { // Strings get interned... if (propertyName=="text") { super.firePropertyChange(propertyName, oldValue, newValue); } } /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { } /** * Sets the String object for the cell being rendered to * value. * * @param value the string value for this cell; if value is * null it sets the text value to an empty string * @see JLabel#setText * */ protected void setValue(Object value) { setText((value == null) ? "" : value.toString()); } } package.html000066400000000000000000000005141276402340000344310ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations

        Classes which support creating visual annotations for graphs. jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/000077500000000000000000000000001276402340000313525ustar00rootroot00000000000000AbsoluteCrossoverScalingControl.java000066400000000000000000000040141276402340000404630ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.visualization.control; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationServer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * Scales to the absolute value passed as an argument. * It first resets the scaling Functions, then uses * the relative CrossoverScalingControl to achieve the * absolute value. * * @author Tom Nelson * */ public class AbsoluteCrossoverScalingControl extends CrossoverScalingControl implements ScalingControl { /** * Scale to the absolute value passed as 'amount'. * * @param vv the VisualizationServer used for rendering; provides the layout and view transformers. * @param amount the amount by which to scale * @param at the point of reference for scaling */ public void scale(VisualizationServer vv, float amount, Point2D at) { MutableTransformer layoutTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); MutableTransformer viewTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW); double modelScale = layoutTransformer.getScale(); double viewScale = viewTransformer.getScale(); double inverseModelScale = Math.sqrt(crossover)/modelScale; double inverseViewScale = Math.sqrt(crossover)/viewScale; Point2D transformedAt = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, at); // return the Functions to 1.0 layoutTransformer.scale(inverseModelScale, inverseModelScale, transformedAt); viewTransformer.scale(inverseViewScale, inverseViewScale, at); super.scale(vv, amount, at); } } AbstractGraphMousePlugin.java000066400000000000000000000036401276402340000370560ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 6, 2005 */ package edu.uci.ics.jung.visualization.control; import java.awt.Cursor; import java.awt.Point; import java.awt.event.MouseEvent; /** * a base class for GraphMousePlugin instances. Holds some members * common to all GraphMousePlugins * @author thomasnelson * */ public abstract class AbstractGraphMousePlugin implements GraphMousePlugin { /** * modifiers to compare against mouse event modifiers */ protected int modifiers; /** * the location in the View where the mouse was pressed */ protected Point down; /** * the special cursor that plugins may display */ protected Cursor cursor; /** * Creates an instance with the specified mouse event modifiers. * @param modifiers the mouse event modifiers to use */ public AbstractGraphMousePlugin(int modifiers) { this.modifiers = modifiers; } /** * getter for mouse modifiers */ public int getModifiers() { return modifiers; } /** * setter for mouse modifiers */ public void setModifiers(int modifiers) { this.modifiers = modifiers; } /** * check the mouse event modifiers against the * instance member modifiers. Default implementation * checks equality. Can be overridden to test with a mask */ public boolean checkModifiers(MouseEvent e) { return e.getModifiers() == modifiers; } /** * @return Returns the cursor. */ public Cursor getCursor() { return cursor; } /** * @param cursor The cursor to set. */ public void setCursor(Cursor cursor) { this.cursor = cursor; } } AbstractModalGraphMouse.java000066400000000000000000000226321276402340000366560ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.Dimension; import java.awt.ItemSelectable; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyListener; import javax.swing.ButtonGroup; import javax.swing.Icon; import javax.swing.JComboBox; import javax.swing.JMenu; import javax.swing.JRadioButtonMenuItem; import javax.swing.event.EventListenerList; import javax.swing.plaf.basic.BasicIconFactory; /** * * AbstractModalGraphMouse is a PluggableGraphMouse class that * manages a collection of plugins for picking and * transforming the graph. Additionally, it carries the notion * of a Mode: Picking or Translating. Switching between modes * allows for a more natural choice of mouse modifiers to * be used for the various plugins. The default modifiers are * intended to mimick those of mainstream software applications * in order to be intuitive to users. * * To change between modes, two different controls are offered, * a combo box and a menu system. These controls are lazily created * in their respective 'getter' methods so they don't impact * code that does not intend to use them. * The menu control can be placed in an unused corner of the * GraphZoomScrollPane, which is a common location for mouse * mode selection menus in mainstream applications. * * Users must implement the loadPlugins() method to create and * install the GraphMousePlugins. The order of the plugins is * important, as they are evaluated against the mask parameters * in the order that they are added. * * @author Tom Nelson */ public abstract class AbstractModalGraphMouse extends PluggableGraphMouse implements ModalGraphMouse, ItemSelectable { /** * used by the scaling plugins for zoom in */ protected float in; /** * used by the scaling plugins for zoom out */ protected float out; /** * a listener for mode changes */ protected ItemListener modeListener; /** * a JComboBox control available to set the mode */ protected JComboBox modeBox; /** * a menu available to set the mode */ protected JMenu modeMenu; /** * the current mode */ protected Mode mode; /** * listeners for mode changes */ protected EventListenerList listenerList = new EventListenerList(); protected GraphMousePlugin pickingPlugin; protected GraphMousePlugin translatingPlugin; protected GraphMousePlugin animatedPickingPlugin; protected GraphMousePlugin scalingPlugin; protected GraphMousePlugin rotatingPlugin; protected GraphMousePlugin shearingPlugin; protected KeyListener modeKeyListener; protected AbstractModalGraphMouse(float in, float out) { this.in = in; this.out = out; } /** * create the plugins, and load the plugins for TRANSFORMING mode * */ protected abstract void loadPlugins(); /** * setter for the Mode. */ public void setMode(Mode mode) { if(this.mode != mode) { fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, this.mode, ItemEvent.DESELECTED)); this.mode = mode; if(mode == Mode.TRANSFORMING) { setTransformingMode(); } else if(mode == Mode.PICKING) { setPickingMode(); } if(modeBox != null) { modeBox.setSelectedItem(mode); } fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, mode, ItemEvent.SELECTED)); } } /* (non-Javadoc) * @see edu.uci.ics.jung.visualization.control.ModalGraphMouse#setPickingMode() */ protected void setPickingMode() { remove(translatingPlugin); remove(rotatingPlugin); remove(shearingPlugin); add(pickingPlugin); add(animatedPickingPlugin); } /* (non-Javadoc) * @see edu.uci.ics.jung.visualization.control.ModalGraphMouse#setTransformingMode() */ protected void setTransformingMode() { remove(pickingPlugin); remove(animatedPickingPlugin); add(translatingPlugin); add(rotatingPlugin); add(shearingPlugin); } /** * @param zoomAtMouse The zoomAtMouse to set. */ public void setZoomAtMouse(boolean zoomAtMouse) { ((ScalingGraphMousePlugin) scalingPlugin).setZoomAtMouse(zoomAtMouse); } /** * listener to set the mode from an external event source */ class ModeListener implements ItemListener { public void itemStateChanged(ItemEvent e) { setMode((Mode) e.getItem()); } } /* (non-Javadoc) * @see edu.uci.ics.jung.visualization.control.ModalGraphMouse#getModeListener() */ public ItemListener getModeListener() { if (modeListener == null) { modeListener = new ModeListener(); } return modeListener; } /** * @return the modeKeyListener */ public KeyListener getModeKeyListener() { return modeKeyListener; } /** * @param modeKeyListener the modeKeyListener to set */ public void setModeKeyListener(KeyListener modeKeyListener) { this.modeKeyListener = modeKeyListener; } /** * @return Returns the modeBox. */ public JComboBox getModeComboBox() { if(modeBox == null) { modeBox = new JComboBox(new Mode[]{Mode.TRANSFORMING, Mode.PICKING}); modeBox.addItemListener(getModeListener()); } modeBox.setSelectedItem(mode); return modeBox; } /** * create (if necessary) and return a menu that will change * the mode * @return the menu */ public JMenu getModeMenu() { if(modeMenu == null) { modeMenu = new JMenu();// { Icon icon = BasicIconFactory.getMenuArrowIcon(); modeMenu.setIcon(BasicIconFactory.getMenuArrowIcon()); modeMenu.setPreferredSize(new Dimension(icon.getIconWidth()+10, icon.getIconHeight()+10)); final JRadioButtonMenuItem transformingButton = new JRadioButtonMenuItem(Mode.TRANSFORMING.toString()); transformingButton.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { setMode(Mode.TRANSFORMING); } }}); final JRadioButtonMenuItem pickingButton = new JRadioButtonMenuItem(Mode.PICKING.toString()); pickingButton.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { setMode(Mode.PICKING); } }}); ButtonGroup radio = new ButtonGroup(); radio.add(transformingButton); radio.add(pickingButton); transformingButton.setSelected(true); modeMenu.add(transformingButton); modeMenu.add(pickingButton); modeMenu.setToolTipText("Menu for setting Mouse Mode"); addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { if(e.getItem() == Mode.TRANSFORMING) { transformingButton.setSelected(true); } else if(e.getItem() == Mode.PICKING) { pickingButton.setSelected(true); } } }}); } return modeMenu; } /** * add a listener for mode changes */ public void addItemListener(ItemListener aListener) { listenerList.add(ItemListener.class,aListener); } /** * remove a listener for mode changes */ public void removeItemListener(ItemListener aListener) { listenerList.remove(ItemListener.class,aListener); } /** * Returns an array of all the ItemListeners added * to this JComboBox with addItemListener(). * * @return all of the ItemListeners added or an empty * array if no listeners have been added * @since 1.4 */ public ItemListener[] getItemListeners() { return listenerList.getListeners(ItemListener.class); } public Object[] getSelectedObjects() { if ( mode == null ) return new Object[0]; else { Object result[] = new Object[1]; result[0] = mode; return result; } } /** * Notifies all listeners that have registered interest for * notification on this event type. * @param e the event of interest * * @see EventListenerList */ protected void fireItemStateChanged(ItemEvent e) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for ( int i = listeners.length-2; i>=0; i-=2 ) { if ( listeners[i]==ItemListener.class ) { ((ItemListener)listeners[i+1]).itemStateChanged(e); } } } } AbstractPopupGraphMousePlugin.java000066400000000000000000000024161276402340000401020ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.visualization.control; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; public abstract class AbstractPopupGraphMousePlugin extends AbstractGraphMousePlugin implements MouseListener { public AbstractPopupGraphMousePlugin() { this(MouseEvent.BUTTON3_MASK); } public AbstractPopupGraphMousePlugin(int modifiers) { super(modifiers); } public void mousePressed(MouseEvent e) { if(e.isPopupTrigger()) { handlePopup(e); e.consume(); } } /** * if this is the popup trigger, process here, otherwise * defer to the superclass */ public void mouseReleased(MouseEvent e) { if(e.isPopupTrigger()) { handlePopup(e); e.consume(); } } protected abstract void handlePopup(MouseEvent e); public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } } AnimatedPickingGraphMousePlugin.java000066400000000000000000000111261276402340000403400ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.Cursor; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Point2D; import javax.swing.JComponent; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.picking.PickedState; /** * AnimatedPickingGraphMousePlugin supports the picking of one Graph * Vertex. When the mouse is released, the graph is translated so that * the picked Vertex is moved to the center of the view. This translation * is conducted in an animation Thread so that the graph slides to its * new position * * @author Tom Nelson */ public class AnimatedPickingGraphMousePlugin extends AbstractGraphMousePlugin implements MouseListener, MouseMotionListener { /** * the picked Vertex */ protected V vertex; /** * Creates an instance with default modifiers of BUTTON1_MASK and CTRL_MASK */ public AnimatedPickingGraphMousePlugin() { this(InputEvent.BUTTON1_MASK | InputEvent.CTRL_MASK); } /** * Creates an instance with the specified mouse event modifiers. * @param selectionModifiers the mouse event modifiers to use. */ public AnimatedPickingGraphMousePlugin(int selectionModifiers) { super(selectionModifiers); this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); } /** * If the event occurs on a Vertex, pick that single Vertex * @param e the event */ @SuppressWarnings("unchecked") public void mousePressed(MouseEvent e) { if (e.getModifiers() == modifiers) { VisualizationViewer vv = (VisualizationViewer) e.getSource(); GraphElementAccessor pickSupport = vv.getPickSupport(); PickedState pickedVertexState = vv.getPickedVertexState(); Layout layout = vv.getGraphLayout(); if (pickSupport != null && pickedVertexState != null) { // p is the screen point for the mouse event Point2D p = e.getPoint(); vertex = pickSupport.getVertex(layout, p.getX(), p.getY()); if (vertex != null) { if (pickedVertexState.isPicked(vertex) == false) { pickedVertexState.clear(); pickedVertexState.pick(vertex, true); } } } e.consume(); } } /** * If a Vertex was picked in the mousePressed event, start a Thread * to animate the translation of the graph so that the picked Vertex * moves to the center of the view * * @param e the event */ @SuppressWarnings("unchecked") public void mouseReleased(MouseEvent e) { if (e.getModifiers() == modifiers) { final VisualizationViewer vv = (VisualizationViewer) e.getSource(); Point2D newCenter = null; if (vertex != null) { // center the picked vertex Layout layout = vv.getGraphLayout(); newCenter = layout.apply(vertex); } else { // they did not pick a vertex to center, so // just center the graph newCenter = vv.getCenter(); } Point2D lvc = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(vv.getCenter()); final double dx = (lvc.getX() - newCenter.getX()) / 10; final double dy = (lvc.getY() - newCenter.getY()) / 10; Runnable animator = new Runnable() { public void run() { for (int i = 0; i < 10; i++) { vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).translate(dx, dy); try { Thread.sleep(100); } catch (InterruptedException ex) { } } } }; Thread thread = new Thread(animator); thread.start(); } } public void mouseClicked(MouseEvent e) { } /** * show a special cursor while the mouse is inside the window */ public void mouseEntered(MouseEvent e) { JComponent c = (JComponent)e.getSource(); c.setCursor(cursor); } /** * revert to the default cursor when the mouse leaves this window */ public void mouseExited(MouseEvent e) { JComponent c = (JComponent)e.getSource(); c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } public void mouseMoved(MouseEvent e) { } public void mouseDragged(MouseEvent arg0) { } } CrossoverScalingControl.java000066400000000000000000000061641276402340000367740ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationServer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * A scaling control that has a crossover point. * When the overall scale of the view and * model is less than the crossover point, the scaling is applied * to the view's transform and the graph nodes, labels, etc grow * smaller. This preserves the overall shape of the graph. * When the scale is larger than the crossover, the scaling is * applied to the graph layout. The graph spreads out, but the * vertices and labels grow no larger than their original size. * * @author Tom Nelson */ public class CrossoverScalingControl implements ScalingControl { /** * Point where scale crosses over from view to layout. */ protected double crossover = 1.0; /** * Sets the crossover point to the specified value. * @param crossover the crossover point to use (defaults to 1.0) */ public void setCrossover(double crossover) { this.crossover = crossover; } /** * @return the current crossover value */ public double getCrossover() { return crossover; } public void scale(VisualizationServer vv, float amount, Point2D at) { MutableTransformer layoutTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); MutableTransformer viewTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW); double modelScale = layoutTransformer.getScale(); double viewScale = viewTransformer.getScale(); double inverseModelScale = Math.sqrt(crossover)/modelScale; double inverseViewScale = Math.sqrt(crossover)/viewScale; double scale = modelScale * viewScale; Point2D transformedAt = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, at); if((scale*amount - crossover)*(scale*amount - crossover) < 0.001) { // close to the control point, return both Functions to a scale of sqrt crossover value layoutTransformer.scale(inverseModelScale, inverseModelScale, transformedAt); viewTransformer.scale(inverseViewScale, inverseViewScale, at); } else if(scale*amount < crossover) { // scale the viewTransformer, return the layoutTransformer to sqrt crossover value viewTransformer.scale(amount, amount, at); layoutTransformer.scale(inverseModelScale, inverseModelScale, transformedAt); } else { // scale the layoutTransformer, return the viewTransformer to crossover value layoutTransformer.scale(amount, amount, transformedAt); viewTransformer.scale(inverseViewScale, inverseViewScale, at); } vv.repaint(); } } CubicCurveEdgeEffects.java000066400000000000000000000105601276402340000362570ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/controlpackage edu.uci.ics.jung.visualization.control; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.CubicCurve2D; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.BasicVisualizationServer; import edu.uci.ics.jung.visualization.VisualizationServer; import edu.uci.ics.jung.visualization.util.ArrowFactory; public class CubicCurveEdgeEffects implements EdgeEffects { protected CubicCurve2D rawEdge = new CubicCurve2D.Float(); protected Shape edgeShape; protected Shape rawArrowShape; protected Shape arrowShape; protected VisualizationServer.Paintable edgePaintable; protected VisualizationServer.Paintable arrowPaintable; public CubicCurveEdgeEffects() { this.rawEdge.setCurve(0.0f, 0.0f, 0.33f, 100, 0.66f, -50, 1.0f, 0.0f); rawArrowShape = ArrowFactory.getNotchedArrow(20, 16, 8); this.edgePaintable = new EdgePaintable(); this.arrowPaintable = new ArrowPaintable(); } // @Override public void startEdgeEffects(BasicVisualizationServer vv, Point2D down, Point2D out) { transformEdgeShape(down, out); vv.addPostRenderPaintable(edgePaintable); } // @Override public void midEdgeEffects(BasicVisualizationServer vv, Point2D down, Point2D out) { transformEdgeShape(down, out); } // @Override public void endEdgeEffects(BasicVisualizationServer vv) { vv.removePostRenderPaintable(edgePaintable); } // @Override public void startArrowEffects(BasicVisualizationServer vv, Point2D down, Point2D out) { transformArrowShape(down, out); vv.addPostRenderPaintable(arrowPaintable); } // @Override public void midArrowEffects(BasicVisualizationServer vv, Point2D down, Point2D out) { transformArrowShape(down, out); } // @Override public void endArrowEffects(BasicVisualizationServer vv) { vv.removePostRenderPaintable(arrowPaintable); } /** * code lifted from PluggableRenderer to move an edge shape into an * arbitrary position */ private void transformEdgeShape(Point2D down, Point2D out) { float x1 = (float) down.getX(); float y1 = (float) down.getY(); float x2 = (float) out.getX(); float y2 = (float) out.getY(); AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1); float dx = x2-x1; float dy = y2-y1; float thetaRadians = (float) Math.atan2(dy, dx); xform.rotate(thetaRadians); float dist = (float) Math.sqrt(dx*dx + dy*dy); xform.scale(dist / rawEdge.getBounds().getWidth(), 1.0); edgeShape = xform.createTransformedShape(rawEdge); } private void transformArrowShape(Point2D down, Point2D out) { float x1 = (float) down.getX(); float y1 = (float) down.getY(); float x2 = (float) out.getX(); float y2 = (float) out.getY(); AffineTransform xform = AffineTransform.getTranslateInstance(x2, y2); float dx = x2-x1; float dy = y2-y1; float thetaRadians = (float) Math.atan2(dy, dx); xform.rotate(thetaRadians); arrowShape = xform.createTransformedShape(rawArrowShape); } /** * Used for the edge creation visual effect during mouse drag */ class EdgePaintable implements VisualizationServer.Paintable { public void paint(Graphics g) { if(edgeShape != null) { Color oldColor = g.getColor(); g.setColor(Color.black); ((Graphics2D)g).draw(edgeShape); g.setColor(oldColor); } } public boolean useTransform() { return false; } } /** * Used for the directed edge creation visual effect during mouse drag */ class ArrowPaintable implements VisualizationServer.Paintable { public void paint(Graphics g) { if(arrowShape != null) { Color oldColor = g.getColor(); g.setColor(Color.black); ((Graphics2D)g).fill(arrowShape); g.setColor(oldColor); } } public boolean useTransform() { return false; } } } DefaultModalGraphMouse.java000066400000000000000000000066431276402340000365030ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.Component; import java.awt.Cursor; import java.awt.ItemSelectable; import java.awt.event.InputEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; /** * * DefaultModalGraphMouse is a PluggableGraphMouse class that * pre-installs a large collection of plugins for picking and * transforming the graph. Additionally, it carries the notion * of a Mode: Picking or Translating. Switching between modes * allows for a more natural choice of mouse modifiers to * be used for the various plugins. The default modifiers are * intended to mimick those of mainstream software applications * in order to be intuitive to users. * * To change between modes, two different controls are offered, * a combo box and a menu system. These controls are lazily created * in their respective 'getter' methods so they don't impact * code that does not intend to use them. * The menu control can be placed in an unused corner of the * GraphZoomScrollPane, which is a common location for mouse * mode selection menus in mainstream applications. * * @author Tom Nelson */ public class DefaultModalGraphMouse extends AbstractModalGraphMouse implements ModalGraphMouse, ItemSelectable { /** * create an instance with default values * */ public DefaultModalGraphMouse() { this(1.1f, 1/1.1f); } /** * create an instance with passed values * @param in override value for scale in * @param out override value for scale out */ public DefaultModalGraphMouse(float in, float out) { super(in,out); loadPlugins(); setModeKeyListener(new ModeKeyAdapter(this)); } /** * create the plugins, and load the plugins for TRANSFORMING mode * */ @Override protected void loadPlugins() { pickingPlugin = new PickingGraphMousePlugin(); animatedPickingPlugin = new AnimatedPickingGraphMousePlugin(); translatingPlugin = new TranslatingGraphMousePlugin(InputEvent.BUTTON1_MASK); scalingPlugin = new ScalingGraphMousePlugin(new CrossoverScalingControl(), 0, in, out); rotatingPlugin = new RotatingGraphMousePlugin(); shearingPlugin = new ShearingGraphMousePlugin(); add(scalingPlugin); setMode(Mode.TRANSFORMING); } public static class ModeKeyAdapter extends KeyAdapter { private char t = 't'; private char p = 'p'; protected ModalGraphMouse graphMouse; public ModeKeyAdapter(ModalGraphMouse graphMouse) { this.graphMouse = graphMouse; } public ModeKeyAdapter(char t, char p, ModalGraphMouse graphMouse) { this.t = t; this.p = p; this.graphMouse = graphMouse; } @Override public void keyTyped(KeyEvent event) { char keyChar = event.getKeyChar(); if(keyChar == t) { ((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); graphMouse.setMode(Mode.TRANSFORMING); } else if(keyChar == p) { ((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); graphMouse.setMode(Mode.PICKING); } } } } EdgeEffects.java000066400000000000000000000012531276402340000343030ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/controlpackage edu.uci.ics.jung.visualization.control; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.BasicVisualizationServer; public interface EdgeEffects { void startEdgeEffects(BasicVisualizationServer vv, Point2D down, Point2D out); void midEdgeEffects(BasicVisualizationServer vv, Point2D down, Point2D out); void endEdgeEffects(BasicVisualizationServer vv); void startArrowEffects(BasicVisualizationServer vv, Point2D down, Point2D out); void midArrowEffects(BasicVisualizationServer vv, Point2D down, Point2D out); void endArrowEffects(BasicVisualizationServer vv); } EdgeSupport.java000066400000000000000000000013351276402340000344010ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/controlpackage edu.uci.ics.jung.visualization.control; import java.awt.geom.Point2D; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.BasicVisualizationServer; /** * interface to support the creation of new edges by the EditingGraphMousePlugin * SimpleEdgeSupport is a sample implementation * @author tanelso * * @param the vertex type * @param the edge type */ public interface EdgeSupport { void startEdgeCreate(BasicVisualizationServer vv, V startVertex, Point2D startPoint, EdgeType edgeType); void midEdgeCreate(BasicVisualizationServer vv, Point2D midPoint); void endEdgeCreate(BasicVisualizationServer vv, V endVertex); } EditingGraphMousePlugin.java000066400000000000000000000147101276402340000366760ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/controlpackage edu.uci.ics.jung.visualization.control; import java.awt.Cursor; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Point2D; import javax.swing.JComponent; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.VisualizationViewer; /** * A plugin that can create vertices, undirected edges, and directed edges * using mouse gestures. * * vertexSupport and edgeSupport member classes are responsible for actually * creating the new graph elements, and for repainting the view when changes * were made. * * @author Tom Nelson * */ public class EditingGraphMousePlugin extends AbstractGraphMousePlugin implements MouseListener, MouseMotionListener { protected VertexSupport vertexSupport; protected EdgeSupport edgeSupport; private Creating createMode = Creating.UNDETERMINED; private enum Creating { EDGE, VERTEX, UNDETERMINED } /** * Creates an instance and prepares shapes for visual effects, using the default modifiers * of BUTTON1_MASK. * @param vertexFactory for creating vertices * @param edgeFactory for creating edges */ public EditingGraphMousePlugin(Supplier vertexFactory, Supplier edgeFactory) { this(MouseEvent.BUTTON1_MASK, vertexFactory, edgeFactory); } /** * Creates an instance and prepares shapes for visual effects. * @param modifiers the mouse event modifiers to use * @param vertexFactory for creating vertices * @param edgeFactory for creating edges */ public EditingGraphMousePlugin(int modifiers, Supplier vertexFactory, Supplier edgeFactory) { super(modifiers); this.cursor = Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR); this.vertexSupport = new SimpleVertexSupport(vertexFactory); this.edgeSupport = new SimpleEdgeSupport(edgeFactory); } /** * Overridden to be more flexible, and pass events with * key combinations. The default responds to both ButtonOne * and ButtonOne+Shift */ @Override public boolean checkModifiers(MouseEvent e) { return (e.getModifiers() & modifiers) != 0; } /** * If the mouse is pressed in an empty area, create a new vertex there. * If the mouse is pressed on an existing vertex, prepare to create * an edge from that vertex to another */ @SuppressWarnings("unchecked") public void mousePressed(MouseEvent e) { if(checkModifiers(e)) { final VisualizationViewer vv = (VisualizationViewer)e.getSource(); final Point2D p = e.getPoint(); GraphElementAccessor pickSupport = vv.getPickSupport(); if(pickSupport != null) { final V vertex = pickSupport.getVertex(vv.getModel().getGraphLayout(), p.getX(), p.getY()); if(vertex != null) { // get ready to make an edge this.createMode = Creating.EDGE; Graph graph = vv.getModel().getGraphLayout().getGraph(); // set default edge type EdgeType edgeType = (graph instanceof DirectedGraph) ? EdgeType.DIRECTED : EdgeType.UNDIRECTED; if((e.getModifiers() & MouseEvent.SHIFT_MASK) != 0 && graph instanceof UndirectedGraph == false) { edgeType = EdgeType.DIRECTED; } edgeSupport.startEdgeCreate(vv, vertex, e.getPoint(), edgeType); } else { // make a new vertex this.createMode = Creating.VERTEX; vertexSupport.startVertexCreate(vv, e.getPoint()); } } } } /** * If startVertex is non-null, and the mouse is released over an * existing vertex, create an undirected edge from startVertex to * the vertex under the mouse pointer. If shift was also pressed, * create a directed edge instead. */ @SuppressWarnings("unchecked") public void mouseReleased(MouseEvent e) { if(checkModifiers(e)) { final VisualizationViewer vv = (VisualizationViewer)e.getSource(); final Point2D p = e.getPoint(); Layout layout = vv.getGraphLayout(); if(createMode == Creating.EDGE) { GraphElementAccessor pickSupport = vv.getPickSupport(); V vertex = null; if(pickSupport != null) { vertex = pickSupport.getVertex(layout, p.getX(), p.getY()); } edgeSupport.endEdgeCreate(vv, vertex); } else if(createMode == Creating.VERTEX){ vertexSupport.endVertexCreate(vv, e.getPoint()); } } createMode = Creating.UNDETERMINED; } /** * If startVertex is non-null, stretch an edge shape between * startVertex and the mouse pointer to simulate edge creation */ @SuppressWarnings("unchecked") public void mouseDragged(MouseEvent e) { if(checkModifiers(e)) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); if(createMode == Creating.EDGE) { edgeSupport.midEdgeCreate(vv, e.getPoint()); } else if(createMode == Creating.VERTEX){ vertexSupport.midVertexCreate(vv, e.getPoint()); } } } public void mouseClicked(MouseEvent e) {} public void mouseEntered(MouseEvent e) { JComponent c = (JComponent)e.getSource(); c.setCursor(cursor); } public void mouseExited(MouseEvent e) { JComponent c = (JComponent)e.getSource(); c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } public void mouseMoved(MouseEvent e) {} public VertexSupport getVertexSupport() { return vertexSupport; } public void setVertexSupport(VertexSupport vertexSupport) { this.vertexSupport = vertexSupport; } public EdgeSupport getEdgeSupport() { return edgeSupport; } public void setEdgeSupport(EdgeSupport edgeSupport) { this.edgeSupport = edgeSupport; } } EditingModalGraphMouse.java000066400000000000000000000224341276402340000364760ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/controlpackage edu.uci.ics.jung.visualization.control; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.ItemSelectable; import java.awt.event.InputEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import javax.swing.ButtonGroup; import javax.swing.Icon; import javax.swing.JComboBox; import javax.swing.JMenu; import javax.swing.JRadioButtonMenuItem; import javax.swing.plaf.basic.BasicIconFactory; import com.google.common.base.Supplier; import edu.uci.ics.jung.visualization.MultiLayerTransformer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.annotations.AnnotatingGraphMousePlugin; public class EditingModalGraphMouse extends AbstractModalGraphMouse implements ModalGraphMouse, ItemSelectable { protected Supplier vertexFactory; protected Supplier edgeFactory; protected EditingGraphMousePlugin editingPlugin; protected LabelEditingGraphMousePlugin labelEditingPlugin; protected EditingPopupGraphMousePlugin popupEditingPlugin; protected AnnotatingGraphMousePlugin annotatingPlugin; protected MultiLayerTransformer basicTransformer; protected RenderContext rc; /** * Creates an instance with the specified rendering context and vertex/edge factories, * and with default zoom in/out values of 1.1 and 1/1.1. * @param rc the rendering context * @param vertexFactory used to construct vertices * @param edgeFactory used to construct edges */ public EditingModalGraphMouse(RenderContext rc, Supplier vertexFactory, Supplier edgeFactory) { this(rc, vertexFactory, edgeFactory, 1.1f, 1/1.1f); } /** * Creates an instance with the specified rendering context and vertex/edge factories, * and with the specified zoom in/out values. * @param rc the rendering context * @param vertexFactory used to construct vertices * @param edgeFactory used to construct edges * @param in amount to zoom in by for each action * @param out amount to zoom out by for each action */ public EditingModalGraphMouse(RenderContext rc, Supplier vertexFactory, Supplier edgeFactory, float in, float out) { super(in,out); this.vertexFactory = vertexFactory; this.edgeFactory = edgeFactory; this.rc = rc; this.basicTransformer = rc.getMultiLayerTransformer(); loadPlugins(); setModeKeyListener(new ModeKeyAdapter(this)); } /** * create the plugins, and load the plugins for TRANSFORMING mode * */ @Override protected void loadPlugins() { pickingPlugin = new PickingGraphMousePlugin(); animatedPickingPlugin = new AnimatedPickingGraphMousePlugin(); translatingPlugin = new TranslatingGraphMousePlugin(InputEvent.BUTTON1_MASK); scalingPlugin = new ScalingGraphMousePlugin(new CrossoverScalingControl(), 0, in, out); rotatingPlugin = new RotatingGraphMousePlugin(); shearingPlugin = new ShearingGraphMousePlugin(); editingPlugin = new EditingGraphMousePlugin(vertexFactory, edgeFactory); labelEditingPlugin = new LabelEditingGraphMousePlugin(); annotatingPlugin = new AnnotatingGraphMousePlugin(rc); popupEditingPlugin = new EditingPopupGraphMousePlugin(vertexFactory, edgeFactory); add(scalingPlugin); setMode(Mode.EDITING); } /** * setter for the Mode. */ @Override public void setMode(Mode mode) { if(this.mode != mode) { fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, this.mode, ItemEvent.DESELECTED)); this.mode = mode; if(mode == Mode.TRANSFORMING) { setTransformingMode(); } else if(mode == Mode.PICKING) { setPickingMode(); } else if(mode == Mode.EDITING) { setEditingMode(); } else if(mode == Mode.ANNOTATING) { setAnnotatingMode(); } if(modeBox != null) { modeBox.setSelectedItem(mode); } fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, mode, ItemEvent.SELECTED)); } } @Override protected void setPickingMode() { remove(translatingPlugin); remove(rotatingPlugin); remove(shearingPlugin); remove(editingPlugin); remove(annotatingPlugin); add(pickingPlugin); add(animatedPickingPlugin); add(labelEditingPlugin); add(popupEditingPlugin); } @Override protected void setTransformingMode() { remove(pickingPlugin); remove(animatedPickingPlugin); remove(editingPlugin); remove(annotatingPlugin); add(translatingPlugin); add(rotatingPlugin); add(shearingPlugin); add(labelEditingPlugin); add(popupEditingPlugin); } protected void setEditingMode() { remove(pickingPlugin); remove(animatedPickingPlugin); remove(translatingPlugin); remove(rotatingPlugin); remove(shearingPlugin); remove(labelEditingPlugin); remove(annotatingPlugin); add(editingPlugin); add(popupEditingPlugin); } protected void setAnnotatingMode() { remove(pickingPlugin); remove(animatedPickingPlugin); remove(translatingPlugin); remove(rotatingPlugin); remove(shearingPlugin); remove(labelEditingPlugin); remove(editingPlugin); remove(popupEditingPlugin); add(annotatingPlugin); } /** * @return the modeBox. */ @Override public JComboBox getModeComboBox() { if(modeBox == null) { modeBox = new JComboBox( new Mode[]{Mode.TRANSFORMING, Mode.PICKING, Mode.EDITING, Mode.ANNOTATING}); modeBox.addItemListener(getModeListener()); } modeBox.setSelectedItem(mode); return modeBox; } /** * create (if necessary) and return a menu that will change * the mode * @return the menu */ @Override public JMenu getModeMenu() { if(modeMenu == null) { modeMenu = new JMenu();// { Icon icon = BasicIconFactory.getMenuArrowIcon(); modeMenu.setIcon(BasicIconFactory.getMenuArrowIcon()); modeMenu.setPreferredSize(new Dimension(icon.getIconWidth()+10, icon.getIconHeight()+10)); final JRadioButtonMenuItem transformingButton = new JRadioButtonMenuItem(Mode.TRANSFORMING.toString()); transformingButton.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { setMode(Mode.TRANSFORMING); } }}); final JRadioButtonMenuItem pickingButton = new JRadioButtonMenuItem(Mode.PICKING.toString()); pickingButton.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { setMode(Mode.PICKING); } }}); final JRadioButtonMenuItem editingButton = new JRadioButtonMenuItem(Mode.EDITING.toString()); editingButton.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { setMode(Mode.EDITING); } }}); ButtonGroup radio = new ButtonGroup(); radio.add(transformingButton); radio.add(pickingButton); radio.add(editingButton); transformingButton.setSelected(true); modeMenu.add(transformingButton); modeMenu.add(pickingButton); modeMenu.add(editingButton); modeMenu.setToolTipText("Menu for setting Mouse Mode"); addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { if(e.getItem() == Mode.TRANSFORMING) { transformingButton.setSelected(true); } else if(e.getItem() == Mode.PICKING) { pickingButton.setSelected(true); } else if(e.getItem() == Mode.EDITING) { editingButton.setSelected(true); } } }}); } return modeMenu; } public static class ModeKeyAdapter extends KeyAdapter { private char t = 't'; private char p = 'p'; private char e = 'e'; private char a = 'a'; protected ModalGraphMouse graphMouse; public ModeKeyAdapter(ModalGraphMouse graphMouse) { this.graphMouse = graphMouse; } public ModeKeyAdapter(char t, char p, char e, char a, ModalGraphMouse graphMouse) { this.t = t; this.p = p; this.e = e; this.a = a; this.graphMouse = graphMouse; } @Override public void keyTyped(KeyEvent event) { char keyChar = event.getKeyChar(); if(keyChar == t) { ((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); graphMouse.setMode(Mode.TRANSFORMING); } else if(keyChar == p) { ((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); graphMouse.setMode(Mode.PICKING); } else if(keyChar == e) { ((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); graphMouse.setMode(Mode.EDITING); } else if(keyChar == a) { ((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); graphMouse.setMode(Mode.ANNOTATING); } } } /** * @return the annotatingPlugin */ public AnnotatingGraphMousePlugin getAnnotatingPlugin() { return annotatingPlugin; } /** * @return the editingPlugin */ public EditingGraphMousePlugin getEditingPlugin() { return editingPlugin; } /** * @return the labelEditingPlugin */ public LabelEditingGraphMousePlugin getLabelEditingPlugin() { return labelEditingPlugin; } /** * @return the popupEditingPlugin */ public EditingPopupGraphMousePlugin getPopupEditingPlugin() { return popupEditingPlugin; } } EditingPopupGraphMousePlugin.java000066400000000000000000000107231276402340000377220ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/controlpackage edu.uci.ics.jung.visualization.control; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.JMenu; import javax.swing.JPopupMenu; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.picking.PickedState; /** * a plugin that uses popup menus to create vertices, undirected edges, * and directed edges. * * @author Tom Nelson * */ public class EditingPopupGraphMousePlugin extends AbstractPopupGraphMousePlugin { protected Supplier vertexFactory; protected Supplier edgeFactory; public EditingPopupGraphMousePlugin(Supplier vertexFactory, Supplier edgeFactory) { this.vertexFactory = vertexFactory; this.edgeFactory = edgeFactory; } @SuppressWarnings({ "unchecked", "serial" }) protected void handlePopup(MouseEvent e) { final VisualizationViewer vv = (VisualizationViewer)e.getSource(); final Layout layout = vv.getGraphLayout(); final Graph graph = layout.getGraph(); final Point2D p = e.getPoint(); GraphElementAccessor pickSupport = vv.getPickSupport(); if(pickSupport != null) { final V vertex = pickSupport.getVertex(layout, p.getX(), p.getY()); final E edge = pickSupport.getEdge(layout, p.getX(), p.getY()); final PickedState pickedVertexState = vv.getPickedVertexState(); final PickedState pickedEdgeState = vv.getPickedEdgeState(); JPopupMenu popup = new JPopupMenu(); if(vertex != null) { Set picked = pickedVertexState.getPicked(); if(picked.size() > 0) { if(graph instanceof UndirectedGraph == false) { JMenu directedMenu = new JMenu("Create Directed Edge"); popup.add(directedMenu); for(final V other : picked) { directedMenu.add(new AbstractAction("["+other+","+vertex+"]") { public void actionPerformed(ActionEvent e) { graph.addEdge(edgeFactory.get(), other, vertex, EdgeType.DIRECTED); vv.repaint(); } }); } } if(graph instanceof DirectedGraph == false) { JMenu undirectedMenu = new JMenu("Create Undirected Edge"); popup.add(undirectedMenu); for(final V other : picked) { undirectedMenu.add(new AbstractAction("[" + other+","+vertex+"]") { public void actionPerformed(ActionEvent e) { graph.addEdge(edgeFactory.get(), other, vertex); vv.repaint(); } }); } } } popup.add(new AbstractAction("Delete Vertex") { public void actionPerformed(ActionEvent e) { pickedVertexState.pick(vertex, false); graph.removeVertex(vertex); vv.repaint(); }}); } else if(edge != null) { popup.add(new AbstractAction("Delete Edge") { public void actionPerformed(ActionEvent e) { pickedEdgeState.pick(edge, false); graph.removeEdge(edge); vv.repaint(); }}); } else { popup.add(new AbstractAction("Create Vertex") { public void actionPerformed(ActionEvent e) { V newVertex = vertexFactory.get(); graph.addVertex(newVertex); layout.setLocation(newVertex, vv.getRenderContext().getMultiLayerTransformer().inverseTransform(p)); vv.repaint(); } }); } if(popup.getComponentCount() > 0) { popup.show(vv, e.getX(), e.getY()); } } } } GraphMouseAdapter.java000066400000000000000000000016471276402340000355210ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 6, 2005 */ package edu.uci.ics.jung.visualization.control; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; /** * Simple extension of MouseAdapter that supplies modifier * checking * * @author Tom Nelson * */ public class GraphMouseAdapter extends MouseAdapter { protected int modifiers; public GraphMouseAdapter(int modifiers) { this.modifiers = modifiers; } public int getModifiers() { return modifiers; } public void setModifiers(int modifiers) { this.modifiers = modifiers; } protected boolean checkModifiers(MouseEvent e) { return e.getModifiers() == modifiers; } } GraphMouseListener.java000066400000000000000000000012051276402340000357140ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * Created on Feb 17, 2004 */ package edu.uci.ics.jung.visualization.control; import java.awt.event.MouseEvent; /** * This interface allows users to register listeners to register to receive * vertex clicks. * * @author danyelf */ public interface GraphMouseListener { void graphClicked(V v, MouseEvent me); void graphPressed(V v, MouseEvent me); void graphReleased(V v, MouseEvent me); } GraphMousePlugin.java000066400000000000000000000016741276402340000353770ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 6, 2005 */ package edu.uci.ics.jung.visualization.control; import java.awt.event.MouseEvent; /** * the interface for all plugins to the PluggableGraphMouse * @author Tom Nelson * */ public interface GraphMousePlugin { /** * @return the mouse event modifiers that will activate this plugin */ int getModifiers(); /** * @param modifiers the mouse event modifiers that will activate this plugin */ void setModifiers(int modifiers); /** * compare the set modifiers against those of the supplied event * @param e an event to compare to * @return whether the member modifiers match the event modifiers */ boolean checkModifiers(MouseEvent e); } LabelEditingGraphMousePlugin.java000066400000000000000000000113211276402340000376310ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.Cursor; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.geom.Point2D; import javax.swing.JOptionPane; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.util.MapSettableTransformer; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; /** * * * @author Tom Nelson */ public class LabelEditingGraphMousePlugin extends AbstractGraphMousePlugin implements MouseListener { /** * the picked Vertex, if any */ protected V vertex; /** * the picked Edge, if any */ protected E edge; /** * create an instance with default settings */ public LabelEditingGraphMousePlugin() { this(InputEvent.BUTTON1_MASK); } /** * create an instance with overrides * @param selectionModifiers for primary selection */ public LabelEditingGraphMousePlugin(int selectionModifiers) { super(selectionModifiers); this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); } /** * For primary modifiers (default, MouseButton1): * pick a single Vertex or Edge that * is under the mouse pointer. If no Vertex or edge is under * the pointer, unselect all picked Vertices and edges, and * set up to draw a rectangle for multiple selection * of contained Vertices. * For additional selection (default Shift+MouseButton1): * Add to the selection, a single Vertex or Edge that is * under the mouse pointer. If a previously picked Vertex * or Edge is under the pointer, it is un-picked. * If no vertex or Edge is under the pointer, set up * to draw a multiple selection rectangle (as above) * but do not unpick previously picked elements. * * @param e the event */ @SuppressWarnings("unchecked") public void mouseClicked(MouseEvent e) { if (e.getModifiers() == modifiers && e.getClickCount() == 2) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); GraphElementAccessor pickSupport = vv.getPickSupport(); if (pickSupport != null) { Function vs = vv.getRenderContext().getVertexLabelTransformer(); if (vs instanceof MapSettableTransformer) { MapSettableTransformer mst = (MapSettableTransformer)vs; Layout layout = vv.getGraphLayout(); // p is the screen point for the mouse event Point2D p = e.getPoint(); V vertex = pickSupport.getVertex(layout, p.getX(), p.getY()); if(vertex != null) { String newLabel = vs.apply(vertex); newLabel = JOptionPane.showInputDialog("New Vertex Label for "+vertex); if(newLabel != null) { mst.set(vertex, newLabel); vv.repaint(); } return; } } Function es = vv.getRenderContext().getEdgeLabelTransformer(); if (es instanceof MapSettableTransformer) { MapSettableTransformer mst = (MapSettableTransformer)es; Layout layout = vv.getGraphLayout(); // p is the screen point for the mouse event Point2D p = e.getPoint(); // take away the view transform Point2D ip = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, p); E edge = pickSupport.getEdge(layout, ip.getX(), ip.getY()); if(edge != null) { String newLabel = JOptionPane.showInputDialog("New Edge Label for "+edge); if(newLabel != null) { mst.set(edge, newLabel); vv.repaint(); } return; } } } e.consume(); } } /** * If the mouse is dragging a rectangle, pick the * Vertices contained in that rectangle * * clean up settings from mousePressed */ public void mouseReleased(MouseEvent e) { } /** * If the mouse is over a picked vertex, drag all picked * vertices with the mouse. * If the mouse is not over a Vertex, draw the rectangle * to select multiple Vertices * */ public void mousePressed(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } } LayoutScalingControl.java000066400000000000000000000025401276402340000362560ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationServer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * LayoutScalingControl applies a scaling transformation to the graph layout. * The Vertices get closer or farther apart, but do not themselves change * size. ScalingGraphMouse uses MouseWheelEvents to apply the scaling. * * @author Tom Nelson */ public class LayoutScalingControl implements ScalingControl { /** * zoom the display in or out, depending on the direction of the * mouse wheel motion. */ public void scale(VisualizationServer vv, float amount, Point2D from) { Point2D ivtfrom = vv.getRenderContext().getMultiLayerTransformer() .inverseTransform(Layer.VIEW, from); MutableTransformer modelTransformer = vv.getRenderContext().getMultiLayerTransformer() .getTransformer(Layer.LAYOUT); modelTransformer.scale(amount, amount, ivtfrom); vv.repaint(); } } LensMagnificationGraphMousePlugin.java000066400000000000000000000077471276402340000407210ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.transform.LensTransformer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * HyperbolicMagnificationGraphMousePlugin changes the magnification * within the Hyperbolic projection of the HyperbolicTransformer. * * @author Tom Nelson */ public class LensMagnificationGraphMousePlugin extends AbstractGraphMousePlugin implements MouseWheelListener { protected final float floor; protected final float ceiling; protected final float delta; /** * Creates an instance with modifier of CTRL_MASK, and default min/max/delta zoom values * of 1/4/0.2. */ public LensMagnificationGraphMousePlugin() { this(MouseEvent.CTRL_MASK); } /** * Creates an instance with modifier of CTRL_MASK, and the specified zoom parameters. * @param floor the minimum zoom value * @param ceiling the maximum zoom value * @param delta the change in zoom value caused by each mouse event */ public LensMagnificationGraphMousePlugin(float floor, float ceiling, float delta) { this(MouseEvent.CTRL_MASK, floor, ceiling, delta); } /** * Creates an instance with the specified modifiers and the default min/max/delta zoom values * of 1/4/0.2. * @param modifiers the mouse event modifiers to specify */ public LensMagnificationGraphMousePlugin(int modifiers) { this(modifiers, 1.0f, 4.0f, .2f); } /** * Creates an instance with the specified mouse event modifiers and zoom parameters. * @param modifiers the mouse event modifiers to specify * @param floor the minimum zoom value * @param ceiling the maximum zoom value * @param delta the change in zoom value caused by each mouse event */ public LensMagnificationGraphMousePlugin(int modifiers, float floor, float ceiling, float delta) { super(modifiers); this.floor = floor; this.ceiling = ceiling; this.delta = delta; } /** * override to check equality with a mask */ public boolean checkModifiers(MouseEvent e) { return (e.getModifiers() & modifiers) != 0; } private void changeMagnification(MutableTransformer transformer, float delta) { if(transformer instanceof LensTransformer) { LensTransformer ht = (LensTransformer)transformer; float magnification = ht.getMagnification() + delta; magnification = Math.max(floor, magnification); magnification = Math.min(magnification, ceiling); ht.setMagnification(magnification); } } /** * zoom the display in or out, depending on the direction of the * mouse wheel motion. */ public void mouseWheelMoved(MouseWheelEvent e) { boolean accepted = checkModifiers(e); float delta = this.delta; if(accepted == true) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); MutableTransformer modelTransformer = vv.getRenderContext().getMultiLayerTransformer() .getTransformer(Layer.LAYOUT); MutableTransformer viewTransformer = vv.getRenderContext().getMultiLayerTransformer() .getTransformer(Layer.VIEW); int amount = e.getWheelRotation(); if(amount < 0) { delta = -delta; } changeMagnification(modelTransformer, delta); changeMagnification(viewTransformer, delta); vv.repaint(); e.consume(); } } } LensTranslatingGraphMousePlugin.java000066400000000000000000000150321276402340000404210ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.Cursor; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.transform.LensTransformer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * Extends TranslatingGraphMousePlugin and adds the capability * to drag and resize the viewing * lens in the graph view. Mouse1 in the center moves the lens, * mouse1 on the edge resizes the lens. The default mouse button and * modifiers can be overridden in the constructor. * * * @author Tom Nelson */ public class LensTranslatingGraphMousePlugin extends TranslatingGraphMousePlugin implements MouseListener, MouseMotionListener { protected boolean dragOnLens; protected boolean dragOnEdge; protected double edgeOffset; /** * create an instance with default modifiers */ public LensTranslatingGraphMousePlugin() { this(MouseEvent.BUTTON1_MASK); } /** * create an instance with passed modifer value * @param modifiers the mouse event modifier to activate this function */ public LensTranslatingGraphMousePlugin(int modifiers) { super(modifiers); } /** * Check the event modifiers. Set the 'down' point for later * use. If this event satisfies the modifiers, change the cursor * to the system 'move cursor' * @param e the event */ public void mousePressed(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); MutableTransformer vt = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW); if(vt instanceof LensTransformer) { vt = ((LensTransformer)vt).getDelegate(); } Point2D p = vt.inverseTransform(e.getPoint()); boolean accepted = checkModifiers(e); if(accepted) { vv.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); testViewCenter(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT), p); testViewCenter(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW), p); vv.repaint(); } super.mousePressed(e); } /** * called to change the location of the lens * @param Function * @param point */ private void setViewCenter(MutableTransformer transformer, Point2D point) { if(transformer instanceof LensTransformer) { LensTransformer ht = (LensTransformer)transformer; ht.setViewCenter(point); } } /** * called to change the radius of the lens * @param Function * @param point */ private void setViewRadius(MutableTransformer transformer, Point2D point) { if(transformer instanceof LensTransformer) { LensTransformer ht = (LensTransformer)transformer; double distanceFromCenter = ht.getDistanceFromCenter(point); ht.setViewRadius(distanceFromCenter+edgeOffset); } } /** * called to set up translating the lens center or changing the size * @param Function * @param point */ private void testViewCenter(MutableTransformer transformer, Point2D point) { if(transformer instanceof LensTransformer) { LensTransformer ht = (LensTransformer)transformer; double distanceFromCenter = ht.getDistanceFromCenter(point); if(distanceFromCenter < 10) { ht.setViewCenter(point); dragOnLens = true; } else if(Math.abs(distanceFromCenter - ht.getViewRadius()) < 10) { edgeOffset = ht.getViewRadius() - distanceFromCenter; ht.setViewRadius(distanceFromCenter+edgeOffset); dragOnEdge = true; } } } /** * unset the 'down' point and change the cursoe back to the system * default cursor */ public void mouseReleased(MouseEvent e) { super.mouseReleased(e); dragOnLens = false; dragOnEdge = false; edgeOffset = 0; } /** * check the modifiers. If accepted, move or resize the lens according * to the dragging of the mouse pointer * @param e the event */ public void mouseDragged(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); MutableTransformer vt = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW); if(vt instanceof LensTransformer) { vt = ((LensTransformer)vt).getDelegate(); } Point2D p = vt.inverseTransform(e.getPoint()); boolean accepted = checkModifiers(e); if(accepted ) { MutableTransformer modelTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); vv.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); if(dragOnLens) { setViewCenter(modelTransformer, p); setViewCenter(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW), p); e.consume(); vv.repaint(); } else if(dragOnEdge) { setViewRadius(modelTransformer, p); setViewRadius(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW), p); e.consume(); vv.repaint(); } else { MutableTransformer mt = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); Point2D iq = vt.inverseTransform(down); iq = mt.inverseTransform(iq); Point2D ip = vt.inverseTransform(e.getPoint()); ip = mt.inverseTransform(ip); float dx = (float) (ip.getX()-iq.getX()); float dy = (float) (ip.getY()-iq.getY()); modelTransformer.translate(dx, dy); down.x = e.getX(); down.y = e.getY(); } } } } ModalGraphMouse.java000066400000000000000000000014171276402340000351700ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 26, 2005 */ package edu.uci.ics.jung.visualization.control; import java.awt.event.ItemListener; import edu.uci.ics.jung.visualization.VisualizationViewer.GraphMouse; /** * Interface for a GraphMouse that supports modality. * * @author Tom Nelson * */ public interface ModalGraphMouse extends GraphMouse { void setMode(Mode mode); /** * @return Returns the modeListener. */ ItemListener getModeListener(); /** */ enum Mode { TRANSFORMING, PICKING, ANNOTATING, EDITING } }ModalLensGraphMouse.java000066400000000000000000000055711276402340000360170ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 26, 2005 */ package edu.uci.ics.jung.visualization.control; import java.awt.Component; import java.awt.Cursor; import java.awt.event.InputEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; /** * an implementation of the AbstractModalGraphMouse that includes plugins for * manipulating a view that is using a LensTransformer. * * @author Tom Nelson * */ public class ModalLensGraphMouse extends AbstractModalGraphMouse implements ModalGraphMouse { /** * not included in the base class */ protected LensMagnificationGraphMousePlugin magnificationPlugin; public ModalLensGraphMouse() { this(1.1f, 1/1.1f); } public ModalLensGraphMouse(float in, float out) { this(in, out, new LensMagnificationGraphMousePlugin()); } public ModalLensGraphMouse(LensMagnificationGraphMousePlugin magnificationPlugin) { this(1.1f, 1/1.1f, magnificationPlugin); } public ModalLensGraphMouse(float in, float out, LensMagnificationGraphMousePlugin magnificationPlugin) { super(in,out); this.in = in; this.out = out; this.magnificationPlugin = magnificationPlugin; loadPlugins(); setModeKeyListener(new ModeKeyAdapter(this)); } protected void loadPlugins() { pickingPlugin = new PickingGraphMousePlugin(); animatedPickingPlugin = new AnimatedPickingGraphMousePlugin(); translatingPlugin = new LensTranslatingGraphMousePlugin(InputEvent.BUTTON1_MASK); scalingPlugin = new ScalingGraphMousePlugin(new CrossoverScalingControl(), 0, in, out); rotatingPlugin = new RotatingGraphMousePlugin(); shearingPlugin = new ShearingGraphMousePlugin(); add(magnificationPlugin); add(scalingPlugin); setMode(Mode.TRANSFORMING); } public static class ModeKeyAdapter extends KeyAdapter { private char t = 't'; private char p = 'p'; protected ModalGraphMouse graphMouse; public ModeKeyAdapter(ModalGraphMouse graphMouse) { this.graphMouse = graphMouse; } public ModeKeyAdapter(char t, char p, ModalGraphMouse graphMouse) { this.t = t; this.p = p; this.graphMouse = graphMouse; } public void keyTyped(KeyEvent event) { char keyChar = event.getKeyChar(); if(keyChar == t) { ((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); graphMouse.setMode(Mode.TRANSFORMING); } else if(keyChar == p) { ((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); graphMouse.setMode(Mode.PICKING); } } } } ModalSatelliteGraphMouse.java000066400000000000000000000023631276402340000370400ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 26, 2005 */ package edu.uci.ics.jung.visualization.control; import java.awt.event.InputEvent; /** * * @author Tom Nelson * */ @SuppressWarnings("rawtypes") public class ModalSatelliteGraphMouse extends DefaultModalGraphMouse implements ModalGraphMouse { public ModalSatelliteGraphMouse() { this(1.1f, 1/1.1f); } public ModalSatelliteGraphMouse(float in, float out) { super(in, out); } protected void loadPlugins() { pickingPlugin = new PickingGraphMousePlugin(); animatedPickingPlugin = new SatelliteAnimatedPickingGraphMousePlugin(); translatingPlugin = new SatelliteTranslatingGraphMousePlugin(InputEvent.BUTTON1_MASK); scalingPlugin = new SatelliteScalingGraphMousePlugin(new CrossoverScalingControl(), 0); rotatingPlugin = new SatelliteRotatingGraphMousePlugin(); shearingPlugin = new SatelliteShearingGraphMousePlugin(); add(scalingPlugin); setMode(Mode.TRANSFORMING); } } MouseListenerTranslator.java000066400000000000000000000045041276402340000370110ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * Created on Feb 17, 2004 */ package edu.uci.ics.jung.visualization.control; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.visualization.VisualizationViewer; /** * This class translates mouse clicks into vertex clicks * * @author danyelf */ public class MouseListenerTranslator extends MouseAdapter { private VisualizationViewer vv; private GraphMouseListener gel; /** * @param gel listens for mouse events * @param vv the viewer used for visualization */ public MouseListenerTranslator(GraphMouseListener gel, VisualizationViewer vv) { this.gel = gel; this.vv = vv; } /** * Transform the point to the coordinate system in the * VisualizationViewer, then use either PickSuuport * (if available) or Layout to find a Vertex * @param point * @return */ private V getVertex(Point2D point) { // adjust for scale and offset in the VisualizationViewer Point2D p = point; //vv.getRenderContext().getBasicTransformer().inverseViewTransform(point); GraphElementAccessor pickSupport = vv.getPickSupport(); Layout layout = vv.getGraphLayout(); V v = null; if(pickSupport != null) { v = pickSupport.getVertex(layout, p.getX(), p.getY()); } return v; } /** * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent) */ public void mouseClicked(MouseEvent e) { V v = getVertex(e.getPoint()); if ( v != null ) { gel.graphClicked(v, e ); } } /** * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent) */ public void mousePressed(MouseEvent e) { V v = getVertex(e.getPoint()); if ( v != null ) { gel.graphPressed(v, e ); } } /** * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent) */ public void mouseReleased(MouseEvent e) { V v = getVertex(e.getPoint()); if ( v != null ) { gel.graphReleased(v, e ); } } } PickingGraphMousePlugin.java000066400000000000000000000312601276402340000366760ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.Color; import java.awt.Cursor; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Collection; import javax.swing.JComponent; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.VisualizationServer.Paintable; import edu.uci.ics.jung.visualization.picking.PickedState; /** * PickingGraphMousePlugin supports the picking of graph elements * with the mouse. MouseButtonOne picks a single vertex * or edge, and MouseButtonTwo adds to the set of selected Vertices * or EdgeType. If a Vertex is selected and the mouse is dragged while * on the selected Vertex, then that Vertex will be repositioned to * follow the mouse until the button is released. * * @author Tom Nelson */ public class PickingGraphMousePlugin extends AbstractGraphMousePlugin implements MouseListener, MouseMotionListener { /** * the picked Vertex, if any */ protected V vertex; /** * the picked Edge, if any */ protected E edge; /** * the x distance from the picked vertex center to the mouse point */ protected double offsetx; /** * the y distance from the picked vertex center to the mouse point */ protected double offsety; /** * controls whether the Vertices may be moved with the mouse */ protected boolean locked; /** * additional modifiers for the action of adding to an existing * selection */ protected int addToSelectionModifiers; /** * used to draw a rectangle to contain picked vertices */ protected Rectangle2D rect = new Rectangle2D.Float(); /** * the Paintable for the lens picking rectangle */ protected Paintable lensPaintable; /** * color for the picking rectangle */ protected Color lensColor = Color.cyan; /** * create an instance with default settings */ public PickingGraphMousePlugin() { this(InputEvent.BUTTON1_MASK, InputEvent.BUTTON1_MASK | InputEvent.SHIFT_MASK); } /** * create an instance with overides * @param selectionModifiers for primary selection * @param addToSelectionModifiers for additional selection */ public PickingGraphMousePlugin(int selectionModifiers, int addToSelectionModifiers) { super(selectionModifiers); this.addToSelectionModifiers = addToSelectionModifiers; this.lensPaintable = new LensPaintable(); this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); } /** * @return Returns the lensColor. */ public Color getLensColor() { return lensColor; } /** * @param lensColor The lensColor to set. */ public void setLensColor(Color lensColor) { this.lensColor = lensColor; } /** * a Paintable to draw the rectangle used to pick multiple * Vertices * @author Tom Nelson * */ class LensPaintable implements Paintable { public void paint(Graphics g) { Color oldColor = g.getColor(); g.setColor(lensColor); ((Graphics2D)g).draw(rect); g.setColor(oldColor); } public boolean useTransform() { return false; } } /** * For primary modifiers (default, MouseButton1): * pick a single Vertex or Edge that * is under the mouse pointer. If no Vertex or edge is under * the pointer, unselect all picked Vertices and edges, and * set up to draw a rectangle for multiple selection * of contained Vertices. * For additional selection (default Shift+MouseButton1): * Add to the selection, a single Vertex or Edge that is * under the mouse pointer. If a previously picked Vertex * or Edge is under the pointer, it is un-picked. * If no vertex or Edge is under the pointer, set up * to draw a multiple selection rectangle (as above) * but do not unpick previously picked elements. * * @param e the event */ @SuppressWarnings("unchecked") public void mousePressed(MouseEvent e) { down = e.getPoint(); VisualizationViewer vv = (VisualizationViewer)e.getSource(); GraphElementAccessor pickSupport = vv.getPickSupport(); PickedState pickedVertexState = vv.getPickedVertexState(); PickedState pickedEdgeState = vv.getPickedEdgeState(); if(pickSupport != null && pickedVertexState != null) { Layout layout = vv.getGraphLayout(); if(e.getModifiers() == modifiers) { rect.setFrameFromDiagonal(down,down); // p is the screen point for the mouse event Point2D ip = e.getPoint(); vertex = pickSupport.getVertex(layout, ip.getX(), ip.getY()); if(vertex != null) { if(pickedVertexState.isPicked(vertex) == false) { pickedVertexState.clear(); pickedVertexState.pick(vertex, true); } // layout.getLocation applies the layout Function so // q is transformed by the layout Function only Point2D q = layout.apply(vertex); // transform the mouse point to graph coordinate system Point2D gp = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.LAYOUT, ip); offsetx = (float) (gp.getX()-q.getX()); offsety = (float) (gp.getY()-q.getY()); } else if((edge = pickSupport.getEdge(layout, ip.getX(), ip.getY())) != null) { pickedEdgeState.clear(); pickedEdgeState.pick(edge, true); } else { vv.addPostRenderPaintable(lensPaintable); pickedEdgeState.clear(); pickedVertexState.clear(); } } else if(e.getModifiers() == addToSelectionModifiers) { vv.addPostRenderPaintable(lensPaintable); rect.setFrameFromDiagonal(down,down); Point2D ip = e.getPoint(); vertex = pickSupport.getVertex(layout, ip.getX(), ip.getY()); if(vertex != null) { boolean wasThere = pickedVertexState.pick(vertex, !pickedVertexState.isPicked(vertex)); if(wasThere) { vertex = null; } else { // layout.getLocation applies the layout Function so // q is transformed by the layout Function only Point2D q = layout.apply(vertex); // translate mouse point to graph coord system Point2D gp = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.LAYOUT, ip); offsetx = (float) (gp.getX()-q.getX()); offsety = (float) (gp.getY()-q.getY()); } } else if((edge = pickSupport.getEdge(layout, ip.getX(), ip.getY())) != null) { pickedEdgeState.pick(edge, !pickedEdgeState.isPicked(edge)); } } } if(vertex != null) e.consume(); } /** * If the mouse is dragging a rectangle, pick the * Vertices contained in that rectangle * * clean up settings from mousePressed */ @SuppressWarnings("unchecked") public void mouseReleased(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); if(e.getModifiers() == modifiers) { if(down != null) { Point2D out = e.getPoint(); if(vertex == null && heyThatsTooClose(down, out, 5) == false) { pickContainedVertices(vv, down, out, true); } } } else if(e.getModifiers() == this.addToSelectionModifiers) { if(down != null) { Point2D out = e.getPoint(); if(vertex == null && heyThatsTooClose(down,out,5) == false) { pickContainedVertices(vv, down, out, false); } } } down = null; vertex = null; edge = null; rect.setFrame(0,0,0,0); vv.removePostRenderPaintable(lensPaintable); vv.repaint(); } /** * If the mouse is over a picked vertex, drag all picked * vertices with the mouse. * If the mouse is not over a Vertex, draw the rectangle * to select multiple Vertices * */ @SuppressWarnings("unchecked") public void mouseDragged(MouseEvent e) { if(locked == false) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); if(vertex != null) { Point p = e.getPoint(); Point2D graphPoint = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(p); Point2D graphDown = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(down); Layout layout = vv.getGraphLayout(); double dx = graphPoint.getX()-graphDown.getX(); double dy = graphPoint.getY()-graphDown.getY(); PickedState ps = vv.getPickedVertexState(); for(V v : ps.getPicked()) { Point2D vp = layout.apply(v); vp.setLocation(vp.getX()+dx, vp.getY()+dy); layout.setLocation(v, vp); } down = p; } else { Point2D out = e.getPoint(); if(e.getModifiers() == this.addToSelectionModifiers || e.getModifiers() == modifiers) { rect.setFrameFromDiagonal(down,out); } } if(vertex != null) e.consume(); vv.repaint(); } } /** * rejects picking if the rectangle is too small, like * if the user meant to select one vertex but moved the * mouse slightly * @param p * @param q * @param min * @return */ private boolean heyThatsTooClose(Point2D p, Point2D q, double min) { return Math.abs(p.getX()-q.getX()) < min && Math.abs(p.getY()-q.getY()) < min; } /** * pick the vertices inside the rectangle created from points 'down' and 'out' (two diagonally * opposed corners of the rectangle) * * @param vv the viewer containing the layout and picked state * @param down one corner of the rectangle * @param out the other corner of the rectangle * @param clear whether to reset existing picked state */ protected void pickContainedVertices(VisualizationViewer vv, Point2D down, Point2D out, boolean clear) { Layout layout = vv.getGraphLayout(); PickedState pickedVertexState = vv.getPickedVertexState(); Rectangle2D pickRectangle = new Rectangle2D.Double(); pickRectangle.setFrameFromDiagonal(down,out); if(pickedVertexState != null) { if(clear) { pickedVertexState.clear(); } GraphElementAccessor pickSupport = vv.getPickSupport(); Collection picked = pickSupport.getVertices(layout, pickRectangle); for(V v : picked) { pickedVertexState.pick(v, true); } } } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { JComponent c = (JComponent)e.getSource(); c.setCursor(cursor); } public void mouseExited(MouseEvent e) { JComponent c = (JComponent)e.getSource(); c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } public void mouseMoved(MouseEvent e) { } /** * @return Returns the locked. */ public boolean isLocked() { return locked; } /** * @param locked The locked to set. */ public void setLocked(boolean locked) { this.locked = locked; } } PluggableGraphMouse.java000066400000000000000000000116161276402340000360400ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 7, 2005 */ package edu.uci.ics.jung.visualization.control; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.util.LinkedHashSet; import java.util.Set; import edu.uci.ics.jung.visualization.VisualizationViewer; /** * a GraphMouse that accepts plugins for various mouse events. * * @author Tom Nelson * * */ public class PluggableGraphMouse implements VisualizationViewer.GraphMouse { MouseListener[] mouseListeners; MouseMotionListener[] mouseMotionListeners; MouseWheelListener[] mouseWheelListeners; Set mousePluginList = new LinkedHashSet(); Set mouseMotionPluginList = new LinkedHashSet(); Set mouseWheelPluginList = new LinkedHashSet(); public void add(GraphMousePlugin plugin) { if(plugin instanceof MouseListener) { mousePluginList.add(plugin); mouseListeners = null; } if(plugin instanceof MouseMotionListener) { mouseMotionPluginList.add((MouseMotionListener)plugin); mouseMotionListeners = null; } if(plugin instanceof MouseWheelListener) { mouseWheelPluginList.add((MouseWheelListener)plugin); mouseWheelListeners = null; } } public void remove(GraphMousePlugin plugin) { if(plugin instanceof MouseListener) { boolean wasThere = mousePluginList.remove(plugin); if(wasThere) mouseListeners = null; } if(plugin instanceof MouseMotionListener) { boolean wasThere = mouseMotionPluginList.remove(plugin); if(wasThere) mouseMotionListeners = null; } if(plugin instanceof MouseWheelListener) { boolean wasThere = mouseWheelPluginList.remove(plugin); if(wasThere) mouseWheelListeners = null; } } private void checkMouseListeners() { if(mouseListeners == null) { mouseListeners = (MouseListener[]) mousePluginList.toArray(new MouseListener[mousePluginList.size()]); } } private void checkMouseMotionListeners() { if(mouseMotionListeners == null){ mouseMotionListeners = (MouseMotionListener[]) mouseMotionPluginList.toArray(new MouseMotionListener[mouseMotionPluginList.size()]); } } private void checkMouseWheelListeners() { if(mouseWheelListeners == null) { mouseWheelListeners = (MouseWheelListener[]) mouseWheelPluginList.toArray(new MouseWheelListener[mouseWheelPluginList.size()]); } } public void mouseClicked(MouseEvent e) { checkMouseListeners(); for(int i=0; i vv = (VisualizationViewer)e.getSource(); boolean accepted = checkModifiers(e); down = e.getPoint(); if(accepted) { vv.setCursor(cursor); } } /** * unset the down point and change the cursor back to the default */ public void mouseReleased(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); down = null; vv.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } /** * check the modifiers. If accepted, use the mouse drag motion * to rotate the graph */ public void mouseDragged(MouseEvent e) { if(down == null) return; VisualizationViewer vv = (VisualizationViewer)e.getSource(); boolean accepted = checkModifiers(e); if(accepted) { MutableTransformer modelTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); // rotate vv.setCursor(cursor); Point2D center = vv.getCenter(); Point2D q = down; Point2D p = e.getPoint(); Point2D v1 = new Point2D.Double(center.getX()-p.getX(), center.getY()-p.getY()); Point2D v2 = new Point2D.Double(center.getX()-q.getX(), center.getY()-q.getY()); double theta = angleBetween(v1, v2); modelTransformer.rotate(theta, vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, center)); down.x = e.getX(); down.y = e.getY(); e.consume(); } } /** * Returns the angle between two vectors from the origin * to points v1 and v2. * @param v1 the first point * @param v2 the second point * @return the angle between two vectors from the origin through points v1 and v2 */ protected double angleBetween(Point2D v1, Point2D v2) { double x1 = v1.getX(); double y1 = v1.getY(); double x2 = v2.getX(); double y2 = v2.getY(); // cross product for direction double cross = x1*y2 - x2*y1; int cw = 1; if(cross > 0) { cw = -1; } // dot product for angle double angle = cw*Math.acos( ( x1*x2 + y1*y2 ) / ( Math.sqrt( x1*x1 + y1*y1 ) * Math.sqrt( x2*x2 + y2*y2 ) ) ); if(Double.isNaN(angle)) { angle = 0; } return angle; } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mouseMoved(MouseEvent e) { } } SatelliteAnimatedPickingGraphMousePlugin.java000066400000000000000000000051711276402340000422120ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Point2D; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; /** * A version of the AnimatedPickingGraphMousePlugin that is for * the SatelliteVisualizationViewer. The difference it that when * you pick a Vertex in the Satellite View, the 'master view' is * translated to move that Vertex to the center. * @see AnimatedPickingGraphMousePlugin * @author Tom Nelson */ public class SatelliteAnimatedPickingGraphMousePlugin extends AnimatedPickingGraphMousePlugin implements MouseListener, MouseMotionListener { /** * create an instance * */ public SatelliteAnimatedPickingGraphMousePlugin() { this(InputEvent.BUTTON1_MASK | InputEvent.CTRL_MASK); } public SatelliteAnimatedPickingGraphMousePlugin(int selectionModifiers) { super(selectionModifiers); } /** * override subclass method to translate the master view instead * of this satellite view * */ @SuppressWarnings("unchecked") public void mouseReleased(MouseEvent e) { if (e.getModifiers() == modifiers) { final VisualizationViewer vv = (VisualizationViewer) e.getSource(); if (vv instanceof SatelliteVisualizationViewer) { final VisualizationViewer vvMaster = ((SatelliteVisualizationViewer) vv).getMaster(); if (vertex != null) { Layout layout = vvMaster.getGraphLayout(); Point2D q = layout.apply(vertex); Point2D lvc = vvMaster.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.LAYOUT, vvMaster.getCenter()); final double dx = (lvc.getX() - q.getX()) / 10; final double dy = (lvc.getY() - q.getY()) / 10; Runnable animator = new Runnable() { public void run() { for (int i = 0; i < 10; i++) { vvMaster.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).translate(dx, dy); try { Thread.sleep(100); } catch (InterruptedException ex) { } } } }; Thread thread = new Thread(animator); thread.start(); } } } } } SatelliteRotatingGraphMousePlugin.java000066400000000000000000000055051276402340000407530ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 15, 2005 */ package edu.uci.ics.jung.visualization.control; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * Mouse events in the SatelliteView that match the modifiers * will cause the Main view to rotate * @see RotatingGraphMousePlugin * @author Tom Nelson * */ public class SatelliteRotatingGraphMousePlugin extends RotatingGraphMousePlugin { public SatelliteRotatingGraphMousePlugin() { super(); } public SatelliteRotatingGraphMousePlugin(int modifiers) { super(modifiers); } /** * check the modifiers. If accepted, use the mouse drag motion * to rotate the graph in the master view */ public void mouseDragged(MouseEvent e) { if(down == null) return; VisualizationViewer vv = (VisualizationViewer)e.getSource(); boolean accepted = checkModifiers(e); if(accepted) { if(vv instanceof SatelliteVisualizationViewer) { VisualizationViewer vvMaster = ((SatelliteVisualizationViewer)vv).getMaster(); MutableTransformer modelTransformerMaster = vvMaster.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); // rotate vv.setCursor(cursor); // I want to compute rotation based on the view coordinates of the // lens center in the satellite view. // translate the master view center to layout coords, then translate // that point to the satellite view's view coordinate system.... Point2D center = vv.getRenderContext().getMultiLayerTransformer().transform(vvMaster.getRenderContext().getMultiLayerTransformer().inverseTransform(vvMaster.getCenter())); Point2D q = down; Point2D p = e.getPoint(); Point2D v1 = new Point2D.Double(center.getX()-p.getX(), center.getY()-p.getY()); Point2D v2 = new Point2D.Double(center.getX()-q.getX(), center.getY()-q.getY()); double theta = angleBetween(v1, v2); modelTransformerMaster.rotate(-theta, vvMaster.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, vvMaster.getCenter())); down.x = e.getX(); down.y = e.getY(); } e.consume(); } } } SatelliteScalingGraphMousePlugin.java000066400000000000000000000035371276402340000405470ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 15, 2005 */ package edu.uci.ics.jung.visualization.control; import java.awt.event.MouseWheelEvent; import edu.uci.ics.jung.visualization.VisualizationViewer; /** * Overrides ScalingGraphMousePlugin so that mouse events in the * satellite view will cause scaling in the main view * * @see ScalingGraphMousePlugin * @author Tom Nelson * */ public class SatelliteScalingGraphMousePlugin extends ScalingGraphMousePlugin { public SatelliteScalingGraphMousePlugin(ScalingControl scaler, int modifiers) { super(scaler, modifiers); } public SatelliteScalingGraphMousePlugin(ScalingControl scaler, int modifiers, float in, float out) { super(scaler, modifiers, in, out); } /** * zoom the master view display in or out, depending on the direction of the * mouse wheel motion. */ public void mouseWheelMoved(MouseWheelEvent e) { boolean accepted = checkModifiers(e); if(accepted == true) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); if(vv instanceof SatelliteVisualizationViewer) { VisualizationViewer vvMaster = ((SatelliteVisualizationViewer)vv).getMaster(); int amount = e.getWheelRotation(); if(amount > 0) { scaler.scale(vvMaster, in, vvMaster.getCenter()); } else if(amount < 0) { scaler.scale(vvMaster, out, vvMaster.getCenter()); } e.consume(); vv.repaint(); } } } } SatelliteShearingGraphMousePlugin.java000066400000000000000000000055511276402340000407250ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 15, 2005 */ package edu.uci.ics.jung.visualization.control; import java.awt.Dimension; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * Overrides ShearingGraphMousePlugin so that mouse events in the * satellite view cause shearing of the main view * * @see ShearingGraphMousePlugin * @author Tom Nelson * */ public class SatelliteShearingGraphMousePlugin extends ShearingGraphMousePlugin { public SatelliteShearingGraphMousePlugin() { super(); } public SatelliteShearingGraphMousePlugin(int modifiers) { super(modifiers); } /** * overridden to shear the main view */ public void mouseDragged(MouseEvent e) { if(down == null) return; VisualizationViewer vv = (VisualizationViewer)e.getSource(); boolean accepted = checkModifiers(e); if(accepted) { if(vv instanceof SatelliteVisualizationViewer) { VisualizationViewer vvMaster = ((SatelliteVisualizationViewer)vv).getMaster(); MutableTransformer modelTransformerMaster = vvMaster.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); vv.setCursor(cursor); Point2D q = down; Point2D p = e.getPoint(); float dx = (float) (p.getX()-q.getX()); float dy = (float) (p.getY()-q.getY()); Dimension d = vv.getSize(); float shx = 2.f*dx/d.height; float shy = 2.f*dy/d.width; // I want to compute shear based on the view coordinates of the // lens center in the satellite view. // translate the master view center to layout coords, then translate // that point to the satellite view's view coordinate system.... Point2D center = vv.getRenderContext().getMultiLayerTransformer().transform(vvMaster.getRenderContext().getMultiLayerTransformer().inverseTransform(vvMaster.getCenter())); if(p.getX() < center.getX()) { shy = -shy; } if(p.getY() < center.getY()) { shx = -shx; } modelTransformerMaster.shear(-shx, -shy, vvMaster.getCenter()); down.x = e.getX(); down.y = e.getY(); } e.consume(); } } } SatelliteTranslatingGraphMousePlugin.java000066400000000000000000000050631276402340000414510ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 15, 2005 */ package edu.uci.ics.jung.visualization.control; import java.awt.Cursor; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * Overrides TranslatingGraphMousePlugin so that mouse events in * the satellite view cause translating of the main view * * @see TranslatingGraphMousePlugin * @author Tom Nelson * */ public class SatelliteTranslatingGraphMousePlugin extends TranslatingGraphMousePlugin { public SatelliteTranslatingGraphMousePlugin() { super(); } public SatelliteTranslatingGraphMousePlugin(int modifiers) { super(modifiers); } /** * Check the modifiers. If accepted, translate the main view according * to the dragging of the mouse pointer in the satellite view * @param e the event */ public void mouseDragged(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); boolean accepted = checkModifiers(e); if(accepted) { if(vv instanceof SatelliteVisualizationViewer) { VisualizationViewer vvMaster = ((SatelliteVisualizationViewer)vv).getMaster(); MutableTransformer modelTransformerMaster = vvMaster.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); vv.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); try { Point2D q = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(down); Point2D p = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(e.getPoint()); float dx = (float) (p.getX()-q.getX()); float dy = (float) (p.getY()-q.getY()); modelTransformerMaster.translate(-dx, -dy); down.x = e.getX(); down.y = e.getY(); } catch(RuntimeException ex) { System.err.println("down = "+down+", e = "+e); throw ex; } } e.consume(); } } } SatelliteVisualizationViewer.java000066400000000000000000000110101276402340000400210ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 15, 2005 */ package edu.uci.ics.jung.visualization.control; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.geom.AffineTransform; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.transform.MutableAffineTransformer; import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer; /** * A VisualizationViewer that can act as a satellite view for another * (master) VisualizationViewer. In this view, the full graph is always visible * and all mouse actions affect the graph in the master view. * * A rectangular shape in the satellite view shows the visible bounds of * the master view. * * @author Tom Nelson * * */ @SuppressWarnings("serial") public class SatelliteVisualizationViewer extends VisualizationViewer { /** * the master VisualizationViewer that this is a satellite view for */ protected VisualizationViewer master; /** * @param master the master VisualizationViewer for which this is a satellite view * @param preferredSize the specified size of the component */ public SatelliteVisualizationViewer(VisualizationViewer master, Dimension preferredSize) { super(master.getModel(), preferredSize); this.master = master; // create a graph mouse with custom plugins to affect the master view ModalGraphMouse gm = new ModalSatelliteGraphMouse(); setGraphMouse(gm); // this adds the Lens to the satellite view addPreRenderPaintable(new ViewLens(this, master)); // get a copy of the current layout transform // it may have been scaled to fit the graph AffineTransform modelLayoutTransform = new AffineTransform(master.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getTransform()); // I want no layout transformations in the satellite view // this resets the auto-scaling that occurs in the super constructor getRenderContext().getMultiLayerTransformer().setTransformer(Layer.LAYOUT, new MutableAffineTransformer(modelLayoutTransform)); // make sure the satellite listens for changes in the master master.addChangeListener(this); // share the picked state of the master setPickedVertexState(master.getPickedVertexState()); setPickedEdgeState(master.getPickedEdgeState()); } /** * @return Returns the master. */ public VisualizationViewer getMaster() { return master; } /** * A four-sided shape that represents the visible part of the * master view and is drawn in the satellite view * * @author Tom Nelson * * */ static class ViewLens implements Paintable { VisualizationViewer master; VisualizationViewer vv; public ViewLens(VisualizationViewer vv, VisualizationViewer master) { this.vv = vv; this.master = master; } public void paint(Graphics g) { ShapeTransformer masterViewTransformer = master.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW); ShapeTransformer masterLayoutTransformer = master.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); ShapeTransformer vvLayoutTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); Shape lens = master.getBounds(); lens = masterViewTransformer.inverseTransform(lens); lens = masterLayoutTransformer.inverseTransform(lens); lens = vvLayoutTransformer.transform(lens); Graphics2D g2d = (Graphics2D)g; Color old = g.getColor(); Color lensColor = master.getBackground(); vv.setBackground(lensColor.darker()); g.setColor(lensColor); g2d.fill(lens); g.setColor(Color.gray); g2d.draw(lens); g.setColor(old); } public boolean useTransform() { return true; } } } ScalingControl.java000066400000000000000000000012321276402340000350550ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.visualization.control; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.VisualizationServer; public interface ScalingControl { /** * zoom the display in or out * @param vv the VisualizationViewer * @param amount how much to adjust scale by * @param at where to adjust scale from */ void scale(VisualizationServer vv, float amount, Point2D at); }ScalingGraphMousePlugin.java000066400000000000000000000066241276402340000367000ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.VisualizationViewer; /** * ScalingGraphMouse applies a scaling transformation to the graph layout. * The Vertices get closer or farther apart, but do not themselves change * size. ScalingGraphMouse uses MouseWheelEvents to apply the scaling. * * @author Tom Nelson */ public class ScalingGraphMousePlugin extends AbstractGraphMousePlugin implements MouseWheelListener { /** * the amount to zoom in by */ protected float in = 1.1f; /** * the amount to zoom out by */ protected float out = 1/1.1f; /** * whether to center the zoom at the current mouse position */ protected boolean zoomAtMouse = true; /** * controls scaling operations */ protected ScalingControl scaler; public ScalingGraphMousePlugin(ScalingControl scaler, int modifiers) { this(scaler, modifiers, 1.1f, 1/1.1f); } public ScalingGraphMousePlugin(ScalingControl scaler, int modifiers, float in, float out) { super(modifiers); this.scaler = scaler; this.in = in; this.out = out; } /** * @param zoomAtMouse The zoomAtMouse to set. */ public void setZoomAtMouse(boolean zoomAtMouse) { this.zoomAtMouse = zoomAtMouse; } public boolean checkModifiers(MouseEvent e) { return e.getModifiers() == modifiers || (e.getModifiers() & modifiers) != 0; } /** * zoom the display in or out, depending on the direction of the * mouse wheel motion. */ public void mouseWheelMoved(MouseWheelEvent e) { boolean accepted = checkModifiers(e); if(accepted == true) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); Point2D mouse = e.getPoint(); Point2D center = vv.getCenter(); int amount = e.getWheelRotation(); if(zoomAtMouse) { if(amount > 0) { scaler.scale(vv, in, mouse); } else if(amount < 0) { scaler.scale(vv, out, mouse); } } else { if(amount > 0) { scaler.scale(vv, in, center); } else if(amount < 0) { scaler.scale(vv, out, center); } } e.consume(); vv.repaint(); } } /** * @return Returns the zoom in value. */ public float getIn() { return in; } /** * @param in The zoom in value to set. */ public void setIn(float in) { this.in = in; } /** * @return Returns the zoom out value. */ public float getOut() { return out; } /** * @param out The zoom out value to set. */ public void setOut(float out) { this.out = out; } public ScalingControl getScaler() { return scaler; } public void setScaler(ScalingControl scaler) { this.scaler = scaler; } } ShearingGraphMousePlugin.java000066400000000000000000000122641276402340000370550ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.util.Collections; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * ShearingGraphMousePlugin allows the user to drag with the mouse * to shear the transform either in the horizontal or vertical direction. * By default, the control or meta key must be depressed to activate * shearing. * * * @author Tom Nelson */ public class ShearingGraphMousePlugin extends AbstractGraphMousePlugin implements MouseListener, MouseMotionListener { private static int mask = MouseEvent.CTRL_MASK; static { if(System.getProperty("os.name").startsWith("Mac")) { mask = MouseEvent.META_MASK; } } /** * create an instance with default modifier values */ public ShearingGraphMousePlugin() { this(MouseEvent.BUTTON1_MASK | mask); } /** * create an instance with passed modifier values * @param modifiers the mouse modifiers to use */ public ShearingGraphMousePlugin(int modifiers) { super(modifiers); Dimension cd = Toolkit.getDefaultToolkit().getBestCursorSize(16,16); BufferedImage cursorImage = new BufferedImage(cd.width,cd.height,BufferedImage.TYPE_INT_ARGB); Graphics g = cursorImage.createGraphics(); Graphics2D g2 = (Graphics2D)g; g2.addRenderingHints(Collections.singletonMap(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)); g.setColor(new Color(0,0,0,0)); g.fillRect(0,0,16,16); int left = 0; int top = 0; int right = 15; int bottom = 15; g.setColor(Color.white); g2.setStroke(new BasicStroke(3)); g.drawLine(left+2,top+5,right-2,top+5); g.drawLine(left+2,bottom-5,right-2,bottom-5); g.drawLine(left+2,top+5,left+4,top+3); g.drawLine(left+2,top+5,left+4,top+7); g.drawLine(right-2,bottom-5,right-4,bottom-3); g.drawLine(right-2,bottom-5,right-4,bottom-7); g.setColor(Color.black); g2.setStroke(new BasicStroke(1)); g.drawLine(left+2,top+5,right-2,top+5); g.drawLine(left+2,bottom-5,right-2,bottom-5); g.drawLine(left+2,top+5,left+4,top+3); g.drawLine(left+2,top+5,left+4,top+7); g.drawLine(right-2,bottom-5,right-4,bottom-3); g.drawLine(right-2,bottom-5,right-4,bottom-7); g.dispose(); cursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(), "RotateCursor"); } public void mousePressed(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); boolean accepted = checkModifiers(e); down = e.getPoint(); if(accepted) { vv.setCursor(cursor); } } public void mouseReleased(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); down = null; vv.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } public void mouseDragged(MouseEvent e) { if(down == null) return; VisualizationViewer vv = (VisualizationViewer)e.getSource(); boolean accepted = checkModifiers(e); if(accepted) { MutableTransformer modelTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); vv.setCursor(cursor); Point2D q = down; Point2D p = e.getPoint(); float dx = (float) (p.getX()-q.getX()); float dy = (float) (p.getY()-q.getY()); Dimension d = vv.getSize(); float shx = 2.f*dx/d.height; float shy = 2.f*dy/d.width; Point2D center = vv.getCenter(); if(p.getX() < center.getX()) { shy = -shy; } if(p.getY() < center.getY()) { shx = -shx; } modelTransformer.shear(shx, shy, center); down.x = e.getX(); down.y = e.getY(); e.consume(); } } public void mouseClicked(MouseEvent e) { // TODO Auto-generated method stub } public void mouseEntered(MouseEvent e) { // TODO Auto-generated method stub } public void mouseExited(MouseEvent e) { // TODO Auto-generated method stub } public void mouseMoved(MouseEvent e) { // TODO Auto-generated method stub } } SimpleEdgeSupport.java000066400000000000000000000044151276402340000355550ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/controlpackage edu.uci.ics.jung.visualization.control; import java.awt.geom.Point2D; import com.google.common.base.Supplier; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.BasicVisualizationServer; public class SimpleEdgeSupport implements EdgeSupport { protected Point2D down; protected EdgeEffects edgeEffects; protected EdgeType edgeType; protected Supplier edgeFactory; protected V startVertex; public SimpleEdgeSupport(Supplier edgeFactory) { this.edgeFactory = edgeFactory; this.edgeEffects = new CubicCurveEdgeEffects(); } // @Override public void startEdgeCreate(BasicVisualizationServer vv, V startVertex, Point2D startPoint, EdgeType edgeType) { this.startVertex = startVertex; this.down = startPoint; this.edgeType = edgeType; this.edgeEffects.startEdgeEffects(vv, startPoint, startPoint); if(edgeType == EdgeType.DIRECTED) { this.edgeEffects.startArrowEffects(vv, startPoint, startPoint); } vv.repaint(); } // @Override public void midEdgeCreate(BasicVisualizationServer vv, Point2D midPoint) { if(startVertex != null) { this.edgeEffects.midEdgeEffects(vv, down, midPoint); if(this.edgeType == EdgeType.DIRECTED) { this.edgeEffects.midArrowEffects(vv, down, midPoint); } vv.repaint(); } } // @Override public void endEdgeCreate(BasicVisualizationServer vv, V endVertex) { if(startVertex != null) { Graph graph = vv.getGraphLayout().getGraph(); graph.addEdge(edgeFactory.get(), startVertex, endVertex, edgeType); vv.repaint(); } startVertex = null; edgeType = EdgeType.UNDIRECTED; edgeEffects.endEdgeEffects(vv); edgeEffects.endArrowEffects(vv); } public EdgeEffects getEdgeEffects() { return edgeEffects; } public void setEdgeEffects(EdgeEffects edgeEffects) { this.edgeEffects = edgeEffects; } public EdgeType getEdgeType() { return edgeType; } public void setEdgeType(EdgeType edgeType) { this.edgeType = edgeType; } public Supplier getEdgeFactory() { return edgeFactory; } public void setEdgeFactory(Supplier edgeFactory) { this.edgeFactory = edgeFactory; } } SimpleVertexSupport.java000066400000000000000000000030251276402340000361620ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/controlpackage edu.uci.ics.jung.visualization.control; import java.awt.geom.Point2D; import com.google.common.base.Supplier; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.visualization.BasicVisualizationServer; /** * sample implementation showing how to use the VertexSupport interface member of the * EditingGraphMousePlugin. * override midVertexCreate and endVertexCreate for more elaborate implementations * @author tanelso * * @param the vertex type * @param the edge type */ public class SimpleVertexSupport implements VertexSupport { protected Supplier vertexFactory; public SimpleVertexSupport(Supplier vertexFactory) { this.vertexFactory = vertexFactory; } public void startVertexCreate(BasicVisualizationServer vv, Point2D point) { V newVertex = vertexFactory.get(); Layout layout = vv.getGraphLayout(); Graph graph = layout.getGraph(); graph.addVertex(newVertex); layout.setLocation(newVertex, vv.getRenderContext().getMultiLayerTransformer().inverseTransform(point)); vv.repaint(); } public void midVertexCreate(BasicVisualizationServer vv, Point2D point) { // noop } public void endVertexCreate(BasicVisualizationServer vv, Point2D point) { //noop } public Supplier getVertexFactory() { return vertexFactory; } public void setVertexFactory(Supplier vertexFactory) { this.vertexFactory = vertexFactory; } } TranslatingGraphMousePlugin.java000066400000000000000000000074331276402340000376050ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.Cursor; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * TranslatingGraphMousePlugin uses a MouseButtonOne press and * drag gesture to translate the graph display in the x and y * direction. The default MouseButtonOne modifier can be overridden * to cause a different mouse gesture to translate the display. * * * @author Tom Nelson */ public class TranslatingGraphMousePlugin extends AbstractGraphMousePlugin implements MouseListener, MouseMotionListener { /** */ public TranslatingGraphMousePlugin() { this(MouseEvent.BUTTON1_MASK); } /** * create an instance with passed modifer value * @param modifiers the mouse event modifier to activate this function */ public TranslatingGraphMousePlugin(int modifiers) { super(modifiers); this.cursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR); } /** * Check the event modifiers. Set the 'down' point for later * use. If this event satisfies the modifiers, change the cursor * to the system 'move cursor' * @param e the event */ public void mousePressed(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); boolean accepted = checkModifiers(e); down = e.getPoint(); if(accepted) { vv.setCursor(cursor); } } /** * unset the 'down' point and change the cursoe back to the system * default cursor */ public void mouseReleased(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); down = null; vv.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } /** * chack the modifiers. If accepted, translate the graph according * to the dragging of the mouse pointer * @param e the event */ public void mouseDragged(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); boolean accepted = checkModifiers(e); if(accepted) { MutableTransformer modelTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); vv.setCursor(cursor); try { Point2D q = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(down); Point2D p = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(e.getPoint()); float dx = (float) (p.getX()-q.getX()); float dy = (float) (p.getY()-q.getY()); modelTransformer.translate(dx, dy); down.x = e.getX(); down.y = e.getY(); } catch(RuntimeException ex) { System.err.println("down = "+down+", e = "+e); throw ex; } e.consume(); vv.repaint(); } } public void mouseClicked(MouseEvent e) { // TODO Auto-generated method stub } public void mouseEntered(MouseEvent e) { // TODO Auto-generated method stub } public void mouseExited(MouseEvent e) { // TODO Auto-generated method stub } public void mouseMoved(MouseEvent e) { // TODO Auto-generated method stub } } VertexSupport.java000066400000000000000000000011641276402340000350120ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/controlpackage edu.uci.ics.jung.visualization.control; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.BasicVisualizationServer; /** * interface to support the creation of new vertices by the EditingGraphMousePlugin. * SimpleVertexSupport is a sample implementation. * @author tanelso * * @param the vertex type */ public interface VertexSupport { void startVertexCreate(BasicVisualizationServer vv, Point2D point); void midVertexCreate(BasicVisualizationServer vv, Point2D point); void endVertexCreate(BasicVisualizationServer vv, Point2D point); } ViewScalingControl.java000066400000000000000000000024501276402340000357130ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationServer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * ViewScalingGraphMouse applies a scaling transform to the View * of the graph. This causes all elements of the graph to grow * larger or smaller. ViewScalingGraphMouse, by default, is activated * by the MouseWheel when the control key is pressed. The control * key modifier can be overridden in the contstructor. * * @author Tom Nelson */ public class ViewScalingControl implements ScalingControl { /** * zoom the display in or out, depending on the direction of the * mouse wheel motion. */ public void scale(VisualizationServer vv, float amount, Point2D from) { MutableTransformer viewTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW); viewTransformer.scale(amount, amount, from); vv.repaint(); } } ViewTranslatingGraphMousePlugin.java000066400000000000000000000070621276402340000404360ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 8, 2005 * */ package edu.uci.ics.jung.visualization.control; import java.awt.Cursor; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * ViewTranslatingGraphMousePlugin uses a MouseButtonOne press and * drag gesture to translate the graph display in the x and y * direction by changing the AffineTransform applied to the Graphics2D. * The default MouseButtonOne modifier can be overridden * to cause a different mouse gesture to translate the display. * * * @author Tom Nelson */ public class ViewTranslatingGraphMousePlugin extends AbstractGraphMousePlugin implements MouseListener, MouseMotionListener { /** */ public ViewTranslatingGraphMousePlugin() { this(MouseEvent.BUTTON1_MASK); } /** * create an instance with passed modifer value * @param modifiers the mouse event modifier to activate this function */ public ViewTranslatingGraphMousePlugin(int modifiers) { super(modifiers); this.cursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR); } /** * Check the event modifiers. Set the 'down' point for later * use. If this event satisfies the modifiers, change the cursor * to the system 'move cursor' * @param e the event */ public void mousePressed(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); boolean accepted = checkModifiers(e); down = e.getPoint(); if(accepted) { vv.setCursor(cursor); } } /** * unset the 'down' point and change the cursoe back to the system * default cursor */ public void mouseReleased(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); down = null; vv.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } /** * chack the modifiers. If accepted, translate the graph according * to the dragging of the mouse pointer * @param e the event */ public void mouseDragged(MouseEvent e) { VisualizationViewer vv = (VisualizationViewer)e.getSource(); boolean accepted = checkModifiers(e); if(accepted) { MutableTransformer viewTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW); vv.setCursor(cursor); try { Point2D q = viewTransformer.inverseTransform(down); Point2D p = viewTransformer.inverseTransform(e.getPoint()); float dx = (float) (p.getX()-q.getX()); float dy = (float) (p.getY()-q.getY()); viewTransformer.translate(dx, dy); down.x = e.getX(); down.y = e.getY(); } catch(RuntimeException ex) { System.err.println("down = "+down+", e = "+e); throw ex; } e.consume(); } } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mouseMoved(MouseEvent e) { } } jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/package.html000066400000000000000000000005651276402340000336410ustar00rootroot00000000000000

        Mechanisms for manipulating and controlling a graph visualization, largely in terms of mouse plugins. jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/000077500000000000000000000000001276402340000320375ustar00rootroot00000000000000AbstractVertexShapeTransformer.java000066400000000000000000000030411276402340000407660ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Created on Jul 16, 2004 * * Copyright (c) 2004, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.decorators; import com.google.common.base.Function; import com.google.common.base.Functions; import edu.uci.ics.jung.visualization.util.VertexShapeFactory; /** * * @author Joshua O'Madadhain */ public abstract class AbstractVertexShapeTransformer implements SettableVertexShapeTransformer { protected Function vsf; protected Function varf; protected VertexShapeFactory factory; public final static int DEFAULT_SIZE = 8; public final static float DEFAULT_ASPECT_RATIO = 1.0f; public AbstractVertexShapeTransformer(Function vsf, Function varf) { this.vsf = vsf; this.varf = varf; factory = new VertexShapeFactory(vsf, varf); } public AbstractVertexShapeTransformer() { this(Functions.constant(DEFAULT_SIZE), Functions.constant(DEFAULT_ASPECT_RATIO)); } public void setSizeTransformer(Function vsf) { this.vsf = vsf; factory = new VertexShapeFactory(vsf, varf); } public void setAspectRatioTransformer(Function varf) { this.varf = varf; factory = new VertexShapeFactory(vsf, varf); } } ConstantDirectionalEdgeValueTransformer.java000066400000000000000000000036161276402340000426050ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Created on Oct 21, 2004 * * Copyright (c) 2004, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.decorators; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.graph.util.EdgeType; /** * Returns the constructor-specified value for each edge type. * * @author Joshua O'Madadhain */ public class ConstantDirectionalEdgeValueTransformer implements Function,E>,Number> { protected Double undirected_value; protected Double directed_value; /** * * @param undirected the value to return if the edge is undirected * @param directed the value to return if the edge is directed */ public ConstantDirectionalEdgeValueTransformer(double undirected, double directed) { this.undirected_value = new Double(undirected); this.directed_value = new Double(directed); } public Number apply(Context,E> context) { Graph graph = context.graph; E e = context.element; if (graph.getEdgeType(e) == EdgeType.DIRECTED) return directed_value; else return undirected_value; } /** * Sets the value returned for undirected edges to value. * @param value the new value to return for undirected edges */ public void setUndirectedValue(double value) { this.undirected_value = value; } /** * Sets the value returned for directed edges to value. * @param value the new value to return for directed edges */ public void setDirectedValue(double value) { this.directed_value = value; } } DirectionalEdgeArrowTransformer.java000066400000000000000000000026061276402340000411070ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Created on Jul 18, 2004 * * Copyright (c) 2004, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.decorators; import java.awt.Shape; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.util.ArrowFactory; /** * Returns wedge arrows for undirected edges and notched arrows * for directed edges, of the specified dimensions. * * @author Joshua O'Madadhain */ public class DirectionalEdgeArrowTransformer implements Function,E>,Shape> { protected Shape undirected_arrow; protected Shape directed_arrow; public DirectionalEdgeArrowTransformer(int length, int width, int notch_depth) { directed_arrow = ArrowFactory.getNotchedArrow(width, length, notch_depth); undirected_arrow = ArrowFactory.getWedgeArrow(width, length); } /** * */ public Shape apply(Context,E> context) { if (context.graph.getEdgeType(context.element) == EdgeType.DIRECTED) return directed_arrow; else return undirected_arrow; } } EdgeShape.java000066400000000000000000000235271276402340000344610ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on March 10, 2005 */ package edu.uci.ics.jung.visualization.decorators; import static com.google.common.base.Preconditions.checkNotNull; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.CubicCurve2D; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.QuadCurve2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RectangularShape; import com.google.common.base.Function; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.EdgeIndexFunction; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.visualization.util.ArrowFactory; /** * An interface for decorators that return a * Shape for a specified edge. * * All edge shapes must be defined so that their endpoints are at * (0,0) and (1,0). They will be scaled, rotated and translated into * position by the PluggableRenderer. * * @author Tom Nelson * @param the vertex type * @param the edge type */ public class EdgeShape { private static final Line2D LINE = new Line2D.Float(0.0f, 0.0f, 1.0f, 0.0f); private static final GeneralPath BENT_LINE = new GeneralPath(); private static final QuadCurve2D QUAD_CURVE = new QuadCurve2D.Float(); private static final CubicCurve2D CUBIC_CURVE = new CubicCurve2D.Float(); private static final Ellipse2D ELLIPSE = new Ellipse2D.Float(-.5f, -.5f, 1, 1); private static Rectangle2D BOX = new Rectangle2D.Float(); private static GeneralPath triangle; private static GeneralPath bowtie; protected final Graph graph; /** * A convenience instance for other edge shapes to use for self-loop edges * where parallel instances will not overlay each other. */ protected final Loop loop; /** * A convenience instance for other edge shapes to use for self-loop edges * where parallel instances overlay each other. */ protected final SimpleLoop simpleLoop; protected final Box box; public EdgeShape(Graph g) { this.graph = g; this.box = new Box(); this.loop = new Loop(); this.simpleLoop = new SimpleLoop(); } private Shape getLoopOrNull(E e) { return getLoopOrNull(e, loop); } private Shape getLoopOrNull(E e, Function loop) { Pair endpoints = graph.getEndpoints(e); checkNotNull(endpoints); boolean isLoop = endpoints.getFirst().equals(endpoints.getSecond()); if (isLoop) { return loop.apply(e); } return null; } public static EdgeShape.Line line(Graph graph) { return new EdgeShape(graph).new Line(); } public static EdgeShape.QuadCurve quadCurve(Graph graph) { return new EdgeShape(graph).new QuadCurve(); } public static EdgeShape.QuadCurve cubicCurve(Graph graph) { return new EdgeShape(graph).new QuadCurve(); } public static EdgeShape.Orthogonal orthogonal(Graph graph) { return new EdgeShape(graph).new Orthogonal(); } public static EdgeShape.Wedge wedge(Graph graph, int width) { return new EdgeShape(graph).new Wedge(width); } /** * An edge shape that renders as a straight line between * the vertex endpoints. */ public class Line implements Function { /** * Get the shape for this edge, returning either the * shared instance or, in the case of self-loop edges, the * Loop shared instance. */ public Shape apply(E e) { Shape loop = getLoopOrNull(e); return loop == null ? LINE : loop; } } private int getIndex(E e, EdgeIndexFunction edgeIndexFunction) { return edgeIndexFunction == null ? 1 : edgeIndexFunction.getIndex(graph, e); } /** * An edge shape that renders as a bent-line between the * vertex endpoints. */ public class BentLine extends ParallelEdgeShapeTransformer { public void setEdgeIndexFunction(EdgeIndexFunction edgeIndexFunction) { this.edgeIndexFunction = edgeIndexFunction; loop.setEdgeIndexFunction(edgeIndexFunction); } /** * Get the shape for this edge, returning either the * shared instance or, in the case of self-loop edges, the * Loop shared instance. */ public Shape apply(E e) { Shape edgeShape = getLoopOrNull(e); if (edgeShape != null) { return edgeShape; } int index = getIndex(e, edgeIndexFunction); float controlY = control_offset_increment + control_offset_increment * index; BENT_LINE.reset(); BENT_LINE.moveTo(0.0f, 0.0f); BENT_LINE.lineTo(0.5f, controlY); BENT_LINE.lineTo(1.0f, 1.0f); return BENT_LINE; } } /** * An edge shape that renders as a QuadCurve between vertex * endpoints. */ public class QuadCurve extends ParallelEdgeShapeTransformer { @Override public void setEdgeIndexFunction(EdgeIndexFunction parallelEdgeIndexFunction) { this.edgeIndexFunction = parallelEdgeIndexFunction; loop.setEdgeIndexFunction(parallelEdgeIndexFunction); } /** * Get the shape for this edge, returning either the * shared instance or, in the case of self-loop edges, the * Loop shared instance. */ public Shape apply(E e) { Shape edgeShape = getLoopOrNull(e); if (edgeShape != null) { return edgeShape; } int index = getIndex(e, edgeIndexFunction); float controlY = control_offset_increment + control_offset_increment * index; QUAD_CURVE.setCurve(0.0f, 0.0f, 0.5f, controlY, 1.0f, 0.0f); return QUAD_CURVE; } } /** * An edge shape that renders as a CubicCurve between vertex * endpoints. The two control points are at * (1/3*length, 2*controlY) and (2/3*length, controlY) * giving a 'spiral' effect. */ public class CubicCurve extends ParallelEdgeShapeTransformer { public void setEdgeIndexFunction(EdgeIndexFunction edgeIndexFunction) { this.edgeIndexFunction = edgeIndexFunction; loop.setEdgeIndexFunction(edgeIndexFunction); } /** * Get the shape for this edge, returning either the * shared instance or, in the case of self-loop edges, the * Loop shared instance. */ public Shape apply(E e) { Shape edgeShape = getLoopOrNull(e); if (edgeShape != null) { return edgeShape; } int index = getIndex(e, edgeIndexFunction); float controlY = control_offset_increment + control_offset_increment * index; CUBIC_CURVE.setCurve(0.0f, 0.0f, 0.33f, 2 * controlY, .66f, -controlY, 1.0f, 0.0f); return CUBIC_CURVE; } } /** * An edge shape that renders as a loop with its nadir at the center of the * vertex. Parallel instances will overlap. * * @author Tom Nelson */ public class SimpleLoop extends ParallelEdgeShapeTransformer { public Shape apply(E e) { return ELLIPSE; } } private Shape buildFrame(RectangularShape shape, int index) { float x = -.5f; float y = -.5f; float diam = 1.f; diam += diam * index/2; x += x * index/2; y += y * index/2; shape.setFrame(x, y, diam, diam); return shape; } /** * An edge shape that renders as a loop with its nadir at the * center of the vertex. Parallel instances will not overlap. */ public class Loop extends ParallelEdgeShapeTransformer { public Shape apply(E e) { return buildFrame(ELLIPSE, getIndex(e, edgeIndexFunction)); } } /** * An edge shape that renders as an isosceles triangle whose * apex is at the destination vertex for directed edges, * and as a "bowtie" shape for undirected edges. * @author Joshua O'Madadhain */ public class Wedge extends ParallelEdgeShapeTransformer { public Wedge(int width) { triangle = ArrowFactory.getWedgeArrow(width, 1); triangle.transform(AffineTransform.getTranslateInstance(1,0)); bowtie = new GeneralPath(GeneralPath.WIND_EVEN_ODD); bowtie.moveTo(0, width/2); bowtie.lineTo(1, -width/2); bowtie.lineTo(1, width/2); bowtie.lineTo(0, -width/2); bowtie.closePath(); } public Shape apply(E e) { Shape edgeShape = getLoopOrNull(e); if (edgeShape != null) { return edgeShape; } return (graph.getEdgeType(e) == EdgeType.DIRECTED) ? triangle : bowtie; } } /** * An edge shape that renders as a diamond with its nadir at the * center of the vertex. Parallel instances will not overlap. */ public class Box extends ParallelEdgeShapeTransformer { public Shape apply(E e) { return buildFrame(BOX, getIndex(e, edgeIndexFunction)); } } /** * An edge shape that renders as a bent-line between the vertex endpoints. */ public class Orthogonal extends ParallelEdgeShapeTransformer { public Shape apply(E e) { Shape loop = getLoopOrNull(e, box); return loop == null ? LINE : loop; } } } EllipseVertexShapeTransformer.java000066400000000000000000000014561276402340000406300ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Created on Jul 16, 2004 * * Copyright (c) 2004, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.decorators; import java.awt.Shape; import com.google.common.base.Function; /** * * @author Joshua O'Madadhain */ public class EllipseVertexShapeTransformer extends AbstractVertexShapeTransformer implements Function { public EllipseVertexShapeTransformer() { } public EllipseVertexShapeTransformer(Function vsf, Function varf) { super(vsf, varf); } public Shape apply(V v) { return factory.getEllipse(v); } } GradientEdgePaintTransformer.java000066400000000000000000000066541276402340000403770ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Created on Apr 8, 2005 * * Copyright (c) 2004, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.decorators; import java.awt.Color; import java.awt.GradientPaint; import java.awt.Paint; import java.awt.geom.Point2D; import com.google.common.base.Function; import com.google.common.base.Predicate; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.util.SelfLoopEdgePredicate; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer; /** * Creates GradientPaint instances which can be used * to paint an Edge. For DirectedEdges, * the color will blend from c1 (source) to * c2 (destination); for UndirectedEdges, * the color will be c1 at each end and c2 * in the middle. * * @author Joshua O'Madadhain */ public class GradientEdgePaintTransformer implements Function { protected Color c1; protected Color c2; protected VisualizationViewer vv; protected BidirectionalTransformer transformer; protected Predicate,E>> selfLoop = new SelfLoopEdgePredicate(); public GradientEdgePaintTransformer(Color c1, Color c2, VisualizationViewer vv) { this.c1 = c1; this.c2 = c2; this.vv = vv; this.transformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); } public Paint apply(E e) { Layout layout = vv.getGraphLayout(); Pair p = layout.getGraph().getEndpoints(e); V b = p.getFirst(); V f = p.getSecond(); Point2D pb = transformer.transform(layout.apply(b)); Point2D pf = transformer.transform(layout.apply(f)); float xB = (float) pb.getX(); float yB = (float) pb.getY(); float xF = (float) pf.getX(); float yF = (float) pf.getY(); if ((layout.getGraph().getEdgeType(e)) == EdgeType.UNDIRECTED) { xF = (xF + xB) / 2; yF = (yF + yB) / 2; } if(selfLoop.apply(Context.,E>getInstance(layout.getGraph(), e))) { yF += 50; xF += 50; } return new GradientPaint(xB, yB, getColor1(e), xF, yF, getColor2(e), true); } /** * Returns c1. Subclasses may override * this method to enable more complex behavior (e.g., for * picked edges). * @param e the edge for which a color is to be retrieved * @return the constructor-supplied color {@code c1} */ protected Color getColor1(E e) { return c1; } /** * Returns c2. Subclasses may override * this method to enable more complex behavior (e.g., for * picked edges). * @param e the edge for which a color is to be retrieved * @return the constructor-supplied color {@code c2} */ protected Color getColor2(E e) { return c2; } } InterpolatingVertexSizeTransformer.java000066400000000000000000000037111276402340000417200ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Created on Nov 3, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.decorators; import com.google.common.base.Function; /** * Provides vertex sizes that are spaced proportionally between * min_size and max_size depending on * * @author Joshua O'Madadhain */ public class InterpolatingVertexSizeTransformer implements Function { protected double min; protected double max; protected Function values; protected int min_size; protected int size_diff; public InterpolatingVertexSizeTransformer(Function values, int min_size, int max_size) { super(); if (min_size < 0 || max_size < 0) throw new IllegalArgumentException("sizes must be non-negative"); if (min_size > max_size) throw new IllegalArgumentException("min_size must be <= max_size"); this.min = 0; this.max = 0; this.values = values; setMinSize(min_size); setMaxSize(max_size); } public Integer apply(V v) { Number n = values.apply(v); double value = min; if (n != null) value = n.doubleValue(); min = Math.min(this.min, value); max = Math.max(this.max, value); if (min == max) return min_size; // interpolate between min and max sizes based on how big value is // with respect to min and max values return min_size + (int)(((value - min) / (max - min)) * size_diff); } public void setMinSize(int min_size) { this.min_size = min_size; } public void setMaxSize(int max_size) { this.size_diff = max_size - this.min_size; } } NumberFormattingTransformer.java000066400000000000000000000020701276402340000403300ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Created on Feb 16, 2009 * * Copyright (c) 2009, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.decorators; import java.text.NumberFormat; import com.google.common.base.Function; /** * Transforms inputs to String representations by chaining an input * {@code Number}-generating {@code Function} with an internal * {@code NumberFormat} instance. * @author Joshua O'Madadhain */ public class NumberFormattingTransformer implements Function { private Function values; private NumberFormat formatter = NumberFormat.getInstance(); public NumberFormattingTransformer(Function values) { this.values = values; } /** * Returns a formatted string for the input. */ public String apply(T input) { return formatter.format(values.apply(input)); } } ParallelEdgeShapeTransformer.java000066400000000000000000000022421276402340000403500ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on March 10, 2005 */ package edu.uci.ics.jung.visualization.decorators; import java.awt.Shape; import com.google.common.base.Function; import edu.uci.ics.jung.graph.util.EdgeIndexFunction; /** * An abstract class for edge-to-Shape functions that work with parallel edges. * * @author Tom Nelson */ public abstract class ParallelEdgeShapeTransformer implements Function { /** Specifies the distance between control points for edges being drawn in parallel. */ protected float control_offset_increment = 20.f; protected EdgeIndexFunction edgeIndexFunction; public void setControlOffsetIncrement(float y) { control_offset_increment = y; } public void setEdgeIndexFunction(EdgeIndexFunction edgeIndexFunction) { this.edgeIndexFunction = edgeIndexFunction; } public EdgeIndexFunction getEdgeIndexFunction() { return edgeIndexFunction; } } PickableEdgePaintTransformer.java000066400000000000000000000031151276402340000403410ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Created on Mar 10, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.decorators; import java.awt.Paint; import com.google.common.base.Function; import edu.uci.ics.jung.visualization.picking.PickedInfo; /** * Paints each edge according to the Paint * parameters given in the constructor, so that picked and * non-picked edges can be made to look different. * * @author Tom Nelson * @author Joshua O'Madadhain * */ public class PickableEdgePaintTransformer implements Function { protected PickedInfo pi; protected Paint draw_paint; protected Paint picked_paint; /** * * @param pi specifies which vertices report as "picked" * @param draw_paint Paint used to draw edge shapes * @param picked_paint Paint used to draw picked edge shapes */ public PickableEdgePaintTransformer(PickedInfo pi, Paint draw_paint, Paint picked_paint) { if (pi == null) throw new IllegalArgumentException("PickedInfo instance must be non-null"); this.pi = pi; this.draw_paint = draw_paint; this.picked_paint = picked_paint; } /** * */ public Paint apply(E e) { if (pi.isPicked(e)) { return picked_paint; } else { return draw_paint; } } } PickableVertexIconTransformer.java000066400000000000000000000030321276402340000405650ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Created on Mar 10, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.decorators; import javax.swing.Icon; import com.google.common.base.Function; import edu.uci.ics.jung.visualization.picking.PickedInfo; /** * Supplies an Icon for each vertex according to the Icon * parameters given in the constructor, so that picked and * non-picked vertices can be made to look different. */ public class PickableVertexIconTransformer implements Function { protected Icon icon; protected Icon picked_icon; protected PickedInfo pi; /** * * @param pi specifies which vertices report as "picked" * @param icon Icon used to represent vertices * @param picked_icon Icon used to represent picked vertices */ public PickableVertexIconTransformer(PickedInfo pi, Icon icon, Icon picked_icon) { if (pi == null) throw new IllegalArgumentException("PickedInfo instance must be non-null"); this.pi = pi; this.icon = icon; this.picked_icon = picked_icon; } /** * Returns the appropriate Icon, depending on picked state. */ public Icon apply(V v) { if (pi.isPicked(v)) return picked_icon; else return icon; } } PickableVertexPaintTransformer.java000066400000000000000000000027641276402340000407630ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Created on Mar 10, 2005 * * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.decorators; import java.awt.Paint; import com.google.common.base.Function; import edu.uci.ics.jung.visualization.picking.PickedInfo; /** * Paints each vertex according to the Paint * parameters given in the constructor, so that picked and * non-picked vertices can be made to look different. */ public class PickableVertexPaintTransformer implements Function { protected Paint fill_paint; protected Paint picked_paint; protected PickedInfo pi; /** * * @param pi specifies which vertices report as "picked" * @param fill_paint Paint used to fill vertex shapes * @param picked_paint Paint used to fill picked vertex shapes */ public PickableVertexPaintTransformer(PickedInfo pi, Paint fill_paint, Paint picked_paint) { if (pi == null) throw new IllegalArgumentException("PickedInfo instance must be non-null"); this.pi = pi; this.fill_paint = fill_paint; this.picked_paint = picked_paint; } public Paint apply(V v) { if (pi.isPicked(v)) return picked_paint; else return fill_paint; } } SettableVertexShapeTransformer.java000066400000000000000000000012161276402340000407700ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Created on Jul 18, 2004 * * Copyright (c) 2004, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.decorators; import java.awt.Shape; import com.google.common.base.Function; /** * * @author Joshua O'Madadhain */ public interface SettableVertexShapeTransformer extends Function { public abstract void setSizeTransformer(Function vsf); public abstract void setAspectRatioTransformer(Function varf); }ToStringLabeller.java000066400000000000000000000014161276402340000360410ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * Created on Apr 13, 2004 */ package edu.uci.ics.jung.visualization.decorators; import com.google.common.base.Function; /** * Labels vertices by their toString. This class functions as a drop-in * replacement for the default StringLabeller method. This class does not * guarantee unique labels; or even consistent ones. * * @author danyelf */ public class ToStringLabeller implements Function { /** * @return o.toString() */ public String apply(Object o) { return o.toString(); } }VertexIconShapeTransformer.java000066400000000000000000000060411276402340000401160ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 1, 2005 */ package edu.uci.ics.jung.visualization.decorators; import java.awt.Image; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.util.HashMap; import java.util.Map; import javax.swing.Icon; import javax.swing.ImageIcon; import com.google.common.base.Function; import edu.uci.ics.jung.visualization.util.ImageShapeUtils; /** * A default implementation that stores images in a Map keyed on the * vertex. Also applies a shaping function to images to extract the * shape of the opaque part of a transparent image. * * @author Tom Nelson */ public class VertexIconShapeTransformer implements Function { protected Map shapeMap = new HashMap(); protected Map iconMap; protected Function delegate; /** * Creates an instance with the specified delegate. * @param delegate the vertex-to-shape function to use if no image is present for the vertex */ public VertexIconShapeTransformer(Function delegate) { this.delegate = delegate; } /** * @return Returns the delegate. */ public Function getDelegate() { return delegate; } /** * @param delegate The delegate to set. */ public void setDelegate(Function delegate) { this.delegate = delegate; } /** * get the shape from the image. If not available, get * the shape from the delegate VertexShapeFunction */ public Shape apply(V v) { Icon icon = iconMap.get(v); if (icon != null && icon instanceof ImageIcon) { Image image = ((ImageIcon) icon).getImage(); Shape shape = (Shape) shapeMap.get(image); if (shape == null) { shape = ImageShapeUtils.getShape(image, 30); if(shape.getBounds().getWidth() > 0 && shape.getBounds().getHeight() > 0) { // don't cache a zero-sized shape, wait for the image // to be ready int width = image.getWidth(null); int height = image.getHeight(null); AffineTransform transform = AffineTransform .getTranslateInstance(-width / 2, -height / 2); shape = transform.createTransformedShape(shape); shapeMap.put(image, shape); } } return shape; } else { return delegate.apply(v); } } /** * @return the iconMap */ public Map getIconMap() { return iconMap; } /** * @param iconMap the iconMap to set */ public void setIconMap(Map iconMap) { this.iconMap = iconMap; } /** * @return the shapeMap */ public Map getShapeMap() { return shapeMap; } /** * @param shapeMap the shapeMap to set */ public void setShapeMap(Map shapeMap) { this.shapeMap = shapeMap; } } package.html000066400000000000000000000005531276402340000342440ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators

        Mechanisms for associating data (shapes, colors, values, strings, etc.) with graph elements. jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/000077500000000000000000000000001276402340000312075ustar00rootroot00000000000000BoundingRectangleCollector.java000066400000000000000000000050231276402340000372340ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layoutpackage edu.uci.ics.jung.visualization.layout; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.List; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.visualization.RenderContext; public class BoundingRectangleCollector { protected RenderContext rc; protected Graph graph; protected Layout layout; protected List rectangles = new ArrayList(); public BoundingRectangleCollector(RenderContext rc, Layout layout) { this.rc = rc; this.layout = layout; this.graph = layout.getGraph(); compute(); } /** * @return the rectangles */ public List getRectangles() { return rectangles; } public void compute() { rectangles.clear(); // Graphics2D g2d = (Graphics2D)g; // g.setColor(Color.cyan); for(E e : graph.getEdges()) { Pair endpoints = graph.getEndpoints(e); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); Point2D p1 = layout.apply(v1); Point2D p2 = layout.apply(v2); float x1 = (float)p1.getX(); float y1 = (float)p1.getY(); float x2 = (float)p2.getX(); float y2 = (float)p2.getY(); boolean isLoop = v1.equals(v2); Shape s2 = rc.getVertexShapeTransformer().apply(v2); Shape edgeShape = rc.getEdgeShapeTransformer().apply(e); AffineTransform xform = AffineTransform.getTranslateInstance(x1,y1); if(isLoop) { Rectangle2D s2Bounds = s2.getBounds2D(); xform.scale(s2Bounds.getWidth(), s2Bounds.getHeight()); xform.translate(0, -edgeShape.getBounds2D().getWidth()/2); } else { float dx = x2-x1; float dy = y2-y1; float theta = (float)Math.atan2(dy,dx); xform.rotate(theta); float dist = (float)p1.distance(p2); xform.scale(dist, 1.0); } edgeShape = xform.createTransformedShape(edgeShape); // edgeShape = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, edgeShape); rectangles.add(edgeShape.getBounds2D()); } for(V v : graph.getVertices()) { Shape shape = rc.getVertexShapeTransformer().apply(v); Point2D p = layout.apply(v); // p = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p); float x = (float)p.getX(); float y = (float)p.getY(); AffineTransform xform = AffineTransform.getTranslateInstance(x, y); shape = xform.createTransformedShape(shape); rectangles.add(shape.getBounds2D()); } } } BoundingRectanglePaintable.java000066400000000000000000000031071276402340000372060ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layoutpackage edu.uci.ics.jung.visualization.layout; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; import java.util.List; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.VisualizationServer; import edu.uci.ics.jung.visualization.util.ChangeEventSupport; public class BoundingRectanglePaintable implements VisualizationServer.Paintable { protected RenderContext rc; protected Graph graph; protected Layout layout; protected List rectangles; public BoundingRectanglePaintable(RenderContext rc, Layout layout) { super(); this.rc = rc; this.layout = layout; this.graph = layout.getGraph(); final BoundingRectangleCollector brc = new BoundingRectangleCollector(rc, layout); this.rectangles = brc.getRectangles(); if(layout instanceof ChangeEventSupport) { ((ChangeEventSupport)layout).addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { brc.compute(); rectangles = brc.getRectangles(); }}); } } public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; g.setColor(Color.cyan); for(Rectangle2D r : rectangles) { g2d.draw(rc.getMultiLayerTransformer().transform(Layer.LAYOUT, r)); } } public boolean useTransform() { return true; } } CachingLayout.java000066400000000000000000000037271276402340000345360ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 23, 2005 */ package edu.uci.ics.jung.visualization.layout; import java.awt.geom.Point2D; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.LayoutDecorator; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.visualization.util.Caching; /** * A LayoutDecorator that caches locations in a clearable Map. This can be used to ensure that * edge endpoints are always the same as vertex locations when they are drawn in the render loop * during the time that the layout's relaxer thread is changing the locations. * * @see LayoutDecorator * @author Tom Nelson * */ public class CachingLayout extends LayoutDecorator implements Caching { protected LoadingCache locations; public CachingLayout(Layout delegate) { super(delegate); Function chain = Functions.compose( new Function() { public Point2D apply(Point2D p) { return (Point2D)p.clone(); }}, delegate); this.locations = CacheBuilder.newBuilder().build(CacheLoader.from(chain)); } @Override public void setGraph(Graph graph) { delegate.setGraph(graph); } public void clear() { this.locations = CacheBuilder.newBuilder().build(new CacheLoader() { public Point2D load(V vertex) { return new Point2D.Double(); } }); } public void init() { } public Point2D apply(V v) { return locations.getUnchecked(v); } } LayoutChangeListener.java000066400000000000000000000002251276402340000360630ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layoutpackage edu.uci.ics.jung.visualization.layout; public interface LayoutChangeListener { void layoutChanged(LayoutEvent evt); } LayoutEvent.java000066400000000000000000000007711276402340000342570ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layoutpackage edu.uci.ics.jung.visualization.layout; import edu.uci.ics.jung.graph.Graph; public class LayoutEvent { V vertex; Graph graph; public LayoutEvent(V vertex, Graph graph) { this.vertex = vertex; this.graph = graph; } public V getVertex() { return vertex; } public void setVertex(V vertex) { this.vertex = vertex; } public Graph getGraph() { return graph; } public void setGraph(Graph graph) { this.graph = graph; } } LayoutEventSupport.java000066400000000000000000000003651276402340000356530ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layoutpackage edu.uci.ics.jung.visualization.layout; public interface LayoutEventSupport { void addLayoutChangeListener(LayoutChangeListener listener); void removeLayoutChangeListener(LayoutChangeListener listener); } LayoutTransition.java000066400000000000000000000033301276402340000353220ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layoutpackage edu.uci.ics.jung.visualization.layout; import java.awt.geom.Point2D; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.StaticLayout; import edu.uci.ics.jung.algorithms.layout.util.Relaxer; import edu.uci.ics.jung.algorithms.layout.util.VisRunner; import edu.uci.ics.jung.algorithms.util.IterativeContext; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.visualization.VisualizationViewer; public class LayoutTransition implements IterativeContext { protected Layout startLayout; protected Layout endLayout; protected Layout transitionLayout; protected boolean done = false; protected int count = 20; protected int counter = 0; protected VisualizationViewer vv; public LayoutTransition(VisualizationViewer vv, Layout startLayout, Layout endLayout) { this.vv = vv; this.startLayout = startLayout; this.endLayout = endLayout; if(endLayout instanceof IterativeContext) { Relaxer relaxer = new VisRunner((IterativeContext)endLayout); relaxer.prerelax(); } this.transitionLayout = new StaticLayout(startLayout.getGraph(), startLayout); vv.setGraphLayout(transitionLayout); } public boolean done() { return done; } public void step() { Graph g = transitionLayout.getGraph(); for(V v : g.getVertices()) { Point2D tp = transitionLayout.apply(v); Point2D fp = endLayout.apply(v); double dx = (fp.getX()-tp.getX())/(count-counter); double dy = (fp.getY()-tp.getY())/(count-counter); transitionLayout.setLocation(v, new Point2D.Double(tp.getX()+dx,tp.getY()+dy)); } counter++; if(counter >= count) { done = true; vv.setGraphLayout(endLayout); } vv.repaint(); } } ObservableCachingLayout.java000066400000000000000000000071711276402340000365400ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 23, 2005 */ package edu.uci.ics.jung.visualization.layout; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.List; import javax.swing.event.ChangeListener; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.LayoutDecorator; import edu.uci.ics.jung.algorithms.util.IterativeContext; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.visualization.util.Caching; import edu.uci.ics.jung.visualization.util.ChangeEventSupport; import edu.uci.ics.jung.visualization.util.DefaultChangeEventSupport; /** * A LayoutDecorator that fires ChangeEvents when certain methods * are called. Used to wrap a Layout so that the visualization * components can be notified of changes. * * @see LayoutDecorator * @author Tom Nelson * */ public class ObservableCachingLayout extends LayoutDecorator implements ChangeEventSupport, Caching, LayoutEventSupport { protected ChangeEventSupport changeSupport = new DefaultChangeEventSupport(this); protected LoadingCache locations; private List> layoutChangeListeners = new ArrayList>(); public ObservableCachingLayout(Layout delegate) { super(delegate); Function chain = Functions. compose( new Function() { public Point2D apply(Point2D p) { return (Point2D) p.clone(); } }, delegate); this.locations = CacheBuilder.newBuilder().build(CacheLoader.from(chain)); } @Override public void step() { super.step(); fireStateChanged(); } @Override public void initialize() { super.initialize(); fireStateChanged(); } @Override public boolean done() { if(delegate instanceof IterativeContext) { return ((IterativeContext)delegate).done(); } return true; } @Override public void setLocation(V v, Point2D location) { super.setLocation(v, location); fireStateChanged(); fireLayoutChanged(v); } public void addChangeListener(ChangeListener l) { changeSupport.addChangeListener(l); } public void removeChangeListener(ChangeListener l) { changeSupport.removeChangeListener(l); } public ChangeListener[] getChangeListeners() { return changeSupport.getChangeListeners(); } public void fireStateChanged() { changeSupport.fireStateChanged(); } @Override public void setGraph(Graph graph) { delegate.setGraph(graph); } public void clear() { this.locations.invalidateAll(); } public void init() { } public Point2D apply(V v) { return locations.getUnchecked(v); } private void fireLayoutChanged(V v) { LayoutEvent evt = new LayoutEvent(v, this.getGraph()); for(LayoutChangeListener listener : layoutChangeListeners) { listener.layoutChanged(evt); } } public void addLayoutChangeListener(LayoutChangeListener listener) { layoutChangeListeners.add(listener); } public void removeLayoutChangeListener(LayoutChangeListener listener) { layoutChangeListeners.remove(listener); } } PersistentLayout.java000066400000000000000000000023131276402340000353300ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Oct 9, 2004 * */ package edu.uci.ics.jung.visualization.layout; import java.awt.geom.Point2D; import java.io.IOException; import java.io.Serializable; import edu.uci.ics.jung.algorithms.layout.Layout; /** * interface for PersistentLayout * Also holds a nested class Point to serialize the * Vertex locations * * @author Tom Nelson */ public interface PersistentLayout extends Layout { void persist(String fileName) throws IOException; void restore(String fileName) throws IOException, ClassNotFoundException; void lock(boolean state); /** * a serializable class to save locations */ @SuppressWarnings("serial") static class Point implements Serializable { public double x; public double y; public Point(double x, double y) { this.x=x; this.y=y; } public Point(Point2D p) { this.x = p.getX(); this.y = p.getY(); } } }PersistentLayoutImpl.java000066400000000000000000000117241276402340000361600ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Oct 8, 2004 * */ package edu.uci.ics.jung.visualization.layout; import java.awt.Dimension; import java.awt.geom.Point2D; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.google.common.base.Function; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.visualization.util.Caching; import edu.uci.ics.jung.visualization.util.ChangeEventSupport; /** * Implementation of PersistentLayout. * Defers to another layout until 'restore' is called, * then it uses the saved vertex locations * * @author Tom Nelson * * */ public class PersistentLayoutImpl extends ObservableCachingLayout implements PersistentLayout, ChangeEventSupport, Caching { /** * a container for Vertices */ protected Map locations; /** * a collection of Vertices that should not move */ protected Set dontmove; /** * whether the graph is locked (stops the VisualizationViewer rendering thread) */ protected boolean locked; /** * create an instance with a passed layout * create containers for graph components * @param layout the layout whose positions are to be persisted */ public PersistentLayoutImpl(Layout layout) { super(layout); this.locations = Maps.asMap( ImmutableSet.copyOf(layout.getGraph().getVertices()), new RandomPointFactory(getSize())); this.dontmove = new HashSet(); } /** * This method calls initialize_local_vertex for each vertex, and * also adds initial coordinate information for each vertex. (The vertex's * initial location is set by calling initializeLocation. */ protected void initializeLocations() { for(V v : getGraph().getVertices()) { Point2D coord = delegate.apply(v); if (!dontmove.contains(v)) initializeLocation(v, coord); } } /** * Sets persisted location for a vertex within the dimensions of the space. * If the vertex has not been persisted, sets a random location. If you want * to initialize in some different way, override this method. * * @param v the vertex whose location is to be initialized * @param coord the location */ protected void initializeLocation(V v, Point2D coord) { Point point = locations.get(v); coord.setLocation(point.x, point.y); } /** * save the Vertex locations to a file * @param fileName the file to save to * @throws IOException if the file cannot be used */ public void persist(String fileName) throws IOException { for(V v : getGraph().getVertices()) { Point p = new Point(transform(v)); locations.put(v, p); } ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream( fileName)); oos.writeObject(locations); oos.close(); } /** * Restore the graph Vertex locations from a file * @param fileName the file to use * @throws IOException for file problems * @throws ClassNotFoundException for classpath problems */ @SuppressWarnings("unchecked") public void restore(String fileName) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream( fileName)); locations = (Map) ois.readObject(); ois.close(); initializeLocations(); locked = true; fireStateChanged(); } public void lock(boolean locked) { this.locked = locked; } /* * (non-Javadoc) * * @see edu.uci.ics.jung.visualization.Layout#incrementsAreDone() */ public boolean done() { return super.done() || locked; } /* * (non-Javadoc) * * @see edu.uci.ics.jung.visualization.Layout#lockVertex(edu.uci.ics.jung.graph.Vertex) */ public void lock(V v, boolean state) { dontmove.add(v); delegate.lock(v, state); } @SuppressWarnings("serial") public static class RandomPointFactory implements Function, Serializable { Dimension d; public RandomPointFactory(Dimension d) { this.d = d; } public edu.uci.ics.jung.visualization.layout.PersistentLayout.Point apply(V v) { double x = Math.random() * d.width; double y = Math.random() * d.height; return new Point(x,y); } } }jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/package.html000066400000000000000000000005541276402340000334740ustar00rootroot00000000000000

        Visualization mechanisms related to graph layout: caching, persistence, event-emitting, etc. jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/package.html000066400000000000000000000005251276402340000321550ustar00rootroot00000000000000

        Frameworks and mechanisms for visualizing JUNG graphs using Swing/AWT. jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/000077500000000000000000000000001276402340000313165ustar00rootroot00000000000000AbstractPickedState.java000066400000000000000000000023171276402340000357710ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * * Created on Apr 2, 2005 */ package edu.uci.ics.jung.visualization.picking; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import javax.swing.event.EventListenerList; /** * An abstract class to support ItemEvents for PickedState * * @author Tom Nelson */ public abstract class AbstractPickedState implements PickedState { protected EventListenerList listenerList = new EventListenerList(); public void addItemListener(ItemListener l) { listenerList.add(ItemListener.class, l); } public void removeItemListener(ItemListener l) { listenerList.remove(ItemListener.class, l); } protected void fireItemStateChanged(ItemEvent e) { Object[] listeners = listenerList.getListenerList(); for ( int i = listeners.length-2; i>=0; i-=2 ) { if ( listeners[i]==ItemListener.class ) { ((ItemListener)listeners[i+1]).itemStateChanged(e); } } } }ClosestShapePickSupport.java000066400000000000000000000117261276402340000367120ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/** * Copyright (c) 2008, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Apr 24, 2008 * */ package edu.uci.ics.jung.visualization.picking; import java.awt.Shape; import java.awt.geom.Point2D; import java.util.Collection; import java.util.ConcurrentModificationException; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationServer; /** * A GraphElementAccessor that finds the closest element to * the pick point, and returns it if it is within the element's shape. * This is best suited to elements with convex shapes that do not overlap. * It differs from ShapePickSupport in that it only checks * the closest element to see whether it contains the pick point. * Possible unexpected odd behaviors: *

          *
        • If the elements overlap, this mechanism may pick another element than the one that's * "on top" (rendered last) if the pick point is closer to the center of an obscured vertex. *
        • If element shapes are not convex, then this mechanism may return null * even if the pick point is inside some element's shape, if the pick point is closer * to the center of another element. *
        * Users who want to avoid either of these should use ShapePickSupport * instead, which is slower but more flexible. If neither of the above conditions * (overlapping elements or non-convex shapes) is true, then ShapePickSupport * and this class should have the same behavior. */ public class ClosestShapePickSupport implements GraphElementAccessor { protected VisualizationServer vv; protected float pickSize; /** * Creates a ShapePickSupport for the vv * VisualizationServer, with the specified pick footprint. * The VisualizationServer is used to fetch the current * Layout. * @param vv source of the current Layout. * @param pickSize the size of the pick footprint for line edges */ public ClosestShapePickSupport(VisualizationServer vv, float pickSize) { this.vv = vv; this.pickSize = pickSize; } /** * Create a ShapePickSupport with the vv * VisualizationServer and default pick footprint. * The footprint defaults to 2. * @param vv source of the current Layout. */ public ClosestShapePickSupport(VisualizationServer vv) { this.vv = vv; } /** * @see edu.uci.ics.jung.algorithms.layout.GraphElementAccessor#getEdge(edu.uci.ics.jung.algorithms.layout.Layout, double, double) */ public E getEdge(Layout layout, double x, double y) { return null; } /** * @see edu.uci.ics.jung.algorithms.layout.GraphElementAccessor#getVertex(edu.uci.ics.jung.algorithms.layout.Layout, double, double) */ public V getVertex(Layout layout, double x, double y) { // first, find the closest vertex to (x,y) double minDistance = Double.MAX_VALUE; V closest = null; while(true) { try { for(V v : layout.getGraph().getVertices()) { Point2D p = layout.apply(v); double dx = p.getX() - x; double dy = p.getY() - y; double dist = dx * dx + dy * dy; if (dist < minDistance) { minDistance = dist; closest = v; } } break; } catch(ConcurrentModificationException cme) {} } // now check to see whether (x,y) is in the shape for this vertex. // get the vertex shape Shape shape = vv.getRenderContext().getVertexShapeTransformer().apply(closest); // get the vertex location Point2D p = layout.apply(closest); // transform the vertex location to screen coords p = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p); double ox = x - p.getX(); double oy = y - p.getY(); if (shape.contains(ox, oy)) return closest; else return null; } /** * @see edu.uci.ics.jung.algorithms.layout.GraphElementAccessor#getVertices(edu.uci.ics.jung.algorithms.layout.Layout, java.awt.Shape) */ public Collection getVertices(Layout layout, Shape rectangle) { // FIXME: RadiusPickSupport and ShapePickSupport are not using the same mechanism! // talk to Tom and make sure I understand which should be used. // in particular, there are some transformations that the latter uses; the latter is also // doing a couple of kinds of filtering. (well, only one--just predicate-based.) // looks to me like the VV could (should) be doing this filtering. (maybe.) // return null; } } LayoutLensShapePickSupport.java000066400000000000000000000200441276402340000373660ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 11, 2005 * */ package edu.uci.ics.jung.visualization.picking; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.Set; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationServer; /** * ShapePickSupport provides access to Vertices and EdgeType based on * their actual shapes. * * @author Tom Nelson * */ public class LayoutLensShapePickSupport extends ShapePickSupport implements GraphElementAccessor { public LayoutLensShapePickSupport(VisualizationServer vv, float pickSize) { super(vv,pickSize); } public LayoutLensShapePickSupport(VisualizationServer vv) { this(vv,2); } public V getVertex(Layout layout, double x, double y) { V closest = null; double minDistance = Double.MAX_VALUE; while(true) { try { for(V v : getFilteredVertices(layout)) { Shape shape = vv.getRenderContext().getVertexShapeTransformer().apply(v); // get the vertex location Point2D p = layout.apply(v); if(p == null) continue; // transform the vertex location to screen coords p = vv.getRenderContext().getMultiLayerTransformer().transform(p); AffineTransform xform = AffineTransform.getTranslateInstance(p.getX(), p.getY()); shape = xform.createTransformedShape(shape); // see if this vertex center is closest to the pick point // among any other containing vertices if(shape.contains(x, y)) { if(style == Style.LOWEST) { // return the first match return v; } else if(style == Style.HIGHEST) { // will return the last match closest = v; } else { Rectangle2D bounds = shape.getBounds2D(); double dx = bounds.getCenterX() - x; double dy = bounds.getCenterY() - y; double dist = dx * dx + dy * dy; if (dist < minDistance) { minDistance = dist; closest = v; } } } } break; } catch(ConcurrentModificationException cme) {} } return closest; } public Collection getVertices(Layout layout, Shape rectangle) { Set pickedVertices = new HashSet(); while(true) { try { for(V v : getFilteredVertices(layout)) { Point2D p = layout.apply(v); if(p == null) continue; p = vv.getRenderContext().getMultiLayerTransformer().transform(p); if(rectangle.contains(p)) { pickedVertices.add(v); } } break; } catch(ConcurrentModificationException cme) {} } return pickedVertices; } public E getEdge(Layout layout, double x, double y) { Point2D ip = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, new Point2D.Double(x,y)); x = ip.getX(); y = ip.getY(); // as a Line has no area, we can't always use edgeshape.contains(point) so we // make a small rectangular pickArea around the point and check if the // edgeshape.intersects(pickArea) Rectangle2D pickArea = new Rectangle2D.Float((float)x-pickSize/2,(float)y-pickSize/2,pickSize,pickSize); E closest = null; double minDistance = Double.MAX_VALUE; while(true) { try { for(E e : getFilteredEdges(layout)) { Pair pair = layout.getGraph().getEndpoints(e); V v1 = pair.getFirst(); V v2 = pair.getSecond(); boolean isLoop = v1.equals(v2); Point2D p1 = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, layout.apply(v1)); Point2D p2 = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, layout.apply(v2)); if(p1 == null || p2 == null) continue; float x1 = (float) p1.getX(); float y1 = (float) p1.getY(); float x2 = (float) p2.getX(); float y2 = (float) p2.getY(); // translate the edge to the starting vertex AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1); Shape edgeShape = vv.getRenderContext().getEdgeShapeTransformer().apply(e); if(isLoop) { // make the loops proportional to the size of the vertex Shape s2 = vv.getRenderContext().getVertexShapeTransformer().apply(v2); Rectangle2D s2Bounds = s2.getBounds2D(); xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight()); // move the loop so that the nadir is centered in the vertex xform.translate(0, -edgeShape.getBounds2D().getHeight()/2); } else { float dx = x2 - x1; float dy = y2 - y1; // rotate the edge to the angle between the vertices double theta = Math.atan2(dy,dx); xform.rotate(theta); // stretch the edge to span the distance between the vertices float dist = (float) Math.sqrt(dx*dx + dy*dy); xform.scale(dist, 1.0f); } // transform the edge to its location and dimensions edgeShape = xform.createTransformedShape(edgeShape); // because of the transform, the edgeShape is now a GeneralPath // see if this edge is the closest of any that intersect if(edgeShape.intersects(pickArea)) { float cx=0; float cy=0; float[] f = new float[6]; PathIterator pi = new GeneralPath(edgeShape).getPathIterator(null); if(pi.isDone()==false) { pi.next(); pi.currentSegment(f); cx = f[0]; cy = f[1]; if(pi.isDone()==false) { pi.currentSegment(f); cx = f[0]; cy = f[1]; } } float dx = (float) (cx - x); float dy = (float) (cy - y); float dist = dx * dx + dy * dy; if (dist < minDistance) { minDistance = dist; closest = e; } } } break; } catch(ConcurrentModificationException cme) {} } return closest; } } MultiPickedState.java000066400000000000000000000041761276402340000353250ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * * Created on Mar 28, 2005 */ package edu.uci.ics.jung.visualization.picking; import java.awt.event.ItemEvent; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; /** * Maintains the state of what has been 'picked' in the graph. * The Sets are constructed so that their iterators * will traverse them in the order in which they are picked. * * @author Tom Nelson * @author Joshua O'Madadhain * */ public class MultiPickedState extends AbstractPickedState implements PickedState { /** * the 'picked' vertices */ protected Set picked = new LinkedHashSet(); public boolean pick(T v, boolean state) { boolean prior_state = this.picked.contains(v); if (state) { picked.add(v); if(prior_state == false) { fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, v, ItemEvent.SELECTED)); } } else { picked.remove(v); if(prior_state == true) { fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, v, ItemEvent.DESELECTED)); } } return prior_state; } public void clear() { Collection unpicks = new ArrayList(picked); for(T v : unpicks) { pick(v, false); } picked.clear(); } public Set getPicked() { return Collections.unmodifiableSet(picked); } public boolean isPicked(T e) { return picked.contains(e); } /** * for the ItemSelectable interface contract */ @SuppressWarnings("unchecked") public T[] getSelectedObjects() { List list = new ArrayList(picked); return (T[])list.toArray(); } } PickedInfo.java000066400000000000000000000007751276402340000341260ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.picking; /** * An interface for classes that return information regarding whether a * given graph element (vertex or edge) has been selected. * * @author danyelf */ public interface PickedInfo { public boolean isPicked(T t); } PickedState.java000066400000000000000000000024221276402340000343020ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * * Created on Apr 2, 2005 */ package edu.uci.ics.jung.visualization.picking; import java.awt.ItemSelectable; import java.util.Set; /** * An interface for classes that keep track of the "picked" state * of edges or vertices. * * @author Tom Nelson * @author Joshua O'Madadhain */ public interface PickedState extends PickedInfo, ItemSelectable { /** * Marks v as "picked" if b == true, * and unmarks v as picked if b == false. * @param v the element to be picked/unpicked * @param b true if {@code v} is to be marked as picked, false if to be marked as unpicked * @return the "picked" state of v prior to this call */ boolean pick(T v, boolean b); /** * Clears the "picked" state from all elements. */ void clear(); /** * @return all "picked" elements. */ Set getPicked(); /** * @return true if v is currently "picked". */ boolean isPicked(T v); }RadiusPickSupport.java000066400000000000000000000071031276402340000355360ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ /* * Created on Mar 19, 2005 * */ package edu.uci.ics.jung.visualization.picking; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.RadiusGraphElementAccessor; /** * Simple implementation of PickSupport that returns the vertex or edge * that is closest to the specified location. This implementation * provides the same picking options that were available in * previous versions of AbstractLayout. * * @author Tom Nelson * @author Joshua O'Madadhain */ public class RadiusPickSupport extends RadiusGraphElementAccessor implements GraphElementAccessor { public RadiusPickSupport() { this(Math.sqrt(Double.MAX_VALUE - 1000)); } /** * Creates an instance with the specified maximum distance. * @param maxDistance the farthest that a vertex can be from the selection point * and still be 'picked' */ public RadiusPickSupport(double maxDistance) { super(maxDistance); } /** * Gets the vertex nearest to the location of the (x,y) location selected, * within a distance of maxDistance. Iterates through all * visible vertices and checks their distance from the click. Override this * method to provide a more efficient implementation. */ public V getVertex(Layout layout, double x, double y) { return getVertex(layout, x, y, this.maxDistance); } /** * Gets the vertex nearest to the location of the (x,y) location selected, * within a distance of maxDistance. Iterates through all * visible vertices and checks their distance from the click. Override this * method to provide a more efficient implementation. * @param layout the layout instance that records the positions for all vertices * @param x the x coordinate of the pick point * @param y the y coordinate of the pick point * @param maxDistance vertices whose from (x, y) is > this cannot be returned * @return the vertex whose center is closest to the pick point (x, y) */ public V getVertex(Layout layout, double x, double y, double maxDistance) { return super.getVertex(layout, x, y, maxDistance); } /** * Gets the edge nearest to the location of the (x,y) location selected. * Calls the longer form of the call. * @param layout the layout instance that records the positions for all vertices * @param x the x coordinate of the pick point * @param y the y coordinate of the pick point * @return the vertex whose center is closest to the pick point (x, y) */ public E getEdge(Layout layout, double x, double y) { return getEdge(layout, x, y, this.maxDistance); } /** * Gets the edge nearest to the location of the (x,y) location selected, * within a distance of maxDistance, Iterates through all * visible edges and checks their distance from the click. Override this * method to provide a more efficient implementation. * * @param x the x coordinate of the pick point * @param y the y coordinate of the pick point * @param maxDistance vertices whose from (x, y) is > this cannot be returned * @return Edge closest to the click. */ public E getEdge(Layout layout, double x, double y, double maxDistance) { return super.getEdge(layout, x, y, maxDistance); } } ShapePickSupport.java000066400000000000000000000435431276402340000353570ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 11, 2005 * */ package edu.uci.ics.jung.visualization.picking; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationServer; /** * A GraphElementAccessor that returns elements whose Shape * contains the specified pick point or region. * * @author Tom Nelson * */ public class ShapePickSupport implements GraphElementAccessor { /** * The available picking heuristics: *
          *
        • Style.CENTERED: returns the element whose * center is closest to the pick point. *
        • Style.LOWEST: returns the first such element * encountered. (If the element collection has a consistent * ordering, this will also be the element "on the bottom", * that is, the one which is rendered first.) *
        • Style.HIGHEST: returns the last such element * encountered. (If the element collection has a consistent * ordering, this will also be the element "on the top", * that is, the one which is rendered last.) *
        * */ public static enum Style { LOWEST, CENTERED, HIGHEST }; protected float pickSize; /** * The VisualizationServer in which the * this instance is being used for picking. Used to * retrieve properties such as the layout, renderer, * vertex and edge shapes, and coordinate transformations. */ protected VisualizationServer vv; /** * The current picking heuristic for this instance. Defaults * to CENTERED. */ protected Style style = Style.CENTERED; /** * Creates a ShapePickSupport for the vv * VisualizationServer, with the specified pick footprint and * the default pick style. * The VisualizationServer is used to access * properties of the current visualization (layout, renderer, * coordinate transformations, vertex/edge shapes, etc.). * @param vv source of the current Layout. * @param pickSize the size of the pick footprint for line edges */ public ShapePickSupport(VisualizationServer vv, float pickSize) { this.vv = vv; this.pickSize = pickSize; } /** * Create a ShapePickSupport for the specified * VisualizationServer with a default pick footprint. * of size 2. * @param vv the visualization server used for rendering */ public ShapePickSupport(VisualizationServer vv) { this.vv = vv; this.pickSize = 2; } /** * Returns the style of picking used by this instance. * This specifies which of the elements, among those * whose shapes contain the pick point, is returned. * The available styles are: *
          *
        • Style.CENTERED: returns the element whose * center is closest to the pick point. *
        • Style.LOWEST: returns the first such element * encountered. (If the element collection has a consistent * ordering, this will also be the element "on the bottom", * that is, the one which is rendered first.) *
        • Style.HIGHEST: returns the last such element * encountered. (If the element collection has a consistent * ordering, this will also be the element "on the top", * that is, the one which is rendered last.) *
        * * @return the style of picking used by this instance */ public Style getStyle() { return style; } /** * Specifies the style of picking to be used by this instance. * This specifies which of the elements, among those * whose shapes contain the pick point, will be returned. * The available styles are: *
          *
        • Style.CENTERED: returns the element whose * center is closest to the pick point. *
        • Style.LOWEST: returns the first such element * encountered. (If the element collection has a consistent * ordering, this will also be the element "on the bottom", * that is, the one which is rendered first.) *
        • Style.HIGHEST: returns the last such element * encountered. (If the element collection has a consistent * ordering, this will also be the element "on the top", * that is, the one which is rendered last.) *
        * @param style the style to set */ public void setStyle(Style style) { this.style = style; } /** * Returns the vertex, if any, whose shape contains (x, y). * If (x,y) is contained in more than one vertex's shape, returns * the vertex whose center is closest to the pick point. * * @param layout the layout instance that records the positions for all vertices * @param x the x coordinate of the pick point * @param y the y coordinate of the pick point * @return the vertex whose shape contains (x,y), and whose center is closest to the pick point */ public V getVertex(Layout layout, double x, double y) { V closest = null; double minDistance = Double.MAX_VALUE; Point2D ip = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, new Point2D.Double(x,y)); x = ip.getX(); y = ip.getY(); while(true) { try { for(V v : getFilteredVertices(layout)) { Shape shape = vv.getRenderContext().getVertexShapeTransformer().apply(v); // get the vertex location Point2D p = layout.apply(v); if(p == null) continue; // transform the vertex location to screen coords p = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p); double ox = x - p.getX(); double oy = y - p.getY(); if(shape.contains(ox, oy)) { if(style == Style.LOWEST) { // return the first match return v; } else if(style == Style.HIGHEST) { // will return the last match closest = v; } else { // return the vertex closest to the // center of a vertex shape Rectangle2D bounds = shape.getBounds2D(); double dx = bounds.getCenterX() - ox; double dy = bounds.getCenterY() - oy; double dist = dx * dx + dy * dy; if (dist < minDistance) { minDistance = dist; closest = v; } } } } break; } catch(ConcurrentModificationException cme) {} } return closest; } /** * Returns the vertices whose layout coordinates are contained in * Shape. * The shape is in screen coordinates, and the graph vertices * are transformed to screen coordinates before they are tested * for inclusion. * @return the Collection of vertices whose layout * coordinates are contained in shape. */ public Collection getVertices(Layout layout, Shape shape) { Set pickedVertices = new HashSet(); // remove the view transform from the rectangle shape = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, shape); while(true) { try { for(V v : getFilteredVertices(layout)) { Point2D p = layout.apply(v); if(p == null) continue; p = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p); if(shape.contains(p)) { pickedVertices.add(v); } } break; } catch(ConcurrentModificationException cme) {} } return pickedVertices; } /** * Returns an edge whose shape intersects the 'pickArea' footprint of the passed * x,y, coordinates. * * @param layout the context in which the location is defined * @param x the x coordinate of the location * @param y the y coordinate of the location * @return an edge whose shape intersects the pick area centered on the location {@code (x,y)} */ public E getEdge(Layout layout, double x, double y) { Point2D ip = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, new Point2D.Double(x,y)); x = ip.getX(); y = ip.getY(); // as a Line has no area, we can't always use edgeshape.contains(point) so we // make a small rectangular pickArea around the point and check if the // edgeshape.intersects(pickArea) Rectangle2D pickArea = new Rectangle2D.Float((float)x-pickSize/2,(float)y-pickSize/2,pickSize,pickSize); E closest = null; double minDistance = Double.MAX_VALUE; while(true) { try { for(E e : getFilteredEdges(layout)) { Shape edgeShape = getTransformedEdgeShape(layout, e); if (edgeShape == null) continue; // because of the transform, the edgeShape is now a GeneralPath // see if this edge is the closest of any that intersect if(edgeShape.intersects(pickArea)) { float cx=0; float cy=0; float[] f = new float[6]; PathIterator pi = new GeneralPath(edgeShape).getPathIterator(null); if(pi.isDone()==false) { pi.next(); pi.currentSegment(f); cx = f[0]; cy = f[1]; if(pi.isDone()==false) { pi.currentSegment(f); cx = f[0]; cy = f[1]; } } float dx = (float) (cx - x); float dy = (float) (cy - y); float dist = dx * dx + dy * dy; if (dist < minDistance) { minDistance = dist; closest = e; } } } break; } catch(ConcurrentModificationException cme) {} } return closest; } /** * Retrieves the shape template for e and * transforms it according to the positions of its endpoints * in layout. * @param layout the Layout which specifies * e's endpoints' positions * @param e the edge whose shape is to be returned * @return the transformed shape */ private Shape getTransformedEdgeShape(Layout layout, E e) { Pair pair = layout.getGraph().getEndpoints(e); V v1 = pair.getFirst(); V v2 = pair.getSecond(); boolean isLoop = v1.equals(v2); Point2D p1 = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, layout.apply(v1)); Point2D p2 = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, layout.apply(v2)); if(p1 == null || p2 == null) return null; float x1 = (float) p1.getX(); float y1 = (float) p1.getY(); float x2 = (float) p2.getX(); float y2 = (float) p2.getY(); // translate the edge to the starting vertex AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1); Shape edgeShape = vv.getRenderContext().getEdgeShapeTransformer().apply(e); if(isLoop) { // make the loops proportional to the size of the vertex Shape s2 = vv.getRenderContext().getVertexShapeTransformer().apply(v2); Rectangle2D s2Bounds = s2.getBounds2D(); xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight()); // move the loop so that the nadir is centered in the vertex xform.translate(0, -edgeShape.getBounds2D().getHeight()/2); } else { float dx = x2 - x1; float dy = y2 - y1; // rotate the edge to the angle between the vertices double theta = Math.atan2(dy,dx); xform.rotate(theta); // stretch the edge to span the distance between the vertices float dist = (float) Math.sqrt(dx*dx + dy*dy); xform.scale(dist, 1.0f); } // transform the edge to its location and dimensions edgeShape = xform.createTransformedShape(edgeShape); return edgeShape; } protected Collection getFilteredVertices(Layout layout) { if(verticesAreFiltered()) { Collection unfiltered = layout.getGraph().getVertices(); Collection filtered = new LinkedHashSet(); for(V v : unfiltered) { if(isVertexRendered(Context.,V>getInstance(layout.getGraph(),v))) { filtered.add(v); } } return filtered; } else { return layout.getGraph().getVertices(); } } protected Collection getFilteredEdges(Layout layout) { if(edgesAreFiltered()) { Collection unfiltered = layout.getGraph().getEdges(); Collection filtered = new LinkedHashSet(); for(E e : unfiltered) { if(isEdgeRendered(Context.,E>getInstance(layout.getGraph(),e))) { filtered.add(e); } } return filtered; } else { return layout.getGraph().getEdges(); } } /** * Quick test to allow optimization of getFilteredVertices(). * @return true if there is an active vertex filtering * mechanism for this visualization, false otherwise */ protected boolean verticesAreFiltered() { Predicate,V>> vertexIncludePredicate = vv.getRenderContext().getVertexIncludePredicate(); return vertexIncludePredicate != null && vertexIncludePredicate.equals(Predicates.alwaysTrue()) == false; } /** * Quick test to allow optimization of getFilteredEdges(). * @return true if there is an active edge filtering * mechanism for this visualization, false otherwise */ protected boolean edgesAreFiltered() { Predicate,E>> edgeIncludePredicate = vv.getRenderContext().getEdgeIncludePredicate(); return edgeIncludePredicate != null && edgeIncludePredicate.equals(Predicates.alwaysTrue()) == false; } /** * Returns true if this vertex in this graph is included * in the collections of elements to be rendered, and false otherwise. * @param context the vertex and graph to be queried * @return true if this vertex is * included in the collections of elements to be rendered, false * otherwise. */ protected boolean isVertexRendered(Context,V> context) { Predicate,V>> vertexIncludePredicate = vv.getRenderContext().getVertexIncludePredicate(); return vertexIncludePredicate == null || vertexIncludePredicate.apply(context); } /** * Returns true if this edge and its endpoints * in this graph are all included in the collections of * elements to be rendered, and false otherwise. * @param context the edge and graph to be queried * @return true if this edge and its endpoints are all * included in the collections of elements to be rendered, false * otherwise. */ protected boolean isEdgeRendered(Context,E> context) { Predicate,V>> vertexIncludePredicate = vv.getRenderContext().getVertexIncludePredicate(); Predicate,E>> edgeIncludePredicate = vv.getRenderContext().getEdgeIncludePredicate(); Graph g = context.graph; E e = context.element; boolean edgeTest = edgeIncludePredicate == null || edgeIncludePredicate.apply(context); Pair endpoints = g.getEndpoints(e); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); boolean endpointsTest = vertexIncludePredicate == null || (vertexIncludePredicate.apply(Context.,V>getInstance(g,v1)) && vertexIncludePredicate.apply(Context.,V>getInstance(g,v2))); return edgeTest && endpointsTest; } /** * Returns the size of the edge picking area. * The picking area is square; the size is specified as the length of one * side, in view coordinates. * @return the size of the edge picking area */ public float getPickSize() { return pickSize; } /** * Sets the size of the edge picking area. * @param pickSize the length of one side of the (square) picking area, in view coordinates */ public void setPickSize(float pickSize) { this.pickSize = pickSize; } } ViewLensShapePickSupport.java000066400000000000000000000237121276402340000370300ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/* * Copyright (c) 2005, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. * Created on Mar 11, 2005 * */ package edu.uci.ics.jung.visualization.picking; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.Set; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationServer; import edu.uci.ics.jung.visualization.transform.MutableTransformerDecorator; /** * ShapePickSupport provides access to Vertices and EdgeType based on * their actual shapes. * * @param the vertex type * @param the edge type * * @author Tom Nelson */ public class ViewLensShapePickSupport extends ShapePickSupport implements GraphElementAccessor { public ViewLensShapePickSupport(VisualizationServer vv, float pickSize) { super(vv, pickSize); } public ViewLensShapePickSupport(VisualizationServer vv) { this(vv, 2); } public V getVertex(Layout layout, double x, double y) { V closest = null; double minDistance = Double.MAX_VALUE; Point2D ip = ((MutableTransformerDecorator)vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)).getDelegate().inverseTransform(new Point2D.Double(x,y)); x = ip.getX(); y = ip.getY(); while(true) { try { for(V v : getFilteredVertices(layout)) { // get the shape Shape shape = vv.getRenderContext().getVertexShapeTransformer().apply(v); // transform the vertex location to screen coords Point2D p = layout.apply(v); if(p == null) continue; AffineTransform xform = AffineTransform.getTranslateInstance(p.getX(), p.getY()); shape = xform.createTransformedShape(shape); // use the LAYOUT transform to move the shape center without // modifying the actual shape Point2D lp = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p); AffineTransform xlate = AffineTransform.getTranslateInstance( lp.getX()-p.getX(),lp.getY()-p.getY()); shape = xlate.createTransformedShape(shape); // now use the VIEW transform to modify the actual shape shape = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.VIEW, shape); //vv.getRenderContext().getMultiLayerTransformer().transform(shape); // see if this vertex center is closest to the pick point // among any other containing vertices if(shape.contains(x, y)) { if(style == Style.LOWEST) { // return the first match return v; } else if(style == Style.HIGHEST) { // will return the last match closest = v; } else { Rectangle2D bounds = shape.getBounds2D(); double dx = bounds.getCenterX() - x; double dy = bounds.getCenterY() - y; double dist = dx * dx + dy * dy; if (dist < minDistance) { minDistance = dist; closest = v; } } } } break; } catch(ConcurrentModificationException cme) {} } return closest; } public Collection getVertices(Layout layout, Shape rectangle) { Set pickedVertices = new HashSet(); // remove the view transform from the rectangle rectangle = ((MutableTransformerDecorator)vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)).getDelegate().inverseTransform(rectangle); while(true) { try { for(V v : getFilteredVertices(layout)) { Point2D p = layout.apply(v); if(p == null) continue; // get the shape Shape shape = vv.getRenderContext().getVertexShapeTransformer().apply(v); AffineTransform xform = AffineTransform.getTranslateInstance(p.getX(), p.getY()); shape = xform.createTransformedShape(shape); shape = vv.getRenderContext().getMultiLayerTransformer().transform(shape); Rectangle2D bounds = shape.getBounds2D(); p.setLocation(bounds.getCenterX(),bounds.getCenterY()); if(rectangle.contains(p)) { pickedVertices.add(v); } } break; } catch(ConcurrentModificationException cme) {} } return pickedVertices; } public E getEdge(Layout layout, double x, double y) { Point2D ip = ((MutableTransformerDecorator)vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)).getDelegate().inverseTransform(new Point2D.Double(x,y)); x = ip.getX(); y = ip.getY(); // as a Line has no area, we can't always use edgeshape.contains(point) so we // make a small rectangular pickArea around the point and check if the // edgeshape.intersects(pickArea) Rectangle2D pickArea = new Rectangle2D.Float((float)x-pickSize/2,(float)y-pickSize/2,pickSize,pickSize); E closest = null; double minDistance = Double.MAX_VALUE; while(true) { try { for(E e : getFilteredEdges(layout)) { Pair pair = layout.getGraph().getEndpoints(e); V v1 = pair.getFirst(); V v2 = pair.getSecond(); boolean isLoop = v1.equals(v2); Point2D p1 = layout.apply(v1); //vv.getRenderContext().getBasicTransformer().transform(layout.transform(v1)); Point2D p2 = layout.apply(v2); //vv.getRenderContext().getBasicTransformer().transform(layout.transform(v2)); if(p1 == null || p2 == null) continue; float x1 = (float) p1.getX(); float y1 = (float) p1.getY(); float x2 = (float) p2.getX(); float y2 = (float) p2.getY(); // translate the edge to the starting vertex AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1); Shape edgeShape = vv.getRenderContext().getEdgeShapeTransformer().apply(e); if(isLoop) { // make the loops proportional to the size of the vertex Shape s2 = vv.getRenderContext().getVertexShapeTransformer().apply(v2); Rectangle2D s2Bounds = s2.getBounds2D(); xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight()); // move the loop so that the nadir is centered in the vertex xform.translate(0, -edgeShape.getBounds2D().getHeight()/2); } else { float dx = x2 - x1; float dy = y2 - y1; // rotate the edge to the angle between the vertices double theta = Math.atan2(dy,dx); xform.rotate(theta); // stretch the edge to span the distance between the vertices float dist = (float) Math.sqrt(dx*dx + dy*dy); xform.scale(dist, 1.0f); } // transform the edge to its location and dimensions edgeShape = xform.createTransformedShape(edgeShape); edgeShape = vv.getRenderContext().getMultiLayerTransformer().transform(edgeShape); // because of the transform, the edgeShape is now a GeneralPath // see if this edge is the closest of any that intersect if(edgeShape.intersects(pickArea)) { float cx=0; float cy=0; float[] f = new float[6]; PathIterator pi = new GeneralPath(edgeShape).getPathIterator(null); if(pi.isDone()==false) { pi.next(); pi.currentSegment(f); cx = f[0]; cy = f[1]; if(pi.isDone()==false) { pi.currentSegment(f); cx = f[0]; cy = f[1]; } } float dx = (float) (cx - x); float dy = (float) (cy - y); float dist = dx * dx + dy * dy; if (dist < minDistance) { minDistance = dist; closest = e; } } } break; } catch(ConcurrentModificationException cme) {} } return closest; } } jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/package.html000066400000000000000000000005271276402340000336030ustar00rootroot00000000000000

        Visualization mechanisms for supporting the selection of graph elements. jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/000077500000000000000000000000001276402340000316635ustar00rootroot00000000000000BasicEdgeArrowRenderingSupport.java000066400000000000000000000176241276402340000405350ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 23, 2005 */ package edu.uci.ics.jung.visualization.renderers; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.RenderContext; public class BasicEdgeArrowRenderingSupport implements EdgeArrowRenderingSupport { public AffineTransform getArrowTransform(RenderContext rc, Shape edgeShape, Shape vertexShape) { GeneralPath path = new GeneralPath(edgeShape); float[] seg = new float[6]; Point2D p1=null; Point2D p2=null; AffineTransform at = new AffineTransform(); // when the PathIterator is done, switch to the line-subdivide // method to get the arrowhead closer. for(PathIterator i=path.getPathIterator(null,1); !i.isDone(); i.next()) { int ret = i.currentSegment(seg); if(ret == PathIterator.SEG_MOVETO) { p2 = new Point2D.Float(seg[0],seg[1]); } else if(ret == PathIterator.SEG_LINETO) { p1 = p2; p2 = new Point2D.Float(seg[0],seg[1]); if(vertexShape.contains(p2)) { at = getArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape); break; } } } return at; } public AffineTransform getReverseArrowTransform(RenderContext rc, Shape edgeShape, Shape vertexShape) { return getReverseArrowTransform(rc, edgeShape, vertexShape, true); } public AffineTransform getReverseArrowTransform(RenderContext rc, Shape edgeShape, Shape vertexShape, boolean passedGo) { GeneralPath path = new GeneralPath(edgeShape); float[] seg = new float[6]; Point2D p1=null; Point2D p2=null; AffineTransform at = new AffineTransform(); for(PathIterator i=path.getPathIterator(null,1); !i.isDone(); i.next()) { int ret = i.currentSegment(seg); if(ret == PathIterator.SEG_MOVETO) { p2 = new Point2D.Float(seg[0],seg[1]); } else if(ret == PathIterator.SEG_LINETO) { p1 = p2; p2 = new Point2D.Float(seg[0],seg[1]); if(passedGo == false && vertexShape.contains(p2)) { passedGo = true; } else if(passedGo==true && vertexShape.contains(p2)==false) { at = getReverseArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape); break; } } } return at; } public AffineTransform getArrowTransform(RenderContext rc, Line2D edgeShape, Shape vertexShape) { float dx = (float) (edgeShape.getX1()-edgeShape.getX2()); float dy = (float) (edgeShape.getY1()-edgeShape.getY2()); // iterate over the line until the edge shape will place the // arrowhead closer than 'arrowGap' to the vertex shape boundary while((dx*dx+dy*dy) > rc.getArrowPlacementTolerance()) { try { edgeShape = getLastOutsideSegment(edgeShape, vertexShape); } catch(IllegalArgumentException e) { System.err.println(e.toString()); return null; } dx = (float) (edgeShape.getX1()-edgeShape.getX2()); dy = (float) (edgeShape.getY1()-edgeShape.getY2()); } double atheta = Math.atan2(dx,dy)+Math.PI/2; AffineTransform at = AffineTransform.getTranslateInstance(edgeShape.getX1(), edgeShape.getY1()); at.rotate(-atheta); return at; } protected AffineTransform getReverseArrowTransform(RenderContext rc, Line2D edgeShape, Shape vertexShape) { float dx = (float) (edgeShape.getX1()-edgeShape.getX2()); float dy = (float) (edgeShape.getY1()-edgeShape.getY2()); // iterate over the line until the edge shape will place the // arrowhead closer than 'arrowGap' to the vertex shape boundary while((dx*dx+dy*dy) > rc.getArrowPlacementTolerance()) { try { edgeShape = getFirstOutsideSegment(edgeShape, vertexShape); } catch(IllegalArgumentException e) { System.err.println(e.toString()); return null; } dx = (float) (edgeShape.getX1()-edgeShape.getX2()); dy = (float) (edgeShape.getY1()-edgeShape.getY2()); } // calculate the angle for the arrowhead double atheta = Math.atan2(dx,dy)-Math.PI/2; AffineTransform at = AffineTransform.getTranslateInstance(edgeShape.getX1(),edgeShape.getY1()); at.rotate(-atheta); return at; } /** * Returns a line that intersects {@code shape}'s boundary. * * @param line line to subdivide * @param shape shape to compare with line * @return a line that intersects the shape boundary * @throws IllegalArgumentException if the passed line's point2 is not inside the shape */ protected Line2D getLastOutsideSegment(Line2D line, Shape shape) { if(shape.contains(line.getP2())==false) { String errorString = "line end point: "+line.getP2()+" is not contained in shape: "+shape.getBounds2D(); throw new IllegalArgumentException(errorString); //return null; } Line2D left = new Line2D.Double(); Line2D right = new Line2D.Double(); // subdivide the line until its left segment intersects // the shape boundary do { subdivide(line, left, right); line = right; } while(shape.contains(line.getP1())==false); // now that right is completely inside shape, // return left, which must be partially outside return left; } /** * Returns a line that intersects {@code shape}'s boundary. * * @param line line to subdivide * @param shape shape to compare with line * @return a line that intersects the shape boundary * @throws IllegalArgumentException if the passed line's point1 is not inside the shape */ protected Line2D getFirstOutsideSegment(Line2D line, Shape shape) { if(shape.contains(line.getP1())==false) { String errorString = "line start point: "+line.getP1()+" is not contained in shape: "+shape.getBounds2D(); throw new IllegalArgumentException(errorString); } Line2D left = new Line2D.Float(); Line2D right = new Line2D.Float(); // subdivide the line until its right side intersects the // shape boundary do { subdivide(line, left, right); line = left; } while(shape.contains(line.getP2())==false); // now that left is completely inside shape, // return right, which must be partially outside return right; } /** * divide a Line2D into 2 new Line2Ds that are returned * in the passed left and right instances, if non-null * @param src the line to divide * @param left the left side, or null * @param right the right side, or null */ protected void subdivide(Line2D src, Line2D left, Line2D right) { double x1 = src.getX1(); double y1 = src.getY1(); double x2 = src.getX2(); double y2 = src.getY2(); double mx = x1 + (x2-x1)/2.0; double my = y1 + (y2-y1)/2.0; if (left != null) { left.setLine(x1, y1, mx, my); } if (right != null) { right.setLine(mx, my, x2, y2); } } } BasicEdgeLabelRenderer.java000066400000000000000000000102761276402340000367120ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 23, 2005 */ package edu.uci.ics.jung.visualization.renderers; import java.awt.Component; import java.awt.Dimension; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator; public class BasicEdgeLabelRenderer implements Renderer.EdgeLabel { public Component prepareRenderer(RenderContext rc, EdgeLabelRenderer graphLabelRenderer, Object value, boolean isSelected, E edge) { return rc.getEdgeLabelRenderer().getEdgeLabelRendererComponent(rc.getScreenDevice(), value, rc.getEdgeFontTransformer().apply(edge), isSelected, edge); } public void labelEdge(RenderContext rc, Layout layout, E e, String label) { if(label == null || label.length() == 0) return; Graph graph = layout.getGraph(); // don't draw edge if either incident vertex is not drawn Pair endpoints = graph.getEndpoints(e); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); if (!rc.getEdgeIncludePredicate().apply(Context.,E>getInstance(graph,e))) return; if (!rc.getVertexIncludePredicate().apply(Context.,V>getInstance(graph,v1)) || !rc.getVertexIncludePredicate().apply(Context.,V>getInstance(graph,v2))) return; Point2D p1 = layout.apply(v1); Point2D p2 = layout.apply(v2); p1 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p1); p2 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p2); float x1 = (float) p1.getX(); float y1 = (float) p1.getY(); float x2 = (float) p2.getX(); float y2 = (float) p2.getY(); GraphicsDecorator g = rc.getGraphicsContext(); float distX = x2 - x1; float distY = y2 - y1; double totalLength = Math.sqrt(distX * distX + distY * distY); double closeness = rc.getEdgeLabelClosenessTransformer().apply(Context.,E>getInstance(graph, e)).doubleValue(); int posX = (int) (x1 + (closeness) * distX); int posY = (int) (y1 + (closeness) * distY); int xDisplacement = (int) (rc.getLabelOffset() * (distY / totalLength)); int yDisplacement = (int) (rc.getLabelOffset() * (-distX / totalLength)); Component component = prepareRenderer(rc, rc.getEdgeLabelRenderer(), label, rc.getPickedEdgeState().isPicked(e), e); Dimension d = component.getPreferredSize(); Shape edgeShape = rc.getEdgeShapeTransformer().apply(e); double parallelOffset = 1; parallelOffset += rc.getParallelEdgeIndexFunction().getIndex(graph, e); parallelOffset *= d.height; if(edgeShape instanceof Ellipse2D) { parallelOffset += edgeShape.getBounds().getHeight(); parallelOffset = -parallelOffset; } AffineTransform old = g.getTransform(); AffineTransform xform = new AffineTransform(old); xform.translate(posX+xDisplacement, posY+yDisplacement); double dx = x2 - x1; double dy = y2 - y1; if(rc.getEdgeLabelRenderer().isRotateEdgeLabels()) { double theta = Math.atan2(dy, dx); if(dx < 0) { theta += Math.PI; } xform.rotate(theta); } if(dx < 0) { parallelOffset = -parallelOffset; } xform.translate(-d.width/2, -(d.height/2-parallelOffset)); g.setTransform(xform); g.draw(component, rc.getRendererPane(), 0, 0, d.width, d.height, true); g.setTransform(old); } } BasicEdgeRenderer.java000066400000000000000000000271161276402340000357530ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 23, 2005 */ package edu.uci.ics.jung.visualization.renderers; import java.awt.Dimension; import java.awt.Paint; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import javax.swing.JComponent; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.graph.util.EdgeIndexFunction; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.decorators.ParallelEdgeShapeTransformer; import edu.uci.ics.jung.visualization.transform.LensTransformer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator; public class BasicEdgeRenderer implements Renderer.Edge { protected EdgeArrowRenderingSupport edgeArrowRenderingSupport = new BasicEdgeArrowRenderingSupport(); public void paintEdge(RenderContext rc, Layout layout, E e) { GraphicsDecorator g2d = rc.getGraphicsContext(); Graph graph = layout.getGraph(); if (!rc.getEdgeIncludePredicate().apply(Context.,E>getInstance(graph,e))) return; // don't draw edge if either incident vertex is not drawn Pair endpoints = graph.getEndpoints(e); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); if (!rc.getVertexIncludePredicate().apply(Context.,V>getInstance(graph,v1)) || !rc.getVertexIncludePredicate().apply(Context.,V>getInstance(graph,v2))) return; Stroke new_stroke = rc.getEdgeStrokeTransformer().apply(e); Stroke old_stroke = g2d.getStroke(); if (new_stroke != null) g2d.setStroke(new_stroke); drawSimpleEdge(rc, layout, e); // restore paint and stroke if (new_stroke != null) g2d.setStroke(old_stroke); } protected Shape prepareFinalEdgeShape(RenderContext rc, Layout layout, E e, int[] coords, boolean[] loop) { Graph graph = layout.getGraph(); Pair endpoints = graph.getEndpoints(e); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); Point2D p1 = layout.apply(v1); Point2D p2 = layout.apply(v2); p1 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p1); p2 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p2); float x1 = (float) p1.getX(); float y1 = (float) p1.getY(); float x2 = (float) p2.getX(); float y2 = (float) p2.getY(); coords[0] = (int)x1; coords[1] = (int)y1; coords[2] = (int)x2; coords[3] = (int)y2; boolean isLoop = loop[0] = v1.equals(v2); Shape s2 = rc.getVertexShapeTransformer().apply(v2); Shape edgeShape = rc.getEdgeShapeTransformer().apply(e); AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1); if(isLoop) { // this is a self-loop. scale it is larger than the vertex // it decorates and translate it so that its nadir is // at the center of the vertex. Rectangle2D s2Bounds = s2.getBounds2D(); xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight()); xform.translate(0, -edgeShape.getBounds2D().getWidth()/2); } else if(rc.getEdgeShapeTransformer() instanceof EdgeShape.Orthogonal) { float dx = x2-x1; float dy = y2-y1; int index = 0; if(rc.getEdgeShapeTransformer() instanceof ParallelEdgeShapeTransformer) { @SuppressWarnings("unchecked") EdgeIndexFunction peif = ((ParallelEdgeShapeTransformer)rc.getEdgeShapeTransformer()) .getEdgeIndexFunction(); index = peif.getIndex(null, e); index *= 20; } GeneralPath gp = new GeneralPath(); gp.moveTo(0,0);// the xform will do the translation to x1,y1 if(x1 > x2) { if(y1 > y2) { gp.lineTo(0, index); gp.lineTo(dx-index, index); gp.lineTo(dx-index, dy); gp.lineTo(dx, dy); } else { gp.lineTo(0, -index); gp.lineTo(dx-index, -index); gp.lineTo(dx-index, dy); gp.lineTo(dx, dy); } } else { if(y1 > y2) { gp.lineTo(0, index); gp.lineTo(dx+index, index); gp.lineTo(dx+index, dy); gp.lineTo(dx, dy); } else { gp.lineTo(0, -index); gp.lineTo(dx+index, -index); gp.lineTo(dx+index, dy); gp.lineTo(dx, dy); } } edgeShape = gp; } else { // this is a normal edge. Rotate it to the angle between // vertex endpoints, then scale it to the distance between // the vertices float dx = x2-x1; float dy = y2-y1; float thetaRadians = (float) Math.atan2(dy, dx); xform.rotate(thetaRadians); float dist = (float) Math.sqrt(dx*dx + dy*dy); xform.scale(dist, 1.0); } edgeShape = xform.createTransformedShape(edgeShape); return edgeShape; } /** * Draws the edge e, whose endpoints are at (x1,y1) * and (x2,y2), on the graphics context g. * The Shape provided by the EdgeShapeFunction instance * is scaled in the x-direction so that its width is equal to the distance between * (x1,y1) and (x2,y2). * @param rc the render context used for rendering the edge * @param layout the layout instance which provides the edge's endpoints' coordinates * @param e the edge to be drawn */ protected void drawSimpleEdge(RenderContext rc, Layout layout, E e) { int[] coords = new int[4]; boolean[] loop = new boolean[1]; Shape edgeShape = prepareFinalEdgeShape(rc, layout, e, coords, loop); int x1 = coords[0]; int y1 = coords[1]; int x2 = coords[2]; int y2 = coords[3]; boolean isLoop = loop[0]; GraphicsDecorator g = rc.getGraphicsContext(); Graph graph = layout.getGraph(); boolean edgeHit = true; boolean arrowHit = true; Rectangle deviceRectangle = null; JComponent vv = rc.getScreenDevice(); if(vv != null) { Dimension d = vv.getSize(); deviceRectangle = new Rectangle(0,0,d.width,d.height); } MutableTransformer vt = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW); if(vt instanceof LensTransformer) { vt = ((LensTransformer)vt).getDelegate(); } edgeHit = vt.transform(edgeShape).intersects(deviceRectangle); if(edgeHit == true) { Paint oldPaint = g.getPaint(); // get Paints for filling and drawing // (filling is done first so that drawing and label use same Paint) Paint fill_paint = rc.getEdgeFillPaintTransformer().apply(e); if (fill_paint != null) { g.setPaint(fill_paint); g.fill(edgeShape); } Paint draw_paint = rc.getEdgeDrawPaintTransformer().apply(e); if (draw_paint != null) { g.setPaint(draw_paint); g.draw(edgeShape); } float scalex = (float)g.getTransform().getScaleX(); float scaley = (float)g.getTransform().getScaleY(); // see if arrows are too small to bother drawing if(scalex < .3 || scaley < .3) return; if (rc.getEdgeArrowPredicate().apply(Context.,E>getInstance(graph, e))) { Stroke new_stroke = rc.getEdgeArrowStrokeTransformer().apply(e); Stroke old_stroke = g.getStroke(); if (new_stroke != null) g.setStroke(new_stroke); Shape destVertexShape = rc.getVertexShapeTransformer().apply(graph.getEndpoints(e).getSecond()); AffineTransform xf = AffineTransform.getTranslateInstance(x2, y2); destVertexShape = xf.createTransformedShape(destVertexShape); arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(destVertexShape).intersects(deviceRectangle); if(arrowHit) { AffineTransform at = edgeArrowRenderingSupport.getArrowTransform(rc, edgeShape, destVertexShape); if(at == null) return; Shape arrow = rc.getEdgeArrowTransformer().apply(Context.,E>getInstance(graph, e)); arrow = at.createTransformedShape(arrow); g.setPaint(rc.getArrowFillPaintTransformer().apply(e)); g.fill(arrow); g.setPaint(rc.getArrowDrawPaintTransformer().apply(e)); g.draw(arrow); } if (graph.getEdgeType(e) == EdgeType.UNDIRECTED) { Shape vertexShape = rc.getVertexShapeTransformer().apply(graph.getEndpoints(e).getFirst()); xf = AffineTransform.getTranslateInstance(x1, y1); vertexShape = xf.createTransformedShape(vertexShape); arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(vertexShape).intersects(deviceRectangle); if(arrowHit) { AffineTransform at = edgeArrowRenderingSupport.getReverseArrowTransform(rc, edgeShape, vertexShape, !isLoop); if(at == null) return; Shape arrow = rc.getEdgeArrowTransformer().apply(Context.,E>getInstance(graph, e)); arrow = at.createTransformedShape(arrow); g.setPaint(rc.getArrowFillPaintTransformer().apply(e)); g.fill(arrow); g.setPaint(rc.getArrowDrawPaintTransformer().apply(e)); g.draw(arrow); } } // restore paint and stroke if (new_stroke != null) g.setStroke(old_stroke); } // restore old paint g.setPaint(oldPaint); } } public EdgeArrowRenderingSupport getEdgeArrowRenderingSupport() { return edgeArrowRenderingSupport; } public void setEdgeArrowRenderingSupport( EdgeArrowRenderingSupport edgeArrowRenderingSupport) { this.edgeArrowRenderingSupport = edgeArrowRenderingSupport; } } BasicRenderer.java000066400000000000000000000075001276402340000351610ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.renderers; import java.util.ConcurrentModificationException; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.visualization.RenderContext; /** * The default implementation of the Renderer used by the * VisualizationViewer. Default Vertex and Edge Renderers * are supplied, or the user may set custom values. The * Vertex and Edge renderers are used in the renderVertex * and renderEdge methods, which are called in the render * loop of the VisualizationViewer. * * @author Tom Nelson */ public class BasicRenderer implements Renderer { Renderer.Vertex vertexRenderer = new BasicVertexRenderer(); Renderer.VertexLabel vertexLabelRenderer = new BasicVertexLabelRenderer(); Renderer.Edge edgeRenderer = new BasicEdgeRenderer(); Renderer.EdgeLabel edgeLabelRenderer = new BasicEdgeLabelRenderer(); public void render(RenderContext renderContext, Layout layout) { // paint all the edges try { for(E e : layout.getGraph().getEdges()) { renderEdge( renderContext, layout, e); renderEdgeLabel( renderContext, layout, e); } } catch(ConcurrentModificationException cme) { renderContext.getScreenDevice().repaint(); } // paint all the vertices try { for(V v : layout.getGraph().getVertices()) { renderVertex( renderContext, layout, v); renderVertexLabel( renderContext, layout, v); } } catch(ConcurrentModificationException cme) { renderContext.getScreenDevice().repaint(); } } public void renderVertex(RenderContext rc, Layout layout, V v) { vertexRenderer.paintVertex(rc, layout, v); } public void renderVertexLabel(RenderContext rc, Layout layout, V v) { vertexLabelRenderer.labelVertex(rc, layout, v, rc.getVertexLabelTransformer().apply(v)); } public void renderEdge(RenderContext rc, Layout layout, E e) { edgeRenderer.paintEdge(rc, layout, e); } public void renderEdgeLabel(RenderContext rc, Layout layout, E e) { edgeLabelRenderer.labelEdge(rc, layout, e, rc.getEdgeLabelTransformer().apply(e)); } public void setVertexRenderer(Renderer.Vertex r) { this.vertexRenderer = r; } public void setEdgeRenderer(Renderer.Edge r) { this.edgeRenderer = r; } /** * @return the edgeLabelRenderer */ public Renderer.EdgeLabel getEdgeLabelRenderer() { return edgeLabelRenderer; } /** * @param edgeLabelRenderer the edgeLabelRenderer to set */ public void setEdgeLabelRenderer(Renderer.EdgeLabel edgeLabelRenderer) { this.edgeLabelRenderer = edgeLabelRenderer; } /** * @return the vertexLabelRenderer */ public Renderer.VertexLabel getVertexLabelRenderer() { return vertexLabelRenderer; } /** * @param vertexLabelRenderer the vertexLabelRenderer to set */ public void setVertexLabelRenderer( Renderer.VertexLabel vertexLabelRenderer) { this.vertexLabelRenderer = vertexLabelRenderer; } /** * @return the edgeRenderer */ public Renderer.Edge getEdgeRenderer() { return edgeRenderer; } /** * @return the vertexRenderer */ public Renderer.Vertex getVertexRenderer() { return vertexRenderer; } }BasicVertexLabelRenderer.java000066400000000000000000000150331276402340000373170ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 23, 2005 */ package edu.uci.ics.jung.visualization.renderers; import java.awt.Component; import java.awt.Dimension; import java.awt.Point; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer; import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator; import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer; import edu.uci.ics.jung.visualization.transform.shape.TransformingGraphics; public class BasicVertexLabelRenderer implements Renderer.VertexLabel { protected Position position = Position.SE; private Positioner positioner = new OutsidePositioner(); public BasicVertexLabelRenderer() { super(); } public BasicVertexLabelRenderer(Position position) { this.position = position; } /** * @return the position */ public Position getPosition() { return position; } /** * @param position the position to set */ public void setPosition(Position position) { this.position = position; } public Component prepareRenderer(RenderContext rc, VertexLabelRenderer graphLabelRenderer, Object value, boolean isSelected, V vertex) { return rc.getVertexLabelRenderer().getVertexLabelRendererComponent(rc.getScreenDevice(), value, rc.getVertexFontTransformer().apply(vertex), isSelected, vertex); } /** * Labels the specified vertex with the specified label. * Uses the font specified by this instance's * VertexFontFunction. (If the font is unspecified, the existing * font for the graphics context is used.) If vertex label centering * is active, the label is centered on the position of the vertex; otherwise * the label is offset slightly. */ public void labelVertex(RenderContext rc, Layout layout, V v, String label) { Graph graph = layout.getGraph(); if (rc.getVertexIncludePredicate().apply(Context.,V>getInstance(graph,v)) == false) { return; } Point2D pt = layout.apply(v); pt = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, pt); float x = (float) pt.getX(); float y = (float) pt.getY(); Component component = prepareRenderer(rc, rc.getVertexLabelRenderer(), label, rc.getPickedVertexState().isPicked(v), v); GraphicsDecorator g = rc.getGraphicsContext(); Dimension d = component.getPreferredSize(); AffineTransform xform = AffineTransform.getTranslateInstance(x, y); Shape shape = rc.getVertexShapeTransformer().apply(v); shape = xform.createTransformedShape(shape); if(rc.getGraphicsContext() instanceof TransformingGraphics) { BidirectionalTransformer transformer = ((TransformingGraphics)rc.getGraphicsContext()).getTransformer(); if(transformer instanceof ShapeTransformer) { ShapeTransformer shapeTransformer = (ShapeTransformer)transformer; shape = shapeTransformer.transform(shape); } } Rectangle2D bounds = shape.getBounds2D(); Point p = null; if(position == Position.AUTO) { Dimension vvd = rc.getScreenDevice().getSize(); if(vvd.width == 0 || vvd.height == 0) { vvd = rc.getScreenDevice().getPreferredSize(); } p = getAnchorPoint(bounds, d, positioner.getPosition(x, y, vvd)); } else { p = getAnchorPoint(bounds, d, position); } g.draw(component, rc.getRendererPane(), p.x, p.y, d.width, d.height, true); } protected Point getAnchorPoint(Rectangle2D vertexBounds, Dimension labelSize, Position position) { double x; double y; int offset = 5; switch(position) { case N: x = vertexBounds.getCenterX()-labelSize.width/2; y = vertexBounds.getMinY()-offset - labelSize.height; return new Point((int)x,(int)y); case NE: x = vertexBounds.getMaxX()+offset; y = vertexBounds.getMinY()-offset-labelSize.height; return new Point((int)x,(int)y); case E: x = vertexBounds.getMaxX()+offset; y = vertexBounds.getCenterY()-labelSize.height/2; return new Point((int)x,(int)y); case SE: x = vertexBounds.getMaxX()+offset; y = vertexBounds.getMaxY()+offset; return new Point((int)x,(int)y); case S: x = vertexBounds.getCenterX()-labelSize.width/2; y = vertexBounds.getMaxY()+offset; return new Point((int)x,(int)y); case SW: x = vertexBounds.getMinX()-offset-labelSize.width; y = vertexBounds.getMaxY()+offset; return new Point((int)x,(int)y); case W: x = vertexBounds.getMinX()-offset-labelSize.width; y = vertexBounds.getCenterY()-labelSize.height/2; return new Point((int)x,(int)y); case NW: x = vertexBounds.getMinX()-offset-labelSize.width; y = vertexBounds.getMinY()-offset-labelSize.height; return new Point((int)x,(int)y); case CNTR: x = vertexBounds.getCenterX()-labelSize.width/2; y = vertexBounds.getCenterY()-labelSize.height/2; return new Point((int)x,(int)y); default: return new Point(); } } public static class InsidePositioner implements Positioner { public Position getPosition(float x, float y, Dimension d) { int cx = d.width/2; int cy = d.height/2; if(x > cx && y > cy) return Position.NW; if(x > cx && y < cy) return Position.SW; if(x < cx && y > cy) return Position.NE; return Position.SE; } } public static class OutsidePositioner implements Positioner { public Position getPosition(float x, float y, Dimension d) { int cx = d.width/2; int cy = d.height/2; if(x > cx && y > cy) return Position.SE; if(x > cx && y < cy) return Position.NE; if(x < cx && y > cy) return Position.SW; return Position.NW; } } /** * @return the positioner */ public Positioner getPositioner() { return positioner; } /** * @param positioner the positioner to set */ public void setPositioner(Positioner positioner) { this.positioner = positioner; } } BasicVertexRenderer.java000066400000000000000000000115741276402340000363650ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 23, 2005 */ package edu.uci.ics.jung.visualization.renderers; import java.awt.Dimension; import java.awt.Paint; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import javax.swing.Icon; import javax.swing.JComponent; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.transform.MutableTransformer; import edu.uci.ics.jung.visualization.transform.MutableTransformerDecorator; import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator; public class BasicVertexRenderer implements Renderer.Vertex { public void paintVertex(RenderContext rc, Layout layout, V v) { Graph graph = layout.getGraph(); if (rc.getVertexIncludePredicate().apply(Context.,V>getInstance(graph,v))) { paintIconForVertex(rc, v, layout); } } /** * Returns the vertex shape in view coordinates. * @param rc the render context used for rendering the vertex * @param v the vertex whose shape is to be returned * @param layout the layout algorithm that provides coordinates for the vertex * @param coords the x and y view coordinates * @return the vertex shape in view coordinates */ protected Shape prepareFinalVertexShape(RenderContext rc, V v, Layout layout, int[] coords) { // get the shape to be rendered Shape shape = rc.getVertexShapeTransformer().apply(v); Point2D p = layout.apply(v); p = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p); float x = (float)p.getX(); float y = (float)p.getY(); coords[0] = (int)x; coords[1] = (int)y; // create a transform that translates to the location of // the vertex to be rendered AffineTransform xform = AffineTransform.getTranslateInstance(x,y); // transform the vertex shape with xtransform shape = xform.createTransformedShape(shape); return shape; } /** * Paint v's icon on g at (x,y). * * @param rc the render context used for rendering the vertex * @param v the vertex to be painted * @param layout the layout algorithm that provides coordinates for the vertex */ protected void paintIconForVertex(RenderContext rc, V v, Layout layout) { GraphicsDecorator g = rc.getGraphicsContext(); boolean vertexHit = true; int[] coords = new int[2]; Shape shape = prepareFinalVertexShape(rc, v, layout, coords); vertexHit = vertexHit(rc, shape); if (vertexHit) { if(rc.getVertexIconTransformer() != null) { Icon icon = rc.getVertexIconTransformer().apply(v); if(icon != null) { g.draw(icon, rc.getScreenDevice(), shape, coords[0], coords[1]); } else { paintShapeForVertex(rc, v, shape); } } else { paintShapeForVertex(rc, v, shape); } } } protected boolean vertexHit(RenderContext rc, Shape s) { JComponent vv = rc.getScreenDevice(); Rectangle deviceRectangle = null; if(vv != null) { Dimension d = vv.getSize(); deviceRectangle = new Rectangle( 0,0, d.width,d.height); } MutableTransformer vt = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW); if(vt instanceof MutableTransformerDecorator) { vt = ((MutableTransformerDecorator)vt).getDelegate(); } return vt.transform(s).intersects(deviceRectangle); } protected void paintShapeForVertex(RenderContext rc, V v, Shape shape) { GraphicsDecorator g = rc.getGraphicsContext(); Paint oldPaint = g.getPaint(); Paint fillPaint = rc.getVertexFillPaintTransformer().apply(v); if(fillPaint != null) { g.setPaint(fillPaint); g.fill(shape); g.setPaint(oldPaint); } Paint drawPaint = rc.getVertexDrawPaintTransformer().apply(v); if(drawPaint != null) { g.setPaint(drawPaint); Stroke oldStroke = g.getStroke(); Stroke stroke = rc.getVertexStrokeTransformer().apply(v); if(stroke != null) { g.setStroke(stroke); } g.draw(shape); g.setPaint(oldPaint); g.setStroke(oldStroke); } } } CachingEdgeRenderer.java000066400000000000000000000170021276402340000362570ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/rendererspackage edu.uci.ics.jung.visualization.renderers; import java.awt.Dimension; import java.awt.Paint; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.swing.JComponent; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.BasicVisualizationServer; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.layout.LayoutChangeListener; import edu.uci.ics.jung.visualization.layout.LayoutEvent; import edu.uci.ics.jung.visualization.layout.LayoutEventSupport; import edu.uci.ics.jung.visualization.transform.LensTransformer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator; public class CachingEdgeRenderer extends BasicEdgeRenderer implements ChangeListener, LayoutChangeListener { protected Map edgeShapeMap = new HashMap(); protected Set dirtyEdges = new HashSet(); @SuppressWarnings({ "rawtypes", "unchecked" }) public CachingEdgeRenderer(BasicVisualizationServer vv) { vv.getRenderContext().getMultiLayerTransformer().addChangeListener(this); Layout layout = vv.getGraphLayout(); if(layout instanceof LayoutEventSupport) { ((LayoutEventSupport)layout).addLayoutChangeListener(this); } } /** * Draws the edge e, whose endpoints are at (x1,y1) * and (x2,y2), on the graphics context g. * The Shape provided by the EdgeShapeFunction instance * is scaled in the x-direction so that its width is equal to the distance between * (x1,y1) and (x2,y2). */ protected void drawSimpleEdge(RenderContext rc, Layout layout, E e) { int[] coords = new int[4]; boolean[] loop = new boolean[1]; Shape edgeShape = edgeShapeMap.get(e); if(edgeShape == null || dirtyEdges.contains(e)) { edgeShape = prepareFinalEdgeShape(rc, layout, e, coords, loop); edgeShapeMap.put(e, edgeShape); dirtyEdges.remove(e); } int x1 = coords[0]; int y1 = coords[1]; int x2 = coords[2]; int y2 = coords[3]; boolean isLoop = loop[0]; GraphicsDecorator g = rc.getGraphicsContext(); Graph graph = layout.getGraph(); boolean edgeHit = true; boolean arrowHit = true; Rectangle deviceRectangle = null; JComponent vv = rc.getScreenDevice(); if(vv != null) { Dimension d = vv.getSize(); deviceRectangle = new Rectangle(0,0,d.width,d.height); } MutableTransformer vt = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW); if(vt instanceof LensTransformer) { vt = ((LensTransformer)vt).getDelegate(); } edgeHit = vt.transform(edgeShape).intersects(deviceRectangle); if(edgeHit == true) { Paint oldPaint = g.getPaint(); // get Paints for filling and drawing // (filling is done first so that drawing and label use same Paint) Paint fill_paint = rc.getEdgeFillPaintTransformer().apply(e); if (fill_paint != null) { g.setPaint(fill_paint); g.fill(edgeShape); } Paint draw_paint = rc.getEdgeDrawPaintTransformer().apply(e); if (draw_paint != null) { g.setPaint(draw_paint); g.draw(edgeShape); } float scalex = (float)g.getTransform().getScaleX(); float scaley = (float)g.getTransform().getScaleY(); // see if arrows are too small to bother drawing if(scalex < .3 || scaley < .3) return; if (rc.getEdgeArrowPredicate().apply(Context.,E>getInstance(graph, e))) { Stroke new_stroke = rc.getEdgeArrowStrokeTransformer().apply(e); Stroke old_stroke = g.getStroke(); if (new_stroke != null) g.setStroke(new_stroke); Shape destVertexShape = rc.getVertexShapeTransformer().apply(graph.getEndpoints(e).getSecond()); AffineTransform xf = AffineTransform.getTranslateInstance(x2, y2); destVertexShape = xf.createTransformedShape(destVertexShape); arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(destVertexShape).intersects(deviceRectangle); if(arrowHit) { AffineTransform at = edgeArrowRenderingSupport.getArrowTransform(rc, edgeShape, destVertexShape); if(at == null) return; Shape arrow = rc.getEdgeArrowTransformer().apply(Context.,E>getInstance(graph, e)); arrow = at.createTransformedShape(arrow); g.setPaint(rc.getArrowFillPaintTransformer().apply(e)); g.fill(arrow); g.setPaint(rc.getArrowDrawPaintTransformer().apply(e)); g.draw(arrow); } if (graph.getEdgeType(e) == EdgeType.UNDIRECTED) { Shape vertexShape = rc.getVertexShapeTransformer().apply(graph.getEndpoints(e).getFirst()); xf = AffineTransform.getTranslateInstance(x1, y1); vertexShape = xf.createTransformedShape(vertexShape); arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(vertexShape).intersects(deviceRectangle); if(arrowHit) { AffineTransform at = edgeArrowRenderingSupport.getReverseArrowTransform(rc, edgeShape, vertexShape, !isLoop); if(at == null) return; Shape arrow = rc.getEdgeArrowTransformer().apply(Context.,E>getInstance(graph, e)); arrow = at.createTransformedShape(arrow); g.setPaint(rc.getArrowFillPaintTransformer().apply(e)); g.fill(arrow); g.setPaint(rc.getArrowDrawPaintTransformer().apply(e)); g.draw(arrow); } } // restore paint and stroke if (new_stroke != null) g.setStroke(old_stroke); } // restore old paint g.setPaint(oldPaint); } } // @Override public void stateChanged(ChangeEvent evt) { System.err.println("got change event "+evt); edgeShapeMap.clear(); } // @Override public void layoutChanged(LayoutEvent evt) { V v = evt.getVertex(); Graph graph = evt.getGraph(); dirtyEdges.addAll(graph.getIncidentEdges(v)); } } CachingRenderer.java000066400000000000000000000005241276402340000354730ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/rendererspackage edu.uci.ics.jung.visualization.renderers; import java.awt.Shape; import java.util.HashMap; import java.util.Map; public class CachingRenderer extends BasicRenderer { protected Map edgeShapeMap = new HashMap(); protected Map vertexShapeMap = new HashMap(); } CachingVertexRenderer.java000066400000000000000000000051221276402340000366700ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/rendererspackage edu.uci.ics.jung.visualization.renderers; import java.awt.Shape; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.swing.Icon; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.visualization.BasicVisualizationServer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.layout.LayoutChangeListener; import edu.uci.ics.jung.visualization.layout.LayoutEvent; import edu.uci.ics.jung.visualization.layout.LayoutEventSupport; import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator; public class CachingVertexRenderer extends BasicVertexRenderer implements ChangeListener, LayoutChangeListener { protected Map vertexShapeMap = new HashMap(); protected Set dirtyVertices = new HashSet(); @SuppressWarnings({ "rawtypes", "unchecked" }) public CachingVertexRenderer(BasicVisualizationServer vv) { vv.getRenderContext().getMultiLayerTransformer().addChangeListener(this); Layout layout = vv.getGraphLayout(); if(layout instanceof LayoutEventSupport) { ((LayoutEventSupport)layout).addLayoutChangeListener(this); } } /** * Paint v's icon on g at (x,y). */ protected void paintIconForVertex(RenderContext rc, V v, Layout layout) { GraphicsDecorator g = rc.getGraphicsContext(); boolean vertexHit = true; int[] coords = new int[2]; Shape shape = vertexShapeMap.get(v); if(shape == null || dirtyVertices.contains(v)) { shape = prepareFinalVertexShape(rc, v, layout, coords); vertexShapeMap.put(v, shape); dirtyVertices.remove(v); } vertexHit = vertexHit(rc, shape); if (vertexHit) { if(rc.getVertexIconTransformer() != null) { Icon icon = rc.getVertexIconTransformer().apply(v); if(icon != null) { g.draw(icon, rc.getScreenDevice(), shape, coords[0], coords[1]); } else { paintShapeForVertex(rc, v, shape); } } else { paintShapeForVertex(rc, v, shape); } } } public void stateChanged(ChangeEvent evt) { System.err.println("got change event "+evt); vertexShapeMap.clear(); } public void layoutChanged(LayoutEvent evt) { this.dirtyVertices.add(evt.getVertex()); } } CenterEdgeArrowRenderingSupport.java000066400000000000000000000132621276402340000407260ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 23, 2005 */ package edu.uci.ics.jung.visualization.renderers; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.RenderContext; public class CenterEdgeArrowRenderingSupport implements EdgeArrowRenderingSupport { public AffineTransform getArrowTransform(RenderContext rc, Shape edgeShape, Shape vertexShape) { GeneralPath path = new GeneralPath(edgeShape); float[] seg = new float[6]; Point2D p1=null; Point2D p2=null; AffineTransform at = new AffineTransform(); // count the segments. int middleSegment = 0; int current = 0; for(PathIterator i=path.getPathIterator(null,1); !i.isDone(); i.next()) { current++; } middleSegment = current/2; // find the middle segment current = 0; for(PathIterator i=path.getPathIterator(null,1); !i.isDone(); i.next()) { current++; int ret = i.currentSegment(seg); if(ret == PathIterator.SEG_MOVETO) { p2 = new Point2D.Float(seg[0],seg[1]); } else if(ret == PathIterator.SEG_LINETO) { p1 = p2; p2 = new Point2D.Float(seg[0],seg[1]); } if(current > middleSegment) { // done at = getArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape); break; } } return at; } public AffineTransform getReverseArrowTransform(RenderContext rc, Shape edgeShape, Shape vertexShape) { return getReverseArrowTransform(rc, edgeShape, vertexShape, true); } /** * Returns a transform to position the arrowhead on this edge shape at the * point where it intersects the passed vertex shape. * * @param rc the rendering context used for rendering the arrow * @param edgeShape the shape used to draw the edge * @param vertexShape the shape used to draw the vertex * @param passedGo (ignored in this implementation) */ public AffineTransform getReverseArrowTransform(RenderContext rc, Shape edgeShape, Shape vertexShape, boolean passedGo) { GeneralPath path = new GeneralPath(edgeShape); float[] seg = new float[6]; Point2D p1=null; Point2D p2=null; AffineTransform at = new AffineTransform(); // count the segments. int middleSegment = 0; int current = 0; for(PathIterator i=path.getPathIterator(null,1); !i.isDone(); i.next()) { current++; } middleSegment = current/2; // find the middle segment current = 0; for(PathIterator i=path.getPathIterator(null,1); !i.isDone(); i.next()) { current++; int ret = i.currentSegment(seg); if(ret == PathIterator.SEG_MOVETO) { p2 = new Point2D.Float(seg[0],seg[1]); } else if(ret == PathIterator.SEG_LINETO) { p1 = p2; p2 = new Point2D.Float(seg[0],seg[1]); } if(current > middleSegment) { // done at = getReverseArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape); break; } } return at; } public AffineTransform getArrowTransform(RenderContext rc, Line2D edgeShape, Shape vertexShape) { // find the midpoint of the edgeShape line, and use it to make the transform Line2D left = new Line2D.Float(); Line2D right = new Line2D.Float(); this.subdivide(edgeShape, left, right); edgeShape = right; float dx = (float) (edgeShape.getX1()-edgeShape.getX2()); float dy = (float) (edgeShape.getY1()-edgeShape.getY2()); double atheta = Math.atan2(dx,dy)+Math.PI/2; AffineTransform at = AffineTransform.getTranslateInstance(edgeShape.getX1(), edgeShape.getY1()); at.rotate(-atheta); return at; } protected AffineTransform getReverseArrowTransform(RenderContext rc, Line2D edgeShape, Shape vertexShape) { // find the midpoint of the edgeShape line, and use it to make the transform Line2D left = new Line2D.Float(); Line2D right = new Line2D.Float(); this.subdivide(edgeShape, left, right); edgeShape = right; float dx = (float) (edgeShape.getX1()-edgeShape.getX2()); float dy = (float) (edgeShape.getY1()-edgeShape.getY2()); // calculate the angle for the arrowhead double atheta = Math.atan2(dx,dy)-Math.PI/2; AffineTransform at = AffineTransform.getTranslateInstance(edgeShape.getX1(),edgeShape.getY1()); at.rotate(-atheta); return at; } /** * divide a Line2D into 2 new Line2Ds that are returned * in the passed left and right instances, if non-null * @param src the line to divide * @param left the left side, or null * @param right the right side, or null */ protected void subdivide(Line2D src, Line2D left, Line2D right) { double x1 = src.getX1(); double y1 = src.getY1(); double x2 = src.getX2(); double y2 = src.getY2(); double mx = x1 + (x2-x1)/2.0; double my = y1 + (y2-y1)/2.0; if (left != null) { left.setLine(x1, y1, mx, my); } if (right != null) { right.setLine(mx, my, x2, y2); } } } Checkmark.java000066400000000000000000000034361276402340000343450ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/rendererspackage edu.uci.ics.jung.visualization.renderers; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.util.Collections; import javax.swing.Icon; /** * a simple Icon that draws a checkmark in the lower-right quadrant of its * area. Used to draw a checkmark on Picked Vertices. * @author Tom Nelson */ public class Checkmark implements Icon { GeneralPath path = new GeneralPath(); AffineTransform highlight = AffineTransform.getTranslateInstance(-1,-1); AffineTransform lowlight = AffineTransform.getTranslateInstance(1,1); AffineTransform shadow = AffineTransform.getTranslateInstance(2,2); Color color; public Checkmark() { this(Color.green); } public Checkmark(Color color) { this.color = color; path.moveTo(10,17); path.lineTo(13,20); path.lineTo(20,13); } public void paintIcon(Component c, Graphics g, int x, int y) { Shape shape = AffineTransform.getTranslateInstance(x, y).createTransformedShape(path); Graphics2D g2d = (Graphics2D)g; g2d.addRenderingHints(Collections.singletonMap(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)); Stroke stroke = g2d.getStroke(); g2d.setStroke(new BasicStroke(4)); g2d.setColor(Color.darkGray); g2d.draw(shadow.createTransformedShape(shape)); g2d.setColor(Color.black); g2d.draw(lowlight.createTransformedShape(shape)); g2d.setColor(Color.white); g2d.draw(highlight.createTransformedShape(shape)); g2d.setColor(color); g2d.draw(shape); g2d.setStroke(stroke); } public int getIconWidth() { return 20; } public int getIconHeight() { return 20; } }DefaultEdgeLabelRenderer.java000066400000000000000000000150331276402340000372510ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Apr 14, 2005 */ package edu.uci.ics.jung.visualization.renderers; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.Rectangle; import java.io.Serializable; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; /** * DefaultEdgeLabelRenderer is similar to the cell renderers * used by the JTable and JTree jfc classes. * * @author Tom Nelson * * */ @SuppressWarnings("serial") public class DefaultEdgeLabelRenderer extends JLabel implements EdgeLabelRenderer, Serializable { protected static Border noFocusBorder = new EmptyBorder(0,0,0,0); protected Color pickedEdgeLabelColor = Color.black; protected boolean rotateEdgeLabels; public DefaultEdgeLabelRenderer(Color pickedEdgeLabelColor) { this(pickedEdgeLabelColor, true); } /** * Creates an instance with the specified properties. * * @param pickedEdgeLabelColor the color to use for rendering the labels of picked edges * @param rotateEdgeLabels whether the */ public DefaultEdgeLabelRenderer(Color pickedEdgeLabelColor, boolean rotateEdgeLabels) { super(); this.pickedEdgeLabelColor = pickedEdgeLabelColor; this.rotateEdgeLabels = rotateEdgeLabels; setOpaque(true); setBorder(noFocusBorder); } /** * @return Returns the rotateEdgeLabels. */ public boolean isRotateEdgeLabels() { return rotateEdgeLabels; } /** * @param rotateEdgeLabels The rotateEdgeLabels to set. */ public void setRotateEdgeLabels(boolean rotateEdgeLabels) { this.rotateEdgeLabels = rotateEdgeLabels; } /** * Overrides JComponent.setForeground to assign * the unselected-foreground color to the specified color. * * @param c set the foreground color to this value */ @Override public void setForeground(Color c) { super.setForeground(c); } /** * Overrides JComponent.setBackground to assign * the unselected-background color to the specified color. * * @param c set the background color to this value */ @Override public void setBackground(Color c) { super.setBackground(c); } /** * Notification from the UIManager that the look and feel * has changed. * Replaces the current UI object with the latest version from the * UIManager. * * @see JComponent#updateUI */ @Override public void updateUI() { super.updateUI(); setForeground(null); setBackground(null); } /** * * Returns the default label renderer for an Edge * * @param vv the VisualizationViewer to render on * @param value the value to assign to the label for * Edge * @param edge the Edge * @return the default label renderer */ public Component getEdgeLabelRendererComponent(JComponent vv, Object value, Font font, boolean isSelected, E edge) { super.setForeground(vv.getForeground()); if(isSelected) setForeground(pickedEdgeLabelColor); super.setBackground(vv.getBackground()); if(font != null) { setFont(font); } else { setFont(vv.getFont()); } setIcon(null); setBorder(noFocusBorder); setValue(value); return this; } /* * Implementation Note * The following methods are overridden as a performance measure to * prune code-paths that are often called in the case of renders * but which we know are unnecessary. Great care should be taken * when writing your own renderer to weigh the benefits and * drawbacks of overriding methods like these. */ /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public boolean isOpaque() { Color back = getBackground(); Component p = getParent(); if (p != null) { p = p.getParent(); } boolean colorMatch = (back != null) && (p != null) && back.equals(p.getBackground()) && p.isOpaque(); return !colorMatch && super.isOpaque(); } /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void validate() {} /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void revalidate() {} /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void repaint(long tm, int x, int y, int width, int height) {} /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void repaint(Rectangle r) { } /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { // Strings get interned... if (propertyName=="text") { super.firePropertyChange(propertyName, oldValue, newValue); } } /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { } /** * Sets the String object for the cell being rendered to * value. * * @param value the string value for this cell; if value is * null it sets the text value to an empty string * @see JLabel#setText * */ protected void setValue(Object value) { setText((value == null) ? "" : value.toString()); } } DefaultVertexLabelRenderer.java000066400000000000000000000135601276402340000376650ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Apr 14, 2005 */ package edu.uci.ics.jung.visualization.renderers; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.Rectangle; import java.io.Serializable; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; /** * DefaultVertexLabelRenderer is similar to the cell renderers * used by the JTable and JTree JFC classes. * * @author Tom Nelson */ @SuppressWarnings("serial") public class DefaultVertexLabelRenderer extends JLabel implements VertexLabelRenderer, Serializable { protected static Border noFocusBorder = new EmptyBorder(0,0,0,0); protected Color pickedVertexLabelColor = Color.black; /** * Creates a default table cell renderer. * * @param pickedVertexLabelColor the color to use for rendering the labels of picked vertices */ public DefaultVertexLabelRenderer(Color pickedVertexLabelColor) { this.pickedVertexLabelColor = pickedVertexLabelColor; setOpaque(true); setBorder(noFocusBorder); } /** * Overrides JComponent.setForeground to assign * the unselected-foreground color to the specified color. * * @param c set the foreground color to this value */ @Override public void setForeground(Color c) { super.setForeground(c); } /** * Overrides JComponent.setBackground to assign * the unselected-background color to the specified color. * * @param c set the background color to this value */ @Override public void setBackground(Color c) { super.setBackground(c); } /** * Notification from the UIManager that the look and feel * has changed. * Replaces the current UI object with the latest version from the * UIManager. * * @see JComponent#updateUI */ @Override public void updateUI() { super.updateUI(); setForeground(null); setBackground(null); } /** * * Returns the default label renderer for a Vertex * * @param vv the VisualizationViewer to render on * @param value the value to assign to the label for * Vertex * @param vertex the Vertex * @return the default label renderer */ public Component getVertexLabelRendererComponent(JComponent vv, Object value, Font font, boolean isSelected, V vertex) { super.setForeground(vv.getForeground()); if(isSelected) setForeground(pickedVertexLabelColor); super.setBackground(vv.getBackground()); if(font != null) { setFont(font); } else { setFont(vv.getFont()); } setIcon(null); setBorder(noFocusBorder); setValue(value); return this; } /* * The following methods are overridden as a performance measure to * to prune code-paths are often called in the case of renders * but which we know are unnecessary. Great care should be taken * when writing your own renderer to weigh the benefits and * drawbacks of overriding methods like these. */ /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public boolean isOpaque() { Color back = getBackground(); Component p = getParent(); if (p != null) { p = p.getParent(); } boolean colorMatch = (back != null) && (p != null) && back.equals(p.getBackground()) && p.isOpaque(); return !colorMatch && super.isOpaque(); } /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void validate() {} /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void revalidate() {} /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void repaint(long tm, int x, int y, int width, int height) {} /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void repaint(Rectangle r) { } /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { // Strings get interned... if (propertyName=="text") { super.firePropertyChange(propertyName, oldValue, newValue); } } /** * Overridden for performance reasons. * See the Implementation Note * for more information. */ @Override public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { } /** * Sets the String object for the cell being rendered to * value. * * @param value the string value for this cell; if value is * null it sets the text value to an empty string * @see JLabel#setText * */ protected void setValue(Object value) { setText((value == null) ? "" : value.toString()); } } EdgeArrowRenderingSupport.java000066400000000000000000000050221276402340000375600ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/rendererspackage edu.uci.ics.jung.visualization.renderers; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import edu.uci.ics.jung.visualization.RenderContext; public interface EdgeArrowRenderingSupport { /** * Returns a transform to position the arrowhead on this edge shape at the * point where it intersects the passed vertex shape. * * @param rc the rendering context used for rendering the arrow * @param edgeShape the shape used to draw the edge * @param vertexShape the shape used to draw the vertex * @return a transform used for positioning the arrowhead for this vertex and edge */ AffineTransform getArrowTransform(RenderContext rc, Shape edgeShape, Shape vertexShape); /** * Returns a transform to position the arrowhead on this edge shape at the * point where it intersects the passed vertex shape. * * @param rc the rendering context used for rendering the arrow * @param edgeShape the shape used to draw the edge * @param vertexShape the shape used to draw the vertex * @return a transform used for positioning the arrowhead for this vertex and edge */ AffineTransform getReverseArrowTransform( RenderContext rc, Shape edgeShape, Shape vertexShape); /** * Returns a transform to position the arrowhead on this edge shape at the * point where it intersects the passed vertex shape. * *

        The Loop edge is a special case because its starting point is not inside * the vertex. The passedGo flag handles this case. * * @param rc the rendering context used for rendering the arrow * @param edgeShape the shape used to draw the edge * @param vertexShape the shape used to draw the vertex * @param passedGo used for rendering loop edges * @return a transform used for positioning the arrowhead for this vertex and edge */ AffineTransform getReverseArrowTransform( RenderContext rc, Shape edgeShape, Shape vertexShape, boolean passedGo); /** * Returns a transform to position the arrowhead on this edge shape at the * point where it intersects the passed vertex shape. * * @param rc the rendering context used for rendering the arrow * @param edgeShape the shape used to draw the edge * @param vertexShape the shape used to draw the vertex * @return a transform used for positioning the arrowhead for this vertex and edge */ AffineTransform getArrowTransform(RenderContext rc, Line2D edgeShape, Shape vertexShape); }EdgeLabelRenderer.java000066400000000000000000000026301276402340000357430ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Apr 14, 2005 */ package edu.uci.ics.jung.visualization.renderers; import java.awt.Component; import java.awt.Font; import javax.swing.JComponent; /** * @author Tom Nelson * * */ public interface EdgeLabelRenderer { /** * Returns the component used for drawing the label. This method is * used to configure the renderer appropriately before drawing. * * @param component the component that is asking the renderer to draw * @param value the value of the cell to be rendered; the details of how to * render the value are up to the renderer implementation. For example, * if {@code value} is the string "true", it could be rendered as the * string or as a checked checkbox. * @param font the font to use in rendering the label * @param isSelected whether the edge is currently selected * @param edge the edge whose label is being drawn * @param the edge type * @return the component used for drawing the label */ Component getEdgeLabelRendererComponent(JComponent component, Object value, Font font, boolean isSelected, E edge); boolean isRotateEdgeLabels(); void setRotateEdgeLabels(boolean state); } GradientVertexRenderer.java000066400000000000000000000112171276402340000370730ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 23, 2005 */ package edu.uci.ics.jung.visualization.renderers; import java.awt.Color; import java.awt.Dimension; import java.awt.GradientPaint; import java.awt.Paint; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import javax.swing.JComponent; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator; /** * A renderer that will fill vertex shapes with a GradientPaint * @author Tom Nelson * * @param the vertex type * @param the edge type */ public class GradientVertexRenderer implements Renderer.Vertex { Color colorOne; Color colorTwo; Color pickedColorOne; Color pickedColorTwo; PickedState pickedState; boolean cyclic; public GradientVertexRenderer(Color colorOne, Color colorTwo, boolean cyclic) { this.colorOne = colorOne; this.colorTwo = colorTwo; this.cyclic = cyclic; } public GradientVertexRenderer(Color colorOne, Color colorTwo, Color pickedColorOne, Color pickedColorTwo, PickedState pickedState, boolean cyclic) { this.colorOne = colorOne; this.colorTwo = colorTwo; this.pickedColorOne = pickedColorOne; this.pickedColorTwo = pickedColorTwo; this.pickedState = pickedState; this.cyclic = cyclic; } public void paintVertex(RenderContext rc, Layout layout, V v) { Graph graph = layout.getGraph(); if (rc.getVertexIncludePredicate().apply(Context.,V>getInstance(graph,v))) { boolean vertexHit = true; // get the shape to be rendered Shape shape = rc.getVertexShapeTransformer().apply(v); Point2D p = layout.apply(v); p = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p); float x = (float)p.getX(); float y = (float)p.getY(); // create a transform that translates to the location of // the vertex to be rendered AffineTransform xform = AffineTransform.getTranslateInstance(x,y); // transform the vertex shape with xtransform shape = xform.createTransformedShape(shape); vertexHit = vertexHit(rc, shape); //rc.getViewTransformer().transform(shape).intersects(deviceRectangle); if (vertexHit) { paintShapeForVertex(rc, v, shape); } } } protected boolean vertexHit(RenderContext rc, Shape s) { JComponent vv = rc.getScreenDevice(); Rectangle deviceRectangle = null; if(vv != null) { Dimension d = vv.getSize(); deviceRectangle = new Rectangle( 0,0, d.width,d.height); } return rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(s).intersects(deviceRectangle); } protected void paintShapeForVertex(RenderContext rc, V v, Shape shape) { GraphicsDecorator g = rc.getGraphicsContext(); Paint oldPaint = g.getPaint(); Rectangle r = shape.getBounds(); float y2 = (float)r.getMaxY(); if(cyclic) { y2 = (float)(r.getMinY()+r.getHeight()/2); } Paint fillPaint = null; if(pickedState != null && pickedState.isPicked(v)) { fillPaint = new GradientPaint((float)r.getMinX(), (float)r.getMinY(), pickedColorOne, (float)r.getMinX(), y2, pickedColorTwo, cyclic); } else { fillPaint = new GradientPaint((float)r.getMinX(), (float)r.getMinY(), colorOne, (float)r.getMinX(), y2, colorTwo, cyclic); } if(fillPaint != null) { g.setPaint(fillPaint); g.fill(shape); g.setPaint(oldPaint); } Paint drawPaint = rc.getVertexDrawPaintTransformer().apply(v); if(drawPaint != null) { g.setPaint(drawPaint); } Stroke oldStroke = g.getStroke(); Stroke stroke = rc.getVertexStrokeTransformer().apply(v); if(stroke != null) { g.setStroke(stroke); } g.draw(shape); g.setPaint(oldPaint); g.setStroke(oldStroke); } } Renderer.java000066400000000000000000000064731276402340000342270ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2003, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.renderers; import java.awt.Dimension; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.visualization.RenderContext; /** * The interface for drawing vertices, edges, and their labels. * Implementations of this class can set specific renderers for * each element, allowing custom control of each. */ public interface Renderer { void render(RenderContext rc, Layout layout); void renderVertex(RenderContext rc, Layout layout, V v); void renderVertexLabel(RenderContext rc, Layout layout, V v); void renderEdge(RenderContext rc, Layout layout, E e); void renderEdgeLabel(RenderContext rc, Layout layout, E e); void setVertexRenderer(Renderer.Vertex r); void setEdgeRenderer(Renderer.Edge r); void setVertexLabelRenderer(Renderer.VertexLabel r); void setEdgeLabelRenderer(Renderer.EdgeLabel r); Renderer.VertexLabel getVertexLabelRenderer(); Renderer.Vertex getVertexRenderer(); Renderer.Edge getEdgeRenderer(); Renderer.EdgeLabel getEdgeLabelRenderer(); interface Vertex { void paintVertex(RenderContext rc, Layout layout, V v); @SuppressWarnings("rawtypes") class NOOP implements Vertex { public void paintVertex(RenderContext rc, Layout layout, Object v) {} }; } interface Edge { void paintEdge(RenderContext rc, Layout layout, E e); EdgeArrowRenderingSupport getEdgeArrowRenderingSupport(); void setEdgeArrowRenderingSupport(EdgeArrowRenderingSupport edgeArrowRenderingSupport); @SuppressWarnings("rawtypes") class NOOP implements Edge { public void paintEdge(RenderContext rc, Layout layout, Object e) {} public EdgeArrowRenderingSupport getEdgeArrowRenderingSupport(){return null;} public void setEdgeArrowRenderingSupport(EdgeArrowRenderingSupport edgeArrowRenderingSupport){} } } interface VertexLabel { void labelVertex(RenderContext rc, Layout layout, V v, String label); Position getPosition(); void setPosition(Position position); void setPositioner(Positioner positioner); Positioner getPositioner(); @SuppressWarnings("rawtypes") class NOOP implements VertexLabel { public void labelVertex(RenderContext rc, Layout layout, Object v, String label) {} public Position getPosition() { return Position.CNTR; } public void setPosition(Position position) {} public Positioner getPositioner() { return new Positioner() { public Position getPosition(float x, float y, Dimension d) { return Position.CNTR; }}; } public void setPositioner(Positioner positioner) { } } enum Position { N, NE, E, SE, S, SW, W, NW, CNTR, AUTO } interface Positioner { Position getPosition(float x, float y, Dimension d); } } interface EdgeLabel { void labelEdge(RenderContext rc, Layout layout, E e, String label); @SuppressWarnings("rawtypes") class NOOP implements EdgeLabel { public void labelEdge(RenderContext rc, Layout layout, Object e, String label) {} } } } ReshapingEdgeRenderer.java000066400000000000000000000421261276402340000366500ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 23, 2005 */ package edu.uci.ics.jung.visualization.renderers; import java.awt.Dimension; import java.awt.Paint; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RectangularShape; import javax.swing.JComponent; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.transform.LensTransformer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; import edu.uci.ics.jung.visualization.transform.shape.TransformingGraphics; /** * uses a flatness argument to break edges into * smaller segments. This produces a more detailed * transformation of the edge shape * * @author Tom Nelson - tomnelson@dev.java.net * * @param the vertex type * @param the edge type */ public class ReshapingEdgeRenderer extends BasicEdgeRenderer implements Renderer.Edge { /** * Draws the edge e, whose endpoints are at (x1,y1) * and (x2,y2), on the graphics context g. * The Shape provided by the EdgeShapeFunction instance * is scaled in the x-direction so that its width is equal to the distance between * (x1,y1) and (x2,y2). */ protected void drawSimpleEdge(RenderContext rc, Layout layout, E e) { TransformingGraphics g = (TransformingGraphics)rc.getGraphicsContext(); Graph graph = layout.getGraph(); Pair endpoints = graph.getEndpoints(e); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); Point2D p1 = layout.apply(v1); Point2D p2 = layout.apply(v2); p1 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p1); p2 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p2); float x1 = (float) p1.getX(); float y1 = (float) p1.getY(); float x2 = (float) p2.getX(); float y2 = (float) p2.getY(); float flatness = 0; MutableTransformer transformer = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW); if(transformer instanceof LensTransformer) { LensTransformer ht = (LensTransformer)transformer; RectangularShape lensShape = ht.getLensShape(); if(lensShape.contains(x1,y1) || lensShape.contains(x2,y2)) { flatness = .05f; } } boolean isLoop = v1.equals(v2); Shape s2 = rc.getVertexShapeTransformer().apply(v2); Shape edgeShape = rc.getEdgeShapeTransformer().apply(e); boolean edgeHit = true; boolean arrowHit = true; Rectangle deviceRectangle = null; JComponent vv = rc.getScreenDevice(); if(vv != null) { Dimension d = vv.getSize(); deviceRectangle = new Rectangle(0,0,d.width,d.height); } AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1); if(isLoop) { // this is a self-loop. scale it is larger than the vertex // it decorates and translate it so that its nadir is // at the center of the vertex. Rectangle2D s2Bounds = s2.getBounds2D(); xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight()); xform.translate(0, -edgeShape.getBounds2D().getWidth()/2); } else { // this is a normal edge. Rotate it to the angle between // vertex endpoints, then scale it to the distance between // the vertices float dx = x2-x1; float dy = y2-y1; float thetaRadians = (float) Math.atan2(dy, dx); xform.rotate(thetaRadians); float dist = (float) Math.sqrt(dx*dx + dy*dy); xform.scale(dist, 1.0); } edgeShape = xform.createTransformedShape(edgeShape); MutableTransformer vt = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW); if(vt instanceof LensTransformer) { vt = ((LensTransformer)vt).getDelegate(); } edgeHit = vt.transform(edgeShape).intersects(deviceRectangle); if(edgeHit == true) { Paint oldPaint = g.getPaint(); // get Paints for filling and drawing // (filling is done first so that drawing and label use same Paint) Paint fill_paint = rc.getEdgeFillPaintTransformer().apply(e); if (fill_paint != null) { g.setPaint(fill_paint); g.fill(edgeShape, flatness); } Paint draw_paint = rc.getEdgeDrawPaintTransformer().apply(e); if (draw_paint != null) { g.setPaint(draw_paint); g.draw(edgeShape, flatness); } float scalex = (float)g.getTransform().getScaleX(); float scaley = (float)g.getTransform().getScaleY(); // see if arrows are too small to bother drawing if(scalex < .3 || scaley < .3) return; if (rc.getEdgeArrowPredicate().apply(Context.,E>getInstance(graph, e))) { Shape destVertexShape = rc.getVertexShapeTransformer().apply(graph.getEndpoints(e).getSecond()); AffineTransform xf = AffineTransform.getTranslateInstance(x2, y2); destVertexShape = xf.createTransformedShape(destVertexShape); arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(destVertexShape).intersects(deviceRectangle); if(arrowHit) { AffineTransform at = edgeArrowRenderingSupport.getArrowTransform(rc, new GeneralPath(edgeShape), destVertexShape); if(at == null) return; Shape arrow = rc.getEdgeArrowTransformer().apply(Context.,E>getInstance(graph, e)); arrow = at.createTransformedShape(arrow); g.setPaint(rc.getArrowFillPaintTransformer().apply(e)); g.fill(arrow); g.setPaint(rc.getArrowDrawPaintTransformer().apply(e)); g.draw(arrow); } if (graph.getEdgeType(e) == EdgeType.UNDIRECTED) { Shape vertexShape = rc.getVertexShapeTransformer().apply(graph.getEndpoints(e).getFirst()); xf = AffineTransform.getTranslateInstance(x1, y1); vertexShape = xf.createTransformedShape(vertexShape); arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(vertexShape).intersects(deviceRectangle); if(arrowHit) { AffineTransform at = edgeArrowRenderingSupport.getReverseArrowTransform(rc, new GeneralPath(edgeShape), vertexShape, !isLoop); if(at == null) return; Shape arrow = rc.getEdgeArrowTransformer().apply(Context.,E>getInstance(graph, e)); arrow = at.createTransformedShape(arrow); g.setPaint(rc.getArrowFillPaintTransformer().apply(e)); g.fill(arrow); g.setPaint(rc.getArrowDrawPaintTransformer().apply(e)); g.draw(arrow); } } } // use existing paint for text if no draw paint specified if (draw_paint == null) g.setPaint(oldPaint); // restore old paint g.setPaint(oldPaint); } } /** * Returns a transform to position the arrowhead on this edge shape at the * point where it intersects the passed vertex shape. */ // public AffineTransform getArrowTransform(RenderContext rc, GeneralPath edgeShape, Shape vertexShape) { // float[] seg = new float[6]; // Point2D p1=null; // Point2D p2=null; // AffineTransform at = new AffineTransform(); // // when the PathIterator is done, switch to the line-subdivide // // method to get the arrowhead closer. // for(PathIterator i=edgeShape.getPathIterator(null,1); !i.isDone(); i.next()) { // int ret = i.currentSegment(seg); // if(ret == PathIterator.SEG_MOVETO) { // p2 = new Point2D.Float(seg[0],seg[1]); // } else if(ret == PathIterator.SEG_LINETO) { // p1 = p2; // p2 = new Point2D.Float(seg[0],seg[1]); // if(vertexShape.contains(p2)) { // at = getArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape); // break; // } // } // } // return at; // } /** * Returns a transform to position the arrowhead on this edge shape at the * point where it intersects the passed vertex shape. */ // public AffineTransform getReverseArrowTransform(RenderContext rc, GeneralPath edgeShape, Shape vertexShape) { // return getReverseArrowTransform(rc, edgeShape, vertexShape, true); // } /** *

        Returns a transform to position the arrowhead on this edge shape at the * point where it intersects the passed vertex shape. * *

        The Loop edge is a special case because its staring point is not inside * the vertex. The passedGo flag handles this case. * * @param edgeShape * @param vertexShape * @param passedGo - used only for Loop edges */ // public AffineTransform getReverseArrowTransform(RenderContext rc, GeneralPath edgeShape, Shape vertexShape, // boolean passedGo) { // float[] seg = new float[6]; // Point2D p1=null; // Point2D p2=null; // // AffineTransform at = new AffineTransform(); // for(PathIterator i=edgeShape.getPathIterator(null,1); !i.isDone(); i.next()) { // int ret = i.currentSegment(seg); // if(ret == PathIterator.SEG_MOVETO) { // p2 = new Point2D.Float(seg[0],seg[1]); // } else if(ret == PathIterator.SEG_LINETO) { // p1 = p2; // p2 = new Point2D.Float(seg[0],seg[1]); // if(passedGo == false && vertexShape.contains(p2)) { // passedGo = true; // } else if(passedGo==true && // vertexShape.contains(p2)==false) { // at = getReverseArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape); // break; // } // } // } // return at; // } /** * This is used for the arrow of a directed and for one of the * arrows for non-directed edges * Get a transform to place the arrow shape on the passed edge at the * point where it intersects the passed shape * @param edgeShape * @param vertexShape * @return */ // public AffineTransform getArrowTransform(RenderContext rc, Line2D edgeShape, Shape vertexShape) { // float dx = (float) (edgeShape.getX1()-edgeShape.getX2()); // float dy = (float) (edgeShape.getY1()-edgeShape.getY2()); // // iterate over the line until the edge shape will place the // // arrowhead closer than 'arrowGap' to the vertex shape boundary // while((dx*dx+dy*dy) > rc.getArrowPlacementTolerance()) { // try { // edgeShape = getLastOutsideSegment(edgeShape, vertexShape); // } catch(IllegalArgumentException e) { // System.err.println(e.toString()); // return null; // } // dx = (float) (edgeShape.getX1()-edgeShape.getX2()); // dy = (float) (edgeShape.getY1()-edgeShape.getY2()); // } // double atheta = Math.atan2(dx,dy)+Math.PI/2; // AffineTransform at = // AffineTransform.getTranslateInstance(edgeShape.getX1(), edgeShape.getY1()); // at.rotate(-atheta); // return at; // } /** * This is used for the reverse-arrow of a non-directed edge * get a transform to place the arrow shape on the passed edge at the * point where it intersects the passed shape * @param edgeShape * @param vertexShape * @return */ // protected AffineTransform getReverseArrowTransform(RenderContext rc, Line2D edgeShape, Shape vertexShape) { // float dx = (float) (edgeShape.getX1()-edgeShape.getX2()); // float dy = (float) (edgeShape.getY1()-edgeShape.getY2()); // // iterate over the line until the edge shape will place the // // arrowhead closer than 'arrowGap' to the vertex shape boundary // while((dx*dx+dy*dy) > rc.getArrowPlacementTolerance()) { // try { // edgeShape = getFirstOutsideSegment(edgeShape, vertexShape); // } catch(IllegalArgumentException e) { // System.err.println(e.toString()); // return null; // } // dx = (float) (edgeShape.getX1()-edgeShape.getX2()); // dy = (float) (edgeShape.getY1()-edgeShape.getY2()); // } // // calculate the angle for the arrowhead // double atheta = Math.atan2(dx,dy)-Math.PI/2; // AffineTransform at = AffineTransform.getTranslateInstance(edgeShape.getX1(),edgeShape.getY1()); // at.rotate(-atheta); // return at; // } /** * Passed Line's point2 must be inside the passed shape or * an IllegalArgumentException is thrown * @param line line to subdivide * @param shape shape to compare with line * @return a line that intersects the shape boundary * @throws IllegalArgumentException if the passed line's point1 is not inside the shape */ // protected Line2D getLastOutsideSegment(Line2D line, Shape shape) { // if(shape.contains(line.getP2())==false) { // String errorString = // "line end point: "+line.getP2()+" is not contained in shape: "+shape.getBounds2D(); // throw new IllegalArgumentException(errorString); // //return null; // } // Line2D left = new Line2D.Double(); // Line2D right = new Line2D.Double(); // // subdivide the line until its left segment intersects // // the shape boundary // do { // subdivide(line, left, right); // line = right; // } while(shape.contains(line.getP1())==false); // // now that right is completely inside shape, // // return left, which must be partially outside // return left; // } /** * Passed Line's point1 must be inside the passed shape or * an IllegalArgumentException is thrown * @param line line to subdivide * @param shape shape to compare with line * @return a line that intersects the shape boundary * @throws IllegalArgumentException if the passed line's point1 is not inside the shape */ // protected Line2D getFirstOutsideSegment(Line2D line, Shape shape) { // // if(shape.contains(line.getP1())==false) { // String errorString = // "line start point: "+line.getP1()+" is not contained in shape: "+shape.getBounds2D(); // throw new IllegalArgumentException(errorString); // } // Line2D left = new Line2D.Float(); // Line2D right = new Line2D.Float(); // // subdivide the line until its right side intersects the // // shape boundary // do { // subdivide(line, left, right); // line = left; // } while(shape.contains(line.getP2())==false); // // now that left is completely inside shape, // // return right, which must be partially outside // return right; // } /** * divide a Line2D into 2 new Line2Ds that are returned * in the passed left and right instances, if non-null * @param src the line to divide * @param left the left side, or null * @param right the right side, or null */ // protected void subdivide(Line2D src, // Line2D left, // Line2D right) { // double x1 = src.getX1(); // double y1 = src.getY1(); // double x2 = src.getX2(); // double y2 = src.getY2(); // // double mx = x1 + (x2-x1)/2.0; // double my = y1 + (y2-y1)/2.0; // if (left != null) { // left.setLine(x1, y1, mx, my); // } // if (right != null) { // right.setLine(mx, my, x2, y2); // } // } } VertexLabelAsShapeRenderer.java000066400000000000000000000102041276402340000376150ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 23, 2005 */ package edu.uci.ics.jung.visualization.renderers; import java.awt.Component; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.Point2D; import java.util.HashMap; import java.util.Map; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator; /** * Renders Vertex Labels, but can also supply Shapes for vertices. * This has the effect of making the vertex label the actual vertex * shape. The user will probably want to center the vertex label * on the vertex location. * * @author Tom Nelson * * @param the vertex type * @param the edge type */ public class VertexLabelAsShapeRenderer implements Renderer.VertexLabel, Function { protected Map shapes = new HashMap(); protected RenderContext rc; public VertexLabelAsShapeRenderer(RenderContext rc) { this.rc = rc; } public Component prepareRenderer(RenderContext rc, VertexLabelRenderer graphLabelRenderer, Object value, boolean isSelected, V vertex) { return rc.getVertexLabelRenderer().getVertexLabelRendererComponent(rc.getScreenDevice(), value, rc.getVertexFontTransformer().apply(vertex), isSelected, vertex); } /** * Labels the specified vertex with the specified label. * Uses the font specified by this instance's * VertexFontFunction. (If the font is unspecified, the existing * font for the graphics context is used.) If vertex label centering * is active, the label is centered on the position of the vertex; otherwise * the label is offset slightly. */ public void labelVertex(RenderContext rc, Layout layout, V v, String label) { Graph graph = layout.getGraph(); if (rc.getVertexIncludePredicate().apply(Context.,V>getInstance(graph,v)) == false) { return; } GraphicsDecorator g = rc.getGraphicsContext(); Component component = prepareRenderer(rc, rc.getVertexLabelRenderer(), label, rc.getPickedVertexState().isPicked(v), v); Dimension d = component.getPreferredSize(); int h_offset = -d.width / 2; int v_offset = -d.height / 2; Point2D p = layout.apply(v); p = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p); int x = (int)p.getX(); int y = (int)p.getY(); g.draw(component, rc.getRendererPane(), x+h_offset, y+v_offset, d.width, d.height, true); Dimension size = component.getPreferredSize(); Rectangle bounds = new Rectangle(-size.width/2 -2, -size.height/2 -2, size.width+4, size.height); shapes.put(v, bounds); } public Shape apply(V v) { Component component = prepareRenderer(rc, rc.getVertexLabelRenderer(), rc.getVertexLabelTransformer().apply(v), rc.getPickedVertexState().isPicked(v), v); Dimension size = component.getPreferredSize(); Rectangle bounds = new Rectangle(-size.width/2 -2, -size.height/2 -2, size.width+4, size.height); return bounds; // Shape shape = shapes.get(v); // if(shape == null) { // return new Rectangle(-20,-20,40,40); // } // else return shape; } public Renderer.VertexLabel.Position getPosition() { return Renderer.VertexLabel.Position.CNTR; } public Renderer.VertexLabel.Positioner getPositioner() { return new Positioner() { public Renderer.VertexLabel.Position getPosition(float x, float y, Dimension d) { return Renderer.VertexLabel.Position.CNTR; }}; } public void setPosition(Renderer.VertexLabel.Position position) { // noop } public void setPositioner(Renderer.VertexLabel.Positioner positioner) { //noop } } VertexLabelRenderer.java000066400000000000000000000025001276402340000363500ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Apr 14, 2005 */ package edu.uci.ics.jung.visualization.renderers; import java.awt.Component; import java.awt.Font; import javax.swing.JComponent; /** * @author Tom Nelson * * */ public interface VertexLabelRenderer { /** * Returns the component used for drawing the label. This method is * used to configure the renderer appropriately before drawing. * * @param vv the component that is asking the renderer to draw * @param value the value of the cell to be rendered; the details of how to * render the value are up to the renderer implementation. For example, * if {@code value} is the string "true", it could be rendered as the * string or as a checked checkbox. * @param font the font to use in rendering the label * @param isSelected whether the vertex is currently selected * @param vertex the edge whose label is being drawn * @param the vertex type * @return the component used for drawing the label */ Component getVertexLabelRendererComponent(JComponent vv, Object value, Font font, boolean isSelected, V vertex); } package.html000066400000000000000000000004761276402340000340740ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers

        Visualization mechanisms relating to rendering. jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/spatial/000077500000000000000000000000001276402340000313275ustar00rootroot00000000000000AggregateGraph.java000066400000000000000000000123621276402340000347670ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/spatialpackage edu.uci.ics.jung.visualization.spatial; import java.util.Collection; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; public class AggregateGraph implements Graph { // @Override public boolean addEdge(E e, V v1, V v2) { // TODO Auto-generated method stub return false; } // @Override public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) { // TODO Auto-generated method stub return false; } // @Override public V getDest(E directedEdge) { // TODO Auto-generated method stub return null; } // @Override public Pair getEndpoints(E edge) { // TODO Auto-generated method stub return null; } // @Override public Collection getInEdges(V vertex) { // TODO Auto-generated method stub return null; } // @Override public V getOpposite(V vertex, E edge) { // TODO Auto-generated method stub return null; } // @Override public Collection getOutEdges(V vertex) { // TODO Auto-generated method stub return null; } // @Override public int getPredecessorCount(V vertex) { // TODO Auto-generated method stub return 0; } // @Override public Collection getPredecessors(V vertex) { // TODO Auto-generated method stub return null; } // @Override public V getSource(E directedEdge) { // TODO Auto-generated method stub return null; } // @Override public int getSuccessorCount(V vertex) { // TODO Auto-generated method stub return 0; } // @Override public Collection getSuccessors(V vertex) { // TODO Auto-generated method stub return null; } // @Override public int inDegree(V vertex) { // TODO Auto-generated method stub return 0; } // @Override public boolean isDest(V vertex, E edge) { // TODO Auto-generated method stub return false; } // @Override public boolean isPredecessor(V v1, V v2) { // TODO Auto-generated method stub return false; } // @Override public boolean isSource(V vertex, E edge) { // TODO Auto-generated method stub return false; } // @Override public boolean isSuccessor(V v1, V v2) { // TODO Auto-generated method stub return false; } // @Override public int outDegree(V vertex) { // TODO Auto-generated method stub return 0; } // @Override public boolean addEdge(E edge, Collection vertices) { // TODO Auto-generated method stub return false; } // @Override public boolean addEdge(E edge, Collection vertices, EdgeType edgeType) { // TODO Auto-generated method stub return false; } // @Override public boolean addVertex(V vertex) { // TODO Auto-generated method stub return false; } // @Override public boolean containsEdge(E edge) { // TODO Auto-generated method stub return false; } // @Override public boolean containsVertex(V vertex) { // TODO Auto-generated method stub return false; } // @Override public int degree(V vertex) { // TODO Auto-generated method stub return 0; } // @Override public E findEdge(V v1, V v2) { // TODO Auto-generated method stub return null; } // @Override public Collection findEdgeSet(V v1, V v2) { // TODO Auto-generated method stub return null; } // @Override public EdgeType getDefaultEdgeType() { // TODO Auto-generated method stub return null; } // @Override public int getEdgeCount() { // TODO Auto-generated method stub return 0; } // @Override public int getEdgeCount(EdgeType edgeType) { // TODO Auto-generated method stub return 0; } // @Override public EdgeType getEdgeType(E edge) { // TODO Auto-generated method stub return null; } // @Override public Collection getEdges() { // TODO Auto-generated method stub return null; } // @Override public Collection getEdges(EdgeType edgeType) { // TODO Auto-generated method stub return null; } // @Override public int getIncidentCount(E edge) { // TODO Auto-generated method stub return 0; } // @Override public Collection getIncidentEdges(V vertex) { // TODO Auto-generated method stub return null; } // @Override public Collection getIncidentVertices(E edge) { // TODO Auto-generated method stub return null; } // @Override public int getNeighborCount(V vertex) { // TODO Auto-generated method stub return 0; } // @Override public Collection getNeighbors(V vertex) { // TODO Auto-generated method stub return null; } // @Override public int getVertexCount() { // TODO Auto-generated method stub return 0; } // @Override public Collection getVertices() { // TODO Auto-generated method stub return null; } // @Override public boolean isIncident(V vertex, E edge) { // TODO Auto-generated method stub return false; } // @Override public boolean isNeighbor(V v1, V v2) { // TODO Auto-generated method stub return false; } // @Override public boolean removeEdge(E edge) { // TODO Auto-generated method stub return false; } // @Override public boolean removeVertex(V vertex) { // TODO Auto-generated method stub return false; } } FastRenderingGraph.java000066400000000000000000000233731276402340000356400ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/spatialpackage edu.uci.ics.jung.visualization.spatial; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Collection; import java.util.HashSet; import java.util.Set; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.EdgeIndexFunction; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; import edu.uci.ics.jung.visualization.BasicVisualizationServer; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.decorators.ParallelEdgeShapeTransformer; /** * maintains caches of vertices and edges that will be the subset of the * delegate graph's elements that are contained in some Rectangle. * * @author tanelso * * @param the vertex type * @param the edge type */ public class FastRenderingGraph implements Graph { protected Graph graph; protected Set vertices = new HashSet(); protected Set edges = new HashSet(); protected boolean dirty; protected Set bounds; protected RenderContext rc; protected BasicVisualizationServer vv; protected Layout layout; public FastRenderingGraph(Graph graph, Set bounds, BasicVisualizationServer vv) { this.graph = graph; this.bounds = bounds; this.vv = vv; this.rc = vv.getRenderContext(); } private void cleanUp() { vertices.clear(); edges.clear(); for(V v : graph.getVertices()) { checkVertex(v); } for(E e : graph.getEdges()) { checkEdge(e); } } private void checkVertex(V v) { // get the shape to be rendered Shape shape = rc.getVertexShapeTransformer().apply(v); Point2D p = layout.apply(v); p = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p); float x = (float)p.getX(); float y = (float)p.getY(); // create a transform that translates to the location of // the vertex to be rendered AffineTransform xform = AffineTransform.getTranslateInstance(x,y); // transform the vertex shape with xtransform shape = xform.createTransformedShape(shape); for(Rectangle2D r : bounds) { if(shape.intersects(r)) { vertices.add(v); } } } private void checkEdge(E e) { Pair endpoints = graph.getEndpoints(e); V v1 = endpoints.getFirst(); V v2 = endpoints.getSecond(); Point2D p1 = layout.apply(v1); Point2D p2 = layout.apply(v2); p1 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p1); p2 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p2); float x1 = (float) p1.getX(); float y1 = (float) p1.getY(); float x2 = (float) p2.getX(); float y2 = (float) p2.getY(); boolean isLoop = v1.equals(v2); Shape s2 = rc.getVertexShapeTransformer().apply(v2); Shape edgeShape = rc.getEdgeShapeTransformer().apply(e); AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1); if(isLoop) { // this is a self-loop. scale it is larger than the vertex // it decorates and translate it so that its nadir is // at the center of the vertex. Rectangle2D s2Bounds = s2.getBounds2D(); xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight()); xform.translate(0, -edgeShape.getBounds2D().getWidth()/2); } else if(rc.getEdgeShapeTransformer() instanceof EdgeShape.Orthogonal) { float dx = x2-x1; float dy = y2-y1; int index = 0; if(rc.getEdgeShapeTransformer() instanceof ParallelEdgeShapeTransformer) { @SuppressWarnings("unchecked") EdgeIndexFunction peif = ((ParallelEdgeShapeTransformer)rc.getEdgeShapeTransformer()) .getEdgeIndexFunction(); index = peif.getIndex(null, e); index *= 20; } GeneralPath gp = new GeneralPath(); gp.moveTo(0,0);// the xform will do the translation to x1,y1 if(x1 > x2) { if(y1 > y2) { gp.lineTo(0, index); gp.lineTo(dx-index, index); gp.lineTo(dx-index, dy); gp.lineTo(dx, dy); } else { gp.lineTo(0, -index); gp.lineTo(dx-index, -index); gp.lineTo(dx-index, dy); gp.lineTo(dx, dy); } } else { if(y1 > y2) { gp.lineTo(0, index); gp.lineTo(dx+index, index); gp.lineTo(dx+index, dy); gp.lineTo(dx, dy); } else { gp.lineTo(0, -index); gp.lineTo(dx+index, -index); gp.lineTo(dx+index, dy); gp.lineTo(dx, dy); } } edgeShape = gp; } else { // this is a normal edge. Rotate it to the angle between // vertex endpoints, then scale it to the distance between // the vertices float dx = x2-x1; float dy = y2-y1; float thetaRadians = (float) Math.atan2(dy, dx); xform.rotate(thetaRadians); float dist = (float) Math.sqrt(dx*dx + dy*dy); xform.scale(dist, 1.0); } edgeShape = xform.createTransformedShape(edgeShape); for(Rectangle2D r : bounds) { if(edgeShape.intersects(r)) { edges.add(e); } } } public Set getBounds() { return bounds; } public void setBounds(Set bounds) { this.bounds = bounds; } public boolean addEdge(E edge, Collection vertices, EdgeType edgeType) { return graph.addEdge(edge, vertices, edgeType); } public boolean addEdge(E edge, Collection vertices) { return graph.addEdge(edge, vertices); } public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) { return graph.addEdge(e, v1, v2, edgeType); } public boolean addEdge(E e, V v1, V v2) { return graph.addEdge(e, v1, v2); } public boolean addVertex(V vertex) { return graph.addVertex(vertex); } public boolean containsEdge(E edge) { return graph.containsEdge(edge); } public boolean containsVertex(V vertex) { return graph.containsVertex(vertex); } public int degree(V vertex) { return graph.degree(vertex); } public E findEdge(V v1, V v2) { return graph.findEdge(v1, v2); } public Collection findEdgeSet(V v1, V v2) { return graph.findEdgeSet(v1, v2); } public EdgeType getDefaultEdgeType() { return graph.getDefaultEdgeType(); } public V getDest(E directedEdge) { return graph.getDest(directedEdge); } public int getEdgeCount() { return graph.getEdgeCount(); } public int getEdgeCount(EdgeType edgeType) { return graph.getEdgeCount(edgeType); } public Collection getEdges() { if(dirty) { cleanUp(); } return edges; } public Collection getEdges(EdgeType edgeType) { return graph.getEdges(edgeType); } public EdgeType getEdgeType(E edge) { return graph.getEdgeType(edge); } public Pair getEndpoints(E edge) { return graph.getEndpoints(edge); } public int getIncidentCount(E edge) { return graph.getIncidentCount(edge); } public Collection getIncidentEdges(V vertex) { return graph.getIncidentEdges(vertex); } public Collection getIncidentVertices(E edge) { return graph.getIncidentVertices(edge); } public Collection getInEdges(V vertex) { return graph.getInEdges(vertex); } public int getNeighborCount(V vertex) { return graph.getNeighborCount(vertex); } public Collection getNeighbors(V vertex) { return graph.getNeighbors(vertex); } public V getOpposite(V vertex, E edge) { return graph.getOpposite(vertex, edge); } public Collection getOutEdges(V vertex) { return graph.getOutEdges(vertex); } public int getPredecessorCount(V vertex) { return graph.getPredecessorCount(vertex); } public Collection getPredecessors(V vertex) { return graph.getPredecessors(vertex); } public V getSource(E directedEdge) { return graph.getSource(directedEdge); } public int getSuccessorCount(V vertex) { return graph.getSuccessorCount(vertex); } public Collection getSuccessors(V vertex) { return graph.getSuccessors(vertex); } public int getVertexCount() { return graph.getVertexCount(); } public Collection getVertices() { if(dirty) cleanUp(); return vertices; } public int inDegree(V vertex) { return graph.inDegree(vertex); } public boolean isDest(V vertex, E edge) { return graph.isDest(vertex, edge); } public boolean isIncident(V vertex, E edge) { return graph.isIncident(vertex, edge); } public boolean isNeighbor(V v1, V v2) { return graph.isNeighbor(v1, v2); } public boolean isPredecessor(V v1, V v2) { return graph.isPredecessor(v1, v2); } public boolean isSource(V vertex, E edge) { return graph.isSource(vertex, edge); } public boolean isSuccessor(V v1, V v2) { return graph.isSuccessor(v1, v2); } public int outDegree(V vertex) { return graph.outDegree(vertex); } public boolean removeEdge(E edge) { return graph.removeEdge(edge); } public boolean removeVertex(V vertex) { return graph.removeVertex(vertex); } } FastRenderingLayout.java000066400000000000000000000030011276402340000360360ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/spatialpackage edu.uci.ics.jung.visualization.spatial; import java.awt.Dimension; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import com.google.common.base.Function; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; /** * break into several rectangular areas, each of which will have a reference Graph * @author tanelso * * @param the vertex type * @param the edge type */ public class FastRenderingLayout implements Layout { protected Layout layout; protected Graph graph; protected Rectangle2D[][] grid; public FastRenderingLayout(Layout layout) { this.layout = layout; } public Graph getGraph() { return layout.getGraph(); } public Dimension getSize() { return layout.getSize(); } public void initialize() { layout.initialize(); } public boolean isLocked(V v) { return layout.isLocked(v); } public void lock(V v, boolean state) { layout.lock(v, state); } public void reset() { layout.reset(); } public void setGraph(Graph graph) { // layout.setGraph(new FastRenderingGraph(graph)); } public void setInitializer(Function initializer) { layout.setInitializer(initializer); } public void setLocation(V v, Point2D location) { layout.setLocation(v, location); } public void setSize(Dimension d) { layout.setSize(d); } public Point2D apply(V arg0) { return layout.apply(arg0); } } jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/subLayout/000077500000000000000000000000001276402340000316615ustar00rootroot00000000000000GraphCollapser.java000066400000000000000000000156201276402340000353570ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/subLayout/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 23, 2005 */ package edu.uci.ics.jung.visualization.subLayout; import com.google.common.base.Preconditions; import java.util.Collection; import java.util.logging.Logger; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Pair; @SuppressWarnings({"rawtypes", "unchecked"}) public class GraphCollapser { private static final Logger logger = Logger.getLogger(GraphCollapser.class.getClass().getName()); private Graph originalGraph; public GraphCollapser(Graph originalGraph) { this.originalGraph = originalGraph; } Graph createGraph() throws InstantiationException, IllegalAccessException { return (Graph)originalGraph.getClass().newInstance(); } public Graph collapse(Graph inGraph, Graph clusterGraph) { if(clusterGraph.getVertexCount() < 2) return inGraph; Graph graph = inGraph; try { graph = createGraph(); } catch(Exception ex) { ex.printStackTrace(); } Collection cluster = clusterGraph.getVertices(); // add all vertices in the delegate, unless the vertex is in the // cluster. for(Object v : inGraph.getVertices()) { if(cluster.contains(v) == false) { graph.addVertex(v); } } // add the clusterGraph as a vertex graph.addVertex(clusterGraph); //add all edges from the inGraph, unless both endpoints of // the edge are in the cluster for(Object e : (Collection)inGraph.getEdges()) { Pair endpoints = inGraph.getEndpoints(e); // don't add edges whose endpoints are both in the cluster if(cluster.containsAll(endpoints) == false) { if(cluster.contains(endpoints.getFirst())) { graph.addEdge(e, clusterGraph, endpoints.getSecond(), inGraph.getEdgeType(e)); } else if(cluster.contains(endpoints.getSecond())) { graph.addEdge(e, endpoints.getFirst(), clusterGraph, inGraph.getEdgeType(e)); } else { graph.addEdge(e,endpoints.getFirst(), endpoints.getSecond(), inGraph.getEdgeType(e)); } } } return graph; } public Graph expand(Graph inGraph, Graph clusterGraph) { Graph graph = inGraph; try { graph = createGraph(); } catch(Exception ex) { ex.printStackTrace(); } Collection cluster = clusterGraph.getVertices(); logger.fine("cluster to expand is "+cluster); // put all clusterGraph vertices and edges into the new Graph for(Object v : cluster) { graph.addVertex(v); for(Object edge : clusterGraph.getIncidentEdges(v)) { Pair endpoints = clusterGraph.getEndpoints(edge); graph.addEdge(edge, endpoints.getFirst(), endpoints.getSecond(), clusterGraph.getEdgeType(edge)); } } // add all the vertices from the current graph except for // the cluster we are expanding for(Object v : inGraph.getVertices()) { if(v.equals(clusterGraph) == false) { graph.addVertex(v); } } // now that all vertices have been added, add the edges, // ensuring that no edge contains a vertex that has not // already been added for(Object v : inGraph.getVertices()) { if(v.equals(clusterGraph) == false) { for(Object edge : inGraph.getIncidentEdges(v)) { Pair endpoints = inGraph.getEndpoints(edge); Object v1 = endpoints.getFirst(); Object v2 = endpoints.getSecond(); if(cluster.containsAll(endpoints) == false) { if(clusterGraph.equals(v1)) { // i need a new v1 Object originalV1 = originalGraph.getEndpoints(edge).getFirst(); Object newV1 = findVertex(graph, originalV1); Preconditions.checkState(newV1 != null, "newV1 for "+originalV1+" was not found!"); graph.addEdge(edge, newV1, v2, inGraph.getEdgeType(edge)); } else if(clusterGraph.equals(v2)) { // i need a new v2 Object originalV2 = originalGraph.getEndpoints(edge).getSecond(); Object newV2 = findVertex(graph, originalV2); Preconditions.checkState(newV2 != null, "newV2 for "+originalV2+" was not found!"); graph.addEdge(edge, v1, newV2, inGraph.getEdgeType(edge)); } else { graph.addEdge(edge, v1, v2, inGraph.getEdgeType(edge)); } } } } } return graph; } Object findVertex(Graph inGraph, Object vertex) { Collection vertices = inGraph.getVertices(); if(vertices.contains(vertex)) { return vertex; } for(Object v : vertices) { if(v instanceof Graph) { Graph g = (Graph)v; if(contains(g, vertex)) { return v; } } } return null; } private boolean contains(Graph inGraph, Object vertex) { boolean contained = false; if(inGraph.getVertices().contains(vertex)) return true; for(Object v : inGraph.getVertices()) { if(v instanceof Graph) { contained |= contains((Graph)v, vertex); } } return contained; } public Graph getClusterGraph(Graph inGraph, Collection picked) { Graph clusterGraph; try { clusterGraph = createGraph(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } for(Object v : picked) { clusterGraph.addVertex(v); Collection edges = inGraph.getIncidentEdges(v); for(Object edge : edges) { Pair endpoints = inGraph.getEndpoints(edge); Object v1 = endpoints.getFirst(); Object v2 = endpoints.getSecond(); if(picked.containsAll(endpoints)) { clusterGraph.addEdge(edge, v1, v2, inGraph.getEdgeType(edge)); } } } return clusterGraph; } } TreeCollapser.java000066400000000000000000000032241276402340000352120ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/subLayout/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 23, 2005 */ package edu.uci.ics.jung.visualization.subLayout; import java.awt.geom.Point2D; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Forest; import edu.uci.ics.jung.graph.util.TreeUtils; public class TreeCollapser { @SuppressWarnings({ "unchecked", "rawtypes" }) public void collapse(Layout layout, Forest tree, Object subRoot) throws InstantiationException, IllegalAccessException { // get a sub tree from subRoot Forest subTree = TreeUtils.getSubTree(tree, subRoot); Object parent = null; Object edge = null; if(tree.getPredecessorCount(subRoot) > 0) { parent = tree.getPredecessors(subRoot).iterator().next(); edge = tree.getInEdges(subRoot).iterator().next(); } tree.removeVertex(subRoot); if(parent != null) { tree.addEdge(edge, parent, subTree); } else { tree.addVertex(subTree); } layout.setLocation(subTree, (Point2D)layout.apply(subRoot)); } @SuppressWarnings({ "unchecked", "rawtypes" }) public void expand(Forest tree, Forest subTree) { Object parent = null; Object edge = null; if(tree.getPredecessorCount(subTree) > 0) { parent = tree.getPredecessors(subTree).iterator().next(); edge = tree.getInEdges(subTree).iterator().next(); } tree.removeVertex(subTree); TreeUtils.addSubTree(tree, subTree, parent, edge); } } package.html000066400000000000000000000005361276402340000340670ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/subLayout

        Visualization mechanisms relating to grouping or hiding specified element sets. jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/000077500000000000000000000000001276402340000317055ustar00rootroot00000000000000AbstractLensSupport.java000066400000000000000000000113201276402340000364500ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 21, 2005 */ package edu.uci.ics.jung.visualization.transform; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.geom.RectangularShape; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.VisualizationServer.Paintable; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; /** * A class to make it easy to add an * examining lens to a jung graph application. See HyperbolicTransformerDemo, * ViewLensSupport and LayoutLensSupport * for examples of how to use it. * * @author Tom Nelson */ public abstract class AbstractLensSupport implements LensSupport { protected VisualizationViewer vv; protected VisualizationViewer.GraphMouse graphMouse; protected LensTransformer lensTransformer; protected ModalGraphMouse lensGraphMouse; protected Lens lens; protected LensControls lensControls; protected String defaultToolTipText; protected static final String instructions = "

        Mouse-Drag the Lens center to move it

        "+ "Mouse-Drag the Lens edge to resize it

        "+ "Ctrl+MouseWheel to change magnification

        "; /** * create the base class, setting common members and creating * a custom GraphMouse * @param vv the VisualizationViewer to work on * @param lensGraphMouse the GraphMouse instance to use for the lens */ public AbstractLensSupport(VisualizationViewer vv, ModalGraphMouse lensGraphMouse) { this.vv = vv; this.graphMouse = vv.getGraphMouse(); this.defaultToolTipText = vv.getToolTipText(); this.lensGraphMouse = lensGraphMouse; } public void activate(boolean state) { if(state) activate(); else deactivate(); } public LensTransformer getLensTransformer() { return lensTransformer; } /** * @return the hyperbolicGraphMouse. */ public ModalGraphMouse getGraphMouse() { return lensGraphMouse; } /** * the background for the hyperbolic projection * @author Tom Nelson */ public static class Lens implements Paintable { LensTransformer lensTransformer; RectangularShape lensShape; Paint paint = Color.decode("0xdddddd"); public Lens(LensTransformer lensTransformer) { this.lensTransformer = lensTransformer; this.lensShape = lensTransformer.getLensShape(); } /** * @return the paint */ public Paint getPaint() { return paint; } /** * @param paint the paint to set */ public void setPaint(Paint paint) { this.paint = paint; } public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; g2d.setPaint(paint); g2d.fill(lensShape); } public boolean useTransform() { return true; } } /** * the background for the hyperbolic projection * @author Tom Nelson * * */ public static class LensControls implements Paintable { LensTransformer lensTransformer; RectangularShape lensShape; Paint paint = Color.gray; public LensControls(LensTransformer lensTransformer) { this.lensTransformer = lensTransformer; this.lensShape = lensTransformer.getLensShape(); } /** * @return the paint */ public Paint getPaint() { return paint; } /** * @param paint the paint to set */ public void setPaint(Paint paint) { this.paint = paint; } public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; g2d.setPaint(paint); g2d.draw(lensShape); int centerX = (int)Math.round(lensShape.getCenterX()); int centerY = (int)Math.round(lensShape.getCenterY()); g.drawOval(centerX-10, centerY-10, 20, 20); } public boolean useTransform() { return true; } } /** * @return the lens */ public Lens getLens() { return lens; } /** * @param lens the lens to set */ public void setLens(Lens lens) { this.lens = lens; } /** * @return the lensControls */ public LensControls getLensControls() { return lensControls; } /** * @param lensControls the lensControls to set */ public void setLensControls(LensControls lensControls) { this.lensControls = lensControls; } } AffineTransformer.java000066400000000000000000000205061276402340000361070ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Apr 16, 2005 */ package edu.uci.ics.jung.visualization.transform; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer; /** * * Provides methods to map points from one coordinate system to * another, by delegating to a wrapped AffineTransform (uniform) * and its inverse. * * @author Tom Nelson */ public class AffineTransformer implements BidirectionalTransformer, ShapeTransformer { protected AffineTransform inverse; /** * The AffineTransform to use; initialized to identity. * */ protected AffineTransform transform = new AffineTransform(); /** * Create an instance that does not transform points. */ public AffineTransformer() { // nothing left to do } /** * Create an instance with the supplied transform. * @param transform the transform to use */ public AffineTransformer(AffineTransform transform) { if(transform != null) this.transform = transform; } /** * @return Returns the transform. */ public AffineTransform getTransform() { return transform; } /** * @param transform The transform to set. */ public void setTransform(AffineTransform transform) { this.transform = transform; } /** * applies the inverse transform to the supplied point * @param p the point to transform * @return the transformed point */ public Point2D inverseTransform(Point2D p) { return getInverse().transform(p, null); } public AffineTransform getInverse() { if(inverse == null) { try { inverse = transform.createInverse(); } catch (NoninvertibleTransformException e) { e.printStackTrace(); } } return inverse; } /** * @return the transform's x scale value */ public double getScaleX() { return transform.getScaleX(); } /** * @return the transform's y scale value */ public double getScaleY() { return transform.getScaleY(); } /** * @return the transform's overall scale magnitude */ public double getScale() { return Math.sqrt(transform.getDeterminant()); } /** * @return the transform's x shear value */ public double getShearX() { return transform.getShearX(); } /** * @return the transform's y shear value */ public double getShearY() { return transform.getShearY(); } /** * @return the transform's x translate value */ public double getTranslateX() { return transform.getTranslateX(); } /** * @return the transform's y translate value */ public double getTranslateY() { return transform.getTranslateY(); } /** * Applies the transform to the supplied point. * * @param p the point to be transformed * @return the transformed point */ public Point2D transform(Point2D p) { if(p == null) return null; return transform.transform(p, null); } /** * Transform the supplied shape from graph (layout) to screen (view) coordinates. * * @return the GeneralPath of the transformed shape */ public Shape transform(Shape shape) { GeneralPath newPath = new GeneralPath(); float[] coords = new float[6]; for(PathIterator iterator=shape.getPathIterator(null); iterator.isDone() == false; iterator.next()) { int type = iterator.currentSegment(coords); switch(type) { case PathIterator.SEG_MOVETO: Point2D p = transform(new Point2D.Float(coords[0], coords[1])); newPath.moveTo((float)p.getX(), (float)p.getY()); break; case PathIterator.SEG_LINETO: p = transform(new Point2D.Float(coords[0], coords[1])); newPath.lineTo((float)p.getX(), (float) p.getY()); break; case PathIterator.SEG_QUADTO: p = transform(new Point2D.Float(coords[0], coords[1])); Point2D q = transform(new Point2D.Float(coords[2], coords[3])); newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY()); break; case PathIterator.SEG_CUBICTO: p = transform(new Point2D.Float(coords[0], coords[1])); q = transform(new Point2D.Float(coords[2], coords[3])); Point2D r = transform(new Point2D.Float(coords[4], coords[5])); newPath.curveTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY(), (float)r.getX(), (float)r.getY()); break; case PathIterator.SEG_CLOSE: newPath.closePath(); break; } } return newPath; } /** * Transform the supplied shape from screen (view) to graph (layout) coordinates. * * @return the GeneralPath of the transformed shape */ public Shape inverseTransform(Shape shape) { GeneralPath newPath = new GeneralPath(); float[] coords = new float[6]; for(PathIterator iterator=shape.getPathIterator(null); iterator.isDone() == false; iterator.next()) { int type = iterator.currentSegment(coords); switch(type) { case PathIterator.SEG_MOVETO: Point2D p = inverseTransform(new Point2D.Float(coords[0], coords[1])); newPath.moveTo((float)p.getX(), (float)p.getY()); break; case PathIterator.SEG_LINETO: p = inverseTransform(new Point2D.Float(coords[0], coords[1])); newPath.lineTo((float)p.getX(), (float) p.getY()); break; case PathIterator.SEG_QUADTO: p = inverseTransform(new Point2D.Float(coords[0], coords[1])); Point2D q = inverseTransform(new Point2D.Float(coords[2], coords[3])); newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY()); break; case PathIterator.SEG_CUBICTO: p = inverseTransform(new Point2D.Float(coords[0], coords[1])); q = inverseTransform(new Point2D.Float(coords[2], coords[3])); Point2D r = inverseTransform(new Point2D.Float(coords[4], coords[5])); newPath.curveTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY(), (float)r.getX(), (float)r.getY()); break; case PathIterator.SEG_CLOSE: newPath.closePath(); break; } } return newPath; } public double getRotation() { double[] unitVector = new double[]{0,0,1,0}; double[] result = new double[4]; transform.transform(unitVector, 0, result, 0, 2); double dy = Math.abs(result[3] - result[1]); double length = Point2D.distance(result[0], result[1], result[2], result[3]); double rotation = Math.asin(dy / length); if (result[3] - result[1] > 0) { if (result[2] - result[0] < 0) { rotation = Math.PI - rotation; } } else { if (result[2] - result[0] > 0) { rotation = 2 * Math.PI - rotation; } else { rotation = rotation + Math.PI; } } return rotation; } @Override public String toString() { return "Transformer using "+transform; } } BidirectionalTransformer.java000066400000000000000000000016441276402340000374710ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Apr 16, 2005 */ package edu.uci.ics.jung.visualization.transform; import java.awt.geom.Point2D; /** * Provides methods to map points from one coordinate system to * another: graph to screen and screen to graph. * * @author Tom Nelson */ public interface BidirectionalTransformer { /** * convert the supplied graph coordinate to the screen coordinate * @param p graph point to convert * @return screen point */ Point2D transform(Point2D p); /** * convert the supplied screen coordinate to the graph coordinate. * @param p screen point to convert * @return the graph point */ Point2D inverseTransform(Point2D p); } HyperbolicTransformer.java000066400000000000000000000107521276402340000370210ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.visualization.transform; import java.awt.Component; import java.awt.geom.Point2D; import edu.uci.ics.jung.algorithms.layout.PolarPoint; /** * HyperbolicTransformer wraps a MutableAffineTransformer and modifies * the transform and inverseTransform methods so that they create a * fisheye projection of the graph points, with points near the * center spread out and points near the edges collapsed onto the * circumference of an ellipse. * * HyperbolicTransformer is not an affine transform, but it uses an * affine transform to cause translation, scaling, rotation, and shearing * while applying a non-affine hyperbolic filter in its transform and * inverseTransform methods. * * @author Tom Nelson */ public class HyperbolicTransformer extends LensTransformer implements MutableTransformer { /** * create an instance, setting values from the passed component * and registering to listen for size changes on the component * @param component the component used for rendering */ public HyperbolicTransformer(Component component) { this(component, new MutableAffineTransformer()); } /** * Create an instance with a possibly shared transform. * * @param component the component used for rendering * @param delegate the transformer to use */ public HyperbolicTransformer(Component component, MutableTransformer delegate) { super(component, delegate); } /** * override base class transform to project the fisheye effect */ public Point2D transform(Point2D graphPoint) { if(graphPoint == null) return null; Point2D viewCenter = getViewCenter(); double viewRadius = getViewRadius(); double ratio = getRatio(); // transform the point from the graph to the view Point2D viewPoint = delegate.transform(graphPoint); // calculate point from center double dx = viewPoint.getX() - viewCenter.getX(); double dy = viewPoint.getY() - viewCenter.getY(); // factor out ellipse dx *= ratio; Point2D pointFromCenter = new Point2D.Double(dx, dy); PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter); double theta = polar.getTheta(); double radius = polar.getRadius(); if(radius > viewRadius) return viewPoint; double mag = Math.tan(Math.PI/2*magnification); radius *= mag; radius = Math.min(radius, viewRadius); radius /= viewRadius; radius *= Math.PI/2; radius = Math.abs(Math.atan(radius)); radius *= viewRadius; Point2D projectedPoint = PolarPoint.polarToCartesian(theta, radius); projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY()); Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(), projectedPoint.getY()+viewCenter.getY()); return translatedBack; } /** * override base class to un-project the fisheye effect */ public Point2D inverseTransform(Point2D viewPoint) { Point2D viewCenter = getViewCenter(); double viewRadius = getViewRadius(); double ratio = getRatio(); double dx = viewPoint.getX() - viewCenter.getX(); double dy = viewPoint.getY() - viewCenter.getY(); // factor out ellipse dx *= ratio; Point2D pointFromCenter = new Point2D.Double(dx, dy); PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter); double radius = polar.getRadius(); if(radius > viewRadius) return delegate.inverseTransform(viewPoint); radius /= viewRadius; radius = Math.abs(Math.tan(radius)); radius /= Math.PI/2; radius *= viewRadius; double mag = Math.tan(Math.PI/2*magnification); radius /= mag; polar.setRadius(radius); Point2D projectedPoint = PolarPoint.polarToCartesian(polar); projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY()); Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(), projectedPoint.getY()+viewCenter.getY()); return delegate.inverseTransform(translatedBack); } }LayoutLensSupport.java000066400000000000000000000060471276402340000361740ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 21, 2005 */ package edu.uci.ics.jung.visualization.transform; import java.awt.Dimension; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalLensGraphMouse; import edu.uci.ics.jung.visualization.picking.LayoutLensShapePickSupport; /** * A class to make it easy to add an * examining lens to a jung graph application. See HyperbolicTransformerDemo * for an example of how to use it. * * @author Tom Nelson * * */ public class LayoutLensSupport extends AbstractLensSupport implements LensSupport { protected GraphElementAccessor pickSupport; public LayoutLensSupport(VisualizationViewer vv) { this(vv, new HyperbolicTransformer( vv, vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT)), new ModalLensGraphMouse()); } /** * Create an instance with the specified parameters. * * @param vv the visualization viewer used for rendering * @param lensTransformer the lens transformer to use * @param lensGraphMouse the lens input handler */ public LayoutLensSupport(VisualizationViewer vv, LensTransformer lensTransformer, ModalGraphMouse lensGraphMouse) { super(vv, lensGraphMouse); this.lensTransformer = lensTransformer; this.pickSupport = vv.getPickSupport(); Dimension d = vv.getSize(); if(d.width <= 0 || d.height <= 0) { d = vv.getPreferredSize(); } lensTransformer.setViewRadius(d.width/5); } public void activate() { if(lens == null) { lens = new Lens(lensTransformer); } if(lensControls == null) { lensControls = new LensControls(lensTransformer); } vv.getRenderContext().setPickSupport(new LayoutLensShapePickSupport(vv)); vv.getRenderContext().getMultiLayerTransformer().setTransformer(Layer.LAYOUT, lensTransformer); vv.prependPreRenderPaintable(lens); vv.addPostRenderPaintable(lensControls); vv.setGraphMouse(lensGraphMouse); vv.setToolTipText(instructions); vv.repaint(); } public void deactivate() { if(lensTransformer != null) { vv.removePreRenderPaintable(lens); vv.removePostRenderPaintable(lensControls); vv.getRenderContext().getMultiLayerTransformer().setTransformer(Layer.LAYOUT, lensTransformer.getDelegate()); } vv.getRenderContext().setPickSupport(pickSupport); vv.setToolTipText(defaultToolTipText); vv.setGraphMouse(graphMouse); vv.repaint(); } } LensSupport.java000066400000000000000000000012251276402340000347670ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 5, 2005 */ package edu.uci.ics.jung.visualization.transform; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; /** * basic API for implementing lens projection support * * @author Tom Nelson * */ public interface LensSupport { void activate(); void deactivate(); void activate(boolean state); LensTransformer getLensTransformer(); ModalGraphMouse getGraphMouse(); }LensTransformer.java000066400000000000000000000127771276402340000356330ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.visualization.transform; import java.awt.Component; import java.awt.Dimension; import java.awt.Shape; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RectangularShape; /** * LensTransformer wraps a MutableAffineTransformer and modifies * the transform and inverseTransform methods so that they create a * projection of the graph points within an elliptical lens. * * LensTransformer uses an * affine transform to cause translation, scaling, rotation, and shearing * while applying a possibly non-affine filter in its transform and * inverseTransform methods. * * @author Tom Nelson */ public abstract class LensTransformer extends MutableTransformerDecorator implements MutableTransformer { /** * the area affected by the transform */ protected RectangularShape lensShape = new Ellipse2D.Float(); protected float magnification = 0.7f; /** * Create an instance with a possibly shared transform. * * @param component the component used for rendering * @param delegate the transformer to use */ public LensTransformer(Component component, MutableTransformer delegate) { super(delegate); setComponent(component); component.addComponentListener(new ComponentListenerImpl()); } /** * Set values from the passed component. * @param component the component used for rendering */ private void setComponent(Component component) { Dimension d = component.getSize(); if(d.width <= 0 || d.height <= 0) { d = component.getPreferredSize(); } float ewidth = d.width/1.5f; float eheight = d.height/1.5f; lensShape.setFrame(d.width/2-ewidth/2, d.height/2-eheight/2, ewidth, eheight); } public float getMagnification() { return magnification; } public void setMagnification(float magnification) { this.magnification = magnification; } public Point2D getViewCenter() { return new Point2D.Double(lensShape.getCenterX(), lensShape.getCenterY()); } public void setViewCenter(Point2D viewCenter) { double width = lensShape.getWidth(); double height = lensShape.getHeight(); lensShape.setFrame(viewCenter.getX()-width/2, viewCenter.getY()-height/2, width, height); } public double getViewRadius() { return lensShape.getHeight()/2; } public void setViewRadius(double viewRadius) { double x = lensShape.getCenterX(); double y = lensShape.getCenterY(); double viewRatio = getRatio(); lensShape.setFrame(x-viewRadius/viewRatio, y-viewRadius, 2*viewRadius/viewRatio, 2*viewRadius); } /** * @return the ratio between the lens height and lens width */ public double getRatio() { return lensShape.getHeight()/lensShape.getWidth(); } public void setLensShape(RectangularShape ellipse) { this.lensShape = ellipse; } public RectangularShape getLensShape() { return lensShape; } public void setToIdentity() { this.delegate.setToIdentity(); } /** * react to size changes on a component */ protected class ComponentListenerImpl extends ComponentAdapter { public void componentResized(ComponentEvent e) { setComponent(e.getComponent()); } } /** * override base class transform to project the fisheye effect */ public abstract Point2D transform(Point2D graphPoint); /** * override base class to un-project the fisheye effect */ public abstract Point2D inverseTransform(Point2D viewPoint); public double getDistanceFromCenter(Point2D p) { double dx = lensShape.getCenterX()-p.getX(); double dy = lensShape.getCenterY()-p.getY(); dx *= getRatio(); return Math.sqrt(dx*dx + dy*dy); } /** * return the supplied shape, translated to the coordinates * that result from calling transform on its center */ public Shape transform(Shape shape) { Rectangle2D bounds = shape.getBounds2D(); Point2D center = new Point2D.Double(bounds.getCenterX(),bounds.getCenterY()); Point2D newCenter = transform(center); double dx = newCenter.getX()-center.getX(); double dy = newCenter.getY()-center.getY(); AffineTransform at = AffineTransform.getTranslateInstance(dx,dy); return at.createTransformedShape(shape); } /** * Returns the supplied shape, translated to the coordinates * that result from calling inverseTransform on its center. */ public Shape inverseTransform(Shape shape) { Rectangle2D bounds = shape.getBounds2D(); Point2D center = new Point2D.Double(bounds.getCenterX(),bounds.getCenterY()); Point2D newCenter = inverseTransform(center); double dx = newCenter.getX()-center.getX(); double dy = newCenter.getY()-center.getY(); AffineTransform at = AffineTransform.getTranslateInstance(dx,dy); return at.createTransformedShape(shape); } }MagnifyTransformer.java000066400000000000000000000126271276402340000363160ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.visualization.transform; import java.awt.Component; import java.awt.geom.Point2D; import edu.uci.ics.jung.algorithms.layout.PolarPoint; /** * MagnifyTransformer wraps a MutableAffineTransformer and modifies * the transform and inverseTransform methods so that they create an * enlarging projection of the graph points. * * MagnifyTransformer uses an * affine transform to cause translation, scaling, rotation, and shearing * while applying a separate magnification filter in its transform and * inverseTransform methods. * * @author Tom Nelson */ public class MagnifyTransformer extends LensTransformer implements MutableTransformer { /** * Create an instance, setting values from the passed component * and registering to listen for size changes on the component. * * @param component the component used for rendering */ public MagnifyTransformer(Component component) { this(component, new MutableAffineTransformer()); } /** * Create an instance with a possibly shared transform. * * @param component the component used for rendering * @param delegate the transformer to use */ public MagnifyTransformer(Component component, MutableTransformer delegate) { super(component, delegate); this.magnification = 3.f; } /** * override base class transform to project the fisheye effect */ public Point2D transform(Point2D graphPoint) { if(graphPoint == null) return null; Point2D viewCenter = getViewCenter(); double viewRadius = getViewRadius(); double ratio = getRatio(); // transform the point from the graph to the view Point2D viewPoint = delegate.transform(graphPoint); // calculate point from center double dx = viewPoint.getX() - viewCenter.getX(); double dy = viewPoint.getY() - viewCenter.getY(); // factor out ellipse dx *= ratio; Point2D pointFromCenter = new Point2D.Double(dx, dy); PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter); double theta = polar.getTheta(); double radius = polar.getRadius(); if(radius > viewRadius) return viewPoint; double mag = magnification; radius *= mag; radius = Math.min(radius, viewRadius); Point2D projectedPoint = PolarPoint.polarToCartesian(theta, radius); projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY()); Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(), projectedPoint.getY()+viewCenter.getY()); return translatedBack; } /** * override base class to un-project the fisheye effect */ public Point2D inverseTransform(Point2D viewPoint) { Point2D viewCenter = getViewCenter(); double viewRadius = getViewRadius(); double ratio = getRatio(); double dx = viewPoint.getX() - viewCenter.getX(); double dy = viewPoint.getY() - viewCenter.getY(); // factor out ellipse dx *= ratio; Point2D pointFromCenter = new Point2D.Double(dx, dy); PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter); double radius = polar.getRadius(); if(radius > viewRadius) return delegate.inverseTransform(viewPoint); double mag = magnification; radius /= mag; polar.setRadius(radius); Point2D projectedPoint = PolarPoint.polarToCartesian(polar); projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY()); Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(), projectedPoint.getY()+viewCenter.getY()); return delegate.inverseTransform(translatedBack); } /** * Magnifies the point, without considering the Lens. * @param graphPoint the point to transform via magnification * @return the transformed point */ public Point2D magnify(Point2D graphPoint) { if(graphPoint == null) return null; Point2D viewCenter = getViewCenter(); double ratio = getRatio(); // transform the point from the graph to the view Point2D viewPoint = graphPoint; // calculate point from center double dx = viewPoint.getX() - viewCenter.getX(); double dy = viewPoint.getY() - viewCenter.getY(); // factor out ellipse dx *= ratio; Point2D pointFromCenter = new Point2D.Double(dx, dy); PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter); double theta = polar.getTheta(); double radius = polar.getRadius(); double mag = magnification; radius *= mag; // radius = Math.min(radius, viewRadius); Point2D projectedPoint = PolarPoint.polarToCartesian(theta, radius); projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY()); Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(), projectedPoint.getY()+viewCenter.getY()); return translatedBack; } }MutableAffineTransformer.java000066400000000000000000000150551276402340000374240ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Apr 16, 2005 */ package edu.uci.ics.jung.visualization.transform; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer; import edu.uci.ics.jung.visualization.util.ChangeEventSupport; import edu.uci.ics.jung.visualization.util.DefaultChangeEventSupport; /** * * Provides methods to mutate the AffineTransform used by AffineTransformer * base class to map points from one coordinate system to * another. * * * @author Tom Nelson * * */ public class MutableAffineTransformer extends AffineTransformer implements MutableTransformer, ShapeTransformer, ChangeEventSupport { protected ChangeEventSupport changeSupport = new DefaultChangeEventSupport(this); /** * create an instance that does not transform points */ public MutableAffineTransformer() { // nothing left to do } /** * Create an instance with the supplied transform * @param transform the transform to use */ public MutableAffineTransformer(AffineTransform transform) { super(transform); } public String toString() { return "MutableAffineTransformer using "+transform; } /** * setter for the scale * fires a PropertyChangeEvent with the AffineTransforms representing * the previous and new values for scale and offset * @param scalex the amount to scale in the x direction * @param scaley the amount to scale in the y direction * @param from the point to transform */ public void scale(double scalex, double scaley, Point2D from) { AffineTransform xf = AffineTransform.getTranslateInstance(from.getX(),from.getY()); xf.scale(scalex, scaley); xf.translate(-from.getX(), -from.getY()); inverse = null; transform.preConcatenate(xf); fireStateChanged(); } /** * setter for the scale * fires a PropertyChangeEvent with the AffineTransforms representing * the previous and new values for scale and offset * @param scalex the amount to scale in the x direction * @param scaley the amount to scale in the y direction * @param from the point to transform */ public void setScale(double scalex, double scaley, Point2D from) { transform.setToIdentity(); scale(scalex, scaley, from); } /** * shears the transform by passed parameters * @param shx x value to shear * @param shy y value to shear * @param from the point to transform */ public void shear(double shx, double shy, Point2D from) { inverse = null; AffineTransform at = AffineTransform.getTranslateInstance(from.getX(), from.getY()); at.shear(shx, shy); at.translate(-from.getX(), -from.getY()); transform.preConcatenate(at); fireStateChanged(); } /** * Replace the Transform's translate x and y values * with the passed values, leaving the scale values * unchanged. * @param tx the x value of the translation * @param ty the y value of the translation */ public void setTranslate(double tx, double ty) { float scalex = (float) transform.getScaleX(); float scaley = (float) transform.getScaleY(); float shearx = (float) transform.getShearX(); float sheary = (float) transform.getShearY(); inverse = null; transform.setTransform(scalex, sheary, shearx, scaley, tx, ty); fireStateChanged(); } /** * Apply the passed values to the current Transform * @param offsetx the x-value * @param offsety the y-value */ public void translate(double offsetx, double offsety) { inverse = null; transform.translate(offsetx, offsety); fireStateChanged(); } /** * preconcatenates the rotation at the supplied point with the current transform * @param theta the angle by which to rotate the point * @param from the point to transform */ public void rotate(double theta, Point2D from) { AffineTransform rotate = AffineTransform.getRotateInstance(theta, from.getX(), from.getY()); inverse = null; transform.preConcatenate(rotate); fireStateChanged(); } /** * rotates the current transform at the supplied points * @param radians angle by which to rotate the supplied coordinates * @param x the x coordinate of the point to transform * @param y the y coordinate of the point to transform */ public void rotate(double radians, double x, double y) { inverse = null; transform.rotate(radians, x, y); fireStateChanged(); } public void concatenate(AffineTransform xform) { inverse = null; transform.concatenate(xform); fireStateChanged(); } public void preConcatenate(AffineTransform xform) { inverse = null; transform.preConcatenate(xform); fireStateChanged(); } /** * Adds a ChangeListener. * @param l the listener to be added */ public void addChangeListener(ChangeListener l) { changeSupport.addChangeListener(l); } /** * Removes a ChangeListener. * @param l the listener to be removed */ public void removeChangeListener(ChangeListener l) { changeSupport.removeChangeListener(l); } /** * Returns an array of all the ChangeListeners added * with addChangeListener(). * * @return all of the ChangeListeners added or an empty * array if no listeners have been added */ public ChangeListener[] getChangeListeners() { return changeSupport.getChangeListeners(); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created. * @see EventListenerList */ public void fireStateChanged() { changeSupport.fireStateChanged(); } public void setToIdentity() { inverse = null; transform.setToIdentity(); fireStateChanged(); } } MutableTransformer.java000066400000000000000000000030261276402340000363060ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 3, 2005 */ package edu.uci.ics.jung.visualization.transform; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer; import edu.uci.ics.jung.visualization.util.ChangeEventSupport; /** * Provides an API for the mutation of a Function * and for adding listeners for changes on the Function * * @author Tom Nelson * * */ public interface MutableTransformer extends ShapeTransformer, ChangeEventSupport { void translate(double dx, double dy); void setTranslate(double dx, double dy); void scale(double sx, double sy, Point2D point); void setScale(double sx, double sy, Point2D point); void rotate(double radians, Point2D point); void rotate(double radians, double x, double y); void shear(double shx, double shy, Point2D from); void concatenate(AffineTransform transform); void preConcatenate(AffineTransform transform); double getScaleX(); double getScaleY(); double getScale(); double getTranslateX(); double getTranslateY(); double getShearX(); double getShearY(); AffineTransform getTransform(); void setToIdentity(); double getRotation(); } MutableTransformerDecorator.java000066400000000000000000000063051276402340000401540ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.visualization.transform; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import javax.swing.event.ChangeListener; /** * a complete decorator that wraps a MutableTransformer. Subclasses * use this to allow them to only declare methods they need to change. * * @author Tom Nelson * */ public abstract class MutableTransformerDecorator implements MutableTransformer { protected MutableTransformer delegate; public MutableTransformerDecorator(MutableTransformer delegate) { if(delegate == null) { delegate = new MutableAffineTransformer(); } this.delegate = delegate; } public MutableTransformer getDelegate() { return delegate; } public void setDelegate(MutableTransformer delegate) { this.delegate = delegate; } public void addChangeListener(ChangeListener l) { delegate.addChangeListener(l); } public void concatenate(AffineTransform transform) { delegate.concatenate(transform); } public void fireStateChanged() { delegate.fireStateChanged(); } public ChangeListener[] getChangeListeners() { return delegate.getChangeListeners(); } public double getScale() { return delegate.getScale(); } public double getScaleX() { return delegate.getScaleX(); } public double getScaleY() { return delegate.getScaleY(); } public double getShearX() { return delegate.getShearX(); } public double getShearY() { return delegate.getShearY(); } public AffineTransform getTransform() { return delegate.getTransform(); } public double getTranslateX() { return delegate.getTranslateX(); } public double getTranslateY() { return delegate.getTranslateY(); } public Point2D inverseTransform(Point2D p) { return delegate.inverseTransform(p); } public Shape inverseTransform(Shape shape) { return delegate.inverseTransform(shape); } public void preConcatenate(AffineTransform transform) { delegate.preConcatenate(transform); } public void removeChangeListener(ChangeListener l) { delegate.removeChangeListener(l); } public void rotate(double radians, Point2D point) { delegate.rotate(radians, point); } public void scale(double sx, double sy, Point2D point) { delegate.scale(sx, sy, point); } public void setScale(double sx, double sy, Point2D point) { delegate.setScale(sx, sy, point); } public void setToIdentity() { delegate.setToIdentity(); } public void setTranslate(double dx, double dy) { delegate.setTranslate(dx, dy); } public void shear(double shx, double shy, Point2D from) { delegate.shear(shx, shy, from); } public Point2D transform(Point2D p) { return delegate.transform(p); } public Shape transform(Shape shape) { return delegate.transform(shape); } public void translate(double dx, double dy) { delegate.translate(dx, dy); } public double getRotation() { return delegate.getRotation(); } public void rotate(double radians, double x, double y) { delegate.rotate(radians, x, y); } } package.html000066400000000000000000000005331276402340000341100ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform

        Visualization mechanisms related to transformations, including lens effects. jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/000077500000000000000000000000001276402340000330055ustar00rootroot00000000000000Graphics2DWrapper.java000066400000000000000000000461061276402340000370670ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 11, 2005 */ package edu.uci.ics.jung.visualization.transform.shape; import java.awt.Color; import java.awt.Composite; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.Paint; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; import java.awt.RenderingHints.Key; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ImageObserver; import java.awt.image.RenderedImage; import java.awt.image.renderable.RenderableImage; import java.text.AttributedCharacterIterator; import java.util.Map; /** * a complete wrapping of Graphics2D, useful as a base class. * Contains no additional methods, other than direct calls * to the delegate. * * @see GraphicsDecorator as an example subclass that * adds additional methods. * * @author Tom Nelson * * */ public class Graphics2DWrapper { protected Graphics2D delegate; public Graphics2DWrapper() { this(null); } public Graphics2DWrapper(Graphics2D delegate) { this.delegate = delegate; } public void setDelegate(Graphics2D delegate) { this.delegate = delegate; } public Graphics2D getDelegate() { return delegate; } /* (non-Javadoc) * @see java.awt.Graphics2D#addRenderingHints(java.util.Map) */ public void addRenderingHints(Map hints) { delegate.addRenderingHints(hints); } /* (non-Javadoc) * @see java.awt.Graphics#clearRect(int, int, int, int) */ public void clearRect(int x, int y, int width, int height) { delegate.clearRect(x, y, width, height); } /* (non-Javadoc) * @see java.awt.Graphics2D#clip(java.awt.Shape) */ public void clip(Shape s) { delegate.clip(s); } /* (non-Javadoc) * @see java.awt.Graphics#clipRect(int, int, int, int) */ public void clipRect(int x, int y, int width, int height) { delegate.clipRect(x, y, width, height); } /* (non-Javadoc) * @see java.awt.Graphics#copyArea(int, int, int, int, int, int) */ public void copyArea(int x, int y, int width, int height, int dx, int dy) { delegate.copyArea(x, y, width, height, dx, dy); } /* (non-Javadoc) * @see java.awt.Graphics#create() */ public Graphics create() { return delegate.create(); } /* (non-Javadoc) * @see java.awt.Graphics#create(int, int, int, int) */ public Graphics create(int x, int y, int width, int height) { return delegate.create(x, y, width, height); } /* (non-Javadoc) * @see java.awt.Graphics#dispose() */ public void dispose() { delegate.dispose(); } /* (non-Javadoc) * @see java.awt.Graphics2D#draw(java.awt.Shape) */ public void draw(Shape s) { delegate.draw(s); } /* (non-Javadoc) * @see java.awt.Graphics2D#draw3DRect(int, int, int, int, boolean) */ public void draw3DRect(int x, int y, int width, int height, boolean raised) { delegate.draw3DRect(x, y, width, height, raised); } /* (non-Javadoc) * @see java.awt.Graphics#drawArc(int, int, int, int, int, int) */ public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { delegate.drawArc(x, y, width, height, startAngle, arcAngle); } /* (non-Javadoc) * @see java.awt.Graphics#drawBytes(byte[], int, int, int, int) */ public void drawBytes(byte[] data, int offset, int length, int x, int y) { delegate.drawBytes(data, offset, length, x, y); } /* (non-Javadoc) * @see java.awt.Graphics#drawChars(char[], int, int, int, int) */ public void drawChars(char[] data, int offset, int length, int x, int y) { delegate.drawChars(data, offset, length, x, y); } /* (non-Javadoc) * @see java.awt.Graphics2D#drawGlyphVector(java.awt.font.GlyphVector, float, float) */ public void drawGlyphVector(GlyphVector g, float x, float y) { delegate.drawGlyphVector(g, x, y); } /* (non-Javadoc) * @see java.awt.Graphics2D#drawImage(java.awt.image.BufferedImage, java.awt.image.BufferedImageOp, int, int) */ public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) { delegate.drawImage(img, op, x, y); } /* (non-Javadoc) * @see java.awt.Graphics2D#drawImage(java.awt.Image, java.awt.geom.AffineTransform, java.awt.image.ImageObserver) */ public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { return delegate.drawImage(img, xform, obs); } /* (non-Javadoc) * @see java.awt.Graphics#drawImage(java.awt.Image, int, int, java.awt.Color, java.awt.image.ImageObserver) */ public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) { return delegate.drawImage(img, x, y, bgcolor, observer); } /* (non-Javadoc) * @see java.awt.Graphics#drawImage(java.awt.Image, int, int, java.awt.image.ImageObserver) */ public boolean drawImage(Image img, int x, int y, ImageObserver observer) { return delegate.drawImage(img, x, y, observer); } /* (non-Javadoc) * @see java.awt.Graphics#drawImage(java.awt.Image, int, int, int, int, java.awt.Color, java.awt.image.ImageObserver) */ public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) { return delegate.drawImage(img, x, y, width, height, bgcolor, observer); } /* (non-Javadoc) * @see java.awt.Graphics#drawImage(java.awt.Image, int, int, int, int, java.awt.image.ImageObserver) */ public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { return delegate.drawImage(img, x, y, width, height, observer); } /* (non-Javadoc) * @see java.awt.Graphics#drawImage(java.awt.Image, int, int, int, int, int, int, int, int, java.awt.Color, java.awt.image.ImageObserver) */ public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) { return delegate.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer); } /* (non-Javadoc) * @see java.awt.Graphics#drawImage(java.awt.Image, int, int, int, int, int, int, int, int, java.awt.image.ImageObserver) */ public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { return delegate.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer); } /* (non-Javadoc) * @see java.awt.Graphics#drawLine(int, int, int, int) */ public void drawLine(int x1, int y1, int x2, int y2) { delegate.drawLine(x1, y1, x2, y2); } /* (non-Javadoc) * @see java.awt.Graphics#drawOval(int, int, int, int) */ public void drawOval(int x, int y, int width, int height) { delegate.drawOval(x, y, width, height); } /* (non-Javadoc) * @see java.awt.Graphics#drawPolygon(int[], int[], int) */ public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { delegate.drawPolygon(xPoints, yPoints, nPoints); } /* (non-Javadoc) * @see java.awt.Graphics#drawPolygon(java.awt.Polygon) */ public void drawPolygon(Polygon p) { delegate.drawPolygon(p); } /* (non-Javadoc) * @see java.awt.Graphics#drawPolyline(int[], int[], int) */ public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { delegate.drawPolyline(xPoints, yPoints, nPoints); } /* (non-Javadoc) * @see java.awt.Graphics#drawRect(int, int, int, int) */ public void drawRect(int x, int y, int width, int height) { delegate.drawRect(x, y, width, height); } /* (non-Javadoc) * @see java.awt.Graphics2D#drawRenderableImage(java.awt.image.renderable.RenderableImage, java.awt.geom.AffineTransform) */ public void drawRenderableImage(RenderableImage img, AffineTransform xform) { delegate.drawRenderableImage(img, xform); } /* (non-Javadoc) * @see java.awt.Graphics2D#drawRenderedImage(java.awt.image.RenderedImage, java.awt.geom.AffineTransform) */ public void drawRenderedImage(RenderedImage img, AffineTransform xform) { delegate.drawRenderedImage(img, xform); } /* (non-Javadoc) * @see java.awt.Graphics#drawRoundRect(int, int, int, int, int, int) */ public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { delegate.drawRoundRect(x, y, width, height, arcWidth, arcHeight); } /* (non-Javadoc) * @see java.awt.Graphics2D#drawString(java.text.AttributedCharacterIterator, float, float) */ public void drawString(AttributedCharacterIterator iterator, float x, float y) { delegate.drawString(iterator, x, y); } /* (non-Javadoc) * @see java.awt.Graphics2D#drawString(java.text.AttributedCharacterIterator, int, int) */ public void drawString(AttributedCharacterIterator iterator, int x, int y) { delegate.drawString(iterator, x, y); } /* (non-Javadoc) * @see java.awt.Graphics2D#drawString(java.lang.String, float, float) */ public void drawString(String s, float x, float y) { delegate.drawString(s, x, y); } /* (non-Javadoc) * @see java.awt.Graphics2D#drawString(java.lang.String, int, int) */ public void drawString(String str, int x, int y) { delegate.drawString(str, x, y); } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ public boolean equals(Object obj) { return delegate.equals(obj); } /* (non-Javadoc) * @see java.awt.Graphics2D#fill(java.awt.Shape) */ public void fill(Shape s) { delegate.fill(s); } /* (non-Javadoc) * @see java.awt.Graphics2D#fill3DRect(int, int, int, int, boolean) */ public void fill3DRect(int x, int y, int width, int height, boolean raised) { delegate.fill3DRect(x, y, width, height, raised); } /* (non-Javadoc) * @see java.awt.Graphics#fillArc(int, int, int, int, int, int) */ public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) { delegate.fillArc(x, y, width, height, startAngle, arcAngle); } /* (non-Javadoc) * @see java.awt.Graphics#fillOval(int, int, int, int) */ public void fillOval(int x, int y, int width, int height) { delegate.fillOval(x, y, width, height); } /* (non-Javadoc) * @see java.awt.Graphics#fillPolygon(int[], int[], int) */ public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { delegate.fillPolygon(xPoints, yPoints, nPoints); } /* (non-Javadoc) * @see java.awt.Graphics#fillPolygon(java.awt.Polygon) */ public void fillPolygon(Polygon p) { delegate.fillPolygon(p); } /* (non-Javadoc) * @see java.awt.Graphics#fillRect(int, int, int, int) */ public void fillRect(int x, int y, int width, int height) { delegate.fillRect(x, y, width, height); } /* (non-Javadoc) * @see java.awt.Graphics#fillRoundRect(int, int, int, int, int, int) */ public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { delegate.fillRoundRect(x, y, width, height, arcWidth, arcHeight); } /* (non-Javadoc) * @see java.awt.Graphics#finalize() */ public void finalize() { delegate.finalize(); } /* (non-Javadoc) * @see java.awt.Graphics2D#getBackground() */ public Color getBackground() { return delegate.getBackground(); } /* (non-Javadoc) * @see java.awt.Graphics#getClip() */ public Shape getClip() { return delegate.getClip(); } /* (non-Javadoc) * @see java.awt.Graphics#getClipBounds() */ public Rectangle getClipBounds() { return delegate.getClipBounds(); } /* (non-Javadoc) * @see java.awt.Graphics#getClipBounds(java.awt.Rectangle) */ public Rectangle getClipBounds(Rectangle r) { return delegate.getClipBounds(r); } /* (non-Javadoc) * @see java.awt.Graphics#getClipRect() */ @SuppressWarnings("deprecation") public Rectangle getClipRect() { return delegate.getClipRect(); } /* (non-Javadoc) * @see java.awt.Graphics#getColor() */ public Color getColor() { return delegate.getColor(); } /* (non-Javadoc) * @see java.awt.Graphics2D#getComposite() */ public Composite getComposite() { return delegate.getComposite(); } /* (non-Javadoc) * @see java.awt.Graphics2D#getDeviceConfiguration() */ public GraphicsConfiguration getDeviceConfiguration() { return delegate.getDeviceConfiguration(); } /* (non-Javadoc) * @see java.awt.Graphics#getFont() */ public Font getFont() { return delegate.getFont(); } /* (non-Javadoc) * @see java.awt.Graphics#getFontMetrics() */ public FontMetrics getFontMetrics() { return delegate.getFontMetrics(); } /* (non-Javadoc) * @see java.awt.Graphics#getFontMetrics(java.awt.Font) */ public FontMetrics getFontMetrics(Font f) { return delegate.getFontMetrics(f); } /* (non-Javadoc) * @see java.awt.Graphics2D#getFontRenderContext() */ public FontRenderContext getFontRenderContext() { return delegate.getFontRenderContext(); } /* (non-Javadoc) * @see java.awt.Graphics2D#getPaint() */ public Paint getPaint() { return delegate.getPaint(); } /* (non-Javadoc) * @see java.awt.Graphics2D#getRenderingHint(java.awt.RenderingHints.Key) */ public Object getRenderingHint(Key hintKey) { return delegate.getRenderingHint(hintKey); } /* (non-Javadoc) * @see java.awt.Graphics2D#getRenderingHints() */ public RenderingHints getRenderingHints() { return delegate.getRenderingHints(); } /* (non-Javadoc) * @see java.awt.Graphics2D#getStroke() */ public Stroke getStroke() { return delegate.getStroke(); } /* (non-Javadoc) * @see java.awt.Graphics2D#getTransform() */ public AffineTransform getTransform() { return delegate.getTransform(); } /* (non-Javadoc) * @see java.lang.Object#hashCode() */ public int hashCode() { return delegate.hashCode(); } /* (non-Javadoc) * @see java.awt.Graphics2D#hit(java.awt.Rectangle, java.awt.Shape, boolean) */ public boolean hit(Rectangle rect, Shape s, boolean onStroke) { return delegate.hit(rect, s, onStroke); } /* (non-Javadoc) * @see java.awt.Graphics#hitClip(int, int, int, int) */ public boolean hitClip(int x, int y, int width, int height) { return delegate.hitClip(x, y, width, height); } /* (non-Javadoc) * @see java.awt.Graphics2D#rotate(double, double, double) */ public void rotate(double theta, double x, double y) { delegate.rotate(theta, x, y); } /* (non-Javadoc) * @see java.awt.Graphics2D#rotate(double) */ public void rotate(double theta) { delegate.rotate(theta); } /* (non-Javadoc) * @see java.awt.Graphics2D#scale(double, double) */ public void scale(double sx, double sy) { delegate.scale(sx, sy); } /* (non-Javadoc) * @see java.awt.Graphics2D#setBackground(java.awt.Color) */ public void setBackground(Color color) { delegate.setBackground(color); } /* (non-Javadoc) * @see java.awt.Graphics#setClip(int, int, int, int) */ public void setClip(int x, int y, int width, int height) { delegate.setClip(x, y, width, height); } /* (non-Javadoc) * @see java.awt.Graphics#setClip(java.awt.Shape) */ public void setClip(Shape clip) { delegate.setClip(clip); } /* (non-Javadoc) * @see java.awt.Graphics#setColor(java.awt.Color) */ public void setColor(Color c) { delegate.setColor(c); } /* (non-Javadoc) * @see java.awt.Graphics2D#setComposite(java.awt.Composite) */ public void setComposite(Composite comp) { delegate.setComposite(comp); } /* (non-Javadoc) * @see java.awt.Graphics#setFont(java.awt.Font) */ public void setFont(Font font) { delegate.setFont(font); } /* (non-Javadoc) * @see java.awt.Graphics2D#setPaint(java.awt.Paint) */ public void setPaint(Paint paint) { delegate.setPaint(paint); } /* (non-Javadoc) * @see java.awt.Graphics#setPaintMode() */ public void setPaintMode() { delegate.setPaintMode(); } /* (non-Javadoc) * @see java.awt.Graphics2D#setRenderingHint(java.awt.RenderingHints.Key, java.lang.Object) */ public void setRenderingHint(Key hintKey, Object hintValue) { delegate.setRenderingHint(hintKey, hintValue); } /* (non-Javadoc) * @see java.awt.Graphics2D#setRenderingHints(java.util.Map) */ public void setRenderingHints(Map hints) { delegate.setRenderingHints(hints); } /* (non-Javadoc) * @see java.awt.Graphics2D#setStroke(java.awt.Stroke) */ public void setStroke(Stroke s) { delegate.setStroke(s); } /* (non-Javadoc) * @see java.awt.Graphics2D#setTransform(java.awt.geom.AffineTransform) */ public void setTransform(AffineTransform Tx) { delegate.setTransform(Tx); } /* (non-Javadoc) * @see java.awt.Graphics#setXORMode(java.awt.Color) */ public void setXORMode(Color c1) { delegate.setXORMode(c1); } /* (non-Javadoc) * @see java.awt.Graphics2D#shear(double, double) */ public void shear(double shx, double shy) { delegate.shear(shx, shy); } /* (non-Javadoc) * @see java.awt.Graphics#toString() */ public String toString() { return delegate.toString(); } /* (non-Javadoc) * @see java.awt.Graphics2D#transform(java.awt.geom.AffineTransform) */ public void transform(AffineTransform Tx) { delegate.transform(Tx); } /* (non-Javadoc) * @see java.awt.Graphics2D#translate(double, double) */ public void translate(double tx, double ty) { delegate.translate(tx, ty); } /* (non-Javadoc) * @see java.awt.Graphics2D#translate(int, int) */ public void translate(int x, int y) { delegate.translate(x, y); } } GraphicsDecorator.java000066400000000000000000000024151276402340000371760ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 11, 2005 */ package edu.uci.ics.jung.visualization.transform.shape; import java.awt.Component; import java.awt.Graphics2D; import java.awt.Shape; import javax.swing.CellRendererPane; import javax.swing.Icon; /** * an extendion of Graphics2DWrapper that adds enhanced * methods for drawing icons and components * * @see TransformingGraphics as an example subclass * * @author Tom Nelson * * */ public class GraphicsDecorator extends Graphics2DWrapper { public GraphicsDecorator() { this(null); } public GraphicsDecorator(Graphics2D delegate) { super(delegate); } public void draw(Icon icon, Component c, Shape clip, int x, int y) { int w = icon.getIconWidth(); int h = icon.getIconHeight(); icon.paintIcon(c, delegate, x-w/2, y-h/2); } public void draw(Component c, CellRendererPane rendererPane, int x, int y, int w, int h, boolean shouldValidate) { rendererPane.paintComponent(delegate, c, c.getParent(), x, y, w, h, shouldValidate); } } HyperbolicShapeTransformer.java000066400000000000000000000214421276402340000411000ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.visualization.transform.shape; import java.awt.Component; import java.awt.Shape; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import edu.uci.ics.jung.algorithms.layout.PolarPoint; import edu.uci.ics.jung.visualization.transform.HyperbolicTransformer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * HyperbolicShapeTransformer extends HyperbolicTransformer and * adds implementations for methods in ShapeFlatnessTransformer. * It modifies the shapes (Vertex, Edge, and Arrowheads) so that * they are distorted by the hyperbolic transformation * * @author Tom Nelson * * */ public class HyperbolicShapeTransformer extends HyperbolicTransformer implements ShapeFlatnessTransformer { /** * Create an instance, setting values from the passed component * and registering to listen for size changes on the component. * @param component the component in which rendering takes place */ public HyperbolicShapeTransformer(Component component) { this(component, null); } /** * Create an instance, setting values from the passed component * and registering to listen for size changes on the component, * with a possibly shared transform delegate. * @param component the component in which rendering takes place * @param delegate the transformer to use */ public HyperbolicShapeTransformer(Component component, MutableTransformer delegate) { super(component, delegate); } /** * Transform the supplied shape with the overridden transform * method so that the shape is distorted by the hyperbolic * transform. * @param shape a shape to transform * @return a GeneralPath for the transformed shape */ public Shape transform(Shape shape) { return transform(shape, 0); } public Shape transform(Shape shape, float flatness) { GeneralPath newPath = new GeneralPath(); float[] coords = new float[6]; PathIterator iterator = null; if(flatness == 0) { iterator = shape.getPathIterator(null); } else { iterator = shape.getPathIterator(null, flatness); } for( ; iterator.isDone() == false; iterator.next()) { int type = iterator.currentSegment(coords); switch(type) { case PathIterator.SEG_MOVETO: Point2D p = _transform(new Point2D.Float(coords[0], coords[1])); newPath.moveTo((float)p.getX(), (float)p.getY()); break; case PathIterator.SEG_LINETO: p = _transform(new Point2D.Float(coords[0], coords[1])); newPath.lineTo((float)p.getX(), (float) p.getY()); break; case PathIterator.SEG_QUADTO: p = _transform(new Point2D.Float(coords[0], coords[1])); Point2D q = _transform(new Point2D.Float(coords[2], coords[3])); newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY()); break; case PathIterator.SEG_CUBICTO: p = _transform(new Point2D.Float(coords[0], coords[1])); q = _transform(new Point2D.Float(coords[2], coords[3])); Point2D r = _transform(new Point2D.Float(coords[4], coords[5])); newPath.curveTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY(), (float)r.getX(), (float)r.getY()); break; case PathIterator.SEG_CLOSE: newPath.closePath(); break; } } return newPath; } public Shape inverseTransform(Shape shape) { GeneralPath newPath = new GeneralPath(); float[] coords = new float[6]; for(PathIterator iterator=shape.getPathIterator(null); iterator.isDone() == false; iterator.next()) { int type = iterator.currentSegment(coords); switch(type) { case PathIterator.SEG_MOVETO: Point2D p = _inverseTransform(new Point2D.Float(coords[0], coords[1])); newPath.moveTo((float)p.getX(), (float)p.getY()); break; case PathIterator.SEG_LINETO: p = _inverseTransform(new Point2D.Float(coords[0], coords[1])); newPath.lineTo((float)p.getX(), (float) p.getY()); break; case PathIterator.SEG_QUADTO: p = _inverseTransform(new Point2D.Float(coords[0], coords[1])); Point2D q = _inverseTransform(new Point2D.Float(coords[2], coords[3])); newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY()); break; case PathIterator.SEG_CUBICTO: p = _inverseTransform(new Point2D.Float(coords[0], coords[1])); q = _inverseTransform(new Point2D.Float(coords[2], coords[3])); Point2D r = _inverseTransform(new Point2D.Float(coords[4], coords[5])); newPath.curveTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY(), (float)r.getX(), (float)r.getY()); break; case PathIterator.SEG_CLOSE: newPath.closePath(); break; } } return newPath; } /** * override base class transform to project the fisheye effect */ private Point2D _transform(Point2D graphPoint) { if(graphPoint == null) return null; Point2D viewCenter = getViewCenter(); double viewRadius = getViewRadius(); double ratio = getRatio(); // transform the point from the graph to the view Point2D viewPoint = graphPoint;//delegate.transform(graphPoint); // calculate point from center double dx = viewPoint.getX() - viewCenter.getX(); double dy = viewPoint.getY() - viewCenter.getY(); // factor out ellipse dx *= ratio; Point2D pointFromCenter = new Point2D.Double(dx, dy); PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter); double theta = polar.getTheta(); double radius = polar.getRadius(); if(radius > viewRadius) return viewPoint; double mag = Math.tan(Math.PI/2*magnification); radius *= mag; radius = Math.min(radius, viewRadius); radius /= viewRadius; radius *= Math.PI/2; radius = Math.abs(Math.atan(radius)); radius *= viewRadius; Point2D projectedPoint = PolarPoint.polarToCartesian(theta, radius); projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY()); Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(), projectedPoint.getY()+viewCenter.getY()); return translatedBack; } /** * override base class to un-project the fisheye effect */ private Point2D _inverseTransform(Point2D viewPoint) { viewPoint = delegate.inverseTransform(viewPoint); Point2D viewCenter = getViewCenter(); double viewRadius = getViewRadius(); double ratio = getRatio(); double dx = viewPoint.getX() - viewCenter.getX(); double dy = viewPoint.getY() - viewCenter.getY(); // factor out ellipse dx *= ratio; Point2D pointFromCenter = new Point2D.Double(dx, dy); PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter); double radius = polar.getRadius(); if(radius > viewRadius) return viewPoint;//elegate.inverseTransform(viewPoint); radius /= viewRadius; radius = Math.abs(Math.tan(radius)); radius /= Math.PI/2; radius *= viewRadius; double mag = Math.tan(Math.PI/2*magnification); radius /= mag; polar.setRadius(radius); Point2D projectedPoint = PolarPoint.polarToCartesian(polar); projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY()); Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(), projectedPoint.getY()+viewCenter.getY()); return translatedBack; //delegate.inverseTransform(translatedBack); } }Intersector.java000066400000000000000000000065011276402340000360740ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.visualization.transform.shape; import java.awt.Rectangle; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.util.HashSet; import java.util.Set; public class Intersector { protected Rectangle rectangle; Line2D line; Set points = new HashSet(); public Intersector(Rectangle rectangle) { this.rectangle = rectangle; } public Intersector(Rectangle rectangle, Line2D line) { this.rectangle = rectangle; intersectLine(line); } public void intersectLine(Line2D line) { this.line = line; points.clear(); float rx0 = (float) rectangle.getMinX(); float ry0 = (float) rectangle.getMinY(); float rx1 = (float) rectangle.getMaxX(); float ry1 = (float) rectangle.getMaxY(); float x1 = (float) line.getX1(); float y1 = (float) line.getY1(); float x2 = (float) line.getX2(); float y2 = (float) line.getY2(); float dy = y2 - y1; float dx = x2 - x1; if(dx != 0) { float m = dy/dx; float b = y1 - m*x1; // base of rect where y == ry0 float x = (ry0 - b) / m; if(rx0 <= x && x <= rx1) { points.add(new Point2D.Float(x, ry0)); } // top where y == ry1 x = (ry1 - b) / m; if(rx0 <= x && x <= rx1) { points.add(new Point2D.Float(x, ry1)); } // left side, where x == rx0 float y = m * rx0 + b; if(ry0 <= y && y <= ry1) { points.add(new Point2D.Float(rx0, y)); } // right side, where x == rx1 y = m * rx1 + b; if(ry0 <= y && y <= ry1) { points.add(new Point2D.Float(rx1, y)); } } else { // base, where y == ry0 float x = x1; if(rx0 <= x && x <= rx1) { points.add(new Point2D.Float(x, ry0)); } // top, where y == ry1 x = x2; if(rx0 <= x && x <= rx1) { points.add(new Point2D.Float(x, ry1)); } } } public Line2D getLine() { return line; } public Set getPoints() { return points; } public Rectangle getRectangle() { return rectangle; } public String toString() { return "Rectangle: "+rectangle+", points:"+points; } public static void main(String[] args) { Rectangle rectangle = new Rectangle(0,0,10,10); Line2D line = new Line2D.Float(4,4,5,5); System.err.println(""+new Intersector(rectangle, line)); System.err.println(""+new Intersector(rectangle, new Line2D.Float(9,11,11,9))); System.err.println(""+new Intersector(rectangle, new Line2D.Float(1,1,3,2))); System.err.println(""+new Intersector(rectangle, new Line2D.Float(4,6,6,4))); } } MagnifyIconGraphics.java000066400000000000000000000052111276402340000374540ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 11, 2005 */ package edu.uci.ics.jung.visualization.transform.shape; import java.awt.Component; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Rectangle2D; import javax.swing.Icon; import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer; /** * Subclassed to apply a magnification transform to an icon. * * @author Tom Nelson * * */ public class MagnifyIconGraphics extends TransformingFlatnessGraphics { public MagnifyIconGraphics(BidirectionalTransformer transformer) { this(transformer, null); } public MagnifyIconGraphics(BidirectionalTransformer Function, Graphics2D delegate) { super(Function, delegate); } public void draw(Icon icon, Component c, Shape clip, int x, int y) { if(transformer instanceof MagnifyShapeTransformer) { MagnifyShapeTransformer mst = (MagnifyShapeTransformer)transformer; int w = icon.getIconWidth(); int h = icon.getIconHeight(); Rectangle2D r = new Rectangle2D.Double(x-w/2,y-h/2,w,h); Shape lens = mst.getLensShape(); if(lens.intersects(r)) { // magnify the whole icon Rectangle2D s = mst.magnify(r).getBounds2D(); if(lens.intersects(s)) { clip = mst.transform(clip); double sx = s.getWidth()/r.getWidth(); double sy = s.getHeight()/r.getHeight(); AffineTransform old = delegate.getTransform(); AffineTransform xform = new AffineTransform(old); xform.translate(s.getMinX(), s.getMinY()); xform.scale(sx, sy); xform.translate(-s.getMinX(), -s.getMinY()); Shape oldClip = delegate.getClip(); delegate.clip(clip); delegate.setTransform(xform); icon.paintIcon(c, delegate, (int)s.getMinX(), (int)s.getMinY()); delegate.setTransform(old); delegate.setClip(oldClip); } else { // clip out the lens so the small icon doesn't get drawn // inside of it Shape oldClip = delegate.getClip(); Area viewBounds = new Area(oldClip); viewBounds.subtract(new Area(lens)); delegate.setClip(viewBounds); icon.paintIcon(c, delegate, (int)r.getMinX(),(int)r.getMinY()); delegate.setClip(oldClip); } } else { icon.paintIcon(c, delegate, (int)r.getMinX(),(int)r.getMinY()); } } } } MagnifyImageLensSupport.java000066400000000000000000000111051276402340000403430ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 21, 2005 */ package edu.uci.ics.jung.visualization.transform.shape; import java.awt.Dimension; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalLensGraphMouse; import edu.uci.ics.jung.visualization.picking.ViewLensShapePickSupport; import edu.uci.ics.jung.visualization.renderers.BasicRenderer; import edu.uci.ics.jung.visualization.renderers.Renderer; import edu.uci.ics.jung.visualization.renderers.ReshapingEdgeRenderer; import edu.uci.ics.jung.visualization.transform.AbstractLensSupport; import edu.uci.ics.jung.visualization.transform.LensTransformer; /** * Changes various visualization settings to activate or deactivate an * examining lens for a jung graph application. * * @author Tom Nelson */ public class MagnifyImageLensSupport extends AbstractLensSupport { protected RenderContext renderContext; protected GraphicsDecorator lensGraphicsDecorator; protected GraphicsDecorator savedGraphicsDecorator; protected Renderer renderer; protected Renderer transformingRenderer; protected GraphElementAccessor pickSupport; protected Renderer.Edge savedEdgeRenderer; protected Renderer.Edge reshapingEdgeRenderer; static final String instructions = "

        Mouse-Drag the Lens center to move it

        "+ "Mouse-Drag the Lens edge to resize it

        "+ "Ctrl+MouseWheel to change magnification

        "; public MagnifyImageLensSupport(VisualizationViewer vv) { this(vv, new MagnifyShapeTransformer(vv), new ModalLensGraphMouse()); } public MagnifyImageLensSupport(VisualizationViewer vv, LensTransformer lensTransformer, ModalGraphMouse lensGraphMouse) { super(vv, lensGraphMouse); this.renderContext = vv.getRenderContext(); this.pickSupport = renderContext.getPickSupport(); this.renderer = vv.getRenderer(); this.transformingRenderer = new BasicRenderer(); this.savedGraphicsDecorator = renderContext.getGraphicsContext(); this.lensTransformer = lensTransformer; this.savedEdgeRenderer = vv.getRenderer().getEdgeRenderer(); this.reshapingEdgeRenderer = new ReshapingEdgeRenderer(); this.reshapingEdgeRenderer.setEdgeArrowRenderingSupport(savedEdgeRenderer.getEdgeArrowRenderingSupport()); Dimension d = vv.getSize(); if(d.width == 0 || d.height == 0) { d = vv.getPreferredSize(); } lensTransformer.setViewRadius(d.width/5); this.lensGraphicsDecorator = new MagnifyIconGraphics(lensTransformer); } public void activate() { lensTransformer.setDelegate(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)); if(lens == null) { lens = new Lens(lensTransformer); } if(lensControls == null) { lensControls = new LensControls(lensTransformer); } renderContext.setPickSupport(new ViewLensShapePickSupport(vv)); lensTransformer.setDelegate(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)); vv.getRenderContext().getMultiLayerTransformer().setTransformer(Layer.VIEW, lensTransformer); this.renderContext.setGraphicsContext(lensGraphicsDecorator); vv.getRenderer().setEdgeRenderer(reshapingEdgeRenderer); vv.addPreRenderPaintable(lens); vv.addPostRenderPaintable(lensControls); vv.setGraphMouse(lensGraphMouse); vv.setToolTipText(instructions); vv.repaint(); } public void deactivate() { renderContext.setPickSupport(pickSupport); vv.getRenderContext().getMultiLayerTransformer().setTransformer(Layer.VIEW, lensTransformer.getDelegate()); vv.removePreRenderPaintable(lens); vv.removePostRenderPaintable(lensControls); this.renderContext.setGraphicsContext(savedGraphicsDecorator); vv.getRenderer().setEdgeRenderer(savedEdgeRenderer); vv.setToolTipText(defaultToolTipText); vv.setGraphMouse(graphMouse); vv.repaint(); } } MagnifyShapeTransformer.java000066400000000000000000000246011276402340000403720ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.visualization.transform.shape; import java.awt.Component; import java.awt.Shape; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import edu.uci.ics.jung.algorithms.layout.PolarPoint; import edu.uci.ics.jung.visualization.transform.MagnifyTransformer; import edu.uci.ics.jung.visualization.transform.MutableTransformer; /** * MagnifyShapeTransformer extends MagnifyTransformer and * adds implementations for methods in ShapeTransformer. * It modifies the shapes (Vertex, Edge, and Arrowheads) so that * they are enlarged by the magnify transformation. * * @author Tom Nelson */ public class MagnifyShapeTransformer extends MagnifyTransformer implements ShapeFlatnessTransformer { /** * Create an instance, setting values from the passed component * and registering to listen for size changes on the component. * * @param component the component used for rendering */ public MagnifyShapeTransformer(Component component) { this(component, null); } /** * Create an instance, setting values from the passed component * and registering to listen for size changes on the component, * with a possibly shared transform delegate. * * @param component the component used for rendering * @param delegate the transformer to use */ public MagnifyShapeTransformer(Component component, MutableTransformer delegate) { super(component, delegate); } /** * Transform the supplied shape with the overridden transform * method so that the shape is distorted by the magnify * transform. * @param shape a shape to transform * @return a GeneralPath for the transformed shape */ public Shape transform(Shape shape) { return transform(shape, 0); } public Shape transform(Shape shape, float flatness) { GeneralPath newPath = new GeneralPath(); float[] coords = new float[6]; PathIterator iterator = null; if(flatness == 0) { iterator = shape.getPathIterator(null); } else { iterator = shape.getPathIterator(null, flatness); } for( ; iterator.isDone() == false; iterator.next()) { int type = iterator.currentSegment(coords); switch(type) { case PathIterator.SEG_MOVETO: Point2D p = _transform(new Point2D.Float(coords[0], coords[1])); newPath.moveTo((float)p.getX(), (float)p.getY()); break; case PathIterator.SEG_LINETO: p = _transform(new Point2D.Float(coords[0], coords[1])); newPath.lineTo((float)p.getX(), (float) p.getY()); break; case PathIterator.SEG_QUADTO: p = _transform(new Point2D.Float(coords[0], coords[1])); Point2D q = _transform(new Point2D.Float(coords[2], coords[3])); newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY()); break; case PathIterator.SEG_CUBICTO: p = _transform(new Point2D.Float(coords[0], coords[1])); q = _transform(new Point2D.Float(coords[2], coords[3])); Point2D r = _transform(new Point2D.Float(coords[4], coords[5])); newPath.curveTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY(), (float)r.getX(), (float)r.getY()); break; case PathIterator.SEG_CLOSE: newPath.closePath(); break; } } return newPath; } public Shape inverseTransform(Shape shape) { GeneralPath newPath = new GeneralPath(); float[] coords = new float[6]; for(PathIterator iterator=shape.getPathIterator(null); iterator.isDone() == false; iterator.next()) { int type = iterator.currentSegment(coords); switch(type) { case PathIterator.SEG_MOVETO: Point2D p = _inverseTransform(new Point2D.Float(coords[0], coords[1])); newPath.moveTo((float)p.getX(), (float)p.getY()); break; case PathIterator.SEG_LINETO: p = _inverseTransform(new Point2D.Float(coords[0], coords[1])); newPath.lineTo((float)p.getX(), (float) p.getY()); break; case PathIterator.SEG_QUADTO: p = _inverseTransform(new Point2D.Float(coords[0], coords[1])); Point2D q = _inverseTransform(new Point2D.Float(coords[2], coords[3])); newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY()); break; case PathIterator.SEG_CUBICTO: p = _inverseTransform(new Point2D.Float(coords[0], coords[1])); q = _inverseTransform(new Point2D.Float(coords[2], coords[3])); Point2D r = _inverseTransform(new Point2D.Float(coords[4], coords[5])); newPath.curveTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY(), (float)r.getX(), (float)r.getY()); break; case PathIterator.SEG_CLOSE: newPath.closePath(); break; } } return newPath; } private Point2D _transform(Point2D graphPoint) { if(graphPoint == null) return null; Point2D viewCenter = getViewCenter(); double viewRadius = getViewRadius(); double ratio = getRatio(); // transform the point from the graph to the view Point2D viewPoint = graphPoint; // calculate point from center double dx = viewPoint.getX() - viewCenter.getX(); double dy = viewPoint.getY() - viewCenter.getY(); // factor out ellipse dx *= ratio; Point2D pointFromCenter = new Point2D.Double(dx, dy); PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter); double theta = polar.getTheta(); double radius = polar.getRadius(); if(radius > viewRadius) return viewPoint; double mag = magnification; radius *= mag; radius = Math.min(radius, viewRadius); Point2D projectedPoint = PolarPoint.polarToCartesian(theta, radius); projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY()); Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(), projectedPoint.getY()+viewCenter.getY()); return translatedBack; } /** * override base class to un-project the fisheye effect */ private Point2D _inverseTransform(Point2D viewPoint) { viewPoint = delegate.inverseTransform(viewPoint); Point2D viewCenter = getViewCenter(); double viewRadius = getViewRadius(); double ratio = getRatio(); double dx = viewPoint.getX() - viewCenter.getX(); double dy = viewPoint.getY() - viewCenter.getY(); // factor out ellipse dx *= ratio; Point2D pointFromCenter = new Point2D.Double(dx, dy); PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter); double radius = polar.getRadius(); if(radius > viewRadius) return viewPoint; double mag = magnification; radius /= mag; polar.setRadius(radius); Point2D projectedPoint = PolarPoint.polarToCartesian(polar); projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY()); Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(), projectedPoint.getY()+viewCenter.getY()); return translatedBack; } /** * Magnify the shape, without considering the Lens. * @param shape the shape to magnify * @return the transformed shape */ public Shape magnify(Shape shape) { return magnify(shape, 0); } public Shape magnify(Shape shape, float flatness) { GeneralPath newPath = new GeneralPath(); float[] coords = new float[6]; PathIterator iterator = null; if(flatness == 0) { iterator = shape.getPathIterator(null); } else { iterator = shape.getPathIterator(null, flatness); } for( ; iterator.isDone() == false; iterator.next()) { int type = iterator.currentSegment(coords); switch(type) { case PathIterator.SEG_MOVETO: Point2D p = magnify(new Point2D.Float(coords[0], coords[1])); newPath.moveTo((float)p.getX(), (float)p.getY()); break; case PathIterator.SEG_LINETO: p = magnify(new Point2D.Float(coords[0], coords[1])); newPath.lineTo((float)p.getX(), (float) p.getY()); break; case PathIterator.SEG_QUADTO: p = magnify(new Point2D.Float(coords[0], coords[1])); Point2D q = magnify(new Point2D.Float(coords[2], coords[3])); newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY()); break; case PathIterator.SEG_CUBICTO: p = magnify(new Point2D.Float(coords[0], coords[1])); q = magnify(new Point2D.Float(coords[2], coords[3])); Point2D r = magnify(new Point2D.Float(coords[4], coords[5])); newPath.curveTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY(), (float)r.getX(), (float)r.getY()); break; case PathIterator.SEG_CLOSE: newPath.closePath(); break; } } return newPath; } }ShapeFlatnessTransformer.java000066400000000000000000000020451276402340000405550ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Apr 16, 2005 */ package edu.uci.ics.jung.visualization.transform.shape; import java.awt.Shape; /** * Provides methods to map points from one coordinate system to * another: graph to screen and screen to graph. * The flatness parameter is used to break a curved shape into * smaller segments in order to perform a more detailed * transformation. * * @author Tom Nelson */ public interface ShapeFlatnessTransformer extends ShapeTransformer { /** * map a shape from graph coordinate system to the * screen coordinate system * @param shape the shape to be transformed * @param flatness used to break the supplied shape into segments * @return a GeneralPath (Shape) representing the screen points of the shape */ Shape transform(Shape shape, float flatness); } ShapeTransformer.java000066400000000000000000000016631276402340000370620ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Apr 16, 2005 */ package edu.uci.ics.jung.visualization.transform.shape; import java.awt.Shape; import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer; /** * Provides methods to map points from one coordinate system to * another: graph to screen and screen to graph. * * @author Tom Nelson */ public interface ShapeTransformer extends BidirectionalTransformer { /** * map a shape from graph coordinate system to the * screen coordinate system * @param shape the Shape to transform * @return a GeneralPath (Shape) representing the screen points of the shape */ Shape transform(Shape shape); Shape inverseTransform(Shape shape); } TransformingFlatnessGraphics.java000066400000000000000000000035231276402340000414260ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 11, 2005 */ package edu.uci.ics.jung.visualization.transform.shape; import java.awt.Graphics2D; import java.awt.Shape; import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer; import edu.uci.ics.jung.visualization.transform.HyperbolicTransformer; /** * subclassed to pass certain operations thru the Function * before the base class method is applied * This is useful when you want to apply non-affine transformations * to the Graphics2D used to draw elements of the graph. * * @author Tom Nelson * * */ public class TransformingFlatnessGraphics extends TransformingGraphics { float flatness = 0; public TransformingFlatnessGraphics(BidirectionalTransformer transformer) { this(transformer, null); } public TransformingFlatnessGraphics(BidirectionalTransformer transformer, Graphics2D delegate) { super(transformer, delegate); } public void draw(Shape s, float flatness) { Shape shape = null; if(transformer instanceof ShapeFlatnessTransformer) { shape = ((ShapeFlatnessTransformer)transformer).transform(s, flatness); } else { shape = ((ShapeTransformer)transformer).transform(s); } delegate.draw(shape); } public void fill(Shape s, float flatness) { Shape shape = null; if(transformer instanceof HyperbolicTransformer) { shape = ((HyperbolicShapeTransformer)transformer).transform(s, flatness); } else { shape = ((ShapeTransformer)transformer).transform(s); } delegate.fill(shape); } } TransformingGraphics.java000066400000000000000000000111041276402340000377200ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 11, 2005 */ package edu.uci.ics.jung.visualization.transform.shape; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.ImageObserver; import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer; /** * subclassed to pass certain operations thru the Function * before the base class method is applied * This is useful when you want to apply non-affine transformations * to the Graphics2D used to draw elements of the graph. * * @author Tom Nelson * * */ public class TransformingGraphics extends GraphicsDecorator { /** * the Function to apply */ protected BidirectionalTransformer transformer; public TransformingGraphics(BidirectionalTransformer transformer) { this(transformer, null); } public TransformingGraphics(BidirectionalTransformer Function, Graphics2D delegate) { super(delegate); this.transformer = Function; } /** * @return Returns the Function. */ public BidirectionalTransformer getTransformer() { return transformer; } /** * @param Function The Function to set. */ public void setTransformer(BidirectionalTransformer Function) { this.transformer = Function; } /** * transform the shape before letting the delegate draw it */ public void draw(Shape s) { Shape shape = ((ShapeTransformer)transformer).transform(s); delegate.draw(shape); } public void draw(Shape s, float flatness) { Shape shape = null; if(transformer instanceof ShapeFlatnessTransformer) { shape = ((ShapeFlatnessTransformer)transformer).transform(s, flatness); } else { shape = ((ShapeTransformer)transformer).transform(s); } delegate.draw(shape); } /** * transform the shape before letting the delegate fill it */ public void fill(Shape s) { Shape shape = ((ShapeTransformer)transformer).transform(s); delegate.fill(shape); } public void fill(Shape s, float flatness) { Shape shape = null; if(transformer instanceof ShapeFlatnessTransformer) { shape = ((ShapeFlatnessTransformer)transformer).transform(s, flatness); } else { shape = ((ShapeTransformer)transformer).transform(s); } delegate.fill(shape); } public boolean drawImage(Image img, int x, int y, ImageObserver observer) { Image image = null; if(transformer instanceof ShapeFlatnessTransformer) { Rectangle2D r = new Rectangle2D.Double(x,y,img.getWidth(observer),img.getHeight(observer)); Rectangle2D s = ((ShapeTransformer)transformer).transform(r).getBounds2D(); image = img.getScaledInstance((int)s.getWidth(), (int)s.getHeight(), Image.SCALE_SMOOTH); x = (int) s.getMinX(); y = (int) s.getMinY(); } else { image = img; } return delegate.drawImage(image, x, y, observer); } public boolean drawImage(Image img, AffineTransform at, ImageObserver observer) { Image image = null; int x = (int)at.getTranslateX(); int y = (int)at.getTranslateY(); if(transformer instanceof ShapeFlatnessTransformer) { Rectangle2D r = new Rectangle2D.Double(x,y,img.getWidth(observer),img.getHeight(observer)); Rectangle2D s = ((ShapeTransformer)transformer).transform(r).getBounds2D(); image = img.getScaledInstance((int)s.getWidth(), (int)s.getHeight(), Image.SCALE_SMOOTH); x = (int) s.getMinX(); y = (int) s.getMinY(); at.setToTranslation(s.getMinX(), s.getMinY()); } else { image = img; } return delegate.drawImage(image, at, observer); } /** * transform the shape before letting the delegate apply 'hit' * with it */ public boolean hit(Rectangle rect, Shape s, boolean onStroke) { Shape shape = ((ShapeTransformer)transformer).transform(s); return delegate.hit(rect, shape, onStroke); } public Graphics create() { return delegate.create(); } public void dispose() { delegate.dispose(); } } ViewLensSupport.java000066400000000000000000000077731276402340000367400ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.visualization.transform.shape; import java.awt.Dimension; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.picking.ViewLensShapePickSupport; import edu.uci.ics.jung.visualization.renderers.Renderer; import edu.uci.ics.jung.visualization.renderers.ReshapingEdgeRenderer; import edu.uci.ics.jung.visualization.transform.AbstractLensSupport; import edu.uci.ics.jung.visualization.transform.LensSupport; import edu.uci.ics.jung.visualization.transform.LensTransformer; /** * Uses a LensTransformer to use in the view * transform. This one will distort Vertex shapes. * * @author Tom Nelson * * */ public class ViewLensSupport extends AbstractLensSupport implements LensSupport { protected RenderContext renderContext; protected GraphicsDecorator lensGraphicsDecorator; protected GraphicsDecorator savedGraphicsDecorator; protected GraphElementAccessor pickSupport; protected Renderer.Edge savedEdgeRenderer; protected Renderer.Edge reshapingEdgeRenderer; public ViewLensSupport(VisualizationViewer vv, LensTransformer lensTransformer, ModalGraphMouse lensGraphMouse) { super(vv, lensGraphMouse); this.renderContext = vv.getRenderContext(); this.pickSupport = renderContext.getPickSupport(); this.savedGraphicsDecorator = renderContext.getGraphicsContext(); this.lensTransformer = lensTransformer; Dimension d = vv.getSize(); lensTransformer.setViewRadius(d.width/5); this.lensGraphicsDecorator = new TransformingFlatnessGraphics(lensTransformer); this.savedEdgeRenderer = vv.getRenderer().getEdgeRenderer(); this.reshapingEdgeRenderer = new ReshapingEdgeRenderer(); this.reshapingEdgeRenderer.setEdgeArrowRenderingSupport(savedEdgeRenderer.getEdgeArrowRenderingSupport()); } public void activate() { lensTransformer.setDelegate(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)); if(lens == null) { lens = new Lens(lensTransformer); } if(lensControls == null) { lensControls = new LensControls(lensTransformer); } renderContext.setPickSupport(new ViewLensShapePickSupport(vv)); lensTransformer.setDelegate(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)); vv.getRenderContext().getMultiLayerTransformer().setTransformer(Layer.VIEW, lensTransformer); this.renderContext.setGraphicsContext(lensGraphicsDecorator); vv.getRenderer().setEdgeRenderer(reshapingEdgeRenderer); vv.prependPreRenderPaintable(lens); vv.addPostRenderPaintable(lensControls); vv.setGraphMouse(lensGraphMouse); vv.setToolTipText(instructions); vv.repaint(); } public void deactivate() { // savedViewTransformer.setTransform(lensTransformer.getDelegate().getTransform()); // vv.setViewTransformer(savedViewTransformer); renderContext.setPickSupport(pickSupport); vv.getRenderContext().getMultiLayerTransformer().setTransformer(Layer.VIEW, lensTransformer.getDelegate()); vv.removePreRenderPaintable(lens); vv.removePostRenderPaintable(lensControls); this.renderContext.setGraphicsContext(savedGraphicsDecorator); vv.setRenderContext(renderContext); vv.setToolTipText(defaultToolTipText); vv.setGraphMouse(graphMouse); vv.getRenderer().setEdgeRenderer(savedEdgeRenderer); vv.repaint(); } }package.html000066400000000000000000000005321276402340000352070ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape

        Visualization mechanisms related to transformation of graph element shapes. jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/000077500000000000000000000000001276402340000306475ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/Animator.java000066400000000000000000000027551276402340000332750ustar00rootroot00000000000000/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * */ package edu.uci.ics.jung.visualization.util; import edu.uci.ics.jung.algorithms.util.IterativeContext; /** * * * @author Tom Nelson - tomnelson@dev.java.net * */ public class Animator implements Runnable { protected IterativeContext process; protected boolean stop; protected Thread thread; /** * how long the relaxer thread pauses between iteration loops. */ protected long sleepTime = 10L; public Animator(IterativeContext process) { this(process, 10L); } public Animator(IterativeContext process, long sleepTime) { this.process = process; this.sleepTime = sleepTime; } /** * @return the relaxer thread sleep time */ public long getSleepTime() { return sleepTime; } /** * @param sleepTime the relaxer thread sleep time to set */ public void setSleepTime(long sleepTime) { this.sleepTime = sleepTime; } public void start() { // in case its running stop(); stop = false; thread = new Thread(this); thread.setPriority(Thread.MIN_PRIORITY); thread.start(); } public synchronized void stop() { stop = true; } public void run() { while (!process.done() && !stop) { process.step(); if (stop) return; try { Thread.sleep(sleepTime); } catch (InterruptedException ie) { } } } } ArrowFactory.java000066400000000000000000000041011276402340000340510ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/* * Created on Oct 19, 2004 * * Copyright (c) 2004, The JUNG Authors * * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * https://github.com/jrtom/jung/blob/master/LICENSE for a description. */ package edu.uci.ics.jung.visualization.util; import java.awt.geom.GeneralPath; /** * A utility class for creating arrowhead shapes. * * @author Joshua O'Madadhain */ public class ArrowFactory { /** * Returns an arrowhead in the shape of a simple isosceles triangle * with the specified base and height measurements. It is placed * with the vertical axis along the negative x-axis, with its base * centered on (0,0). * * @param base the width of the arrow's base * @param height the arrow's height * @return a path in the form of an isosceles triangle with dimensions {@code (base, height)} */ public static GeneralPath getWedgeArrow(float base, float height) { GeneralPath arrow = new GeneralPath(); arrow.moveTo(0,0); arrow.lineTo( - height, base/2.0f); arrow.lineTo( - height, -base/2.0f); arrow.lineTo( 0, 0 ); return arrow; } /** * Returns an arrowhead in the shape of an isosceles triangle * with an isoceles-triangle notch taken out of the base, * with the specified base and height measurements. It is placed * with the vertical axis along the negative x-axis, with its base * centered on (0,0). * * @param base the width of the arrow's base * @param height the arrow's height * @param notch_height the height of the arrow's notch * @return a path in the form of a notched isosceles triangle */ public static GeneralPath getNotchedArrow(float base, float height, float notch_height) { GeneralPath arrow = new GeneralPath(); arrow.moveTo(0,0); arrow.lineTo(-height, base/2.0f); arrow.lineTo(-(height - notch_height), 0); arrow.lineTo(-height, -base/2.0f); arrow.lineTo(0,0); return arrow; } } jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/Caching.java000066400000000000000000000011001276402340000330360ustar00rootroot00000000000000/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * */ package edu.uci.ics.jung.visualization.util; /** * Interface to provide external controls to an * implementing class that manages a cache. * @author Tom Nelson - tomnelson@dev.java.net * */ public interface Caching { /** * ititialize resources for a cache * */ void init(); /** * clear cache * */ void clear(); } ChangeEventSupport.java000066400000000000000000000020061276402340000352150ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 18, 2005 */ package edu.uci.ics.jung.visualization.util; import javax.swing.event.ChangeListener; /** * the implementing class provides support for ChangeEvents. * * @author Tom Nelson - tomnelson@dev.java.net * */ public interface ChangeEventSupport { void addChangeListener(ChangeListener l); /** * Removes a ChangeListener. * @param l the listener to be removed */ void removeChangeListener(ChangeListener l); /** * Returns an array of all the ChangeListeners added * with addChangeListener(). * * @return all of the ChangeListeners added or an empty * array if no listeners have been added */ ChangeListener[] getChangeListeners(); void fireStateChanged(); }DefaultChangeEventSupport.java000066400000000000000000000046431276402340000365330ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Aug 18, 2005 */ package edu.uci.ics.jung.visualization.util; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; /** * Basic implementation of ChangeEventSupport, using * standard jdk classes * * @author Tom Nelson - tomnelson@dev.java.net * */ public class DefaultChangeEventSupport implements ChangeEventSupport { Object eventSource; /** * holds the registered listeners */ protected EventListenerList listenerList = new EventListenerList(); /** * Only one ChangeEvent is needed * instance since the * event's only state is the source property. The source of events * generated is always "this". */ protected transient ChangeEvent changeEvent; public DefaultChangeEventSupport(Object eventSource) { this.eventSource = eventSource; } public void addChangeListener(ChangeListener l) { listenerList.add(ChangeListener.class, l); } public void removeChangeListener(ChangeListener l) { listenerList.remove(ChangeListener.class, l); } public ChangeListener[] getChangeListeners() { return listenerList.getListeners(ChangeListener.class); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created. * The primary listeners will be views that need to be repainted * because of changes in this model instance * @see EventListenerList */ public void fireStateChanged() { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==ChangeListener.class) { // Lazily create the event: if (changeEvent == null) changeEvent = new ChangeEvent(eventSource); ((ChangeListener)listeners[i+1]).stateChanged(changeEvent); } } } } GeneralPathAsString.java000066400000000000000000000033401276402340000353000ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/utilpackage edu.uci.ics.jung.visualization.util; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; public class GeneralPathAsString { public static String toString(GeneralPath newPath) { StringBuilder sb = new StringBuilder(); float[] coords = new float[6]; for(PathIterator iterator=newPath.getPathIterator(null); iterator.isDone() == false; iterator.next()) { int type = iterator.currentSegment(coords); switch(type) { case PathIterator.SEG_MOVETO: Point2D p = new Point2D.Float(coords[0], coords[1]); sb.append("moveTo "+p+"--"); break; case PathIterator.SEG_LINETO: p = new Point2D.Float(coords[0], coords[1]); sb.append("lineTo "+p+"--"); break; case PathIterator.SEG_QUADTO: p = new Point2D.Float(coords[0], coords[1]); Point2D q = new Point2D.Float(coords[2], coords[3]); sb.append("quadTo "+p+" controlled by "+q); break; case PathIterator.SEG_CUBICTO: p = new Point2D.Float(coords[0], coords[1]); q = new Point2D.Float(coords[2], coords[3]); Point2D r = new Point2D.Float(coords[4], coords[5]); sb.append("cubeTo "+p+" controlled by "+q+","+r); break; case PathIterator.SEG_CLOSE: newPath.closePath(); sb.append("close"); break; } } return sb.toString(); } } ImageShapeUtils.java000066400000000000000000000071051276402340000344620ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/* * Copyright (c) 2015, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Nov 7, 2015 */ package edu.uci.ics.jung.visualization.util; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.IOException; import javax.imageio.ImageIO; import edu.uci.ics.jung.visualization.FourPassImageShaper; public class ImageShapeUtils { /** * Given the fileName of an image, possibly with a transparent * background, return the Shape of the opaque part of the image * @param fileName name of the image, loaded from the classpath * @return the Shape */ public static Shape getShape(String fileName) { return getShape(fileName, Integer.MAX_VALUE); } /** * Given the fileName of an image, possibly with a transparent * background, return the Shape of the opaque part of the image * @param fileName name of the image, loaded from the classpath * @param max the maximum dimension of the traced shape * @return the Shape * * @see #getShape(Image, int) */ public static Shape getShape(String fileName, int max) { BufferedImage image = null; try { image = ImageIO.read(ImageShapeUtils.class.getResource(fileName)); } catch(IOException ex) { ex.printStackTrace(); } return getShape(image, max); } /** * Given an image, possibly with a transparent background, return * the Shape of the opaque part of the image * @param image the image whose shape is to be returned * @return the Shape */ public static Shape getShape(Image image) { return getShape(image, Integer.MAX_VALUE); } public static Shape getShape(Image image, int max) { BufferedImage bi = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB); Graphics g = bi.createGraphics(); g.drawImage(image, 0, 0, null); g.dispose(); return getShape(bi, max); } /** * Given an image, possibly with a transparent background, return * the Shape of the opaque part of the image * * If the image is larger than max in either direction, scale the * image down to max-by-max, do the trace (on fewer points) then * scale the resulting shape back up to the size of the original * image. * * @param image the image to trace * @param max used to restrict number of points in the resulting shape * @return the Shape */ public static Shape getShape(BufferedImage image, int max) { float width = image.getWidth(); float height = image.getHeight(); if(width > max || height > max) { BufferedImage smaller = new BufferedImage(max, max, BufferedImage.TYPE_INT_ARGB); Graphics g = smaller.createGraphics(); AffineTransform at = AffineTransform.getScaleInstance(max/width,max/height); AffineTransform back = AffineTransform.getScaleInstance(width/max,height/max); Graphics2D g2 = (Graphics2D)g; g2.drawImage(image, at, null); g2.dispose(); return back.createTransformedShape(getShape(smaller)); } else { return FourPassImageShaper.getShape(image); } } } LabelWrapper.java000066400000000000000000000037341276402340000340220ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/* * Copyright (c) 2005, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * */ package edu.uci.ics.jung.visualization.util; import com.google.common.base.Function; /** * A utility to wrap long lines, creating html strings * with line breaks at a settable max line length * * @author Tom Nelson - tomnelson@dev.java.net * */ public class LabelWrapper implements Function { int lineLength; public static final String breaker = "

        "; /** * Create an instance with default line break length = 10 * */ public LabelWrapper() { this(10); } /** * Create an instance with passed line break length * @param lineLength the max length for lines */ public LabelWrapper(int lineLength) { this.lineLength = lineLength; } /** * call 'wrap' to transform the passed String */ public String apply(String str) { if(str != null) { return wrap(str); } else { return null; } } /** * line-wrap the passed String as an html string with * break Strings inserted. * * @param str * @return */ private String wrap(String str) { StringBuilder buf = new StringBuilder(str); int len = lineLength; while(len < buf.length()) { int idx = buf.lastIndexOf(" ", len); if(idx != -1) { buf.replace(idx, idx+1, breaker); len = idx + breaker.length() +lineLength; } else { buf.insert(len, breaker); len += breaker.length() + lineLength; } } buf.insert(0, ""); return buf.toString(); } public static void main(String[] args) { String[] lines = { "This is a line with many short words that I will break into shorter lines.", "thisisalinewithnobreakssowhoknowswhereitwillwrap", "short line" }; LabelWrapper w = new LabelWrapper(10); for(int i=0; i implements EdgeIndexFunction { protected Map edge_index = new HashMap(); protected Predicate predicate; /** * @param graph the graph with respect to which the index is calculated */ private PredicatedParallelEdgeIndexFunction() { } public static PredicatedParallelEdgeIndexFunction getInstance() { return new PredicatedParallelEdgeIndexFunction(); } /** * Returns the index for the specified edge. * Calculates the indices for e and for all edges parallel * to e. * @param graph the graph with respect to which the index is calculated * @param e the edge whose index is to be calculated * * @return the index of the edge with respect to this index function */ public int getIndex(Graph graph, E e) { if(predicate.apply(e)) { return 0; } Integer index = edge_index.get(e); if(index == null) { Pair endpoints = graph.getEndpoints(e); V u = endpoints.getFirst(); V v = endpoints.getSecond(); if(u.equals(v)) { index = getIndex(graph, e, v); } else { index = getIndex(graph, e, u, v); } } return index.intValue(); } protected int getIndex(Graph graph, E e, V v, V u) { Collection commonEdgeSet = new HashSet(graph.getIncidentEdges(u)); commonEdgeSet.retainAll(graph.getIncidentEdges(v)); for(Iterator iterator=commonEdgeSet.iterator(); iterator.hasNext(); ) { E edge = iterator.next(); Pair ep = graph.getEndpoints(edge); V first = ep.getFirst(); V second = ep.getSecond(); // remove loops if(first.equals(second) == true) { iterator.remove(); } // remove edges in opposite direction if(first.equals(v) == false) { iterator.remove(); } } int count=0; for(E other : commonEdgeSet) { if(e.equals(other) == false) { edge_index.put(other, count); count++; } } edge_index.put(e, count); return count; } protected int getIndex(Graph graph, E e, V v) { Collection commonEdgeSet = new HashSet(); for(E another : graph.getIncidentEdges(v)) { V u = graph.getOpposite(v, another); if(u.equals(v)) { commonEdgeSet.add(another); } } int count=0; for(E other : commonEdgeSet) { if(e.equals(other) == false) { edge_index.put(other, count); count++; } } edge_index.put(e, count); return count; } public Predicate getPredicate() { return predicate; } public void setPredicate(Predicate predicate) { this.predicate = predicate; } /** * Resets the indices for this edge and its parallel edges. * Should be invoked when an edge parallel to e * has been added or removed in this graph. * @param graph the graph with respect to which the index is calculated * @param e the edge whose indices are to be reset for {@code graph} */ public void reset(Graph graph, E e) { Pair endpoints = graph.getEndpoints(e); getIndex(graph, e, endpoints.getFirst()); getIndex(graph, e, endpoints.getFirst(), endpoints.getSecond()); } /** * Clears all edge indices for all edges in all graphs. * Does not recalculate the indices. */ public void reset() { edge_index.clear(); } } VertexShapeFactory.java000066400000000000000000000205061276402340000352240ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/* * Copyright (c) 2003, The JUNG Authors * All rights reserved. * * This software is open-source under the BSD license; see either "license.txt" * or https://github.com/jrtom/jung/blob/master/LICENSE for a description. * * Created on Jul 20, 2004 */ package edu.uci.ics.jung.visualization.util; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import com.google.common.base.Function; import com.google.common.base.Functions; /** * A utility class for generating Shapes for drawing vertices. * The available shapes include rectangles, rounded rectangles, ellipses, * regular polygons, and regular stars. The dimensions of the requested * shapes are defined by the specified vertex size function (specified by * a {@code Function}) and vertex aspect ratio function * (specified by a {@code Function}) implementations: the width * of the bounding box of the shape is given by the vertex size, and the * height is given by the size multiplied by the vertex's aspect ratio. * * @author Joshua O'Madadhain */ public class VertexShapeFactory { protected Function vsf; protected Function varf; /** * Creates an instance with the specified vertex size and aspect ratio functions. * * @param vsf provides a size (width) for each vertex * @param varf provides a height/width ratio for each vertex */ public VertexShapeFactory(Function vsf, Function varf) { this.vsf = vsf; this.varf = varf; } /** * Creates a VertexShapeFactory with a constant size of * 10 and a constant aspect ratio of 1. */ public VertexShapeFactory() { this(Functions.constant(10), Functions.constant(1.0f)); } private static final Rectangle2D theRectangle = new Rectangle2D.Float(); /** * Returns a Rectangle2D whose width and * height are defined by this instance's size and * aspect ratio functions for this vertex. * * @param v the vertex for which the shape will be drawn * @return a rectangle for this vertex */ public Rectangle2D getRectangle(V v) { float width = vsf.apply(v); float height = width * varf.apply(v); float h_offset = -(width / 2); float v_offset = -(height / 2); theRectangle.setFrame(h_offset, v_offset, width, height); return theRectangle; } private static final Ellipse2D theEllipse = new Ellipse2D.Float(); /** * Returns a Ellipse2D whose width and * height are defined by this instance's size and * aspect ratio functions for this vertex. * * @param v the vertex for which the shape will be drawn * @return an ellipse for this vertex */ public Ellipse2D getEllipse(V v) { theEllipse.setFrame(getRectangle(v)); return theEllipse; } private static final RoundRectangle2D theRoundRectangle = new RoundRectangle2D.Float(); /** * Returns a RoundRectangle2D whose width and * height are defined by this instance's size and * aspect ratio functions for this vertex. The arc size is * set to be half the minimum of the height and width of the frame. * * @param v the vertex for which the shape will be drawn * @return an round rectangle for this vertex */ public RoundRectangle2D getRoundRectangle(V v) { Rectangle2D frame = getRectangle(v); float arc_size = (float)Math.min(frame.getHeight(), frame.getWidth()) / 2; theRoundRectangle.setRoundRect(frame.getX(), frame.getY(), frame.getWidth(), frame.getHeight(), arc_size, arc_size); return theRoundRectangle; } private static final GeneralPath thePolygon = new GeneralPath(); /** * Returns a regular num_sides-sided * Polygon whose bounding * box's width and height are defined by this instance's size and * aspect ratio functions for this vertex. * * @param v the vertex for which the shape will be drawn * @param num_sides the number of sides of the polygon; must be ≥ 3. * @return a regular polygon for this vertex */ public Shape getRegularPolygon(V v, int num_sides) { if (num_sides < 3) throw new IllegalArgumentException("Number of sides must be >= 3"); Rectangle2D frame = getRectangle(v); float width = (float)frame.getWidth(); float height = (float)frame.getHeight(); // generate coordinates double angle = 0; thePolygon.reset(); thePolygon.moveTo(0,0); thePolygon.lineTo(width, 0); double theta = (2 * Math.PI) / num_sides; for (int i = 2; i < num_sides; i++) { angle -= theta; float delta_x = (float) (width * Math.cos(angle)); float delta_y = (float) (width * Math.sin(angle)); Point2D prev = thePolygon.getCurrentPoint(); thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y); } thePolygon.closePath(); // scale polygon to be right size, translate to center at (0,0) Rectangle2D r = thePolygon.getBounds2D(); double scale_x = width / r.getWidth(); double scale_y = height / r.getHeight(); float translationX = (float) (r.getMinX() + r.getWidth()/2); float translationY = (float) (r.getMinY() + r.getHeight()/2); AffineTransform at = AffineTransform.getScaleInstance(scale_x, scale_y); at.translate(-translationX, -translationY); Shape shape = at.createTransformedShape(thePolygon); return shape; } /** * Returns a regular Polygon of num_points * points whose bounding * box's width and height are defined by this instance's size and * aspect ratio functions for this vertex. * * @param v the vertex for which the shape will be drawn * @param num_points the number of points of the polygon; must be ≥ 5. * @return an star shape for this vertex */ public Shape getRegularStar(V v, int num_points) { if (num_points < 5) throw new IllegalArgumentException("Number of sides must be >= 5"); Rectangle2D frame = getRectangle(v); float width = (float) frame.getWidth(); float height = (float) frame.getHeight(); // generate coordinates double theta = (2 * Math.PI) / num_points; double angle = -theta/2; thePolygon.reset(); thePolygon.moveTo(0,0); float delta_x = width * (float)Math.cos(angle); float delta_y = width * (float)Math.sin(angle); Point2D prev = thePolygon.getCurrentPoint(); thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y); for (int i = 1; i < num_points; i++) { angle += theta; delta_x = width * (float)Math.cos(angle); delta_y = width * (float)Math.sin(angle); prev = thePolygon.getCurrentPoint(); thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y); angle -= theta*2; delta_x = width * (float)Math.cos(angle); delta_y = width * (float)Math.sin(angle); prev = thePolygon.getCurrentPoint(); thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y); } thePolygon.closePath(); // scale polygon to be right size, translate to center at (0,0) Rectangle2D r = thePolygon.getBounds2D(); double scale_x = width / r.getWidth(); double scale_y = height / r.getHeight(); float translationX = (float) (r.getMinX() + r.getWidth()/2); float translationY = (float) (r.getMinY() + r.getHeight()/2); AffineTransform at = AffineTransform.getScaleInstance(scale_x, scale_y); at.translate(-translationX, -translationY); Shape shape = at.createTransformedShape(thePolygon); return shape; } } jung-jung-2.1.1/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/package.html000066400000000000000000000004611276402340000331310ustar00rootroot00000000000000

        Utilities for graph visualization. jung-jung-2.1.1/jung-visualization/src/site/000077500000000000000000000000001276402340000210125ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/site/site.xml000066400000000000000000000004441276402340000225020ustar00rootroot00000000000000 ${project.name}

        jung-jung-2.1.1/jung-visualization/src/test/000077500000000000000000000000001276402340000210255ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/test/java/000077500000000000000000000000001276402340000217465ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/test/java/edu/000077500000000000000000000000001276402340000225235ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/test/java/edu/uci/000077500000000000000000000000001276402340000233035ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/test/java/edu/uci/ics/000077500000000000000000000000001276402340000240615ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/test/java/edu/uci/ics/jung/000077500000000000000000000000001276402340000250245ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/test/java/edu/uci/ics/jung/visualization/000077500000000000000000000000001276402340000277255ustar00rootroot00000000000000BasicVisualizationServerTest.java000066400000000000000000000016351276402340000363500ustar00rootroot00000000000000jung-jung-2.1.1/jung-visualization/src/test/java/edu/uci/ics/jung/visualizationpackage edu.uci.ics.jung.visualization; import edu.uci.ics.jung.algorithms.layout.CircleLayout; import edu.uci.ics.jung.graph.SparseGraph; import edu.uci.ics.jung.visualization.picking.PickedState; import junit.framework.TestCase; public class BasicVisualizationServerTest extends TestCase { /* * Previously, a bug was introduced where the RenderContext in BasicVisualizationServer was reassigned, resulting * in data like pickedVertexState to be lost. */ public void testRenderContextNotOverridden() { SparseGraph graph = new SparseGraph(); CircleLayout layout = new CircleLayout(graph); BasicVisualizationServer server = new BasicVisualizationServer(layout); PickedState pickedVertexState = server.getRenderContext().getPickedVertexState(); assertNotNull(pickedVertexState); } } jung-jung-2.1.1/jung-visualization/src/test/java/edu/uci/ics/jung/visualization/TestImageShaper.java000066400000000000000000000032451276402340000336210ustar00rootroot00000000000000package edu.uci.ics.jung.visualization; import java.awt.Shape; import java.awt.geom.PathIterator; import java.awt.image.BufferedImage; import edu.uci.ics.jung.visualization.util.ImageShapeUtils; import junit.framework.TestCase; public class TestImageShaper extends TestCase { BufferedImage image; @Override protected void setUp() throws Exception { super.setUp(); int width = 6; int height = 5; image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); for(int i=0; i vv; float crossover; float scale; @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void setUp() { sc = new CrossoverScalingControl(); vv = new BasicVisualizationServer(new FRLayout(new SparseGraph())); } public void testCrossover() { crossover = 2.0f; scale = .5f; sc.setCrossover(crossover); sc.scale(vv, scale, new Point2D.Double()); // System.err.println("crossover="+crossover); // System.err.println("scale="+scale); // System.err.println("layout scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale()); // System.err.println("view scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getScale()); } public void testCrossover2() { crossover = 2.0f; scale = 1.5f; sc.setCrossover(crossover); sc.scale(vv, scale, new Point2D.Double()); // System.err.println("crossover="+crossover); // System.err.println("scale="+scale); // System.err.println("layout scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale()); // System.err.println("view scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getScale()); } public void testCrossover3() { crossover = 2.0f; scale = 2.5f; sc.setCrossover(crossover); sc.scale(vv, scale, new Point2D.Double()); // System.err.println("crossover="+crossover); // System.err.println("scale="+scale); // System.err.println("layout scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale()); // System.err.println("view scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getScale()); } public void testCrossover4() { crossover = 0.5f; scale = 2.5f; sc.setCrossover(crossover); sc.scale(vv, scale, new Point2D.Double()); // System.err.println("crossover="+crossover); // System.err.println("scale="+scale); // System.err.println("layout scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale()); // System.err.println("view scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getScale()); } public void testCrossover5() { crossover = 0.5f; scale = .3f; sc.setCrossover(crossover); sc.scale(vv, scale, new Point2D.Double()); // System.err.println("crossover="+crossover); // System.err.println("scale="+scale); // System.err.println("layout scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale()); // System.err.println("view scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getScale()); } } jung-jung-2.1.1/pom.xml000066400000000000000000000226471276402340000147450ustar00rootroot00000000000000 4.0.0 net.sf.jung jung-parent 2.1.1 pom JUNG (parent metadata project) http://jrtom.github.io/jung/ The BSD License https://github.com/jrtom/jung/blob/master/LICENSE repo https://github.com/jrtom/jung scm:git:git://github.com/jrtom/jung.git scm:git:git://github.com/jrtom/jung.git JUNG the Java Universal Network/Graph Framework--is a software library that provides a common and extendible language for the modeling, analysis, and visualization of data that can be represented as a graph or network. It is written in Java, which allows JUNG-based applications to make use of the extensive built-in capabilities of the Java API, as well as those of other existing third-party Java libraries. The JUNG architecture is designed to support a variety of representations of entities and their relations, such as directed and undirected graphs, multi-modal graphs, graphs with parallel edges, and hypergraphs. It provides a mechanism for annotating graphs, entities, and relations with metadata. This facilitates the creation of analytic tools for complex data sets that can examine the relations between entities as well as the metadata attached to each entity and relation. The current distribution of JUNG includes implementations of a number of algorithms from graph theory, data mining, and social network analysis, such as routines for clustering, decomposition, optimization, random graph generation, statistical analysis, and calculation of network distances, flows, and importance measures (centrality, PageRank, HITS, etc.). JUNG also provides a visualization framework that makes it easy to construct tools for the interactive exploration of network data. Users can use one of the layout algorithms provided, or use the framework to create their own custom layouts. In addition, filtering mechanisms are provided which allow users to focus their attention, or their algorithms, on specific portions of the graph. jung-api jung-graph-impl jung-algorithms jung-io jung-visualization jung-samples 3.1.1 UTF-8 UTF-8 1.6 4.12 19.0 2.7 3.5.1 1.6 2.6 2.10.4 2.5 3.6 2.5.3 2.19.1 3.0.1 eflat Joshua joshua.omadadhain+maven@gmail.com https://sites.google.com/site/joshuaomadadhain Google http://www.google.com -8 Owner Developer offkey Danyel offkey@sf.net Microsoft Research http://research.microsoft.com -8 Owner Developer tomnelson Tom tomnelson@sf.net ICCI http://www.intergratedcc.com -5 Developer junit junit ${junit.version} com.google.guava guava ${guava.version} maven-compiler-plugin ${compiler.plugin.version} ${java.version} ${java.version} maven-jar-plugin ${jar.plugin.version} true maven-release-plugin {release.plugin.version} forked-path false ${arguments} -Psonatype-oss-release maven-javadoc-plugin ${javadoc.plugin.version} true http://docs.oracle.com/javaee/6/api/ http://docs.oracle.com/javase/6/docs/api/ maven-surefire-report-plugin ${surefire.plugin.version} org.codehaus.mojo cobertura-maven-plugin ${cobertura.plugin.version} maven-pmd-plugin ${pmd.plugin.version} ${java.version} true maven-jxr-plugin ${jxr.plugin.version} sonatype-nexus-snapshots Sonatype Nexus Snapshots https://oss.sonatype.org/content/repositories/snapshots/ sonatype-nexus-staging Nexus Release Repository https://oss.sonatype.org/service/local/staging/deploy/maven2/ sonatype-oss-release maven-source-plugin ${source.plugin.version} attach-sources jar-no-fork maven-javadoc-plugin ${javadoc.plugin.version} attach-javadocs jar maven-gpg-plugin ${gpg.plugin.version} sign-artifacts verify sign jung-jung-2.1.1/tools/000077500000000000000000000000001276402340000145555ustar00rootroot00000000000000jung-jung-2.1.1/tools/deploy_snapshots.sh000077500000000000000000000006371276402340000205200ustar00rootroot00000000000000# see https://coderwall.com/p/9b_lfq if [ "$TRAVIS_REPO_SLUG" == "jrtom/jung" ] && \ [ "$TRAVIS_JDK_VERSION" == "oraclejdk7" ] && \ [ "$TRAVIS_PULL_REQUEST" == "false" ] && \ [ "$TRAVIS_BRANCH" == "master" ]; then echo -e "Publishing maven snapshot...\n" mvn clean source:jar deploy --settings="tools/settings.xml" -DskipTests=true -Dmaven.javadoc.skip=true echo -e "Published maven snapshot" fi jung-jung-2.1.1/tools/mvn-deploy.sh000077500000000000000000000007451276402340000172140ustar00rootroot00000000000000#!/bin/bash if [ $# -lt 1 ]; then echo "usage $0 [ ...]" exit 1; fi key=$1 shift #validate key keystatus=$(gpg --list-keys | grep ${key} | awk '{print $1}') if [ "${keystatus}" != "pub" ]; then echo "Could not find public key with label ${key}" echo -n "Available keys from: " gpg --list-keys | grep --invert-match '^sub' exit 64 fi mvn "$@" -P '!examples' -P sonatype-oss-release \ -Dgpg.skip=false -Dgpg.keyname=${key} \ clean site:jar deploy jung-jung-2.1.1/tools/settings.xml000066400000000000000000000006551276402340000171450ustar00rootroot00000000000000 sonatype-nexus-snapshots ${env.CI_DEPLOY_USERNAME} ${env.CI_DEPLOY_PASSWORD}