first commit
This commit is contained in:
BIN
perl/lib/Graph-Easy-0.76.tar.gz
Normal file
BIN
perl/lib/Graph-Easy-0.76.tar.gz
Normal file
Binary file not shown.
58
perl/lib/Graph-Easy-0.76/Build.PL
Normal file
58
perl/lib/Graph-Easy-0.76/Build.PL
Normal file
@@ -0,0 +1,58 @@
|
||||
# We need at least Perl 5.8.2 for proper Unicode support
|
||||
use 5.008002;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Spec;
|
||||
use lib File::Spec->catdir(File::Spec->curdir(), "inc");
|
||||
|
||||
# recommends 'Graph::Easy::As_svg' => 0.23;
|
||||
use Test::Run::Builder;
|
||||
|
||||
my $build = Test::Run::Builder->new(
|
||||
'module_name' => "Graph::Easy",
|
||||
configure_requires =>
|
||||
{
|
||||
'Module::Build' => '0.36',
|
||||
},
|
||||
build_requires =>
|
||||
{
|
||||
'Test::More' => '0.62',
|
||||
},
|
||||
'requires' =>
|
||||
{
|
||||
'Scalar::Util' => '1.13',
|
||||
'perl' => '5.8.2',
|
||||
'strict' => 0,
|
||||
'warnings' => 0,
|
||||
'vars' => 0,
|
||||
},
|
||||
'recommends' =>
|
||||
{
|
||||
'Graph::Easy::As_svg' => 0.23
|
||||
},
|
||||
'license' => "gpl",
|
||||
meta_merge =>
|
||||
{
|
||||
resources =>
|
||||
{
|
||||
repository => "https://bitbucket.org/shlomif/perl-graph-easy",
|
||||
},
|
||||
keywords =>
|
||||
[
|
||||
'generation',
|
||||
'graph',
|
||||
'graphviz',
|
||||
'text generation',
|
||||
'text',
|
||||
],
|
||||
},
|
||||
create_makefile_pl => 'traditional',
|
||||
'scripts' =>
|
||||
[
|
||||
'bin/graph-easy',
|
||||
],
|
||||
);
|
||||
|
||||
$build->create_build_script;
|
||||
2007
perl/lib/Graph-Easy-0.76/CHANGES
Normal file
2007
perl/lib/Graph-Easy-0.76/CHANGES
Normal file
File diff suppressed because it is too large
Load Diff
73
perl/lib/Graph-Easy-0.76/INSTALL
Normal file
73
perl/lib/Graph-Easy-0.76/INSTALL
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
=pod
|
||||
|
||||
=head1 Graph-Easy
|
||||
|
||||
I<Note:> This package was formerly known as Graph::Simple.
|
||||
|
||||
=head1 INSTALLATION
|
||||
|
||||
=head2 Linux, Unix, and similar systems:
|
||||
|
||||
To install this module type the following:
|
||||
|
||||
Untar the package:
|
||||
|
||||
tar -xzf Graph-Easy-x.xx.tar.gz
|
||||
|
||||
where x.xx is the current revision. Then change into the directory:
|
||||
|
||||
chdir Graph-Easy-x.xx/
|
||||
|
||||
Proceed with creating the makefile and running the test suite:
|
||||
|
||||
perl Makefile.PL
|
||||
make test
|
||||
|
||||
If all tests pass, install the package as root user:
|
||||
|
||||
sudo make install
|
||||
|
||||
=head2 Windows
|
||||
|
||||
You need two things under Windows:
|
||||
|
||||
=over 2
|
||||
|
||||
=item Perl
|
||||
|
||||
You can get it from ActiveState:
|
||||
|
||||
http://activestate.com/store/activeperl/download
|
||||
|
||||
=item nmake
|
||||
|
||||
See here for how to get and install nmake:
|
||||
|
||||
http://johnbokma.com/perl/make-for-windows.html
|
||||
|
||||
=back
|
||||
|
||||
After installing C<Perl> and C<nmake>, you can install Graph::Easy normally, just replacing
|
||||
C<make> with C<nmake> in the install instructions above:
|
||||
|
||||
perl Makefile.PL
|
||||
nmake
|
||||
nmake test
|
||||
nmake install
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
You also might want to install the following packages from CPAN:
|
||||
|
||||
Graph::Easy::As_svg provide SVG (Scalable Vector Graphics) output
|
||||
Graph::Easy::Manual comprehensive manual in POD and HTML
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2007 by Tels L<http://bloodgate.com/perl/>
|
||||
|
||||
This library is free software; you can redistribute it and/or modify
|
||||
it under the same terms of the GPL version 2.
|
||||
|
||||
=cut
|
||||
335
perl/lib/Graph-Easy-0.76/LICENSE
Normal file
335
perl/lib/Graph-Easy-0.76/LICENSE
Normal file
@@ -0,0 +1,335 @@
|
||||
=pod
|
||||
|
||||
=head1 LICENSES
|
||||
|
||||
=head2 Colorschemes
|
||||
|
||||
This product includes color specifications and designs developed by Cynthia
|
||||
Brewer (http://colorbrewer.org/). The following license applies to them:
|
||||
|
||||
Apache-Style Software License for ColorBrewer Color Schemes v1.1
|
||||
|
||||
Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State
|
||||
University. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions as source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
2. The end-user documentation included with the redistribution, if any,
|
||||
must include the following acknowledgment:
|
||||
|
||||
This product includes color specifications and designs developed by Cynthia
|
||||
Brewer (http://colorbrewer.org/).
|
||||
|
||||
Alternately, this acknowledgment may appear in the software itself, if and
|
||||
wherever such third-party acknowledgments normally appear.
|
||||
|
||||
3. The name "ColorBrewer" must not be used to endorse or promote products
|
||||
derived from this software without prior written permission. For written
|
||||
permission, please contact Cynthia Brewer at cbrewer at psu dot edu.
|
||||
4. Products derived from this software may not be called "ColorBrewer", nor
|
||||
may "ColorBrewer" appear in their name, without prior written permission
|
||||
of Cynthia Brewer.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED 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 CYNTHIA
|
||||
BREWER, MARK HARROWER, OR THE PENNSYLVANIA STATE UNIVERSITY 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.
|
||||
|
||||
=head2 All other things included in this package
|
||||
|
||||
To the rest of the code, documentation, scripts etc. the following
|
||||
license applies:
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
=cut
|
||||
|
||||
769
perl/lib/Graph-Easy-0.76/MANIFEST
Normal file
769
perl/lib/Graph-Easy-0.76/MANIFEST
Normal file
@@ -0,0 +1,769 @@
|
||||
bench/bench.pl
|
||||
bench/serie.pl
|
||||
bench/stress.pl
|
||||
bench/test.dot
|
||||
bench/test.txt
|
||||
bin/graph-easy
|
||||
Build.PL
|
||||
CHANGES
|
||||
examples/as_ascii
|
||||
examples/as_boxart
|
||||
examples/as_boxart_html
|
||||
examples/as_graphviz
|
||||
examples/as_html
|
||||
examples/as_svg
|
||||
examples/as_txt
|
||||
examples/ascii.pl
|
||||
examples/base.css
|
||||
examples/common.pl
|
||||
examples/complex.txt
|
||||
examples/fun.tpl
|
||||
examples/history.txt
|
||||
examples/html.pl
|
||||
examples/parse
|
||||
examples/syntax.pl
|
||||
examples/syntax.tpl
|
||||
examples/wikicrawl.pl
|
||||
inc/Test/Run/Builder.pm
|
||||
INSTALL
|
||||
lib/Graph/Easy.pm
|
||||
lib/Graph/Easy/As_ascii.pm
|
||||
lib/Graph/Easy/As_graphml.pm
|
||||
lib/Graph/Easy/As_graphviz.pm
|
||||
lib/Graph/Easy/As_txt.pm
|
||||
lib/Graph/Easy/As_vcg.pm
|
||||
lib/Graph/Easy/Attributes.pm
|
||||
lib/Graph/Easy/Base.pm
|
||||
lib/Graph/Easy/Edge.pm
|
||||
lib/Graph/Easy/Edge/Cell.pm
|
||||
lib/Graph/Easy/Group.pm
|
||||
lib/Graph/Easy/Group/Anon.pm
|
||||
lib/Graph/Easy/Group/Cell.pm
|
||||
lib/Graph/Easy/Layout.pm
|
||||
lib/Graph/Easy/Layout/Chain.pm
|
||||
lib/Graph/Easy/Layout/Force.pm
|
||||
lib/Graph/Easy/Layout/Grid.pm
|
||||
lib/Graph/Easy/Layout/Path.pm
|
||||
lib/Graph/Easy/Layout/Repair.pm
|
||||
lib/Graph/Easy/Layout/Scout.pm
|
||||
lib/Graph/Easy/Node.pm
|
||||
lib/Graph/Easy/Node/Anon.pm
|
||||
lib/Graph/Easy/Node/Cell.pm
|
||||
lib/Graph/Easy/Node/Empty.pm
|
||||
lib/Graph/Easy/Parser.pm
|
||||
lib/Graph/Easy/Parser/Graphviz.pm
|
||||
lib/Graph/Easy/Parser/VCG.pm
|
||||
lib/Graph/Easy/Util.pm
|
||||
LICENSE
|
||||
Makefile.PL
|
||||
MANIFEST This list of files
|
||||
MANIFEST.SKIP
|
||||
META.json
|
||||
META.yml
|
||||
README
|
||||
scripts/bump-version-number.pl
|
||||
t/anon.t
|
||||
t/anon_group.t
|
||||
t/as_txt.t
|
||||
t/as_vcg.t
|
||||
t/ascii.t
|
||||
t/astar.t
|
||||
t/attributes.t
|
||||
t/base.t
|
||||
t/boxart.t
|
||||
t/cell.t
|
||||
t/chain.t
|
||||
t/class.t
|
||||
t/cluster.t
|
||||
t/copy.t
|
||||
t/custom.t
|
||||
t/delete.t
|
||||
t/dot/4_loose.dot
|
||||
t/drop.t
|
||||
t/easypm.t
|
||||
t/edge.t
|
||||
t/edge_cell.t
|
||||
t/fb.t
|
||||
t/fun/0000.txt
|
||||
t/fun/0010.txt
|
||||
t/fun/0011.txt
|
||||
t/fun/0020.txt
|
||||
t/fun/0030.txt
|
||||
t/fun/0131.txt
|
||||
t/fun/0200.txt
|
||||
t/fun/biofuel.txt
|
||||
t/fun/geek_dating.txt
|
||||
t/fun/overview.txt
|
||||
t/gdl.t
|
||||
t/graph-maker.t
|
||||
t/graph.t
|
||||
t/graphml.t
|
||||
t/graphml_yed.t
|
||||
t/graphviz.t
|
||||
t/group.t
|
||||
t/group/0010.txt
|
||||
t/group/0131.txt
|
||||
t/group/0230.txt
|
||||
t/gv.t
|
||||
t/heap.t
|
||||
t/html.t
|
||||
t/in/0_empty_group.txt
|
||||
t/in/0_empty_groups.txt
|
||||
t/in/10_repair.txt
|
||||
t/in/10borders.txt
|
||||
t/in/18_multiples.txt
|
||||
t/in/1_bidi_loop.txt
|
||||
t/in/1_empty_group.txt
|
||||
t/in/1_selfloop.txt
|
||||
t/in/1_selfloop_2.txt
|
||||
t/in/1_selfloop_label.txt
|
||||
t/in/1_undirected_loop.txt
|
||||
t/in/1node.txt
|
||||
t/in/25_autosplit_empty.txt
|
||||
t/in/2_autolabel.txt
|
||||
t/in/2_autosplit_empty.txt
|
||||
t/in/2_autosplit_escaped.txt
|
||||
t/in/2_bidi_astar.txt
|
||||
t/in/2_bidi_endpoint.txt
|
||||
t/in/2_class.txt
|
||||
t/in/2_classes.txt
|
||||
t/in/2_cluster.txt
|
||||
t/in/2_cluster_2.txt
|
||||
t/in/2_cluster_3.txt
|
||||
t/in/2_dot.txt
|
||||
t/in/2_dot_dot_dash.txt
|
||||
t/in/2_edges.txt
|
||||
t/in/2_flow.txt
|
||||
t/in/2_graph_label.txt
|
||||
t/in/2_group.txt
|
||||
t/in/2_group_labelpos.txt
|
||||
t/in/2_group_multicell.txt
|
||||
t/in/2_group_no_border.txt
|
||||
t/in/2_invisible_left.txt
|
||||
t/in/2_invisible_right.txt
|
||||
t/in/2_label.txt
|
||||
t/in/2_label_align.txt
|
||||
t/in/2_list_attr.txt
|
||||
t/in/2_long_labels.txt
|
||||
t/in/2_newlines.txt
|
||||
t/in/2_selfloop.txt
|
||||
t/in/2_selfloop_flow_down.txt
|
||||
t/in/2_split_bug.txt
|
||||
t/in/2_wrap.txt
|
||||
t/in/2_zeros.txt
|
||||
t/in/2nodes.txt
|
||||
t/in/3_autosplit_hang.txt
|
||||
t/in/3_bend_bug.txt
|
||||
t/in/3_cache_bug.txt
|
||||
t/in/3_cluster.txt
|
||||
t/in/3_colors.txt
|
||||
t/in/3_corrupt.txt
|
||||
t/in/3_edge_labels_from_class.txt
|
||||
t/in/3_edge_repair.txt
|
||||
t/in/3_edge_start.txt
|
||||
t/in/3_empty_group.txt
|
||||
t/in/3_flow.txt
|
||||
t/in/3_group_align_center.txt
|
||||
t/in/3_inherit.txt
|
||||
t/in/3_invisible_both.txt
|
||||
t/in/3_joining.txt
|
||||
t/in/3_joint.txt
|
||||
t/in/3_joint_short.txt
|
||||
t/in/3_list_attr.txt
|
||||
t/in/3_lists.txt
|
||||
t/in/3_nodes_5_edges.txt
|
||||
t/in/3_selfloop.txt
|
||||
t/in/3_selfloop_flip.txt
|
||||
t/in/3_selfloop_flow_down.txt
|
||||
t/in/3_selfloop_flow_left.txt
|
||||
t/in/3_selfloop_flow_up.txt
|
||||
t/in/3_split_attribute.txt
|
||||
t/in/3nodes.txt
|
||||
t/in/4_2x2nodes.txt
|
||||
t/in/4_att.txt
|
||||
t/in/4_autosplit_class.txt
|
||||
t/in/4_autosplit_empty.txt
|
||||
t/in/4_autosplit_offset.txt
|
||||
t/in/4_autosplit_shape.txt
|
||||
t/in/4_bend_bug.txt
|
||||
t/in/4_bug_basename.txt
|
||||
t/in/4_bug_joint_2.txt
|
||||
t/in/4_collapse.txt
|
||||
t/in/4_comma.txt
|
||||
t/in/4_cross.txt
|
||||
t/in/4_cross_inv.txt
|
||||
t/in/4_cross_split.txt
|
||||
t/in/4_cross_split_hor.txt
|
||||
t/in/4_edge_cross.txt
|
||||
t/in/4_edge_labels.txt
|
||||
t/in/4_edge_types.txt
|
||||
t/in/4_endless_loop.txt
|
||||
t/in/4_endless_loop_2.txt
|
||||
t/in/4_flow.txt
|
||||
t/in/4_flow_chain.txt
|
||||
t/in/4_invisible.txt
|
||||
t/in/4_joint.txt
|
||||
t/in/4_joint_bug_flags.txt
|
||||
t/in/4_list_attr.txt
|
||||
t/in/4_lists.txt
|
||||
t/in/4_minlen.txt
|
||||
t/in/4_near.txt
|
||||
t/in/4_node_edge.txt
|
||||
t/in/4_nodes_5_edges.txt
|
||||
t/in/4_nodes_6_edges.txt
|
||||
t/in/4_nodes_edge.txt
|
||||
t/in/4groups.txt
|
||||
t/in/4groups_class.txt
|
||||
t/in/4nodes.txt
|
||||
t/in/5_a-star_bug.txt
|
||||
t/in/5_arrow_styles.txt
|
||||
t/in/5_flow.txt
|
||||
t/in/5_group_repair.txt
|
||||
t/in/5_group_split.txt
|
||||
t/in/5_joint.txt
|
||||
t/in/5_joint_bug2.txt
|
||||
t/in/5_joint_label.txt
|
||||
t/in/5_long_edge_labels.txt
|
||||
t/in/5_multicell.txt
|
||||
t/in/5_offsets.txt
|
||||
t/in/5_offsets_2.txt
|
||||
t/in/5_rounded.txt
|
||||
t/in/5_tree_joint.txt
|
||||
t/in/6_autosplit_class.txt
|
||||
t/in/6_chain_10_edges.txt
|
||||
t/in/6_chained.txt
|
||||
t/in/6_empty_row.txt
|
||||
t/in/6_fanout.txt
|
||||
t/in/6_group_align.txt
|
||||
t/in/6_joint.txt
|
||||
t/in/6_multicell.txt
|
||||
t/in/6_multicell_offset.txt
|
||||
t/in/6_nested_groups.txt
|
||||
t/in/6_ranks.txt
|
||||
t/in/6_split_join_loop.txt
|
||||
t/in/7_cluster.txt
|
||||
t/in/7_star.txt
|
||||
t/in/7_tree.txt
|
||||
t/in/8_align.txt
|
||||
t/in/8_basename.txt
|
||||
t/in/8_chain.txt
|
||||
t/in/8_endless_loop.txt
|
||||
t/in/8_flow.txt
|
||||
t/in/8_invisible.txt
|
||||
t/in/8_labels.txt
|
||||
t/in/8_optimize_bend.txt
|
||||
t/in/8_points.txt
|
||||
t/in/9_chain.txt
|
||||
t/in/9_cross.txt
|
||||
t/in/9_flow_south.txt
|
||||
t/in/dot/0_empty.dot
|
||||
t/in/dot/10_numbers.dot
|
||||
t/in/dot/16_split.dot
|
||||
t/in/dot/2_bool.dot
|
||||
t/in/dot/2_comment_inside_attr.dot
|
||||
t/in/dot/2_graph_label_bottom.dot
|
||||
t/in/dot/2_group_labelloc.dot
|
||||
t/in/dot/2_ignore.dot
|
||||
t/in/dot/2_linewidth.dot
|
||||
t/in/dot/2_no_spaces.dot
|
||||
t/in/dot/2_nospace.dot
|
||||
t/in/dot/2_ports.dot
|
||||
t/in/dot/2_setlinewidth.dot
|
||||
t/in/dot/2_square_bracket_in_attr.dot
|
||||
t/in/dot/2_strict.dot
|
||||
t/in/dot/3_colors.dot
|
||||
t/in/dot/3_empty_record.dot
|
||||
t/in/dot/3_empty_record_LR.dot
|
||||
t/in/dot/3_graph_label_long.dot
|
||||
t/in/dot/3_ids.dot
|
||||
t/in/dot/3_invis.dot
|
||||
t/in/dot/3_node_label.dot
|
||||
t/in/dot/3_output_lone.dot
|
||||
t/in/dot/4_cluster_labeljust.dot
|
||||
t/in/dot/4_compass.dot
|
||||
t/in/dot/4_html_like.dot
|
||||
t/in/dot/4_record.dot
|
||||
t/in/dot/4_strings.dot
|
||||
t/in/dot/4_uppercase.dot
|
||||
t/in/dot/5_scope_atr.dot
|
||||
t/in/dot/5_scopes.dot
|
||||
t/in/dot/5_scopes_chain.dot
|
||||
t/in/dot/5_scopes_uni.dot
|
||||
t/in/dot/6_2_cluster.dot
|
||||
t/in/dot/6_comments.dot
|
||||
t/in/dot/6_group_align.dot
|
||||
t/in/dot/7_record.dot
|
||||
t/in/dot/9_back.dot
|
||||
t/in/dot/9_edge_styles.dot
|
||||
t/in/dot/9_stacking.dot
|
||||
t/in/dot/9_tree.dot
|
||||
t/in/gdl/1_color_code.gdl
|
||||
t/in/gdl/2_bottom_to_top.gdl
|
||||
t/in/gdl/2_left_to_right.gdl
|
||||
t/in/gdl/2_right_to_left.gdl
|
||||
t/in/gdl/2_top_to_bottom.gdl
|
||||
t/in/README
|
||||
t/layers.t
|
||||
t/layout.t
|
||||
t/layout_r.t
|
||||
t/layouter.t
|
||||
t/layouter/edge_label.txt
|
||||
t/layouter/layouter.txt
|
||||
t/layouter/layouter_chain.txt
|
||||
t/layouter/layouter_loop.txt
|
||||
t/layouter/multiples.txt
|
||||
t/layouter/state.txt
|
||||
t/messages.t
|
||||
t/nesting.t
|
||||
t/node.t
|
||||
t/node_mc.t
|
||||
t/out/0_empty_group.txt
|
||||
t/out/0_empty_groups.txt
|
||||
t/out/10_repair.txt
|
||||
t/out/10borders.txt
|
||||
t/out/18_multiples.txt
|
||||
t/out/1_bidi_loop.txt
|
||||
t/out/1_empty_group.txt
|
||||
t/out/1_selfloop.txt
|
||||
t/out/1_selfloop_2.txt
|
||||
t/out/1_selfloop_label.txt
|
||||
t/out/1_undirected_loop.txt
|
||||
t/out/1node.txt
|
||||
t/out/25_autosplit_empty.txt
|
||||
t/out/2_autolabel.txt
|
||||
t/out/2_autosplit_empty.txt
|
||||
t/out/2_autosplit_escaped.txt
|
||||
t/out/2_bidi_astar.txt
|
||||
t/out/2_bidi_endpoint.txt
|
||||
t/out/2_class.txt
|
||||
t/out/2_classes.txt
|
||||
t/out/2_cluster.txt
|
||||
t/out/2_cluster_2.txt
|
||||
t/out/2_cluster_3.txt
|
||||
t/out/2_dot.txt
|
||||
t/out/2_dot_dot_dash.txt
|
||||
t/out/2_edges.txt
|
||||
t/out/2_flow.txt
|
||||
t/out/2_graph_label.txt
|
||||
t/out/2_group.txt
|
||||
t/out/2_group_labelpos.txt
|
||||
t/out/2_group_multicell.txt
|
||||
t/out/2_group_no_border.txt
|
||||
t/out/2_invisible_left.txt
|
||||
t/out/2_invisible_right.txt
|
||||
t/out/2_label.txt
|
||||
t/out/2_label_align.txt
|
||||
t/out/2_list_attr.txt
|
||||
t/out/2_long_labels.txt
|
||||
t/out/2_newlines.txt
|
||||
t/out/2_nodes_inv.txt
|
||||
t/out/2_selfloop.txt
|
||||
t/out/2_selfloop_flow_down.txt
|
||||
t/out/2_split_bug.txt
|
||||
t/out/2_wrap.txt
|
||||
t/out/2_zeros.txt
|
||||
t/out/2nodes.txt
|
||||
t/out/3_autosplit_hang.txt
|
||||
t/out/3_bend_bug.txt
|
||||
t/out/3_cache_bug.txt
|
||||
t/out/3_cluster.txt
|
||||
t/out/3_colors.txt
|
||||
t/out/3_corrupt.txt
|
||||
t/out/3_edge_labels_from_class.txt
|
||||
t/out/3_edge_repair.txt
|
||||
t/out/3_edge_start.txt
|
||||
t/out/3_empty_group.txt
|
||||
t/out/3_flow.txt
|
||||
t/out/3_group_align_center.txt
|
||||
t/out/3_inherit.txt
|
||||
t/out/3_invisible_both.txt
|
||||
t/out/3_joining.txt
|
||||
t/out/3_joint.txt
|
||||
t/out/3_joint_short.txt
|
||||
t/out/3_list_attr.txt
|
||||
t/out/3_lists.txt
|
||||
t/out/3_nodes_5_edges.txt
|
||||
t/out/3_selfloop.txt
|
||||
t/out/3_selfloop_flip.txt
|
||||
t/out/3_selfloop_flow_down.txt
|
||||
t/out/3_selfloop_flow_left.txt
|
||||
t/out/3_selfloop_flow_up.txt
|
||||
t/out/3_split_attribute.txt
|
||||
t/out/3nodes.txt
|
||||
t/out/4_2x2nodes.txt
|
||||
t/out/4_att.txt
|
||||
t/out/4_autosplit_class.txt
|
||||
t/out/4_autosplit_empty.txt
|
||||
t/out/4_autosplit_offset.txt
|
||||
t/out/4_autosplit_shape.txt
|
||||
t/out/4_bend_bug.txt
|
||||
t/out/4_bug_basename.txt
|
||||
t/out/4_bug_joint_2.txt
|
||||
t/out/4_collapse.txt
|
||||
t/out/4_comma.txt
|
||||
t/out/4_cross.txt
|
||||
t/out/4_cross_inv.txt
|
||||
t/out/4_cross_split.txt
|
||||
t/out/4_cross_split_hor.txt
|
||||
t/out/4_edge_cross.txt
|
||||
t/out/4_edge_labels.txt
|
||||
t/out/4_edge_types.txt
|
||||
t/out/4_endless_loop.txt
|
||||
t/out/4_endless_loop_2.txt
|
||||
t/out/4_flow.txt
|
||||
t/out/4_flow_chain.txt
|
||||
t/out/4_invisible.txt
|
||||
t/out/4_joint.txt
|
||||
t/out/4_joint_bug_flags.txt
|
||||
t/out/4_list_attr.txt
|
||||
t/out/4_lists.txt
|
||||
t/out/4_minlen.txt
|
||||
t/out/4_near.txt
|
||||
t/out/4_node_edge.txt
|
||||
t/out/4_nodes_5_edges.txt
|
||||
t/out/4_nodes_6_edges.txt
|
||||
t/out/4_nodes_edge.txt
|
||||
t/out/4groups.txt
|
||||
t/out/4groups_class.txt
|
||||
t/out/4nodes.txt
|
||||
t/out/5_a-star_bug.txt
|
||||
t/out/5_arrow_styles.txt
|
||||
t/out/5_flow.txt
|
||||
t/out/5_group_repair.txt
|
||||
t/out/5_group_split.txt
|
||||
t/out/5_joint.txt
|
||||
t/out/5_joint_bug2.txt
|
||||
t/out/5_joint_label.txt
|
||||
t/out/5_long_edge_labels.txt
|
||||
t/out/5_multicell.txt
|
||||
t/out/5_offsets.txt
|
||||
t/out/5_offsets_2.txt
|
||||
t/out/5_rounded.txt
|
||||
t/out/5_tree_joint.txt
|
||||
t/out/6_autosplit_class.txt
|
||||
t/out/6_chain_10_edges.txt
|
||||
t/out/6_chained.txt
|
||||
t/out/6_empty_row.txt
|
||||
t/out/6_fanout.txt
|
||||
t/out/6_group_align.txt
|
||||
t/out/6_joint.txt
|
||||
t/out/6_multicell.txt
|
||||
t/out/6_multicell_offset.txt
|
||||
t/out/6_nested_groups.txt
|
||||
t/out/6_ranks.txt
|
||||
t/out/6_split_join_loop.txt
|
||||
t/out/7_cluster.txt
|
||||
t/out/7_star.txt
|
||||
t/out/7_tree.txt
|
||||
t/out/8_align.txt
|
||||
t/out/8_basename.txt
|
||||
t/out/8_chain.txt
|
||||
t/out/8_endless_loop.txt
|
||||
t/out/8_flow.txt
|
||||
t/out/8_invisible.txt
|
||||
t/out/8_labels.txt
|
||||
t/out/8_optimize_bend.txt
|
||||
t/out/8_points.txt
|
||||
t/out/9_chain.txt
|
||||
t/out/9_cross.txt
|
||||
t/out/9_flow_south.txt
|
||||
t/out/dot/0_empty.txt
|
||||
t/out/dot/10_numbers.txt
|
||||
t/out/dot/16_split.txt
|
||||
t/out/dot/2_bool.txt
|
||||
t/out/dot/2_comment_inside_attr.txt
|
||||
t/out/dot/2_graph_label_bottom.txt
|
||||
t/out/dot/2_group_labelloc.txt
|
||||
t/out/dot/2_ignore.txt
|
||||
t/out/dot/2_linewidth.txt
|
||||
t/out/dot/2_no_spaces.txt
|
||||
t/out/dot/2_nospace.txt
|
||||
t/out/dot/2_ports.txt
|
||||
t/out/dot/2_setlinewidth.txt
|
||||
t/out/dot/2_square_bracket_in_attr.txt
|
||||
t/out/dot/2_strict.txt
|
||||
t/out/dot/3_colors.txt
|
||||
t/out/dot/3_empty_record.txt
|
||||
t/out/dot/3_empty_record_LR.txt
|
||||
t/out/dot/3_graph_label_long.txt
|
||||
t/out/dot/3_ids.txt
|
||||
t/out/dot/3_invis.txt
|
||||
t/out/dot/3_node_label.txt
|
||||
t/out/dot/3_output_lone.txt
|
||||
t/out/dot/4_cluster_labeljust.txt
|
||||
t/out/dot/4_compass.txt
|
||||
t/out/dot/4_html_like.txt
|
||||
t/out/dot/4_loose.txt
|
||||
t/out/dot/4_record.txt
|
||||
t/out/dot/4_strings.txt
|
||||
t/out/dot/4_uppercase.txt
|
||||
t/out/dot/5_scope_atr.txt
|
||||
t/out/dot/5_scopes.txt
|
||||
t/out/dot/5_scopes_chain.txt
|
||||
t/out/dot/5_scopes_uni.txt
|
||||
t/out/dot/6_2_cluster.txt
|
||||
t/out/dot/6_comments.txt
|
||||
t/out/dot/6_group_align.txt
|
||||
t/out/dot/7_record.txt
|
||||
t/out/dot/9_back.txt
|
||||
t/out/dot/9_edge_styles.txt
|
||||
t/out/dot/9_stacking.txt
|
||||
t/out/dot/9_tree.txt
|
||||
t/out/drop_result.txt
|
||||
t/out/gdl/1_color_code.txt
|
||||
t/out/gdl/2_bottom_to_top.txt
|
||||
t/out/gdl/2_left_to_right.txt
|
||||
t/out/gdl/2_right_to_left.txt
|
||||
t/out/gdl/2_top_to_bottom.txt
|
||||
t/parse_att.t
|
||||
t/parse_edge.t
|
||||
t/parser.t
|
||||
t/parser_dot.t
|
||||
t/parser_dot_html.t
|
||||
t/path.t
|
||||
t/pod.t
|
||||
t/pod_cov.t
|
||||
t/re_layout.t
|
||||
t/split.t
|
||||
t/stress/0001.txt
|
||||
t/stress/0002.txt
|
||||
t/stress/0003.txt
|
||||
t/stress/0004.txt
|
||||
t/stress/0005.txt
|
||||
t/stress/0006.txt
|
||||
t/stress/0010.txt
|
||||
t/stress/0011.txt
|
||||
t/stress/0012.txt
|
||||
t/stress/0020.txt
|
||||
t/stress/anon.txt
|
||||
t/stress/drop.txt
|
||||
t/style-trailing-space.t
|
||||
t/syntax/0000.txt
|
||||
t/syntax/0001.txt
|
||||
t/syntax/0002.txt
|
||||
t/syntax/0003.txt
|
||||
t/syntax/0010.txt
|
||||
t/syntax/0011.txt
|
||||
t/syntax/0020.txt
|
||||
t/syntax/0021.txt
|
||||
t/syntax/0030.txt
|
||||
t/syntax/0040.txt
|
||||
t/syntax/0050.txt
|
||||
t/syntax/0060.txt
|
||||
t/syntax/0061.txt
|
||||
t/syntax/0062.txt
|
||||
t/syntax/0063.txt
|
||||
t/syntax/0070.txt
|
||||
t/syntax/0080.txt
|
||||
t/syntax/0090.txt
|
||||
t/syntax/0100.txt
|
||||
t/syntax/0102.txt
|
||||
t/syntax/0110.txt
|
||||
t/syntax/0120.txt
|
||||
t/syntax/0130.txt
|
||||
t/syntax/0131.txt
|
||||
t/syntax/0140.txt
|
||||
t/syntax/0150.txt
|
||||
t/syntax/0160.txt
|
||||
t/syntax/0170.txt
|
||||
t/syntax/0171.txt
|
||||
t/syntax/0180.txt
|
||||
t/syntax/0190.txt
|
||||
t/syntax/0200.txt
|
||||
t/syntax/0210.txt
|
||||
t/syntax/0220.txt
|
||||
t/syntax/0230.txt
|
||||
t/syntax/0240.txt
|
||||
t/syntax/0250.txt
|
||||
t/syntax/0251.txt
|
||||
t/syntax/0252.txt
|
||||
t/syntax/0254.txt
|
||||
t/txt/0_empty_group.txt
|
||||
t/txt/0_empty_groups.txt
|
||||
t/txt/10_repair.txt
|
||||
t/txt/10borders.txt
|
||||
t/txt/18_multiples.txt
|
||||
t/txt/1_empty_group.txt
|
||||
t/txt/1_undirected_loop.txt
|
||||
t/txt/25_autosplit_empty.txt
|
||||
t/txt/2_autolabel.txt
|
||||
t/txt/2_autosplit_empty.txt
|
||||
t/txt/2_autosplit_escaped.txt
|
||||
t/txt/2_class.txt
|
||||
t/txt/2_classes.txt
|
||||
t/txt/2_cluster.txt
|
||||
t/txt/2_cluster_2.txt
|
||||
t/txt/2_cluster_3.txt
|
||||
t/txt/2_dot.txt
|
||||
t/txt/2_dot_dot_dash.txt
|
||||
t/txt/2_edges.txt
|
||||
t/txt/2_flow.txt
|
||||
t/txt/2_graph_label.txt
|
||||
t/txt/2_group.txt
|
||||
t/txt/2_group_labelpos.txt
|
||||
t/txt/2_group_multicell.txt
|
||||
t/txt/2_group_no_border.txt
|
||||
t/txt/2_invisible_left.txt
|
||||
t/txt/2_invisible_right.txt
|
||||
t/txt/2_label.txt
|
||||
t/txt/2_label_align.txt
|
||||
t/txt/2_list_attr.txt
|
||||
t/txt/2_long_labels.txt
|
||||
t/txt/2_newlines.txt
|
||||
t/txt/2_selfloop.txt
|
||||
t/txt/2_selfloop_flow_down.txt
|
||||
t/txt/2_split_bug.txt
|
||||
t/txt/2_wrap.txt
|
||||
t/txt/2_zeros.txt
|
||||
t/txt/2nodes.txt
|
||||
t/txt/3_autosplit_hang.txt
|
||||
t/txt/3_cache_bug.txt
|
||||
t/txt/3_cluster.txt
|
||||
t/txt/3_colors.txt
|
||||
t/txt/3_corrupt.txt
|
||||
t/txt/3_edge_labels_from_class.txt
|
||||
t/txt/3_edge_repair.txt
|
||||
t/txt/3_edge_start.txt
|
||||
t/txt/3_empty_group.txt
|
||||
t/txt/3_flow.txt
|
||||
t/txt/3_group_align_center.txt
|
||||
t/txt/3_inherit.txt
|
||||
t/txt/3_invisible_both.txt
|
||||
t/txt/3_joining.txt
|
||||
t/txt/3_joint.txt
|
||||
t/txt/3_joint_short.txt
|
||||
t/txt/3_list_attr.txt
|
||||
t/txt/3_lists.txt
|
||||
t/txt/3_nodes_5_edges.txt
|
||||
t/txt/3_selfloop.txt
|
||||
t/txt/3_selfloop_flip.txt
|
||||
t/txt/3_selfloop_flow_down.txt
|
||||
t/txt/3_selfloop_flow_left.txt
|
||||
t/txt/3_selfloop_flow_up.txt
|
||||
t/txt/3_split_attribute.txt
|
||||
t/txt/3nodes.txt
|
||||
t/txt/4_2x2nodes.txt
|
||||
t/txt/4_att.txt
|
||||
t/txt/4_autosplit_class.txt
|
||||
t/txt/4_autosplit_empty.txt
|
||||
t/txt/4_autosplit_offset.txt
|
||||
t/txt/4_autosplit_shape.txt
|
||||
t/txt/4_bug_basename.txt
|
||||
t/txt/4_bug_joint_2.txt
|
||||
t/txt/4_collapse.txt
|
||||
t/txt/4_comma.txt
|
||||
t/txt/4_cross.txt
|
||||
t/txt/4_cross_inv.txt
|
||||
t/txt/4_cross_split.txt
|
||||
t/txt/4_cross_split_hor.txt
|
||||
t/txt/4_edge_cross.txt
|
||||
t/txt/4_edge_labels.txt
|
||||
t/txt/4_edge_types.txt
|
||||
t/txt/4_endless_loop.txt
|
||||
t/txt/4_endless_loop_2.txt
|
||||
t/txt/4_flow.txt
|
||||
t/txt/4_flow_chain.txt
|
||||
t/txt/4_invisible.txt
|
||||
t/txt/4_joint.txt
|
||||
t/txt/4_joint_bug_flags.txt
|
||||
t/txt/4_list_attr.txt
|
||||
t/txt/4_lists.txt
|
||||
t/txt/4_minlen.txt
|
||||
t/txt/4_near.txt
|
||||
t/txt/4_node_edge.txt
|
||||
t/txt/4_nodes_5_edges.txt
|
||||
t/txt/4_nodes_6_edges.txt
|
||||
t/txt/4_nodes_edge.txt
|
||||
t/txt/4groups.txt
|
||||
t/txt/4groups_class.txt
|
||||
t/txt/4nodes.txt
|
||||
t/txt/5_arrow_styles.txt
|
||||
t/txt/5_flow.txt
|
||||
t/txt/5_group_repair.txt
|
||||
t/txt/5_group_split.txt
|
||||
t/txt/5_joint.txt
|
||||
t/txt/5_joint_bug2.txt
|
||||
t/txt/5_joint_label.txt
|
||||
t/txt/5_long_edge_labels.txt
|
||||
t/txt/5_multicell.txt
|
||||
t/txt/5_offsets.txt
|
||||
t/txt/5_offsets_2.txt
|
||||
t/txt/5_rounded.txt
|
||||
t/txt/5_tree_joint.txt
|
||||
t/txt/6_autosplit_class.txt
|
||||
t/txt/6_chain_10_edges.txt
|
||||
t/txt/6_chained.txt
|
||||
t/txt/6_empty_row.txt
|
||||
t/txt/6_fanout.txt
|
||||
t/txt/6_group_align.txt
|
||||
t/txt/6_joint.txt
|
||||
t/txt/6_multicell.txt
|
||||
t/txt/6_multicell_offset.txt
|
||||
t/txt/6_nested_groups.txt
|
||||
t/txt/6_ranks.txt
|
||||
t/txt/6_split_join_loop.txt
|
||||
t/txt/7_cluster.txt
|
||||
t/txt/7_star.txt
|
||||
t/txt/7_tree.txt
|
||||
t/txt/8_align.txt
|
||||
t/txt/8_basename.txt
|
||||
t/txt/8_chain.txt
|
||||
t/txt/8_endless_loop.txt
|
||||
t/txt/8_flow.txt
|
||||
t/txt/8_invisible.txt
|
||||
t/txt/8_labels.txt
|
||||
t/txt/8_optimize_bend.txt
|
||||
t/txt/8_points.txt
|
||||
t/txt/9_chain.txt
|
||||
t/txt/9_cross.txt
|
||||
t/txt/9_flow_south.txt
|
||||
t/txt/dot/0_empty.txt
|
||||
t/txt/dot/10_numbers.txt
|
||||
t/txt/dot/16_split.txt
|
||||
t/txt/dot/2_bool.txt
|
||||
t/txt/dot/2_comment_inside_attr.txt
|
||||
t/txt/dot/2_graph_label_bottom.txt
|
||||
t/txt/dot/2_group_labelloc.txt
|
||||
t/txt/dot/2_ignore.txt
|
||||
t/txt/dot/2_linewidth.txt
|
||||
t/txt/dot/2_no_spaces.txt
|
||||
t/txt/dot/2_nospace.txt
|
||||
t/txt/dot/2_ports.txt
|
||||
t/txt/dot/2_setlinewidth.txt
|
||||
t/txt/dot/2_square_bracket_in_attr.txt
|
||||
t/txt/dot/2_strict.txt
|
||||
t/txt/dot/3_colors.txt
|
||||
t/txt/dot/3_empty_record.txt
|
||||
t/txt/dot/3_empty_record_LR.txt
|
||||
t/txt/dot/3_graph_label_long.txt
|
||||
t/txt/dot/3_ids.txt
|
||||
t/txt/dot/3_invis.txt
|
||||
t/txt/dot/3_node_label.txt
|
||||
t/txt/dot/3_output_lone.txt
|
||||
t/txt/dot/4_cluster_labeljust.txt
|
||||
t/txt/dot/4_compass.txt
|
||||
t/txt/dot/4_html_like.txt
|
||||
t/txt/dot/4_loose.txt
|
||||
t/txt/dot/4_record.txt
|
||||
t/txt/dot/4_strings.txt
|
||||
t/txt/dot/4_uppercase.txt
|
||||
t/txt/dot/5_scope_atr.txt
|
||||
t/txt/dot/5_scopes.txt
|
||||
t/txt/dot/5_scopes_chain.txt
|
||||
t/txt/dot/5_scopes_uni.txt
|
||||
t/txt/dot/6_2_cluster.txt
|
||||
t/txt/dot/6_comments.txt
|
||||
t/txt/dot/6_group_align.txt
|
||||
t/txt/dot/7_record.txt
|
||||
t/txt/dot/9_back.txt
|
||||
t/txt/dot/9_edge_styles.txt
|
||||
t/txt/dot/9_stacking.txt
|
||||
t/txt/dot/9_tree.txt
|
||||
t/txt/gdl/1_color_code.txt
|
||||
t/txt/gdl/2_bottom_to_top.txt
|
||||
t/txt/gdl/2_left_to_right.txt
|
||||
t/txt/gdl/2_right_to_left.txt
|
||||
t/txt/gdl/2_top_to_bottom.txt
|
||||
t/use_class.t
|
||||
t/vcg.t
|
||||
TODO
|
||||
20
perl/lib/Graph-Easy-0.76/MANIFEST.SKIP
Normal file
20
perl/lib/Graph-Easy-0.76/MANIFEST.SKIP
Normal file
@@ -0,0 +1,20 @@
|
||||
^Build\z
|
||||
^_build/
|
||||
^blib.*
|
||||
^fun\z
|
||||
^gdl
|
||||
^Graph-Easy-[0-9]
|
||||
^Makefile.(old|bak)\z
|
||||
^Makefile\z
|
||||
^MYMETA\.yml\z
|
||||
^MYMETA\.json\z
|
||||
^[^\\\/]*\.pl
|
||||
pm_to_blib
|
||||
\.svn
|
||||
.*\.tar\.gz
|
||||
tmon.out
|
||||
^todos[\\\/]
|
||||
^[\w\._-]+\.(html|txt|png|gif|dot|pl|svg|old|bak|org|vcg|gdl|ps|graphml)
|
||||
^wikicrawl
|
||||
~\z
|
||||
.*\.swp
|
||||
177
perl/lib/Graph-Easy-0.76/META.json
Normal file
177
perl/lib/Graph-Easy-0.76/META.json
Normal file
@@ -0,0 +1,177 @@
|
||||
{
|
||||
"abstract" : "Convert or render graphs (as ASCII, HTML, SVG or via Graphviz)",
|
||||
"author" : [
|
||||
"unknown"
|
||||
],
|
||||
"dynamic_config" : 1,
|
||||
"generated_by" : "Module::Build version 0.4218",
|
||||
"keywords" : [
|
||||
"generation",
|
||||
"graph",
|
||||
"graphviz",
|
||||
"text generation",
|
||||
"text"
|
||||
],
|
||||
"license" : [
|
||||
"gpl_1"
|
||||
],
|
||||
"meta-spec" : {
|
||||
"url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
|
||||
"version" : "2"
|
||||
},
|
||||
"name" : "Graph-Easy",
|
||||
"prereqs" : {
|
||||
"build" : {
|
||||
"requires" : {
|
||||
"Test::More" : "0.62"
|
||||
}
|
||||
},
|
||||
"configure" : {
|
||||
"requires" : {
|
||||
"Module::Build" : "0.36"
|
||||
}
|
||||
},
|
||||
"runtime" : {
|
||||
"recommends" : {
|
||||
"Graph::Easy::As_svg" : "0.23"
|
||||
},
|
||||
"requires" : {
|
||||
"Scalar::Util" : "1.13",
|
||||
"perl" : "v5.8.2",
|
||||
"strict" : "0",
|
||||
"vars" : "0",
|
||||
"warnings" : "0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"provides" : {
|
||||
"Graph::Easy" : {
|
||||
"file" : "lib/Graph/Easy.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::As_ascii" : {
|
||||
"file" : "lib/Graph/Easy/As_ascii.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::As_graphml" : {
|
||||
"file" : "lib/Graph/Easy/As_graphml.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::As_graphviz" : {
|
||||
"file" : "lib/Graph/Easy/As_graphviz.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::As_txt" : {
|
||||
"file" : "lib/Graph/Easy/As_txt.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::As_vcg" : {
|
||||
"file" : "lib/Graph/Easy/As_vcg.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Attributes" : {
|
||||
"file" : "lib/Graph/Easy/Attributes.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Base" : {
|
||||
"file" : "lib/Graph/Easy/Base.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Edge" : {
|
||||
"file" : "lib/Graph/Easy/Edge.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Edge::Cell" : {
|
||||
"file" : "lib/Graph/Easy/Edge/Cell.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Edge::Cell::Empty" : {
|
||||
"file" : "lib/Graph/Easy/Edge/Cell.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Group" : {
|
||||
"file" : "lib/Graph/Easy/Group.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Group::Anon" : {
|
||||
"file" : "lib/Graph/Easy/Group/Anon.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Group::Cell" : {
|
||||
"file" : "lib/Graph/Easy/Group/Cell.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Heap" : {
|
||||
"file" : "lib/Graph/Easy/Layout/Scout.pm"
|
||||
},
|
||||
"Graph::Easy::Layout" : {
|
||||
"file" : "lib/Graph/Easy/Layout.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Layout::Chain" : {
|
||||
"file" : "lib/Graph/Easy/Layout/Chain.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Layout::Force" : {
|
||||
"file" : "lib/Graph/Easy/Layout/Force.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Layout::Grid" : {
|
||||
"file" : "lib/Graph/Easy/Layout/Grid.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Layout::Path" : {
|
||||
"file" : "lib/Graph/Easy/Layout/Path.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Layout::Repair" : {
|
||||
"file" : "lib/Graph/Easy/Layout/Repair.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Layout::Scout" : {
|
||||
"file" : "lib/Graph/Easy/Layout/Scout.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Node" : {
|
||||
"file" : "lib/Graph/Easy/Node.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Node::Anon" : {
|
||||
"file" : "lib/Graph/Easy/Node/Anon.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Node::Cell" : {
|
||||
"file" : "lib/Graph/Easy/Node/Cell.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Node::Empty" : {
|
||||
"file" : "lib/Graph/Easy/Node/Empty.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Parser" : {
|
||||
"file" : "lib/Graph/Easy/Parser.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Parser::Graphviz" : {
|
||||
"file" : "lib/Graph/Easy/Parser/Graphviz.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Parser::VCG" : {
|
||||
"file" : "lib/Graph/Easy/Parser/VCG.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Util" : {
|
||||
"file" : "lib/Graph/Easy/Util.pm"
|
||||
}
|
||||
},
|
||||
"release_status" : "stable",
|
||||
"resources" : {
|
||||
"license" : [
|
||||
"http://www.gnu.org/licenses/old-licenses/gpl-1.0.txt"
|
||||
],
|
||||
"repository" : {
|
||||
"url" : "https://bitbucket.org/shlomif/perl-graph-easy"
|
||||
}
|
||||
},
|
||||
"version" : "0.76"
|
||||
}
|
||||
122
perl/lib/Graph-Easy-0.76/META.yml
Normal file
122
perl/lib/Graph-Easy-0.76/META.yml
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
abstract: 'Convert or render graphs (as ASCII, HTML, SVG or via Graphviz)'
|
||||
author:
|
||||
- unknown
|
||||
build_requires:
|
||||
Test::More: '0.62'
|
||||
configure_requires:
|
||||
Module::Build: '0.36'
|
||||
dynamic_config: 1
|
||||
generated_by: 'Module::Build version 0.4218, CPAN::Meta::Converter version 2.150001'
|
||||
keywords:
|
||||
- generation
|
||||
- graph
|
||||
- graphviz
|
||||
- 'text generation'
|
||||
- text
|
||||
license: gpl
|
||||
meta-spec:
|
||||
url: http://module-build.sourceforge.net/META-spec-v1.4.html
|
||||
version: '1.4'
|
||||
name: Graph-Easy
|
||||
provides:
|
||||
Graph::Easy:
|
||||
file: lib/Graph/Easy.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::As_ascii:
|
||||
file: lib/Graph/Easy/As_ascii.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::As_graphml:
|
||||
file: lib/Graph/Easy/As_graphml.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::As_graphviz:
|
||||
file: lib/Graph/Easy/As_graphviz.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::As_txt:
|
||||
file: lib/Graph/Easy/As_txt.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::As_vcg:
|
||||
file: lib/Graph/Easy/As_vcg.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Attributes:
|
||||
file: lib/Graph/Easy/Attributes.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Base:
|
||||
file: lib/Graph/Easy/Base.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Edge:
|
||||
file: lib/Graph/Easy/Edge.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Edge::Cell:
|
||||
file: lib/Graph/Easy/Edge/Cell.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Edge::Cell::Empty:
|
||||
file: lib/Graph/Easy/Edge/Cell.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Group:
|
||||
file: lib/Graph/Easy/Group.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Group::Anon:
|
||||
file: lib/Graph/Easy/Group/Anon.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Group::Cell:
|
||||
file: lib/Graph/Easy/Group/Cell.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Heap:
|
||||
file: lib/Graph/Easy/Layout/Scout.pm
|
||||
Graph::Easy::Layout:
|
||||
file: lib/Graph/Easy/Layout.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Layout::Chain:
|
||||
file: lib/Graph/Easy/Layout/Chain.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Layout::Force:
|
||||
file: lib/Graph/Easy/Layout/Force.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Layout::Grid:
|
||||
file: lib/Graph/Easy/Layout/Grid.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Layout::Path:
|
||||
file: lib/Graph/Easy/Layout/Path.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Layout::Repair:
|
||||
file: lib/Graph/Easy/Layout/Repair.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Layout::Scout:
|
||||
file: lib/Graph/Easy/Layout/Scout.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Node:
|
||||
file: lib/Graph/Easy/Node.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Node::Anon:
|
||||
file: lib/Graph/Easy/Node/Anon.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Node::Cell:
|
||||
file: lib/Graph/Easy/Node/Cell.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Node::Empty:
|
||||
file: lib/Graph/Easy/Node/Empty.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Parser:
|
||||
file: lib/Graph/Easy/Parser.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Parser::Graphviz:
|
||||
file: lib/Graph/Easy/Parser/Graphviz.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Parser::VCG:
|
||||
file: lib/Graph/Easy/Parser/VCG.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Util:
|
||||
file: lib/Graph/Easy/Util.pm
|
||||
recommends:
|
||||
Graph::Easy::As_svg: '0.23'
|
||||
requires:
|
||||
Scalar::Util: '1.13'
|
||||
perl: v5.8.2
|
||||
strict: '0'
|
||||
vars: '0'
|
||||
warnings: '0'
|
||||
resources:
|
||||
license: http://www.gnu.org/licenses/old-licenses/gpl-1.0.txt
|
||||
repository: https://bitbucket.org/shlomif/perl-graph-easy
|
||||
version: '0.76'
|
||||
178
perl/lib/Graph-Easy-0.76/MYMETA.json
Normal file
178
perl/lib/Graph-Easy-0.76/MYMETA.json
Normal file
@@ -0,0 +1,178 @@
|
||||
{
|
||||
"abstract" : "Convert or render graphs (as ASCII, HTML, SVG or via Graphviz)",
|
||||
"author" : [
|
||||
"unknown"
|
||||
],
|
||||
"dynamic_config" : 0,
|
||||
"generated_by" : "Module::Build version 0.4218, CPAN::Meta::Converter version 2.150010",
|
||||
"keywords" : [
|
||||
"generation",
|
||||
"graph",
|
||||
"graphviz",
|
||||
"text generation",
|
||||
"text"
|
||||
],
|
||||
"license" : [
|
||||
"gpl_1"
|
||||
],
|
||||
"meta-spec" : {
|
||||
"url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
|
||||
"version" : 2
|
||||
},
|
||||
"name" : "Graph-Easy",
|
||||
"prereqs" : {
|
||||
"build" : {
|
||||
"requires" : {
|
||||
"ExtUtils::MakeMaker" : "0"
|
||||
}
|
||||
},
|
||||
"configure" : {
|
||||
"requires" : {
|
||||
"ExtUtils::MakeMaker" : "0"
|
||||
}
|
||||
},
|
||||
"runtime" : {
|
||||
"recommends" : {
|
||||
"Graph::Easy::As_svg" : "0.23"
|
||||
},
|
||||
"requires" : {
|
||||
"Scalar::Util" : "1.13",
|
||||
"Test::More" : "0.62",
|
||||
"strict" : "0",
|
||||
"vars" : "0",
|
||||
"warnings" : "0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"provides" : {
|
||||
"Graph::Easy" : {
|
||||
"file" : "lib/Graph/Easy.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::As_ascii" : {
|
||||
"file" : "lib/Graph/Easy/As_ascii.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::As_graphml" : {
|
||||
"file" : "lib/Graph/Easy/As_graphml.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::As_graphviz" : {
|
||||
"file" : "lib/Graph/Easy/As_graphviz.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::As_txt" : {
|
||||
"file" : "lib/Graph/Easy/As_txt.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::As_vcg" : {
|
||||
"file" : "lib/Graph/Easy/As_vcg.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Attributes" : {
|
||||
"file" : "lib/Graph/Easy/Attributes.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Base" : {
|
||||
"file" : "lib/Graph/Easy/Base.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Edge" : {
|
||||
"file" : "lib/Graph/Easy/Edge.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Edge::Cell" : {
|
||||
"file" : "lib/Graph/Easy/Edge/Cell.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Edge::Cell::Empty" : {
|
||||
"file" : "lib/Graph/Easy/Edge/Cell.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Group" : {
|
||||
"file" : "lib/Graph/Easy/Group.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Group::Anon" : {
|
||||
"file" : "lib/Graph/Easy/Group/Anon.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Group::Cell" : {
|
||||
"file" : "lib/Graph/Easy/Group/Cell.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Heap" : {
|
||||
"file" : "lib/Graph/Easy/Layout/Scout.pm"
|
||||
},
|
||||
"Graph::Easy::Layout" : {
|
||||
"file" : "lib/Graph/Easy/Layout.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Layout::Chain" : {
|
||||
"file" : "lib/Graph/Easy/Layout/Chain.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Layout::Force" : {
|
||||
"file" : "lib/Graph/Easy/Layout/Force.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Layout::Grid" : {
|
||||
"file" : "lib/Graph/Easy/Layout/Grid.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Layout::Path" : {
|
||||
"file" : "lib/Graph/Easy/Layout/Path.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Layout::Repair" : {
|
||||
"file" : "lib/Graph/Easy/Layout/Repair.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Layout::Scout" : {
|
||||
"file" : "lib/Graph/Easy/Layout/Scout.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Node" : {
|
||||
"file" : "lib/Graph/Easy/Node.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Node::Anon" : {
|
||||
"file" : "lib/Graph/Easy/Node/Anon.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Node::Cell" : {
|
||||
"file" : "lib/Graph/Easy/Node/Cell.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Node::Empty" : {
|
||||
"file" : "lib/Graph/Easy/Node/Empty.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Parser" : {
|
||||
"file" : "lib/Graph/Easy/Parser.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Parser::Graphviz" : {
|
||||
"file" : "lib/Graph/Easy/Parser/Graphviz.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Parser::VCG" : {
|
||||
"file" : "lib/Graph/Easy/Parser/VCG.pm",
|
||||
"version" : "0.76"
|
||||
},
|
||||
"Graph::Easy::Util" : {
|
||||
"file" : "lib/Graph/Easy/Util.pm"
|
||||
}
|
||||
},
|
||||
"release_status" : "stable",
|
||||
"resources" : {
|
||||
"license" : [
|
||||
"http://www.gnu.org/licenses/old-licenses/gpl-1.0.txt"
|
||||
],
|
||||
"repository" : {
|
||||
"url" : "https://bitbucket.org/shlomif/perl-graph-easy"
|
||||
}
|
||||
},
|
||||
"version" : "0.76",
|
||||
"x_serialization_backend" : "JSON::PP version 4.02"
|
||||
}
|
||||
123
perl/lib/Graph-Easy-0.76/MYMETA.yml
Normal file
123
perl/lib/Graph-Easy-0.76/MYMETA.yml
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
abstract: 'Convert or render graphs (as ASCII, HTML, SVG or via Graphviz)'
|
||||
author:
|
||||
- unknown
|
||||
build_requires:
|
||||
ExtUtils::MakeMaker: '0'
|
||||
configure_requires:
|
||||
ExtUtils::MakeMaker: '0'
|
||||
dynamic_config: 0
|
||||
generated_by: 'Module::Build version 0.4218, CPAN::Meta::Converter version 2.150010'
|
||||
keywords:
|
||||
- generation
|
||||
- graph
|
||||
- graphviz
|
||||
- 'text generation'
|
||||
- text
|
||||
license: gpl
|
||||
meta-spec:
|
||||
url: http://module-build.sourceforge.net/META-spec-v1.4.html
|
||||
version: '1.4'
|
||||
name: Graph-Easy
|
||||
provides:
|
||||
Graph::Easy:
|
||||
file: lib/Graph/Easy.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::As_ascii:
|
||||
file: lib/Graph/Easy/As_ascii.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::As_graphml:
|
||||
file: lib/Graph/Easy/As_graphml.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::As_graphviz:
|
||||
file: lib/Graph/Easy/As_graphviz.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::As_txt:
|
||||
file: lib/Graph/Easy/As_txt.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::As_vcg:
|
||||
file: lib/Graph/Easy/As_vcg.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Attributes:
|
||||
file: lib/Graph/Easy/Attributes.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Base:
|
||||
file: lib/Graph/Easy/Base.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Edge:
|
||||
file: lib/Graph/Easy/Edge.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Edge::Cell:
|
||||
file: lib/Graph/Easy/Edge/Cell.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Edge::Cell::Empty:
|
||||
file: lib/Graph/Easy/Edge/Cell.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Group:
|
||||
file: lib/Graph/Easy/Group.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Group::Anon:
|
||||
file: lib/Graph/Easy/Group/Anon.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Group::Cell:
|
||||
file: lib/Graph/Easy/Group/Cell.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Heap:
|
||||
file: lib/Graph/Easy/Layout/Scout.pm
|
||||
Graph::Easy::Layout:
|
||||
file: lib/Graph/Easy/Layout.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Layout::Chain:
|
||||
file: lib/Graph/Easy/Layout/Chain.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Layout::Force:
|
||||
file: lib/Graph/Easy/Layout/Force.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Layout::Grid:
|
||||
file: lib/Graph/Easy/Layout/Grid.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Layout::Path:
|
||||
file: lib/Graph/Easy/Layout/Path.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Layout::Repair:
|
||||
file: lib/Graph/Easy/Layout/Repair.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Layout::Scout:
|
||||
file: lib/Graph/Easy/Layout/Scout.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Node:
|
||||
file: lib/Graph/Easy/Node.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Node::Anon:
|
||||
file: lib/Graph/Easy/Node/Anon.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Node::Cell:
|
||||
file: lib/Graph/Easy/Node/Cell.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Node::Empty:
|
||||
file: lib/Graph/Easy/Node/Empty.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Parser:
|
||||
file: lib/Graph/Easy/Parser.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Parser::Graphviz:
|
||||
file: lib/Graph/Easy/Parser/Graphviz.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Parser::VCG:
|
||||
file: lib/Graph/Easy/Parser/VCG.pm
|
||||
version: '0.76'
|
||||
Graph::Easy::Util:
|
||||
file: lib/Graph/Easy/Util.pm
|
||||
recommends:
|
||||
Graph::Easy::As_svg: '0.23'
|
||||
requires:
|
||||
Scalar::Util: '1.13'
|
||||
Test::More: '0.62'
|
||||
strict: '0'
|
||||
vars: '0'
|
||||
warnings: '0'
|
||||
resources:
|
||||
license: http://www.gnu.org/licenses/old-licenses/gpl-1.0.txt
|
||||
repository: https://bitbucket.org/shlomif/perl-graph-easy
|
||||
version: '0.76'
|
||||
x_serialization_backend: 'CPAN::Meta::YAML version 0.018'
|
||||
982
perl/lib/Graph-Easy-0.76/Makefile
Normal file
982
perl/lib/Graph-Easy-0.76/Makefile
Normal file
@@ -0,0 +1,982 @@
|
||||
# This Makefile is for the Graph::Easy extension to perl.
|
||||
#
|
||||
# It was generated automatically by MakeMaker version
|
||||
# 7.36 (Revision: 73600) from the contents of
|
||||
# Makefile.PL. Don't edit this file, edit Makefile.PL instead.
|
||||
#
|
||||
# ANY CHANGES MADE HERE WILL BE LOST!
|
||||
#
|
||||
# MakeMaker ARGV: ()
|
||||
#
|
||||
|
||||
# MakeMaker Parameters:
|
||||
|
||||
# BUILD_REQUIRES => { }
|
||||
# CONFIGURE_REQUIRES => { }
|
||||
# EXE_FILES => [q[bin/graph-easy]]
|
||||
# INSTALLDIRS => q[site]
|
||||
# NAME => q[Graph::Easy]
|
||||
# PL_FILES => { }
|
||||
# PREREQ_PM => { Scalar::Util=>q[1.13], Test::More=>q[0.62], strict=>q[0], vars=>q[0], warnings=>q[0] }
|
||||
# TEST_REQUIRES => { }
|
||||
# VERSION_FROM => q[lib/Graph/Easy.pm]
|
||||
|
||||
# --- MakeMaker post_initialize section:
|
||||
|
||||
|
||||
# --- MakeMaker const_config section:
|
||||
|
||||
SHELL = C:\windows\system32\cmd.exe
|
||||
|
||||
# These definitions are from config.sh (via C:/Strawberry/perl/lib/Config.pm).
|
||||
# They may have been overridden via Makefile.PL or on the command line.
|
||||
AR = ar
|
||||
CC = gcc
|
||||
CCCDLFLAGS =
|
||||
CCDLFLAGS =
|
||||
DLEXT = xs.dll
|
||||
DLSRC = dl_win32.xs
|
||||
EXE_EXT = .exe
|
||||
FULL_AR =
|
||||
LD = g++
|
||||
LDDLFLAGS = -mdll -s -L"C:\STRAWB~1\perl\lib\CORE" -L"C:\STRAWB~1\c\lib"
|
||||
LDFLAGS = -s -L"C:\STRAWB~1\perl\lib\CORE" -L"C:\STRAWB~1\c\lib"
|
||||
LIBC =
|
||||
LIB_EXT = .a
|
||||
OBJ_EXT = .o
|
||||
OSNAME = MSWin32
|
||||
OSVERS = 10.0.17763.529
|
||||
RANLIB = rem
|
||||
SITELIBEXP = C:\STRAWB~1\perl\site\lib
|
||||
SITEARCHEXP = C:\STRAWB~1\perl\site\lib
|
||||
SO = dll
|
||||
VENDORARCHEXP = C:\STRAWB~1\perl\vendor\lib
|
||||
VENDORLIBEXP = C:\STRAWB~1\perl\vendor\lib
|
||||
|
||||
|
||||
# --- MakeMaker constants section:
|
||||
AR_STATIC_ARGS = cr
|
||||
DIRFILESEP = /
|
||||
DFSEP = $(DIRFILESEP)
|
||||
NAME = Graph::Easy
|
||||
NAME_SYM = Graph_Easy
|
||||
VERSION = 0.76
|
||||
VERSION_MACRO = VERSION
|
||||
VERSION_SYM = 0_76
|
||||
DEFINE_VERSION = -D$(VERSION_MACRO)=\"$(VERSION)\"
|
||||
XS_VERSION = 0.76
|
||||
XS_VERSION_MACRO = XS_VERSION
|
||||
XS_DEFINE_VERSION = -D$(XS_VERSION_MACRO)=\"$(XS_VERSION)\"
|
||||
INST_ARCHLIB = blib\arch
|
||||
INST_SCRIPT = blib\script
|
||||
INST_BIN = blib\bin
|
||||
INST_LIB = blib\lib
|
||||
INST_MAN1DIR = blib\man1
|
||||
INST_MAN3DIR = blib\man3
|
||||
MAN1EXT = 1
|
||||
MAN3EXT = 3
|
||||
INSTALLDIRS = site
|
||||
DESTDIR =
|
||||
PREFIX = $(SITEPREFIX)
|
||||
PERLPREFIX = C:\STRAWB~1\perl
|
||||
SITEPREFIX = C:\STRAWB~1\perl\site
|
||||
VENDORPREFIX = C:\STRAWB~1\perl\vendor
|
||||
INSTALLPRIVLIB = C:\STRAWB~1\perl\lib
|
||||
DESTINSTALLPRIVLIB = $(DESTDIR)$(INSTALLPRIVLIB)
|
||||
INSTALLSITELIB = C:\STRAWB~1\perl\site\lib
|
||||
DESTINSTALLSITELIB = $(DESTDIR)$(INSTALLSITELIB)
|
||||
INSTALLVENDORLIB = C:\STRAWB~1\perl\vendor\lib
|
||||
DESTINSTALLVENDORLIB = $(DESTDIR)$(INSTALLVENDORLIB)
|
||||
INSTALLARCHLIB = C:\STRAWB~1\perl\lib
|
||||
DESTINSTALLARCHLIB = $(DESTDIR)$(INSTALLARCHLIB)
|
||||
INSTALLSITEARCH = C:\STRAWB~1\perl\site\lib
|
||||
DESTINSTALLSITEARCH = $(DESTDIR)$(INSTALLSITEARCH)
|
||||
INSTALLVENDORARCH = C:\STRAWB~1\perl\vendor\lib
|
||||
DESTINSTALLVENDORARCH = $(DESTDIR)$(INSTALLVENDORARCH)
|
||||
INSTALLBIN = C:\STRAWB~1\perl\bin
|
||||
DESTINSTALLBIN = $(DESTDIR)$(INSTALLBIN)
|
||||
INSTALLSITEBIN = C:\STRAWB~1\perl\site\bin
|
||||
DESTINSTALLSITEBIN = $(DESTDIR)$(INSTALLSITEBIN)
|
||||
INSTALLVENDORBIN = C:\STRAWB~1\perl\bin
|
||||
DESTINSTALLVENDORBIN = $(DESTDIR)$(INSTALLVENDORBIN)
|
||||
INSTALLSCRIPT = C:\STRAWB~1\perl\bin
|
||||
DESTINSTALLSCRIPT = $(DESTDIR)$(INSTALLSCRIPT)
|
||||
INSTALLSITESCRIPT = C:\STRAWB~1\perl\site\bin
|
||||
DESTINSTALLSITESCRIPT = $(DESTDIR)$(INSTALLSITESCRIPT)
|
||||
INSTALLVENDORSCRIPT = C:\STRAWB~1\perl\bin
|
||||
DESTINSTALLVENDORSCRIPT = $(DESTDIR)$(INSTALLVENDORSCRIPT)
|
||||
INSTALLMAN1DIR = none
|
||||
DESTINSTALLMAN1DIR = $(DESTDIR)$(INSTALLMAN1DIR)
|
||||
INSTALLSITEMAN1DIR = $(INSTALLMAN1DIR)
|
||||
DESTINSTALLSITEMAN1DIR = $(DESTDIR)$(INSTALLSITEMAN1DIR)
|
||||
INSTALLVENDORMAN1DIR = $(INSTALLMAN1DIR)
|
||||
DESTINSTALLVENDORMAN1DIR = $(DESTDIR)$(INSTALLVENDORMAN1DIR)
|
||||
INSTALLMAN3DIR = none
|
||||
DESTINSTALLMAN3DIR = $(DESTDIR)$(INSTALLMAN3DIR)
|
||||
INSTALLSITEMAN3DIR = $(INSTALLMAN3DIR)
|
||||
DESTINSTALLSITEMAN3DIR = $(DESTDIR)$(INSTALLSITEMAN3DIR)
|
||||
INSTALLVENDORMAN3DIR = $(INSTALLMAN3DIR)
|
||||
DESTINSTALLVENDORMAN3DIR = $(DESTDIR)$(INSTALLVENDORMAN3DIR)
|
||||
PERL_LIB = C:\STRAWB~1\perl\lib
|
||||
PERL_ARCHLIB = C:\STRAWB~1\perl\lib
|
||||
PERL_ARCHLIBDEP = C:\STRAWB~1\perl\lib
|
||||
LIBPERL_A = libperl.a
|
||||
FIRST_MAKEFILE = Makefile
|
||||
MAKEFILE_OLD = Makefile.old
|
||||
MAKE_APERL_FILE = Makefile.aperl
|
||||
PERLMAINCC = $(CC)
|
||||
PERL_INC = C:\STRAWB~1\perl\lib\CORE
|
||||
PERL_INCDEP = C:\STRAWB~1\perl\lib\CORE
|
||||
PERL = "C:\Strawberry\perl\bin\perl.exe"
|
||||
FULLPERL = "C:\Strawberry\perl\bin\perl.exe"
|
||||
ABSPERL = $(PERL)
|
||||
PERLRUN = $(PERL)
|
||||
FULLPERLRUN = $(FULLPERL)
|
||||
ABSPERLRUN = $(ABSPERL)
|
||||
PERLRUNINST = $(PERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)"
|
||||
FULLPERLRUNINST = $(FULLPERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)"
|
||||
ABSPERLRUNINST = $(ABSPERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)"
|
||||
PERL_CORE = 0
|
||||
PERM_DIR = 755
|
||||
PERM_RW = 644
|
||||
PERM_RWX = 755
|
||||
|
||||
MAKEMAKER = C:/Strawberry/perl/lib/ExtUtils/MakeMaker.pm
|
||||
MM_VERSION = 7.36
|
||||
MM_REVISION = 73600
|
||||
|
||||
# FULLEXT = Pathname for extension directory (eg Foo/Bar/Oracle).
|
||||
# BASEEXT = Basename part of FULLEXT. May be just equal FULLEXT. (eg Oracle)
|
||||
# PARENT_NAME = NAME without BASEEXT and no trailing :: (eg Foo::Bar)
|
||||
# DLBASE = Basename part of dynamic library. May be just equal BASEEXT.
|
||||
MAKE = gmake
|
||||
FULLEXT = Graph\Easy
|
||||
BASEEXT = Easy
|
||||
PARENT_NAME = Graph
|
||||
DLBASE = $(BASEEXT)
|
||||
VERSION_FROM = lib/Graph/Easy.pm
|
||||
OBJECT =
|
||||
LDFROM = $(OBJECT)
|
||||
LINKTYPE = dynamic
|
||||
BOOTDEP =
|
||||
|
||||
# Handy lists of source code files:
|
||||
XS_FILES =
|
||||
C_FILES =
|
||||
O_FILES =
|
||||
H_FILES =
|
||||
MAN1PODS =
|
||||
MAN3PODS =
|
||||
|
||||
# Where is the Config information that we are using/depend on
|
||||
CONFIGDEP = $(PERL_ARCHLIBDEP)$(DFSEP)Config.pm $(PERL_INCDEP)$(DFSEP)config.h
|
||||
|
||||
# Where to build things
|
||||
INST_LIBDIR = $(INST_LIB)\Graph
|
||||
INST_ARCHLIBDIR = $(INST_ARCHLIB)\Graph
|
||||
|
||||
INST_AUTODIR = $(INST_LIB)\auto\$(FULLEXT)
|
||||
INST_ARCHAUTODIR = $(INST_ARCHLIB)\auto\$(FULLEXT)
|
||||
|
||||
INST_STATIC =
|
||||
INST_DYNAMIC =
|
||||
INST_BOOT =
|
||||
|
||||
# Extra linker info
|
||||
EXPORT_LIST = $(BASEEXT).def
|
||||
PERL_ARCHIVE = $(PERL_INC)\libperl530.a
|
||||
PERL_ARCHIVEDEP = $(PERL_INCDEP)\libperl530.a
|
||||
PERL_ARCHIVE_AFTER =
|
||||
|
||||
|
||||
TO_INST_PM = lib/Graph/Easy.pm \
|
||||
lib/Graph/Easy/As_ascii.pm \
|
||||
lib/Graph/Easy/As_graphml.pm \
|
||||
lib/Graph/Easy/As_graphviz.pm \
|
||||
lib/Graph/Easy/As_txt.pm \
|
||||
lib/Graph/Easy/As_vcg.pm \
|
||||
lib/Graph/Easy/Attributes.pm \
|
||||
lib/Graph/Easy/Base.pm \
|
||||
lib/Graph/Easy/Edge.pm \
|
||||
lib/Graph/Easy/Edge/Cell.pm \
|
||||
lib/Graph/Easy/Group.pm \
|
||||
lib/Graph/Easy/Group/Anon.pm \
|
||||
lib/Graph/Easy/Group/Cell.pm \
|
||||
lib/Graph/Easy/Layout.pm \
|
||||
lib/Graph/Easy/Layout/Chain.pm \
|
||||
lib/Graph/Easy/Layout/Force.pm \
|
||||
lib/Graph/Easy/Layout/Grid.pm \
|
||||
lib/Graph/Easy/Layout/Path.pm \
|
||||
lib/Graph/Easy/Layout/Repair.pm \
|
||||
lib/Graph/Easy/Layout/Scout.pm \
|
||||
lib/Graph/Easy/Node.pm \
|
||||
lib/Graph/Easy/Node/Anon.pm \
|
||||
lib/Graph/Easy/Node/Cell.pm \
|
||||
lib/Graph/Easy/Node/Empty.pm \
|
||||
lib/Graph/Easy/Parser.pm \
|
||||
lib/Graph/Easy/Parser/Graphviz.pm \
|
||||
lib/Graph/Easy/Parser/VCG.pm \
|
||||
lib/Graph/Easy/Util.pm
|
||||
|
||||
|
||||
# --- MakeMaker platform_constants section:
|
||||
MM_Win32_VERSION = 7.36
|
||||
|
||||
|
||||
# --- MakeMaker tool_autosplit section:
|
||||
# Usage: $(AUTOSPLITFILE) FileToSplit AutoDirToSplitInto
|
||||
AUTOSPLITFILE = $(ABSPERLRUN) -e "use AutoSplit; autosplit($$$$ARGV[0], $$$$ARGV[1], 0, 1, 1)" --
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker tool_xsubpp section:
|
||||
|
||||
|
||||
# --- MakeMaker tools_other section:
|
||||
CHMOD = $(ABSPERLRUN) -MExtUtils::Command -e chmod --
|
||||
CP = $(ABSPERLRUN) -MExtUtils::Command -e cp --
|
||||
MV = $(ABSPERLRUN) -MExtUtils::Command -e mv --
|
||||
NOOP = rem
|
||||
NOECHO = @
|
||||
RM_F = $(ABSPERLRUN) -MExtUtils::Command -e rm_f --
|
||||
RM_RF = $(ABSPERLRUN) -MExtUtils::Command -e rm_rf --
|
||||
TEST_F = $(ABSPERLRUN) -MExtUtils::Command -e test_f --
|
||||
TOUCH = $(ABSPERLRUN) -MExtUtils::Command -e touch --
|
||||
UMASK_NULL = umask 0
|
||||
DEV_NULL = > NUL
|
||||
MKPATH = $(ABSPERLRUN) -MExtUtils::Command -e mkpath --
|
||||
EQUALIZE_TIMESTAMP = $(ABSPERLRUN) -MExtUtils::Command -e eqtime --
|
||||
FALSE = $(ABSPERLRUN) -e "exit 1" --
|
||||
TRUE = $(ABSPERLRUN) -e "exit 0" --
|
||||
ECHO = $(ABSPERLRUN) -l -e "binmode STDOUT, qq{:raw}; print qq{@ARGV}" --
|
||||
ECHO_N = $(ABSPERLRUN) -e "print qq{@ARGV}" --
|
||||
UNINST = 0
|
||||
VERBINST = 0
|
||||
MOD_INSTALL = $(ABSPERLRUN) -MExtUtils::Install -e "install([ from_to => {@ARGV}, verbose => '$(VERBINST)', uninstall_shadows => '$(UNINST)', dir_mode => '$(PERM_DIR)' ]);" --
|
||||
DOC_INSTALL = $(ABSPERLRUN) -MExtUtils::Command::MM -e perllocal_install --
|
||||
UNINSTALL = $(ABSPERLRUN) -MExtUtils::Command::MM -e uninstall --
|
||||
WARN_IF_OLD_PACKLIST = $(ABSPERLRUN) -MExtUtils::Command::MM -e warn_if_old_packlist --
|
||||
MACROSTART =
|
||||
MACROEND =
|
||||
USEMAKEFILE = -f
|
||||
FIXIN = pl2bat.bat
|
||||
CP_NONEMPTY = $(ABSPERLRUN) -MExtUtils::Command::MM -e cp_nonempty --
|
||||
|
||||
|
||||
# --- MakeMaker makemakerdflt section:
|
||||
makemakerdflt : all
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker dist section:
|
||||
TAR = tar
|
||||
TARFLAGS = cvf
|
||||
ZIP = zip
|
||||
ZIPFLAGS = -r
|
||||
COMPRESS = gzip --best
|
||||
SUFFIX = .gz
|
||||
SHAR = shar
|
||||
PREOP = $(NOECHO) $(NOOP)
|
||||
POSTOP = $(NOECHO) $(NOOP)
|
||||
TO_UNIX = $(NOECHO) $(NOOP)
|
||||
CI = ci -u
|
||||
RCS_LABEL = rcs -Nv$(VERSION_SYM): -q
|
||||
DIST_CP = best
|
||||
DIST_DEFAULT = tardist
|
||||
DISTNAME = Graph-Easy
|
||||
DISTVNAME = Graph-Easy-0.76
|
||||
|
||||
|
||||
# --- MakeMaker macro section:
|
||||
|
||||
|
||||
# --- MakeMaker depend section:
|
||||
|
||||
|
||||
# --- MakeMaker cflags section:
|
||||
|
||||
|
||||
# --- MakeMaker const_loadlibs section:
|
||||
|
||||
|
||||
# --- MakeMaker const_cccmd section:
|
||||
|
||||
|
||||
# --- MakeMaker post_constants section:
|
||||
|
||||
|
||||
# --- MakeMaker pasthru section:
|
||||
|
||||
PASTHRU = LIBPERL_A="$(LIBPERL_A)"\
|
||||
LINKTYPE="$(LINKTYPE)"\
|
||||
PREFIX="$(PREFIX)"\
|
||||
PASTHRU_DEFINE="$(DEFINE) $(PASTHRU_DEFINE)"\
|
||||
PASTHRU_INC="$(INC) $(PASTHRU_INC)"
|
||||
|
||||
|
||||
# --- MakeMaker special_targets section:
|
||||
.SUFFIXES : .xs .c .C .cpp .i .s .cxx .cc $(OBJ_EXT)
|
||||
|
||||
.PHONY: all config static dynamic test linkext manifest blibdirs clean realclean disttest distdir pure_all subdirs clean_subdirs makemakerdflt manifypods realclean_subdirs subdirs_dynamic subdirs_pure_nolink subdirs_static subdirs-test_dynamic subdirs-test_static test_dynamic test_static
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker c_o section:
|
||||
|
||||
|
||||
# --- MakeMaker xs_c section:
|
||||
|
||||
|
||||
# --- MakeMaker xs_o section:
|
||||
|
||||
|
||||
# --- MakeMaker top_targets section:
|
||||
all :: pure_all
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
pure_all :: config pm_to_blib subdirs linkext
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
subdirs :: $(MYEXTLIB)
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
config :: $(FIRST_MAKEFILE) blibdirs
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
help :
|
||||
perldoc ExtUtils::MakeMaker
|
||||
|
||||
|
||||
# --- MakeMaker blibdirs section:
|
||||
blibdirs : $(INST_LIBDIR)$(DFSEP).exists $(INST_ARCHLIB)$(DFSEP).exists $(INST_AUTODIR)$(DFSEP).exists $(INST_ARCHAUTODIR)$(DFSEP).exists $(INST_BIN)$(DFSEP).exists $(INST_SCRIPT)$(DFSEP).exists $(INST_MAN1DIR)$(DFSEP).exists $(INST_MAN3DIR)$(DFSEP).exists
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
# Backwards compat with 6.18 through 6.25
|
||||
blibdirs.ts : blibdirs
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
$(INST_LIBDIR)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_LIBDIR)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_LIBDIR)
|
||||
$(NOECHO) $(TOUCH) $(INST_LIBDIR)$(DFSEP).exists
|
||||
|
||||
$(INST_ARCHLIB)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_ARCHLIB)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_ARCHLIB)
|
||||
$(NOECHO) $(TOUCH) $(INST_ARCHLIB)$(DFSEP).exists
|
||||
|
||||
$(INST_AUTODIR)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_AUTODIR)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_AUTODIR)
|
||||
$(NOECHO) $(TOUCH) $(INST_AUTODIR)$(DFSEP).exists
|
||||
|
||||
$(INST_ARCHAUTODIR)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_ARCHAUTODIR)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_ARCHAUTODIR)
|
||||
$(NOECHO) $(TOUCH) $(INST_ARCHAUTODIR)$(DFSEP).exists
|
||||
|
||||
$(INST_BIN)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_BIN)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_BIN)
|
||||
$(NOECHO) $(TOUCH) $(INST_BIN)$(DFSEP).exists
|
||||
|
||||
$(INST_SCRIPT)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_SCRIPT)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_SCRIPT)
|
||||
$(NOECHO) $(TOUCH) $(INST_SCRIPT)$(DFSEP).exists
|
||||
|
||||
$(INST_MAN1DIR)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_MAN1DIR)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_MAN1DIR)
|
||||
$(NOECHO) $(TOUCH) $(INST_MAN1DIR)$(DFSEP).exists
|
||||
|
||||
$(INST_MAN3DIR)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_MAN3DIR)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_MAN3DIR)
|
||||
$(NOECHO) $(TOUCH) $(INST_MAN3DIR)$(DFSEP).exists
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker linkext section:
|
||||
|
||||
linkext :: dynamic
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker dlsyms section:
|
||||
|
||||
Easy.def: Makefile.PL
|
||||
$(PERLRUN) -MExtUtils::Mksymlists \
|
||||
-e "Mksymlists('NAME'=>\"Graph::Easy\", 'DLBASE' => '$(BASEEXT)', 'DL_FUNCS' => { }, 'FUNCLIST' => [], 'IMPORTS' => { }, 'DL_VARS' => []);"
|
||||
|
||||
|
||||
# --- MakeMaker dynamic_bs section:
|
||||
|
||||
BOOTSTRAP =
|
||||
|
||||
|
||||
# --- MakeMaker dynamic section:
|
||||
|
||||
dynamic :: $(FIRST_MAKEFILE) config $(INST_BOOT) $(INST_DYNAMIC)
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker dynamic_lib section:
|
||||
|
||||
|
||||
# --- MakeMaker static section:
|
||||
|
||||
## $(INST_PM) has been moved to the all: target.
|
||||
## It remains here for awhile to allow for old usage: "make static"
|
||||
static :: $(FIRST_MAKEFILE) $(INST_STATIC)
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker static_lib section:
|
||||
|
||||
|
||||
# --- MakeMaker manifypods section:
|
||||
|
||||
POD2MAN_EXE = $(PERLRUN) "-MExtUtils::Command::MM" -e pod2man "--"
|
||||
POD2MAN = $(POD2MAN_EXE)
|
||||
|
||||
|
||||
manifypods : pure_all config
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker processPL section:
|
||||
|
||||
|
||||
# --- MakeMaker installbin section:
|
||||
|
||||
EXE_FILES = bin/graph-easy
|
||||
|
||||
pure_all :: $(INST_SCRIPT)\graph-easy
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
realclean ::
|
||||
$(RM_F) \
|
||||
$(INST_SCRIPT)\graph-easy
|
||||
|
||||
$(INST_SCRIPT)\graph-easy : bin/graph-easy $(FIRST_MAKEFILE) $(INST_SCRIPT)$(DFSEP).exists $(INST_BIN)$(DFSEP).exists
|
||||
$(NOECHO) $(RM_F) $(INST_SCRIPT)\graph-easy
|
||||
$(CP) bin/graph-easy $(INST_SCRIPT)\graph-easy
|
||||
$(FIXIN) $(INST_SCRIPT)\graph-easy
|
||||
-$(NOECHO) $(CHMOD) $(PERM_RWX) $(INST_SCRIPT)\graph-easy
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker subdirs section:
|
||||
|
||||
# none
|
||||
|
||||
# --- MakeMaker clean_subdirs section:
|
||||
clean_subdirs :
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker clean section:
|
||||
|
||||
# Delete temporary files but do not touch installed files. We don't delete
|
||||
# the Makefile here so a later make realclean still has a makefile to use.
|
||||
|
||||
clean :: clean_subdirs
|
||||
- $(RM_F) \
|
||||
$(BASEEXT).bso $(BASEEXT).def \
|
||||
$(BASEEXT).exp $(BASEEXT).x \
|
||||
$(BOOTSTRAP) $(INST_ARCHAUTODIR)\extralibs.all \
|
||||
$(INST_ARCHAUTODIR)\extralibs.ld $(MAKE_APERL_FILE) \
|
||||
*$(LIB_EXT) *$(OBJ_EXT) \
|
||||
*perl.core MYMETA.json \
|
||||
MYMETA.yml blibdirs.ts \
|
||||
core core.*perl.*.? \
|
||||
core.[0-9] core.[0-9][0-9] \
|
||||
core.[0-9][0-9][0-9] core.[0-9][0-9][0-9][0-9] \
|
||||
core.[0-9][0-9][0-9][0-9][0-9] lib$(BASEEXT).def \
|
||||
mon.out perl \
|
||||
perl$(EXE_EXT) perl.exe \
|
||||
perlmain.c pm_to_blib \
|
||||
pm_to_blib.ts so_locations \
|
||||
tmon.out
|
||||
- $(RM_RF) \
|
||||
blib dll.base \
|
||||
dll.exp
|
||||
$(NOECHO) $(RM_F) $(MAKEFILE_OLD)
|
||||
- $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD) $(DEV_NULL)
|
||||
|
||||
|
||||
# --- MakeMaker realclean_subdirs section:
|
||||
# so clean is forced to complete before realclean_subdirs runs
|
||||
realclean_subdirs : clean
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker realclean section:
|
||||
# Delete temporary files (via clean) and also delete dist files
|
||||
realclean purge :: realclean_subdirs
|
||||
- $(RM_F) \
|
||||
$(FIRST_MAKEFILE) $(MAKEFILE_OLD)
|
||||
- $(RM_RF) \
|
||||
$(DISTVNAME)
|
||||
|
||||
|
||||
# --- MakeMaker metafile section:
|
||||
metafile : create_distdir
|
||||
$(NOECHO) $(ECHO) Generating META.yml
|
||||
$(NOECHO) $(ECHO) --- > META_new.yml
|
||||
$(NOECHO) $(ECHO) "abstract: unknown" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) author: >> META_new.yml
|
||||
$(NOECHO) $(ECHO) " - unknown" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) build_requires: >> META_new.yml
|
||||
$(NOECHO) $(ECHO) " ExtUtils::MakeMaker: '0'" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) configure_requires: >> META_new.yml
|
||||
$(NOECHO) $(ECHO) " ExtUtils::MakeMaker: '0'" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) "dynamic_config: 1" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) "generated_by: 'ExtUtils::MakeMaker version 7.36, CPAN::Meta::Converter version 2.150010'" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) "license: unknown" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) meta-spec: >> META_new.yml
|
||||
$(NOECHO) $(ECHO) " url: http://module-build.sourceforge.net/META-spec-v1.4.html" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) " version: '1.4'" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) "name: Graph-Easy" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) no_index: >> META_new.yml
|
||||
$(NOECHO) $(ECHO) " directory:" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) " - t" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) " - inc" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) requires: >> META_new.yml
|
||||
$(NOECHO) $(ECHO) " Scalar::Util: '1.13'" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) " Test::More: '0.62'" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) " strict: '0'" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) " vars: '0'" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) " warnings: '0'" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) "version: '0.76'" >> META_new.yml
|
||||
$(NOECHO) $(ECHO) "x_serialization_backend: 'CPAN::Meta::YAML version 0.018'" >> META_new.yml
|
||||
-$(NOECHO) $(MV) META_new.yml $(DISTVNAME)/META.yml
|
||||
$(NOECHO) $(ECHO) Generating META.json
|
||||
$(NOECHO) $(ECHO) { > META_new.json
|
||||
$(NOECHO) $(ECHO) " \"abstract\" : \"unknown\"," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"author\" : [" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"unknown\"" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " ]," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"dynamic_config\" : 1," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"generated_by\" : \"ExtUtils::MakeMaker version 7.36, CPAN::Meta::Converter version 2.150010\"," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"license\" : [" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"unknown\"" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " ]," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"meta-spec\" : {" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"url\" : \"http://search.cpan.org/perldoc?CPAN::Meta::Spec\"," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"version\" : 2" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " }," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"name\" : \"Graph-Easy\"," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"no_index\" : {" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"directory\" : [" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"t\"," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"inc\"" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " ]" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " }," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"prereqs\" : {" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"build\" : {" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"requires\" : {" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"ExtUtils::MakeMaker\" : \"0\"" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " }" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " }," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"configure\" : {" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"requires\" : {" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"ExtUtils::MakeMaker\" : \"0\"" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " }" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " }," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"runtime\" : {" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"requires\" : {" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"Scalar::Util\" : \"1.13\"," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"Test::More\" : \"0.62\"," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"strict\" : \"0\"," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"vars\" : \"0\"," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"warnings\" : \"0\"" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " }" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " }" >> META_new.json
|
||||
$(NOECHO) $(ECHO) " }," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"release_status\" : \"stable\"," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"version\" : \"0.76\"," >> META_new.json
|
||||
$(NOECHO) $(ECHO) " \"x_serialization_backend\" : \"JSON::PP version 4.02\"" >> META_new.json
|
||||
$(NOECHO) $(ECHO) } >> META_new.json
|
||||
-$(NOECHO) $(MV) META_new.json $(DISTVNAME)/META.json
|
||||
|
||||
|
||||
# --- MakeMaker signature section:
|
||||
signature :
|
||||
cpansign -s
|
||||
|
||||
|
||||
# --- MakeMaker dist_basics section:
|
||||
distclean :: realclean distcheck
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
distcheck :
|
||||
$(PERLRUN) "-MExtUtils::Manifest=fullcheck" -e fullcheck
|
||||
|
||||
skipcheck :
|
||||
$(PERLRUN) "-MExtUtils::Manifest=skipcheck" -e skipcheck
|
||||
|
||||
manifest :
|
||||
$(PERLRUN) "-MExtUtils::Manifest=mkmanifest" -e mkmanifest
|
||||
|
||||
veryclean : realclean
|
||||
$(RM_F) *~ */*~ *.orig */*.orig *.bak */*.bak *.old */*.old
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker dist_core section:
|
||||
|
||||
dist : $(DIST_DEFAULT) $(FIRST_MAKEFILE)
|
||||
$(NOECHO) $(ABSPERLRUN) -l -e "print 'Warning: Makefile possibly out of date with $(VERSION_FROM)'\
|
||||
if -e '$(VERSION_FROM)' and -M '$(VERSION_FROM)' < -M '$(FIRST_MAKEFILE)';" --
|
||||
|
||||
tardist : $(DISTVNAME).tar$(SUFFIX)
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
uutardist : $(DISTVNAME).tar$(SUFFIX)
|
||||
uuencode $(DISTVNAME).tar$(SUFFIX) $(DISTVNAME).tar$(SUFFIX) > $(DISTVNAME).tar$(SUFFIX)_uu
|
||||
$(NOECHO) $(ECHO) 'Created $(DISTVNAME).tar$(SUFFIX)_uu'
|
||||
|
||||
$(DISTVNAME).tar$(SUFFIX) : distdir
|
||||
$(PREOP)
|
||||
$(TO_UNIX)
|
||||
$(TAR) $(TARFLAGS) $(DISTVNAME).tar $(DISTVNAME)
|
||||
$(RM_RF) $(DISTVNAME)
|
||||
$(COMPRESS) $(DISTVNAME).tar
|
||||
$(NOECHO) $(ECHO) 'Created $(DISTVNAME).tar$(SUFFIX)'
|
||||
$(POSTOP)
|
||||
|
||||
zipdist : $(DISTVNAME).zip
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
$(DISTVNAME).zip : distdir
|
||||
$(PREOP)
|
||||
$(ZIP) $(ZIPFLAGS) $(DISTVNAME).zip $(DISTVNAME)
|
||||
$(RM_RF) $(DISTVNAME)
|
||||
$(NOECHO) $(ECHO) 'Created $(DISTVNAME).zip'
|
||||
$(POSTOP)
|
||||
|
||||
shdist : distdir
|
||||
$(PREOP)
|
||||
$(SHAR) $(DISTVNAME) > $(DISTVNAME).shar
|
||||
$(RM_RF) $(DISTVNAME)
|
||||
$(NOECHO) $(ECHO) 'Created $(DISTVNAME).shar'
|
||||
$(POSTOP)
|
||||
|
||||
|
||||
# --- MakeMaker distdir section:
|
||||
create_distdir :
|
||||
$(RM_RF) $(DISTVNAME)
|
||||
$(PERLRUN) "-MExtUtils::Manifest=manicopy,maniread" \
|
||||
-e "manicopy(maniread(),'$(DISTVNAME)', '$(DIST_CP)');"
|
||||
|
||||
distdir : create_distdir distmeta
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker dist_test section:
|
||||
disttest : distdir
|
||||
cd $(DISTVNAME) && $(ABSPERLRUN) Makefile.PL
|
||||
cd $(DISTVNAME) && $(MAKE) $(PASTHRU)
|
||||
cd $(DISTVNAME) && $(MAKE) test $(PASTHRU)
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker dist_ci section:
|
||||
ci :
|
||||
$(ABSPERLRUN) -MExtUtils::Manifest=maniread -e "@all = sort keys %{ maniread() };\
|
||||
print(qq{Executing $(CI) @all\n});\
|
||||
system(qq{$(CI) @all}) == 0 or die $$!;\
|
||||
print(qq{Executing $(RCS_LABEL) ...\n});\
|
||||
system(qq{$(RCS_LABEL) @all}) == 0 or die $$!;" --
|
||||
|
||||
|
||||
# --- MakeMaker distmeta section:
|
||||
distmeta : create_distdir metafile
|
||||
$(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) -MExtUtils::Manifest=maniadd -e "exit unless -e q{META.yml};\
|
||||
eval { maniadd({q{META.yml} => q{Module YAML meta-data (added by MakeMaker)}}) }\
|
||||
or die \"Could not add META.yml to MANIFEST: $${'^@'}\"" --
|
||||
$(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) -MExtUtils::Manifest=maniadd -e "exit unless -f q{META.json};\
|
||||
eval { maniadd({q{META.json} => q{Module JSON meta-data (added by MakeMaker)}}) }\
|
||||
or die \"Could not add META.json to MANIFEST: $${'^@'}\"" --
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker distsignature section:
|
||||
distsignature : distmeta
|
||||
$(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) -MExtUtils::Manifest=maniadd -e "eval { maniadd({q{SIGNATURE} => q{Public-key signature (added by MakeMaker)}}) }\
|
||||
or die \"Could not add SIGNATURE to MANIFEST: $${'^@'}\"" --
|
||||
$(NOECHO) cd $(DISTVNAME) && $(TOUCH) SIGNATURE
|
||||
cd $(DISTVNAME) && cpansign -s
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker install section:
|
||||
|
||||
install :: pure_install doc_install
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
install_perl :: pure_perl_install doc_perl_install
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
install_site :: pure_site_install doc_site_install
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
install_vendor :: pure_vendor_install doc_vendor_install
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
pure_install :: pure_$(INSTALLDIRS)_install
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
doc_install :: doc_$(INSTALLDIRS)_install
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
pure__install : pure_site_install
|
||||
$(NOECHO) $(ECHO) INSTALLDIRS not defined, defaulting to INSTALLDIRS=site
|
||||
|
||||
doc__install : doc_site_install
|
||||
$(NOECHO) $(ECHO) INSTALLDIRS not defined, defaulting to INSTALLDIRS=site
|
||||
|
||||
pure_perl_install :: all
|
||||
$(NOECHO) $(MOD_INSTALL) \
|
||||
read "$(PERL_ARCHLIB)\auto\$(FULLEXT)\.packlist" \
|
||||
write "$(DESTINSTALLARCHLIB)\auto\$(FULLEXT)\.packlist" \
|
||||
"$(INST_LIB)" "$(DESTINSTALLPRIVLIB)" \
|
||||
"$(INST_ARCHLIB)" "$(DESTINSTALLARCHLIB)" \
|
||||
"$(INST_BIN)" "$(DESTINSTALLBIN)" \
|
||||
"$(INST_SCRIPT)" "$(DESTINSTALLSCRIPT)" \
|
||||
"$(INST_MAN1DIR)" "$(DESTINSTALLMAN1DIR)" \
|
||||
"$(INST_MAN3DIR)" "$(DESTINSTALLMAN3DIR)"
|
||||
$(NOECHO) $(WARN_IF_OLD_PACKLIST) \
|
||||
"$(SITEARCHEXP)\auto\$(FULLEXT)"
|
||||
|
||||
|
||||
pure_site_install :: all
|
||||
$(NOECHO) $(MOD_INSTALL) \
|
||||
read "$(SITEARCHEXP)\auto\$(FULLEXT)\.packlist" \
|
||||
write "$(DESTINSTALLSITEARCH)\auto\$(FULLEXT)\.packlist" \
|
||||
"$(INST_LIB)" "$(DESTINSTALLSITELIB)" \
|
||||
"$(INST_ARCHLIB)" "$(DESTINSTALLSITEARCH)" \
|
||||
"$(INST_BIN)" "$(DESTINSTALLSITEBIN)" \
|
||||
"$(INST_SCRIPT)" "$(DESTINSTALLSITESCRIPT)" \
|
||||
"$(INST_MAN1DIR)" "$(DESTINSTALLSITEMAN1DIR)" \
|
||||
"$(INST_MAN3DIR)" "$(DESTINSTALLSITEMAN3DIR)"
|
||||
$(NOECHO) $(WARN_IF_OLD_PACKLIST) \
|
||||
"$(PERL_ARCHLIB)\auto\$(FULLEXT)"
|
||||
|
||||
pure_vendor_install :: all
|
||||
$(NOECHO) $(MOD_INSTALL) \
|
||||
read "$(VENDORARCHEXP)\auto\$(FULLEXT)\.packlist" \
|
||||
write "$(DESTINSTALLVENDORARCH)\auto\$(FULLEXT)\.packlist" \
|
||||
"$(INST_LIB)" "$(DESTINSTALLVENDORLIB)" \
|
||||
"$(INST_ARCHLIB)" "$(DESTINSTALLVENDORARCH)" \
|
||||
"$(INST_BIN)" "$(DESTINSTALLVENDORBIN)" \
|
||||
"$(INST_SCRIPT)" "$(DESTINSTALLVENDORSCRIPT)" \
|
||||
"$(INST_MAN1DIR)" "$(DESTINSTALLVENDORMAN1DIR)" \
|
||||
"$(INST_MAN3DIR)" "$(DESTINSTALLVENDORMAN3DIR)"
|
||||
|
||||
|
||||
doc_perl_install :: all
|
||||
$(NOECHO) $(ECHO) Appending installation info to "$(DESTINSTALLARCHLIB)/perllocal.pod"
|
||||
-$(NOECHO) $(MKPATH) "$(DESTINSTALLARCHLIB)"
|
||||
-$(NOECHO) $(DOC_INSTALL) \
|
||||
"Module" "$(NAME)" \
|
||||
"installed into" "$(INSTALLPRIVLIB)" \
|
||||
LINKTYPE "$(LINKTYPE)" \
|
||||
VERSION "$(VERSION)" \
|
||||
EXE_FILES "$(EXE_FILES)" \
|
||||
>> "$(DESTINSTALLARCHLIB)\perllocal.pod"
|
||||
|
||||
doc_site_install :: all
|
||||
$(NOECHO) $(ECHO) Appending installation info to "$(DESTINSTALLARCHLIB)/perllocal.pod"
|
||||
-$(NOECHO) $(MKPATH) "$(DESTINSTALLARCHLIB)"
|
||||
-$(NOECHO) $(DOC_INSTALL) \
|
||||
"Module" "$(NAME)" \
|
||||
"installed into" "$(INSTALLSITELIB)" \
|
||||
LINKTYPE "$(LINKTYPE)" \
|
||||
VERSION "$(VERSION)" \
|
||||
EXE_FILES "$(EXE_FILES)" \
|
||||
>> "$(DESTINSTALLARCHLIB)\perllocal.pod"
|
||||
|
||||
doc_vendor_install :: all
|
||||
$(NOECHO) $(ECHO) Appending installation info to "$(DESTINSTALLARCHLIB)/perllocal.pod"
|
||||
-$(NOECHO) $(MKPATH) "$(DESTINSTALLARCHLIB)"
|
||||
-$(NOECHO) $(DOC_INSTALL) \
|
||||
"Module" "$(NAME)" \
|
||||
"installed into" "$(INSTALLVENDORLIB)" \
|
||||
LINKTYPE "$(LINKTYPE)" \
|
||||
VERSION "$(VERSION)" \
|
||||
EXE_FILES "$(EXE_FILES)" \
|
||||
>> "$(DESTINSTALLARCHLIB)\perllocal.pod"
|
||||
|
||||
|
||||
uninstall :: uninstall_from_$(INSTALLDIRS)dirs
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
uninstall_from_perldirs ::
|
||||
$(NOECHO) $(UNINSTALL) "$(PERL_ARCHLIB)\auto\$(FULLEXT)\.packlist"
|
||||
|
||||
uninstall_from_sitedirs ::
|
||||
$(NOECHO) $(UNINSTALL) "$(SITEARCHEXP)\auto\$(FULLEXT)\.packlist"
|
||||
|
||||
uninstall_from_vendordirs ::
|
||||
$(NOECHO) $(UNINSTALL) "$(VENDORARCHEXP)\auto\$(FULLEXT)\.packlist"
|
||||
|
||||
|
||||
# --- MakeMaker force section:
|
||||
# Phony target to force checking subdirectories.
|
||||
FORCE :
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker perldepend section:
|
||||
|
||||
|
||||
# --- MakeMaker makefile section:
|
||||
# We take a very conservative approach here, but it's worth it.
|
||||
# We move Makefile to Makefile.old here to avoid gnu make looping.
|
||||
$(FIRST_MAKEFILE) : Makefile.PL $(CONFIGDEP)
|
||||
$(NOECHO) $(ECHO) "Makefile out-of-date with respect to $?"
|
||||
$(NOECHO) $(ECHO) "Cleaning current config before rebuilding Makefile..."
|
||||
-$(NOECHO) $(RM_F) $(MAKEFILE_OLD)
|
||||
-$(NOECHO) $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD)
|
||||
- $(MAKE) $(USEMAKEFILE) $(MAKEFILE_OLD) clean $(DEV_NULL)
|
||||
$(PERLRUN) Makefile.PL
|
||||
$(NOECHO) $(ECHO) "==> Your Makefile has been rebuilt. <=="
|
||||
$(NOECHO) $(ECHO) "==> Please rerun the $(MAKE) command. <=="
|
||||
$(FALSE)
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker staticmake section:
|
||||
|
||||
# --- MakeMaker makeaperl section ---
|
||||
MAP_TARGET = perl
|
||||
FULLPERL = "C:\Strawberry\perl\bin\perl.exe"
|
||||
MAP_PERLINC = "-Iblib\arch" "-Iblib\lib" "-IC:\STRAWB~1\perl\lib" "-IC:\STRAWB~1\perl\lib"
|
||||
|
||||
$(MAP_TARGET) :: $(MAKE_APERL_FILE)
|
||||
$(MAKE) $(USEMAKEFILE) $(MAKE_APERL_FILE) $@
|
||||
|
||||
$(MAKE_APERL_FILE) : static $(FIRST_MAKEFILE) pm_to_blib
|
||||
$(NOECHO) $(ECHO) Writing \"$(MAKE_APERL_FILE)\" for this $(MAP_TARGET)
|
||||
$(NOECHO) $(PERLRUNINST) \
|
||||
Makefile.PL DIR="" \
|
||||
MAKEFILE=$(MAKE_APERL_FILE) LINKTYPE=static \
|
||||
MAKEAPERL=1 NORECURS=1 CCCDLFLAGS=
|
||||
|
||||
|
||||
# --- MakeMaker test section:
|
||||
TEST_VERBOSE=0
|
||||
TEST_TYPE=test_$(LINKTYPE)
|
||||
TEST_FILE = test.pl
|
||||
TEST_FILES = t/*.t
|
||||
TESTDB_SW = -d
|
||||
|
||||
testdb :: testdb_$(LINKTYPE)
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
test :: $(TEST_TYPE)
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
# Occasionally we may face this degenerate target:
|
||||
test_ : test_dynamic
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
subdirs-test_dynamic :: dynamic pure_all
|
||||
|
||||
test_dynamic :: subdirs-test_dynamic
|
||||
$(FULLPERLRUN) "-MExtUtils::Command::MM" "-MTest::Harness" "-e" "undef *Test::Harness::Switches; test_harness($(TEST_VERBOSE), '$(INST_LIB)', '$(INST_ARCHLIB)')" $(TEST_FILES)
|
||||
|
||||
testdb_dynamic :: dynamic pure_all
|
||||
$(FULLPERLRUN) $(TESTDB_SW) "-I$(INST_LIB)" "-I$(INST_ARCHLIB)" $(TEST_FILE)
|
||||
|
||||
subdirs-test_static :: static pure_all
|
||||
|
||||
test_static :: subdirs-test_static
|
||||
$(FULLPERLRUN) "-MExtUtils::Command::MM" "-MTest::Harness" "-e" "undef *Test::Harness::Switches; test_harness($(TEST_VERBOSE), '$(INST_LIB)', '$(INST_ARCHLIB)')" $(TEST_FILES)
|
||||
|
||||
testdb_static :: static pure_all
|
||||
$(FULLPERLRUN) $(TESTDB_SW) "-I$(INST_LIB)" "-I$(INST_ARCHLIB)" $(TEST_FILE)
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker ppd section:
|
||||
# Creates a PPD (Perl Package Description) for a binary distribution.
|
||||
ppd :
|
||||
$(NOECHO) $(ECHO) "<SOFTPKG NAME=\"Graph-Easy\" VERSION=\"0.76\">" > Graph-Easy.ppd
|
||||
$(NOECHO) $(ECHO) " <ABSTRACT></ABSTRACT>" >> Graph-Easy.ppd
|
||||
$(NOECHO) $(ECHO) " <AUTHOR></AUTHOR>" >> Graph-Easy.ppd
|
||||
$(NOECHO) $(ECHO) " <IMPLEMENTATION>" >> Graph-Easy.ppd
|
||||
$(NOECHO) $(ECHO) " <REQUIRE NAME=\"Scalar::Util\" VERSION=\"1.13\" />" >> Graph-Easy.ppd
|
||||
$(NOECHO) $(ECHO) " <REQUIRE NAME=\"Test::More\" VERSION=\"0.62\" />" >> Graph-Easy.ppd
|
||||
$(NOECHO) $(ECHO) " <REQUIRE NAME=\"strict::\" />" >> Graph-Easy.ppd
|
||||
$(NOECHO) $(ECHO) " <REQUIRE NAME=\"vars::\" />" >> Graph-Easy.ppd
|
||||
$(NOECHO) $(ECHO) " <REQUIRE NAME=\"warnings::\" />" >> Graph-Easy.ppd
|
||||
$(NOECHO) $(ECHO) " <ARCHITECTURE NAME=\"MSWin32-x64-multi-thread-5.30\" />" >> Graph-Easy.ppd
|
||||
$(NOECHO) $(ECHO) " <CODEBASE HREF=\"\" />" >> Graph-Easy.ppd
|
||||
$(NOECHO) $(ECHO) " </IMPLEMENTATION>" >> Graph-Easy.ppd
|
||||
$(NOECHO) $(ECHO) ^</SOFTPKG^> >> Graph-Easy.ppd
|
||||
|
||||
|
||||
# --- MakeMaker pm_to_blib section:
|
||||
|
||||
pm_to_blib : $(FIRST_MAKEFILE) $(TO_INST_PM)
|
||||
$(NOECHO) $(ABSPERLRUN) -MExtUtils::Install -e "pm_to_blib({@ARGV}, '$(INST_LIB)\auto', q[$(PM_FILTER)], '$(PERM_DIR)')" -- \
|
||||
lib/Graph/Easy.pm blib\lib\Graph\Easy.pm \
|
||||
lib/Graph/Easy/As_ascii.pm blib\lib\Graph\Easy\As_ascii.pm \
|
||||
lib/Graph/Easy/As_graphml.pm blib\lib\Graph\Easy\As_graphml.pm \
|
||||
lib/Graph/Easy/As_graphviz.pm blib\lib\Graph\Easy\As_graphviz.pm \
|
||||
lib/Graph/Easy/As_txt.pm blib\lib\Graph\Easy\As_txt.pm \
|
||||
lib/Graph/Easy/As_vcg.pm blib\lib\Graph\Easy\As_vcg.pm \
|
||||
lib/Graph/Easy/Attributes.pm blib\lib\Graph\Easy\Attributes.pm \
|
||||
lib/Graph/Easy/Base.pm blib\lib\Graph\Easy\Base.pm \
|
||||
lib/Graph/Easy/Edge.pm blib\lib\Graph\Easy\Edge.pm \
|
||||
lib/Graph/Easy/Edge/Cell.pm blib\lib\Graph\Easy\Edge\Cell.pm \
|
||||
lib/Graph/Easy/Group.pm blib\lib\Graph\Easy\Group.pm \
|
||||
lib/Graph/Easy/Group/Anon.pm blib\lib\Graph\Easy\Group\Anon.pm \
|
||||
lib/Graph/Easy/Group/Cell.pm blib\lib\Graph\Easy\Group\Cell.pm \
|
||||
lib/Graph/Easy/Layout.pm blib\lib\Graph\Easy\Layout.pm \
|
||||
lib/Graph/Easy/Layout/Chain.pm blib\lib\Graph\Easy\Layout\Chain.pm \
|
||||
lib/Graph/Easy/Layout/Force.pm blib\lib\Graph\Easy\Layout\Force.pm \
|
||||
lib/Graph/Easy/Layout/Grid.pm blib\lib\Graph\Easy\Layout\Grid.pm \
|
||||
lib/Graph/Easy/Layout/Path.pm blib\lib\Graph\Easy\Layout\Path.pm \
|
||||
lib/Graph/Easy/Layout/Repair.pm blib\lib\Graph\Easy\Layout\Repair.pm \
|
||||
lib/Graph/Easy/Layout/Scout.pm blib\lib\Graph\Easy\Layout\Scout.pm
|
||||
$(NOECHO) $(ABSPERLRUN) -MExtUtils::Install -e "pm_to_blib({@ARGV}, '$(INST_LIB)\auto', q[$(PM_FILTER)], '$(PERM_DIR)')" -- \
|
||||
lib/Graph/Easy/Node.pm blib\lib\Graph\Easy\Node.pm \
|
||||
lib/Graph/Easy/Node/Anon.pm blib\lib\Graph\Easy\Node\Anon.pm \
|
||||
lib/Graph/Easy/Node/Cell.pm blib\lib\Graph\Easy\Node\Cell.pm \
|
||||
lib/Graph/Easy/Node/Empty.pm blib\lib\Graph\Easy\Node\Empty.pm \
|
||||
lib/Graph/Easy/Parser.pm blib\lib\Graph\Easy\Parser.pm \
|
||||
lib/Graph/Easy/Parser/Graphviz.pm blib\lib\Graph\Easy\Parser\Graphviz.pm \
|
||||
lib/Graph/Easy/Parser/VCG.pm blib\lib\Graph\Easy\Parser\VCG.pm \
|
||||
lib/Graph/Easy/Util.pm blib\lib\Graph\Easy\Util.pm
|
||||
$(NOECHO) $(TOUCH) pm_to_blib
|
||||
|
||||
|
||||
# --- MakeMaker selfdocument section:
|
||||
|
||||
# here so even if top_targets is overridden, these will still be defined
|
||||
# gmake will silently still work if any are .PHONY-ed but nmake won't
|
||||
|
||||
static ::
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
dynamic ::
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
config ::
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker postamble section:
|
||||
|
||||
|
||||
# End.
|
||||
21
perl/lib/Graph-Easy-0.76/Makefile.PL
Normal file
21
perl/lib/Graph-Easy-0.76/Makefile.PL
Normal file
@@ -0,0 +1,21 @@
|
||||
# Note: this file was auto-generated by Module::Build::Compat version 0.4218
|
||||
require 5.008002;
|
||||
use ExtUtils::MakeMaker;
|
||||
WriteMakefile
|
||||
(
|
||||
'NAME' => 'Graph::Easy',
|
||||
'VERSION_FROM' => 'lib/Graph/Easy.pm',
|
||||
'PREREQ_PM' => {
|
||||
'Scalar::Util' => '1.13',
|
||||
'Test::More' => '0.62',
|
||||
'strict' => 0,
|
||||
'vars' => 0,
|
||||
'warnings' => 0
|
||||
},
|
||||
'INSTALLDIRS' => 'site',
|
||||
'EXE_FILES' => [
|
||||
'bin/graph-easy'
|
||||
],
|
||||
'PL_FILES' => {}
|
||||
)
|
||||
;
|
||||
75
perl/lib/Graph-Easy-0.76/README
Normal file
75
perl/lib/Graph-Easy-0.76/README
Normal file
@@ -0,0 +1,75 @@
|
||||
Graph-Easy
|
||||
==========
|
||||
|
||||
This module lets you create graphs (nodes/vertices connected by edges/arcs,
|
||||
not pie charts!) and then lay them out on a flat surface.
|
||||
|
||||
Once laid out, the graph can be converted into various output formats like
|
||||
ASCII art, HTML or SVG. You can also output the graph in graphviz format
|
||||
and let dot/neato/circo etc. do the layout for you.
|
||||
|
||||
Graphs can be either generated by Perl code, parsed from a simple text format
|
||||
that is human readable and maintainable, or parsed from Graphviz code.
|
||||
|
||||
For instance this input:
|
||||
|
||||
[ Bonn ] -> [ Berlin ]
|
||||
[ Berlin ] -> [ Frankfurt ] { border: 1px dotted black; }
|
||||
[ Frankfurt ] -> [ Dresden ]
|
||||
[ Berlin ] ..> [ Potsdam ]
|
||||
[ Potsdam ] => [ Cottbus ]
|
||||
|
||||
would be rendered in ASCII as:
|
||||
|
||||
+------+ +--------+ ............. +---------+
|
||||
| Bonn | --> | Berlin | --> : Frankfurt : --> | Dresden |
|
||||
+------+ +--------+ ............. +---------+
|
||||
:
|
||||
:
|
||||
v
|
||||
+---------+ +---------+
|
||||
| Potsdam | ==> | Cottbus |
|
||||
+---------+ +---------+
|
||||
|
||||
The HTML or SVG output would look similar except be more pretty :o)
|
||||
|
||||
Manual
|
||||
======
|
||||
|
||||
The manual is contained in the extra package Graph::Easy::Manual, which
|
||||
also contains a Pod2HTML converter, that can handle embedded graphs
|
||||
in POD files.
|
||||
|
||||
You can also view the manual online at:
|
||||
|
||||
http://bloodgate.com/perl/graph/manual/
|
||||
|
||||
Many more examples and documentation, especially on integrating this into
|
||||
a Mediawiki installation, can be found at:
|
||||
|
||||
http://bloodgate.com/perl/graph/
|
||||
|
||||
Have fun!
|
||||
|
||||
SVG Output
|
||||
==========
|
||||
|
||||
You also might want to install Graph::Easy::As_svg from CPAN, it provides
|
||||
you with the ability to generate SVG (Scalable Vector Graphics) files.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
See INSTALL on how to install this module.
|
||||
|
||||
AUTHOR
|
||||
======
|
||||
|
||||
Copyright (C) 2004 - 2008 by Tels http://bloodgate.com/
|
||||
|
||||
This library is free software; you can redistribute it and/or modify
|
||||
it under the same terms of the GPL version 2.
|
||||
|
||||
This module was formerly known as Graph-Simple, but has been renamed
|
||||
because it can also easily create non-simple graphs.
|
||||
|
||||
227
perl/lib/Graph-Easy-0.76/TODO
Normal file
227
perl/lib/Graph-Easy-0.76/TODO
Normal file
@@ -0,0 +1,227 @@
|
||||
Graph-Easy
|
||||
==========
|
||||
|
||||
See Graph::Easy under LIMITATIONS for some hot topics. In addition:
|
||||
|
||||
Important short-term TODO:
|
||||
|
||||
* sort_sub is no longer used in Heap, but the Layouter uses it (find out why)
|
||||
|
||||
* add for edges:
|
||||
+ weight,
|
||||
+ taillabel, taillink, tailtitle, headlabel, headlink, headtitle
|
||||
(or should these be startlabel, endlabel etc.?)
|
||||
+ a method to set the direction to bidirectional/undirected
|
||||
* graphviz parsing roundtrip:
|
||||
+ anon nodes lose their " " label
|
||||
+ border-width is wrongly dropped
|
||||
+ t/in/dot/9_edge_styles.dot is wrong
|
||||
+ nodes with HTML-like labels lose their outer shape (the label itself
|
||||
can have a border on the TABLE, as well as the node outside
|
||||
as well as the individual TD elements)
|
||||
* Combining table cells goes wrong if there is a "hole" in a row
|
||||
of cells. We need to gather them with their coordinates and only
|
||||
combine cells that are next to each other.
|
||||
* setting "size" as class attribute doesn't work
|
||||
* setting "offset: -2,0;" causes problems for multi-row nodes because
|
||||
the offset is taken into effect before growing the node
|
||||
* [ a ] { label: a; } - remove the superflous label upon parsing
|
||||
|
||||
* VCG/GDL
|
||||
+ debug, finish the attribute remapping and add more test cases
|
||||
+ implement support for \fn \fb \fI \fu \fB (bold underline etc.)
|
||||
+ implement support for \f03 (colors)
|
||||
+ implement support for \f- (hor line)
|
||||
+ implement full color-remapping support (in both directions)
|
||||
+ support subgraphs
|
||||
+ support regions
|
||||
+ support "nearedges:no"
|
||||
+ generally handle all attribute names without "_", too
|
||||
+ add support for "anchor"
|
||||
+ GDL has portsharing only as attributes for the top graph, while
|
||||
in Graph::Easy this attribute can be set for each edge
|
||||
|
||||
* layouter:
|
||||
+ head/tail label/title and link are currently ignored
|
||||
+ implement autosplit and autojoin for edges
|
||||
+ don't build chains across groups
|
||||
+ route multiple edges to/from a node in the proper order (shortest first)
|
||||
+ edges without a specific start/end port should not block ports that
|
||||
are reserved for edges with a start/end port number
|
||||
+ placing a node with an origin/offset inside another node results in
|
||||
endless loops as this condition is not checked and the placement
|
||||
of the grandparent node will thus always fail
|
||||
+ last-resort placing of node should first try to place it more near
|
||||
to where the edge(s) are connected
|
||||
+ allow end/start without specifying a side: "[ A ]--> { end: 0; } [ B ]"
|
||||
+ t/in/5_joint.txt - the rendering order is C,A,B, so that the edge
|
||||
from A to Z comes before B to Z. And since the layouter "knows" it
|
||||
should not block the last port on B, it makes a bend. In this case, tho,
|
||||
it could just go along B, because the edges join each other anyway.
|
||||
+ handle the special case where a node relative to another belongs to
|
||||
a different group than the parent/child
|
||||
|
||||
Recursive layouter:
|
||||
+ an empty group should consist of one cell (with the label and border)
|
||||
+ lay out all groups first, then interlink them together
|
||||
|
||||
* as_graphviz():
|
||||
+ links to/from empty groups fail
|
||||
+ attributes should be always checked against the default attribute and
|
||||
output if necessary, to make setting attributes in classes work -
|
||||
currently doing edge { color: blue; } will be ignored
|
||||
+ finish HTML-like labels (esp. with borders)
|
||||
These things seem to be actually not possible in Graphviz:
|
||||
+ border-styles: wave, dot-dot-dash and dot-dash
|
||||
+ edge-styles: wave, dot-dot-dash and dot-dash
|
||||
+ text-styles: underline, overline, strike-through, italic and bold
|
||||
|
||||
* Parser/Graphviz:
|
||||
+ see also the section CAVEATS in Graph::Easy::Parser::Graphviz
|
||||
+ style=filled should result in color => fillcolor, not color => fontcolor
|
||||
+ parse input in Latin1 charset
|
||||
+ parse "A|{ B|C }" (record shape with hor/ver nesting)
|
||||
+ nodes with shape record, but an edge going from the aggregate node have
|
||||
the edges rendered in dot starting/ending *somewhere* on the node with the
|
||||
record shape. We always (re-)connect these edges to the first part of the
|
||||
autosplit node. Maybe we should balance them to use parts with as little
|
||||
edges as possible. (The entire feature is quite bogus, since it is not
|
||||
clear from the resulting image where the edge really starts/ends, at the
|
||||
aggregate node or at the specific part where the arrow/line ends up
|
||||
pointing to/from...:-/
|
||||
+ attributes unknown to dot, but valid under Graph::Easy (like "labelpos")
|
||||
cause an error instead of a warning
|
||||
+ autosplit nodes (record) lose their attributes, these need to
|
||||
be carried over from the temp. node.
|
||||
+ parse nested tables
|
||||
|
||||
* as_ascii:
|
||||
+ better support for different shapes (circle, box, polygon etc)
|
||||
+ implement pod-formatted labels (*bold*, /italic/,
|
||||
_underline_, -l-i-n-e-t-h-r-o-u-g-h-, ~overline~, "code")
|
||||
+ rendering of "(group)" is empty (need a recursive layouter for that,
|
||||
since the current layouter doesn't add any group cells if a group doesn't
|
||||
have any node or edge at all)
|
||||
|
||||
* as_html:
|
||||
+ fill on edges
|
||||
+ v-- and --^ edges (mis-aligned arrows)
|
||||
(complete edge-arrow alignment in HTML)
|
||||
+ shift arrows on hor edge end/starts (non-short) left/right, too
|
||||
+ output of node-clusters is slightly wrong
|
||||
+ there is no space between two nodes placed next (with filler
|
||||
cell) to each other. Make filler cells output a ?
|
||||
+ bidir. self-loops are rendered with only one arrow: [A] <--> [A]
|
||||
+ define missing HTML edge pieces: CROSS sections with end/start points
|
||||
+ define JOINTs with start/end pieces (6 for each joints, making 24 types)
|
||||
+ implement HTML nodes as triangles, house, etc. using slanted edges
|
||||
|
||||
* fix nesting with pod-formatted labels
|
||||
|
||||
* edges between groups (ala "( 1 [A ]) -> ( 2 [B] )") or between a node
|
||||
and a group are missing in HTML, ASCII, BOXART and SVG.
|
||||
|
||||
* It would be good if we could remove Node::Empty (it blocks a cell
|
||||
just to draw the right/bottom border pieces)
|
||||
(we might put these "invisible" nodes into a different "cells" field,
|
||||
which will be rendered, but not queried for path finding etc)
|
||||
|
||||
Output:
|
||||
|
||||
* selfloop edges should counter the general flow:
|
||||
|
||||
Until done
|
||||
+------------+
|
||||
v |
|
||||
+-------+ +----------------+ +-----+
|
||||
| Start | --> | Main | --> | End |
|
||||
+-------+ +----------------+ +-----+
|
||||
|
||||
versus (loop still going left):
|
||||
|
||||
Until done
|
||||
+------------+
|
||||
v |
|
||||
+-----+ +----------------+ +-------+
|
||||
| End | <-- | Main | <-- | Start |
|
||||
+-----+ +----------------+ +-------+
|
||||
|
||||
* support two different arrow shapes on bidirectional edges
|
||||
|
||||
* as_txt():
|
||||
+ output of node clusters and node chains is not optimal
|
||||
+ links between groups are missing
|
||||
* as_ascii() and others: grow cells around point-shaped nodes to intrude:
|
||||
|
||||
...........................
|
||||
: : | : : :
|
||||
: : | : : :
|
||||
: : v : : :
|
||||
...........................
|
||||
: : : : :
|
||||
:-----> : * : <---- : :
|
||||
: : : : :
|
||||
...........................
|
||||
(at least the edge pieces could omit their left/right spacer in ASCII)
|
||||
|
||||
* as_boxart has some incorrect corner pieces:
|
||||
echo "[A|B|C||D]" | perl examples/as_boxart
|
||||
┌───┐───┐───┐
|
||||
│ A │ B │ C │
|
||||
└───┘───┘───┘
|
||||
│ D │
|
||||
└───┘
|
||||
echo "[A| |C||D| |E]" |perl examples/as_boxart
|
||||
┌───┐ ┌───┐
|
||||
│ A │ │ C │
|
||||
└───┘ └───┘
|
||||
│ │ │ │
|
||||
│ D │ │ E │
|
||||
└───┘ └───┘
|
||||
|
||||
Layout:
|
||||
|
||||
* allow user to specify max graph width (in cells) to avoid overly wide graphs
|
||||
* auto-grow nodes to be multicelled depending on the dimensions of their label
|
||||
("main page" gets 2x1, while "a \nb \nc \nd \ne \n" gets 1x2 cells)
|
||||
This currently causes problems and weird layouts.
|
||||
* Use the seed to generate randomized layouts
|
||||
|
||||
Rendering/Layout:
|
||||
|
||||
* allow "align: center, middle|top|bottom" for vertical alignment of labels.
|
||||
* add padding attributes (especially useful for HTML/SVG output)
|
||||
* add "shape" for groups:
|
||||
+ rect
|
||||
+ compact (the default, what it is now)
|
||||
+ none (no background, no border, no label)
|
||||
* add attribute "opacity" to set alpha channel on entire objects more easily
|
||||
* add attribute "shrink" (yes, no) to nodes to make them as compact as poss.
|
||||
|
||||
General:
|
||||
|
||||
* allow multiple subclasses ala CSS:
|
||||
|
||||
node.red { color: red; }
|
||||
node.green { color: green; }
|
||||
|
||||
[ Red ] { class: red green; } -> [ Green ] { class: green red; }
|
||||
|
||||
* Implement more class selectors:
|
||||
+ #id (object with ID id)
|
||||
|
||||
* implement pseudo-class "step" for animations (see POD)
|
||||
|
||||
* add some possibility to have different fonts, sizes and colors inside one
|
||||
label ala (when labelstyle=pod):
|
||||
FG<red|red text> BG<red|red background> FS<2em|big text>
|
||||
|
||||
Optimizing:
|
||||
|
||||
* put framebuffer related routines into own package (Graph::Easy::As_ascii)
|
||||
to avoid the dilemma that we need them from both Node and Graph.
|
||||
Likewise, some routines used by objects (e.g. graph, node etc) should
|
||||
be in a super-package and inherited)
|
||||
* improve the after-layout optimizer
|
||||
* less memory: store border and edge styles as ints instead of "solid" etc
|
||||
|
||||
96
perl/lib/Graph-Easy-0.76/bench/bench.pl
Normal file
96
perl/lib/Graph-Easy-0.76/bench/bench.pl
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
use Benchmark;
|
||||
use Graph::Easy;
|
||||
use Time::HiRes qw/time/;
|
||||
use strict;
|
||||
use Devel::Size qw/total_size/;
|
||||
|
||||
print "# Graph::Easy v", $Graph::Easy::VERSION,"\n";
|
||||
|
||||
print "Creating graph...\n";
|
||||
|
||||
my ($g,$n,$last);
|
||||
time_it ( \&create, shift);
|
||||
|
||||
print "Creating txt...\n";
|
||||
time_it ( \&as_txt );
|
||||
|
||||
# dump the text for later
|
||||
#print STDERR $g->as_txt(); exit;
|
||||
#print STDERR $g->as_graphviz(); exit;
|
||||
|
||||
# $g->timeout(20) if $g->can('timeout');
|
||||
print $g->as_ascii() if $g->nodes() < 40;
|
||||
|
||||
# for profile with -d:DProf
|
||||
#for (0..5) { $g->layout(); } exit;
|
||||
|
||||
print "\n";
|
||||
|
||||
exit if shift;
|
||||
|
||||
print "Benchmarking...\n";
|
||||
|
||||
$n = $g->node('1');
|
||||
|
||||
timethese (-5,
|
||||
{
|
||||
'node cnt' => sub { scalar $g->nodes(); },
|
||||
'edge cnt' => sub { scalar $g->edges(); },
|
||||
|
||||
'nodes' => sub { my @O = $g->nodes(); },
|
||||
'edges' => sub { my @O = $g->edges(); },
|
||||
|
||||
"conn's" => sub { $n->connections(); },
|
||||
|
||||
"succ's" => sub { scalar $n->successors(); },
|
||||
"succ' cnt" => sub { my @O = $n->successors(); },
|
||||
"edges_to" => sub { my @O = $n->edges_to($last) },
|
||||
# "layout" => sub { $g->layout(); },
|
||||
# "as_txt" => sub { $g->as_txt(); },
|
||||
|
||||
} );
|
||||
|
||||
sub time_it
|
||||
{
|
||||
my $time = time;
|
||||
|
||||
my $r = shift;
|
||||
|
||||
&$r(@_);
|
||||
|
||||
printf ("Took %0.4fs\n", time - $time);
|
||||
}
|
||||
|
||||
sub as_txt
|
||||
{
|
||||
my $t = $g->as_txt();
|
||||
}
|
||||
|
||||
sub create
|
||||
{
|
||||
my $cnt = abs(shift || 1000);
|
||||
|
||||
$g = Graph::Easy->new();
|
||||
|
||||
$n = Graph::Easy::Node->new('0');
|
||||
$last = Graph::Easy::Node->new('1');
|
||||
|
||||
for (2..$cnt)
|
||||
{
|
||||
my $node = Graph::Easy::Node->new($_);
|
||||
$g->add_edge($last, $node);
|
||||
my $n2 = Graph::Easy::Node->new($_.'A');
|
||||
$g->add_edge($last, $n2);
|
||||
my $n3 = Graph::Easy::Node->new($_.'B');
|
||||
$g->add_edge($last, $n3);
|
||||
$last = $node;
|
||||
}
|
||||
# prior to 0.25, the two calls to nodes() and edges() will take O(N) time, further
|
||||
# slowing down this routine by about 10-20%.
|
||||
print "Have now ", scalar $g->nodes(), " nodes and ", scalar $g->edges()," edges.\n";
|
||||
|
||||
print "Graph objects takes ", total_size($g), " bytes.\n";
|
||||
}
|
||||
|
||||
124
perl/lib/Graph-Easy-0.76/bench/serie.pl
Normal file
124
perl/lib/Graph-Easy-0.76/bench/serie.pl
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
use Benchmark;
|
||||
use Graph::Easy;
|
||||
use Time::HiRes qw/time/;
|
||||
use strict;
|
||||
use Devel::Size qw/total_size/;
|
||||
|
||||
print "# Graph::Easy v", $Graph::Easy::VERSION,"\n";
|
||||
|
||||
my @results;
|
||||
|
||||
my ($n,$last,$g, $size);
|
||||
|
||||
my @counts = ( qw/5 10 50 100 200 500 1000/ );
|
||||
|
||||
for my $count (@counts)
|
||||
{
|
||||
print "Creating graph with ", $count * 3, " nodes and edges...\n";
|
||||
my $rc = [ ];
|
||||
push @$rc, time_it ( \&create, $count);
|
||||
|
||||
$size = total_size($g);
|
||||
print "Graph objects takes $size bytes.\n";
|
||||
|
||||
print "Creating txt...\n";
|
||||
|
||||
print $g->as_ascii() if $count == 5;
|
||||
|
||||
if ($Graph::Easy::VERSION < 0.25 && ($count > 500))
|
||||
{
|
||||
print "Skipping as_foo() tests.\n";
|
||||
push @$rc, 0, 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
push @$rc,
|
||||
time_it ( \&as_txt ),
|
||||
time_it ( \&as_ascii);
|
||||
}
|
||||
|
||||
push @$rc, $size;
|
||||
|
||||
push @results, $rc;
|
||||
}
|
||||
|
||||
print "Results\n";
|
||||
|
||||
for my $r (@results)
|
||||
{
|
||||
print join (" ", @$r),"\n";
|
||||
}
|
||||
|
||||
print " <tr>\n <th>Graph::Easy v$Graph::Easy::VERSION</th>\n <th>"
|
||||
. join ("</th>\n <th>", @counts) . "</th>\n </tr>\n";
|
||||
|
||||
my $i = 0;
|
||||
for my $t ( qw/Creation as_txt as_ascii Memory/ )
|
||||
{
|
||||
print " <tr>\n <td>$t</td>\n";
|
||||
for my $r (@results)
|
||||
{
|
||||
print " <td>$r->[$i]</td>\n";
|
||||
}
|
||||
print " </tr>\n";
|
||||
$i++;
|
||||
}
|
||||
|
||||
#print STDERR $g->as_graphviz();
|
||||
|
||||
1;
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub time_it
|
||||
{
|
||||
my $time = time;
|
||||
|
||||
my $r = shift;
|
||||
|
||||
&$r(@_);
|
||||
|
||||
my $took = sprintf ("%0.4f", time - $time);
|
||||
|
||||
print "Took ${took}s\n";
|
||||
$took;
|
||||
}
|
||||
|
||||
sub as_txt
|
||||
{
|
||||
my $t = $g->as_txt();
|
||||
}
|
||||
|
||||
sub as_ascii
|
||||
{
|
||||
my $t = $g->as_ascii();
|
||||
}
|
||||
|
||||
sub create
|
||||
{
|
||||
my $cnt = abs(shift || 1000);
|
||||
|
||||
$g = Graph::Easy->new();
|
||||
|
||||
$n = Graph::Easy::Node->new('0');
|
||||
$last = Graph::Easy::Node->new('1');
|
||||
|
||||
for (2..$cnt+1)
|
||||
{
|
||||
my $node = Graph::Easy::Node->new($_);
|
||||
$g->add_edge($last, $node);
|
||||
my $n2 = Graph::Easy::Node->new($_.'A');
|
||||
$g->add_edge($last, $n2);
|
||||
my $n3 = Graph::Easy::Node->new($_.'B');
|
||||
$g->add_edge($last, $n3);
|
||||
$last = $node;
|
||||
}
|
||||
# prior to 0.25, the two calls to nodes() and edges() will take O(N) time, further
|
||||
# slowing down this routine by about 10-20%.
|
||||
print "Have now ", scalar $g->nodes(), " nodes and ", scalar $g->edges()," edges.\n";
|
||||
|
||||
$g->{timeout} = 120;
|
||||
}
|
||||
|
||||
137
perl/lib/Graph-Easy-0.76/bench/stress.pl
Normal file
137
perl/lib/Graph-Easy-0.76/bench/stress.pl
Normal file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
BEGIN
|
||||
{
|
||||
use lib 'lib';
|
||||
$|++;
|
||||
}
|
||||
|
||||
use Scalar::Util qw/weaken/;
|
||||
use Time::HiRes qw/time/;
|
||||
use Data::Dumper;
|
||||
use Graph::Easy;
|
||||
|
||||
my $N1 = shift || 5000;
|
||||
my $N2 = shift || 40000;
|
||||
my $STEP = shift || 2;
|
||||
|
||||
# results
|
||||
my $RC = [];
|
||||
|
||||
print "Using Graph::Easy v$Graph::Easy::VERSION\n";
|
||||
|
||||
for (my $N = $N1; $N < $N2; $N *= $STEP)
|
||||
{
|
||||
my @R = ($N);
|
||||
my $start = time();
|
||||
|
||||
print scalar localtime(), " start\n";
|
||||
normal($N);
|
||||
print scalar localtime(), " done, took ", sprintf("%.2f", time() - $start)," seconds\n";
|
||||
push @R, sprintf("%.2f",time() - $start);
|
||||
|
||||
$start = time();
|
||||
|
||||
print scalar localtime(), " start\n";
|
||||
|
||||
my $graph = graph($N); # return the graph to show that creation sep.
|
||||
|
||||
print scalar localtime(), " done creation, took ", sprintf("%.2f", time() - $start)," seconds\n";
|
||||
push @R, sprintf("%.2f",time() - $start);
|
||||
|
||||
$start = time();
|
||||
$graph = undef;
|
||||
|
||||
print scalar localtime(), " done destroy, took ", sprintf("%.2f", time() - $start)," seconds\n";
|
||||
push @R, sprintf("%.2f",time() - $start);
|
||||
|
||||
$start = time();
|
||||
|
||||
push @$RC, [ @R ];
|
||||
|
||||
}
|
||||
|
||||
print "\n";
|
||||
print "\n", join("\t\t", 'N', 'Normal', 'Graph-Easy'), "\tGraph-Easy\n";
|
||||
print join("\t\t", '', '', 'Create','Destroy'), "\n";
|
||||
print '-' x 70,"\n";
|
||||
|
||||
# print results
|
||||
for my $R (@$RC)
|
||||
{
|
||||
print join("\t\t", @$R), "\n";
|
||||
}
|
||||
|
||||
sub graph
|
||||
{
|
||||
my $N = shift;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
# create N objects, and "link" them together
|
||||
for my $i (1..$N)
|
||||
{
|
||||
my $b = $i; $b++;
|
||||
$graph->add_edge($i,$b);
|
||||
}
|
||||
print Dumper($graph),"\n" if $N < 10;
|
||||
$graph;
|
||||
}
|
||||
|
||||
sub normal
|
||||
{
|
||||
my $N = shift;
|
||||
|
||||
my $container = {};
|
||||
|
||||
my $old_object;
|
||||
|
||||
# create N objects, and "link" them together
|
||||
for my $i (1..$N)
|
||||
{
|
||||
my $o = new_object($i);
|
||||
$container->{nodes}->{$i} = $o;
|
||||
|
||||
$o->{graph} = $container;
|
||||
weaken($o->{graph});
|
||||
|
||||
if ($old_object)
|
||||
{
|
||||
my $link = new_link($old_object, $o, $i);
|
||||
$container->{edges}->{$i} = $link;
|
||||
|
||||
$link->{graph} = $container;
|
||||
{
|
||||
no warnings;
|
||||
|
||||
weaken($link->{graph});
|
||||
weaken($link->{to}->{graph});
|
||||
weaken($link->{from}->{graph});
|
||||
}
|
||||
}
|
||||
|
||||
$old_object = $o;
|
||||
}
|
||||
print Dumper($container),"\n" if $N < 10;
|
||||
}
|
||||
|
||||
sub new_object
|
||||
{
|
||||
my $id = shift;
|
||||
|
||||
my $o = bless { id => $id, att => {}, }, 'main';
|
||||
|
||||
$o;
|
||||
}
|
||||
|
||||
sub new_link
|
||||
{
|
||||
my ($a,$b,$id) = @_;
|
||||
|
||||
my $link = bless { id => $id, from => $a, to => $b, att => {} }, 'main';
|
||||
|
||||
$a->{edges}->{$id} = $link;
|
||||
$b->{edges}->{$id} = $link;
|
||||
|
||||
$link;
|
||||
}
|
||||
18
perl/lib/Graph-Easy-0.76/bench/test.dot
Normal file
18
perl/lib/Graph-Easy-0.76/bench/test.dot
Normal file
@@ -0,0 +1,18 @@
|
||||
digraph GRAPH_0 {
|
||||
|
||||
// Generated by Graph::Easy 0.38 at Sat Dec 31 16:13:04 2005
|
||||
|
||||
edge [ arrowhead=open ];
|
||||
graph [ rankdir=LR ];
|
||||
node [
|
||||
fontsize=11,
|
||||
fillcolor=white,
|
||||
style=filled,
|
||||
shape=box ];
|
||||
|
||||
Berlin [ URL="/wiki/index.php/Berlin" ]
|
||||
Bonn [ URL="/wiki/index.php/Bonn" ]
|
||||
|
||||
Bonn -> Berlin
|
||||
|
||||
}
|
||||
2
perl/lib/Graph-Easy-0.76/bench/test.txt
Normal file
2
perl/lib/Graph-Easy-0.76/bench/test.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
graph { autolink: name; }
|
||||
[ Bonn ] -> [ Berlin ]
|
||||
0
perl/lib/Graph-Easy-0.76/blib/arch/.exists
Normal file
0
perl/lib/Graph-Easy-0.76/blib/arch/.exists
Normal file
0
perl/lib/Graph-Easy-0.76/blib/lib/Graph/.exists
Normal file
0
perl/lib/Graph-Easy-0.76/blib/lib/Graph/.exists
Normal file
4203
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy.pm
Normal file
4203
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy.pm
Normal file
File diff suppressed because it is too large
Load Diff
1428
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/As_ascii.pm
Normal file
1428
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/As_ascii.pm
Normal file
File diff suppressed because it is too large
Load Diff
396
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/As_graphml.pm
Normal file
396
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/As_graphml.pm
Normal file
@@ -0,0 +1,396 @@
|
||||
#############################################################################
|
||||
# Output an Graph::Easy object as GraphML text
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::As_graphml;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Graph::Easy::Attributes;
|
||||
|
||||
# map the Graph::Easy attribute types to a GraphML name:
|
||||
my $attr_type_to_name =
|
||||
{
|
||||
ATTR_STRING() => 'string',
|
||||
ATTR_COLOR() => 'string',
|
||||
ATTR_ANGLE() => 'double',
|
||||
ATTR_PORT() => 'string',
|
||||
ATTR_UINT() => 'integer',
|
||||
ATTR_URL() => 'string',
|
||||
|
||||
ATTR_LIST() => 'string',
|
||||
ATTR_LCTEXT() => 'string',
|
||||
ATTR_TEXT() => 'string',
|
||||
};
|
||||
|
||||
sub _graphml_attr_keys
|
||||
{
|
||||
my ($self, $tpl, $tpl_no_default, $class, $att, $ids, $id) = @_;
|
||||
|
||||
my $base_class = $class; $base_class =~ s/\..*//;
|
||||
$base_class = 'graph' if $base_class =~ /group/;
|
||||
$ids->{$base_class} = {} unless ref $ids->{$base_class};
|
||||
|
||||
my $txt = '';
|
||||
for my $name (sort keys %$att)
|
||||
{
|
||||
my $entry = $self->_attribute_entry($class,$name);
|
||||
# get a fresh template
|
||||
my $t = $tpl;
|
||||
$t = $tpl_no_default unless defined $entry->[ ATTR_DEFAULT_SLOT ];
|
||||
|
||||
# only keep it once
|
||||
next if exists $ids->{$base_class}->{$name};
|
||||
|
||||
$t =~ s/##id##/$$id/;
|
||||
|
||||
# node.foo => node, group.bar => graph
|
||||
$t =~ s/##class##/$base_class/;
|
||||
$t =~ s/##name##/$name/;
|
||||
$t =~ s/##type##/$attr_type_to_name->{ $entry->[ ATTR_TYPE_SLOT ] || ATTR_COLOR }/eg;
|
||||
|
||||
# will only be there and thus replaced if we have a default
|
||||
if ($t =~ /##default##/)
|
||||
{
|
||||
my $def = $entry->[ ATTR_DEFAULT_SLOT ];
|
||||
# not a simple value?
|
||||
$def = $self->default_attribute($name) if ref $def;
|
||||
$t =~ s/##default##/$def/;
|
||||
}
|
||||
|
||||
# remember name => ID
|
||||
$ids->{$base_class}->{$name} = $$id; $$id++;
|
||||
# append the definition
|
||||
$txt .= $t;
|
||||
}
|
||||
$txt;
|
||||
}
|
||||
|
||||
# yED example:
|
||||
|
||||
# <data key="d0">
|
||||
# <y:ShapeNode>
|
||||
# <y:Geometry height="30.0" width="30.0" x="277.0" y="96.0"/>
|
||||
# <y:Fill color="#FFCC00" transparent="false"/>
|
||||
# <y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
# <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="11.0" x="9.5" y="5.6494140625">1</y:NodeLabel>
|
||||
# <y:Shape type="ellipse"/>
|
||||
# </y:ShapeNode>
|
||||
# </data>
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
sub _as_graphml
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
my $args = $_[0];
|
||||
$args = { name => $_[0] } if ref($args) ne 'HASH' && @_ == 1;
|
||||
$args = { @_ } if ref($args) ne 'HASH' && @_ > 1;
|
||||
|
||||
$args->{format} = 'graph-easy' unless defined $args->{format};
|
||||
|
||||
if ($args->{format} !~ /^(graph-easy|Graph::Easy|yED)\z/i)
|
||||
{
|
||||
return $self->error("Format '$args->{format}' not understood by as_graphml.");
|
||||
}
|
||||
my $format = $args->{format};
|
||||
|
||||
# Convert the graph to a textual representation - does not need layout().
|
||||
|
||||
my $schema = "http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd";
|
||||
$schema = "http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd" if $format eq 'yED';
|
||||
my $y_schema = '';
|
||||
$y_schema = "\n xmlns:y=\"http://www.yworks.com/xml/graphml\"" if $format eq 'yED';
|
||||
|
||||
my $txt = <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"##Y##
|
||||
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
|
||||
##SCHEMA##">
|
||||
|
||||
<!-- Created by Graph::Easy v##VERSION## at ##DATE## -->
|
||||
|
||||
EOF
|
||||
;
|
||||
|
||||
$txt =~ s/##DATE##/scalar localtime()/e;
|
||||
$txt =~ s/##VERSION##/$Graph::Easy::VERSION/;
|
||||
$txt =~ s/##SCHEMA##/$schema/;
|
||||
$txt =~ s/##Y##/$y_schema/;
|
||||
|
||||
# <key id="d0" for="node" attr.name="color" attr.type="string">
|
||||
# <default>yellow</default>
|
||||
# </key>
|
||||
# <key id="d1" for="edge" attr.name="weight" attr.type="double"/>
|
||||
|
||||
# First gather all possible attributes, then add defines for them. This
|
||||
# avoids lengthy re-definitions of attributes that aren't used:
|
||||
|
||||
my %keys;
|
||||
|
||||
my $tpl = ' <key id="##id##" for="##class##" attr.name="##name##" attr.type="##type##">'
|
||||
."\n <default>##default##</default>\n"
|
||||
." </key>\n";
|
||||
my $tpl_no_default = ' <key id="##id##" for="##class##" attr.name="##name##" attr.type="##type##"/>'."\n";
|
||||
|
||||
# for yED:
|
||||
# <key for="node" id="d0" yfiles.type="nodegraphics"/>
|
||||
# <key attr.name="description" attr.type="string" for="node" id="d1"/>
|
||||
# <key for="edge" id="d2" yfiles.type="edgegraphics"/>
|
||||
# <key attr.name="description" attr.type="string" for="edge" id="d3"/>
|
||||
# <key for="graphml" id="d4" yfiles.type="resources"/>
|
||||
|
||||
# we need to remember the mapping between attribute name and ID:
|
||||
my $ids = {};
|
||||
my $id = 'd0';
|
||||
|
||||
###########################################################################
|
||||
# first the class attributes
|
||||
for my $class (sort keys %{$self->{att}})
|
||||
{
|
||||
my $att = $self->{att}->{$class};
|
||||
|
||||
$txt .=
|
||||
$self->_graphml_attr_keys( $tpl, $tpl_no_default, $class, $att, $ids, \$id);
|
||||
|
||||
}
|
||||
|
||||
my @nodes = $self->sorted_nodes('name','id');
|
||||
|
||||
###########################################################################
|
||||
# now the attributes on the objects:
|
||||
for my $o (@nodes, ord_values ( $self->{edges} ))
|
||||
{
|
||||
$txt .=
|
||||
$self->_graphml_attr_keys( $tpl, $tpl_no_default, $o->class(),
|
||||
$o->raw_attributes(), $ids, \$id);
|
||||
}
|
||||
$txt .= "\n" unless $id eq 'd0';
|
||||
|
||||
my $indent = ' ';
|
||||
$txt .= $indent . '<graph id="G" edgedefault="' . $self->type() . "\">\n";
|
||||
|
||||
# output graph attributes:
|
||||
$txt .= $self->_attributes_as_graphml($self,' ',$ids->{graph});
|
||||
|
||||
# output groups recursively
|
||||
my @groups = $self->groups_within(0);
|
||||
foreach my $g (@groups)
|
||||
{
|
||||
$txt .= $g->as_graphml($indent.' ',$ids); # marks nodes as processed if nec.
|
||||
}
|
||||
|
||||
$indent = ' ';
|
||||
foreach my $n (@nodes)
|
||||
{
|
||||
next if $n->{group}; # already done in a group
|
||||
$txt .= $n->as_graphml($indent,$ids); # <node id="..." ...>
|
||||
}
|
||||
|
||||
$txt .= "\n";
|
||||
|
||||
foreach my $n (@nodes)
|
||||
{
|
||||
next if $n->{group}; # already done in a group
|
||||
|
||||
my @out = $n->sorted_successors();
|
||||
# for all outgoing connections
|
||||
foreach my $other (@out)
|
||||
{
|
||||
# in case there exists more than one edge from $n --> $other
|
||||
my @edges = $n->edges_to($other);
|
||||
for my $edge (sort { $a->{id} <=> $b->{id} } @edges)
|
||||
{
|
||||
$txt .= $edge->as_graphml($indent,$ids); # <edge id="..." ...>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$txt .= " </graph>\n</graphml>\n";
|
||||
$txt;
|
||||
}
|
||||
|
||||
sub _safe_xml
|
||||
{
|
||||
# make a text XML safe
|
||||
my ($self,$txt) = @_;
|
||||
|
||||
$txt =~ s/&/&/g; # quote &
|
||||
$txt =~ s/>/>/g; # quote >
|
||||
$txt =~ s/</</g; # quote <
|
||||
$txt =~ s/"/"/g; # quote "
|
||||
$txt =~ s/'/'/g; # quote '
|
||||
$txt =~ s/\\\\/\\/g; # "\\" to "\"
|
||||
|
||||
$txt;
|
||||
}
|
||||
|
||||
sub _attributes_as_graphml
|
||||
{
|
||||
# output the attributes of an object
|
||||
my ($graph, $self, $indent, $ids) = @_;
|
||||
|
||||
my $tpl = "$indent <data key=\"##id##\">##value##</data>\n";
|
||||
my $att = $self->get_attributes();
|
||||
my $txt = '';
|
||||
for my $n (sort keys %$att)
|
||||
{
|
||||
next unless exists $ids->{$n};
|
||||
my $def = $self->default_attribute($n);
|
||||
next if defined $def && $def eq $att->{$n};
|
||||
my $t = $tpl;
|
||||
$t =~ s/##id##/$ids->{$n}/;
|
||||
$t =~ s/##value##/$graph->_safe_xml($att->{$n})/e;
|
||||
$txt .= $t;
|
||||
}
|
||||
$txt;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Group;
|
||||
|
||||
use strict;
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
sub as_graphml
|
||||
{
|
||||
my ($self, $indent, $ids) = @_;
|
||||
|
||||
my $txt = $indent . '<graph id="' . $self->_safe_xml($self->{name}) . '" edgedefault="' .
|
||||
$self->{graph}->type() . "\">\n";
|
||||
$txt .= $self->{graph}->_attributes_as_graphml($self, $indent, $ids->{graph});
|
||||
|
||||
foreach my $n (ord_values ( $self->{nodes} ))
|
||||
{
|
||||
my @out = $n->sorted_successors();
|
||||
|
||||
$txt .= $n->as_graphml($indent.' ', $ids); # <node id="..." ...>
|
||||
|
||||
# for all outgoing connections
|
||||
foreach my $other (@out)
|
||||
{
|
||||
# in case there exists more than one edge from $n --> $other
|
||||
my @edges = $n->edges_to($other);
|
||||
for my $edge (sort { $a->{id} <=> $b->{id} } @edges)
|
||||
{
|
||||
$txt .= $edge->as_graphml($indent.' ',$ids);
|
||||
}
|
||||
$txt .= "\n" if @edges > 0;
|
||||
}
|
||||
}
|
||||
|
||||
# output groups recursively
|
||||
my @groups = $self->groups_within(0);
|
||||
foreach my $g (@groups)
|
||||
{
|
||||
$txt .= $g->_as_graphml($indent.' ',$ids); # marks nodes as processed if nec.
|
||||
}
|
||||
|
||||
# XXX TODO: edges from/to this group
|
||||
|
||||
# close this group
|
||||
$txt .= $indent . "</graph>";
|
||||
|
||||
$txt;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Node;
|
||||
|
||||
use strict;
|
||||
|
||||
sub as_graphml
|
||||
{
|
||||
my ($self, $indent, $ids) = @_;
|
||||
|
||||
my $g = $self->{graph};
|
||||
my $txt = $indent . '<node id="' . $g->_safe_xml($self->{name}) . "\">\n";
|
||||
|
||||
$txt .= $g->_attributes_as_graphml($self, $indent, $ids->{node});
|
||||
|
||||
$txt .= "$indent</node>\n";
|
||||
|
||||
return $txt;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Edge;
|
||||
|
||||
use strict;
|
||||
|
||||
sub as_graphml
|
||||
{
|
||||
my ($self, $indent, $ids) = @_;
|
||||
|
||||
my $g = $self->{graph};
|
||||
my $txt = $indent . '<edge source="' . $g->_safe_xml($self->{from}->{name}) .
|
||||
'" target="' . $g->_safe_xml($self->{to}->{name}) . "\">\n";
|
||||
|
||||
$txt .= $g->_attributes_as_graphml($self, $indent, $ids->{edge});
|
||||
|
||||
$txt .= "$indent</edge>\n";
|
||||
|
||||
$txt;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::As_graphml - Generate a GraphML text from a Graph::Easy object
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
$graph->add_edge ('Bonn', 'Berlin');
|
||||
|
||||
print $graph->as_graphml();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<Graph::Easy::As_graphml> contains just the code for converting a
|
||||
L<Graph::Easy|Graph::Easy> object to a GraphML text.
|
||||
|
||||
=head2 Attributes
|
||||
|
||||
Attributes are output in the format that C<Graph::Easy> specifies. More
|
||||
details about the valid attributes and their default values can be found
|
||||
in the Graph::Easy online manual:
|
||||
|
||||
L<http://bloodgate.com/perl/graph/manual/>.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
Exports nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>, L<http://graphml.graphdrawing.org/>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2008 by Tels L<http://bloodgate.com>
|
||||
|
||||
See the LICENSE file for information.
|
||||
|
||||
=cut
|
||||
|
||||
1249
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/As_graphviz.pm
Normal file
1249
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/As_graphviz.pm
Normal file
File diff suppressed because it is too large
Load Diff
487
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/As_txt.pm
Normal file
487
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/As_txt.pm
Normal file
@@ -0,0 +1,487 @@
|
||||
#############################################################################
|
||||
# Output an Graph::Easy object as textual description
|
||||
#
|
||||
|
||||
package Graph::Easy::As_txt;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub _as_txt
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
# Convert the graph to a textual representation - does not need layout().
|
||||
$self->_assign_ranks();
|
||||
|
||||
# generate the class attributes first
|
||||
my $txt = '';
|
||||
my $att = $self->{att};
|
||||
for my $class (sort keys %$att)
|
||||
{
|
||||
|
||||
my $out = $self->_remap_attributes(
|
||||
$class, $att->{$class}, {}, 'noquote', 'encode' );
|
||||
|
||||
my $att = '';
|
||||
for my $atr (sort keys %$out)
|
||||
{
|
||||
# border is handled special below
|
||||
next if $atr =~ /^border/;
|
||||
$att .= " $atr: $out->{$atr};\n";
|
||||
}
|
||||
|
||||
# edges do not have a border
|
||||
if ($class !~ /^edge/)
|
||||
{
|
||||
my $border = $self->border_attribute($class) || '';
|
||||
|
||||
# 'solid 1px #000000' =~ /^solid/;
|
||||
# 'solid 1px #000000' =~ /^solid 1px #000000/;
|
||||
$border = '' if $self->default_attribute($class,'border') =~ /^$border/;
|
||||
|
||||
$att .= " border: $border;\n" if $border ne '';
|
||||
}
|
||||
|
||||
if ($att ne '')
|
||||
{
|
||||
# the following makes short, single definitions to fit on one line
|
||||
if ($att !~ /\n.*\n/ && length($att) < 40)
|
||||
{
|
||||
$att =~ s/\n/ /; $att =~ s/^ / /;
|
||||
}
|
||||
else
|
||||
{
|
||||
$att = "\n$att";
|
||||
}
|
||||
$txt .= "$class {$att}\n";
|
||||
}
|
||||
}
|
||||
|
||||
$txt .= "\n" if $txt ne ''; # insert newline
|
||||
|
||||
my @nodes = $self->sorted_nodes('name','id');
|
||||
|
||||
my $count = 0;
|
||||
# output nodes with attributes first, sorted by their name
|
||||
foreach my $n (@nodes)
|
||||
{
|
||||
$n->{_p} = undef; # mark as not yet processed
|
||||
my $att = $n->attributes_as_txt();
|
||||
if ($att ne '')
|
||||
{
|
||||
$n->{_p} = 1; # mark as processed
|
||||
$count++;
|
||||
$txt .= $n->as_pure_txt() . $att . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$txt .= "\n" if $count > 0; # insert a newline
|
||||
|
||||
# output groups first, with their nodes
|
||||
foreach my $gn (sort keys %{$self->{groups}})
|
||||
{
|
||||
my $group = $self->{groups}->{$gn};
|
||||
$txt .= $group->as_txt(); # marks nodes as processed if nec.
|
||||
$count++;
|
||||
}
|
||||
|
||||
# XXX TODO:
|
||||
# Output all nodes with rank=0 first, and also follow their successors
|
||||
# What is left will then be done next, with rank=1 etc.
|
||||
# This output order let's us output node chains in compact form as:
|
||||
# [A]->[B]->[C]->[D]
|
||||
# [B]->[E]
|
||||
# instead of having:
|
||||
# [A]->[B]
|
||||
# [B]->[E]
|
||||
# [B]->[C] etc
|
||||
|
||||
@nodes = $self->sorted_nodes('rank','name');
|
||||
foreach my $n (@nodes)
|
||||
{
|
||||
my @out = $n->sorted_successors();
|
||||
my $first = $n->as_pure_txt(); # [ A | B ]
|
||||
if ( defined $n->{autosplit} || ((@out == 0) && ( (scalar $n->predecessors() || 0) == 0)))
|
||||
{
|
||||
# single node without any connections (unless already output)
|
||||
next if exists $n->{autosplit} && !defined $n->{autosplit};
|
||||
$txt .= $first . "\n" unless defined $n->{_p};
|
||||
}
|
||||
|
||||
$first = $n->_as_part_txt(); # [ A.0 ]
|
||||
# for all outgoing connections
|
||||
foreach my $other (@out)
|
||||
{
|
||||
# in case there exists more than one edge from $n --> $other
|
||||
my @edges = $n->edges_to($other);
|
||||
for my $edge (sort { $a->{id} <=> $b->{id} } @edges)
|
||||
{
|
||||
$txt .= $first . $edge->as_txt() . $other->_as_part_txt() . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $n (@nodes)
|
||||
{
|
||||
delete $n->{_p}; # clean up
|
||||
}
|
||||
|
||||
$txt;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Group;
|
||||
|
||||
use strict;
|
||||
|
||||
sub as_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
my $n = '';
|
||||
if (!$self->isa('Graph::Easy::Group::Anon'))
|
||||
{
|
||||
$n = $self->{name};
|
||||
# quote special chars in name
|
||||
$n =~ s/([\[\]\(\)\{\}\#])/\\$1/g;
|
||||
$n = ' ' . $n;
|
||||
}
|
||||
|
||||
my $txt = "($n";
|
||||
|
||||
$n = $self->{nodes};
|
||||
|
||||
$txt .= (keys %$n > 0 ? "\n" : ' ');
|
||||
for my $name ( sort keys %$n )
|
||||
{
|
||||
$n->{$name}->{_p} = 1; # mark as processed
|
||||
$txt .= ' ' . $n->{$name}->as_pure_txt() . "\n";
|
||||
}
|
||||
$txt .= ")" . $self->attributes_as_txt() . "\n\n";
|
||||
|
||||
# insert all the edges of the group
|
||||
|
||||
#
|
||||
$txt;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Node;
|
||||
|
||||
use strict;
|
||||
|
||||
sub attributes_as_txt
|
||||
{
|
||||
# return the attributes of this node as text description
|
||||
my ($self, $remap) = @_;
|
||||
|
||||
# nodes that were autosplit
|
||||
if (exists $self->{autosplit})
|
||||
{
|
||||
# other nodes are invisible in as_txt:
|
||||
return '' unless defined $self->{autosplit};
|
||||
# the first one might have had a label set
|
||||
}
|
||||
|
||||
my $att = '';
|
||||
my $class = $self->class();
|
||||
my $g = $self->{graph};
|
||||
|
||||
# XXX TODO: remove atttributes that are simple the default attributes
|
||||
|
||||
my $attributes = $self->{att};
|
||||
if (exists $self->{autosplit})
|
||||
{
|
||||
# for the first node in a row of autosplit nodes, we need to create
|
||||
# the correct attributes, e.g. "silver|red|" instead of just silver:
|
||||
my $basename = $self->{autosplit_basename};
|
||||
$attributes = { };
|
||||
|
||||
my $parts = $self->{autosplit_parts};
|
||||
# gather all possible attribute names, otherwise an attribute set
|
||||
# on only one part (like via "color: |red;" would not show up:
|
||||
my $names = {};
|
||||
for my $child ($self, @$parts)
|
||||
{
|
||||
for my $k (sort keys %{$child->{att}})
|
||||
{
|
||||
$names->{$k} = undef;
|
||||
}
|
||||
}
|
||||
|
||||
for my $k (sort keys %$names)
|
||||
{
|
||||
next if $k eq 'basename';
|
||||
my $val = $self->{att}->{$k};
|
||||
$val = '' unless defined $val;
|
||||
my $first = $val; my $not_equal = 0;
|
||||
$val .= '|';
|
||||
for my $child (@$parts)
|
||||
{
|
||||
# only consider our own autosplit parts (check should not be nec.)
|
||||
# next if !exists $child->{autosplit_basename} ||
|
||||
# $child->{autosplit_basename} ne $basename;
|
||||
|
||||
my $v = $child->{att}->{$k}; $v = '' if !defined $v;
|
||||
$not_equal ++ if $v ne $first;
|
||||
$val .= $v . '|';
|
||||
}
|
||||
# all parts equal, so do "red|red|red" => "red"
|
||||
$val = $first if $not_equal == 0;
|
||||
|
||||
$val =~ s/\|+\z/\|/; # "silver|||" => "silver|"
|
||||
$val =~ s/\|\z// if $val =~ /\|.*\|/; # "silver|" => "silver|"
|
||||
# but "red|blue|" => "red|blue"
|
||||
$attributes->{$k} = $val unless $val eq '|'; # skip '|'
|
||||
}
|
||||
$attributes->{basename} = $self->{att}->{basename} if defined $self->{att}->{basename};
|
||||
}
|
||||
|
||||
my $new = $g->_remap_attributes( $self, $attributes, $remap, 'noquote', 'encode' );
|
||||
|
||||
# For nodes, we do not output their group attribute, since they simple appear
|
||||
# at the right place in the txt:
|
||||
delete $new->{group};
|
||||
|
||||
# for groups inside groups, insert their group attribute
|
||||
$new->{group} = $self->{group}->{name}
|
||||
if $self->isa('Graph::Easy::Group') && exists $self->{group};
|
||||
|
||||
if (defined $self->{origin})
|
||||
{
|
||||
$new->{origin} = $self->{origin}->{name};
|
||||
$new->{offset} = join(',', $self->offset());
|
||||
}
|
||||
|
||||
# shorten output for multi-celled nodes
|
||||
# for "rows: 2;" still output "rows: 2;", because it is shorter
|
||||
if (exists $new->{columns})
|
||||
{
|
||||
$new->{size} = ($new->{columns}||1) . ',' . ($new->{rows}||1);
|
||||
delete $new->{rows};
|
||||
delete $new->{columns};
|
||||
# don't output the default size
|
||||
delete $new->{size} if $new->{size} eq '1,1';
|
||||
}
|
||||
|
||||
for my $atr (sort keys %$new)
|
||||
{
|
||||
next if $atr =~ /^border/; # handled special
|
||||
|
||||
$att .= "$atr: $new->{$atr}; ";
|
||||
}
|
||||
|
||||
if (!$self->isa_cell())
|
||||
{
|
||||
my $border;
|
||||
if (!exists $self->{autosplit})
|
||||
{
|
||||
$border = $self->border_attribute();
|
||||
}
|
||||
else
|
||||
{
|
||||
$border = Graph::Easy::_border_attribute(
|
||||
$attributes->{borderstyle}||'',
|
||||
$attributes->{borderwidth}||'',
|
||||
$attributes->{bordercolor}||'');
|
||||
}
|
||||
|
||||
# XXX TODO: should do this for all attributes, not only for border
|
||||
# XXX TODO: this seems wrong anyway
|
||||
|
||||
# don't include default border
|
||||
$border = '' if ref $g && $g->attribute($class,'border') eq $border;
|
||||
$att .= "border: $border; " if $border ne '';
|
||||
}
|
||||
|
||||
# if we have a subclass, we probably need to include it
|
||||
my $c = '';
|
||||
$c = $1 if $class =~ /\.(\w+)/;
|
||||
|
||||
# but we do not need to include it if our group has a nodeclass attribute
|
||||
$c = '' if ref($self->{group}) && $self->{group}->attribute('nodeclass') eq $c;
|
||||
|
||||
# include our subclass as attribute
|
||||
$att .= "class: $c; " if $c ne '' && $c ne 'anon';
|
||||
|
||||
# generate attribute text if nec.
|
||||
$att = ' { ' . $att . '}' if $att ne '';
|
||||
|
||||
$att;
|
||||
}
|
||||
|
||||
sub _as_part_txt
|
||||
{
|
||||
# for edges, we need the name of the part of the first part, not the entire
|
||||
# autosplit text
|
||||
my $self = shift;
|
||||
|
||||
my $name = $self->{name};
|
||||
|
||||
# quote special chars in name
|
||||
$name =~ s/([\[\]\|\{\}\#])/\\$1/g;
|
||||
|
||||
'[ ' . $name . ' ]';
|
||||
}
|
||||
|
||||
sub as_pure_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
if (exists $self->{autosplit} && defined $self->{autosplit})
|
||||
{
|
||||
my $name = $self->{autosplit};
|
||||
|
||||
# quote special chars in name (but not |)
|
||||
$name =~ s/([\[\]\{\}\#])/\\$1/g;
|
||||
|
||||
return '[ '. $name .' ]'
|
||||
}
|
||||
|
||||
my $name = $self->{name};
|
||||
|
||||
# quote special chars in name
|
||||
$name =~ s/([\[\]\|\{\}\#])/\\$1/g;
|
||||
|
||||
'[ ' . $name . ' ]';
|
||||
}
|
||||
|
||||
sub as_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
if (exists $self->{autosplit})
|
||||
{
|
||||
return '' unless defined $self->{autosplit};
|
||||
my $name = $self->{autosplit};
|
||||
# quote special chars in name (but not |)
|
||||
$name =~ s/([\[\]\{\}\#])/\\$1/g;
|
||||
return '[ ' . $name . ' ]'
|
||||
}
|
||||
|
||||
my $name = $self->{name};
|
||||
|
||||
# quote special chars in name
|
||||
$name =~ s/([\[\]\|\{\}\#])/\\$1/g;
|
||||
|
||||
'[ ' . $name . ' ]' . $self->attributes_as_txt();
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Edge;
|
||||
|
||||
my $styles = {
|
||||
solid => '--',
|
||||
dotted => '..',
|
||||
double => '==',
|
||||
'double-dash' => '= ',
|
||||
dashed => '- ',
|
||||
'dot-dash' => '.-',
|
||||
'dot-dot-dash' => '..-',
|
||||
wave => '~~',
|
||||
};
|
||||
|
||||
sub _as_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# '- Name ' or ''
|
||||
my $n = $self->{att}->{label}; $n = '' unless defined $n;
|
||||
|
||||
my $left = ' '; $left = ' <' if $self->{bidirectional};
|
||||
my $right = '> '; $right = ' ' if $self->{undirected};
|
||||
|
||||
my $s = $self->style() || 'solid';
|
||||
|
||||
my $style = '--';
|
||||
|
||||
# suppress border on edges
|
||||
my $suppress = { all => { label => undef } };
|
||||
if ($s =~ /^(bold|bold-dash|broad|wide|invisible)\z/)
|
||||
{
|
||||
# output "--> { style: XXX; }"
|
||||
$style = '--';
|
||||
}
|
||||
else
|
||||
{
|
||||
# output "-->" or "..>" etc
|
||||
$suppress->{all}->{style} = undef;
|
||||
|
||||
$style = $styles->{ $s };
|
||||
if (!defined $style)
|
||||
{
|
||||
require Carp;
|
||||
Carp::confess ("Unknown edge style '$s'\n");
|
||||
}
|
||||
}
|
||||
|
||||
$n = $style . " $n " if $n ne '';
|
||||
|
||||
# make " - " into " - - "
|
||||
$style = $style . $style if $self->{undirected} && substr($style,1,1) eq ' ';
|
||||
|
||||
# ' - Name -->' or ' --> ' or ' -- '
|
||||
my $a = $self->attributes_as_txt($suppress) . ' '; $a =~ s/^\s//;
|
||||
$left . $n . $style . $right . $a;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::As_txt - Generate textual description from graph object
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
my $bonn = Graph::Easy::Node->new(
|
||||
name => 'Bonn',
|
||||
);
|
||||
my $berlin = Graph::Easy::Node->new(
|
||||
name => 'Berlin',
|
||||
);
|
||||
|
||||
$graph->add_edge ($bonn, $berlin);
|
||||
|
||||
print $graph->as_txt();
|
||||
|
||||
# prints something like:
|
||||
|
||||
# [ Bonn ] -> [ Berlin ]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<Graph::Easy::As_txt> contains just the code for converting a
|
||||
L<Graph::Easy|Graph::Easy> object to a human-readable textual description.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
Exports nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2007 by Tels L<http://bloodgate.com>
|
||||
|
||||
See the LICENSE file for information.
|
||||
|
||||
=cut
|
||||
|
||||
586
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/As_vcg.pm
Normal file
586
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/As_vcg.pm
Normal file
@@ -0,0 +1,586 @@
|
||||
#############################################################################
|
||||
# Output the graph as VCG or GDL text.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::As_vcg;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $vcg_remap = {
|
||||
node => {
|
||||
align => \&_vcg_remap_align,
|
||||
autolabel => undef,
|
||||
autolink => undef,
|
||||
autotitle => undef,
|
||||
background => undef,
|
||||
basename => undef,
|
||||
class => undef,
|
||||
colorscheme => undef,
|
||||
columns => undef,
|
||||
flow => undef,
|
||||
fontsize => undef,
|
||||
format => undef,
|
||||
group => undef,
|
||||
id => undef,
|
||||
link => undef,
|
||||
linkbase => undef,
|
||||
offset => undef,
|
||||
origin => undef,
|
||||
pointstyle => undef,
|
||||
rank => 'level',
|
||||
rotate => undef,
|
||||
rows => undef,
|
||||
shape => \&_vcg_remap_shape,
|
||||
size => undef,
|
||||
textstyle => undef,
|
||||
textwrap => undef,
|
||||
title => undef,
|
||||
},
|
||||
edge => {
|
||||
color => 'color', # this entry overrides 'all'!
|
||||
align => undef,
|
||||
arrowshape => undef,
|
||||
arrowstyle => undef,
|
||||
autojoin => undef,
|
||||
autolabel => undef,
|
||||
autolink => undef,
|
||||
autosplit => undef,
|
||||
autotitle => undef,
|
||||
border => undef,
|
||||
bordercolor => undef,
|
||||
borderstyle => undef,
|
||||
borderwidth => undef,
|
||||
colorscheme => undef,
|
||||
end => undef,
|
||||
fontsize => undef,
|
||||
format => undef,
|
||||
id => undef,
|
||||
labelcolor => 'textcolor',
|
||||
link => undef,
|
||||
linkbase => undef,
|
||||
minlen => undef,
|
||||
start => undef,
|
||||
# XXX TODO: remap unknown styles
|
||||
style => 'linestyle',
|
||||
textstyle => undef,
|
||||
textwrap => undef,
|
||||
title => undef,
|
||||
},
|
||||
graph => {
|
||||
align => \&_vcg_remap_align,
|
||||
flow => \&_vcg_remap_flow,
|
||||
label => 'title',
|
||||
type => undef,
|
||||
},
|
||||
group => {
|
||||
},
|
||||
all => {
|
||||
background => undef,
|
||||
color => 'textcolor',
|
||||
comment => undef,
|
||||
fill => 'color',
|
||||
font => 'fontname',
|
||||
},
|
||||
always => {
|
||||
},
|
||||
# this routine will handle all custom "x-dot-..." attributes
|
||||
x => \&_remap_custom_vcg_attributes,
|
||||
};
|
||||
|
||||
sub _remap_custom_vcg_attributes
|
||||
{
|
||||
my ($self, $name, $value) = @_;
|
||||
|
||||
# drop anything that is not starting with "x-vcg-..."
|
||||
return (undef,undef) unless $name =~ /^x-vcg-/;
|
||||
|
||||
$name =~ s/^x-vcg-//; # "x-vcg-foo" => "foo"
|
||||
($name,$value);
|
||||
}
|
||||
|
||||
my $vcg_shapes = {
|
||||
rect => 'box',
|
||||
diamond => 'rhomb',
|
||||
triangle => 'triangle',
|
||||
invtriangle => 'triangle',
|
||||
ellipse => 'ellipse',
|
||||
circle => 'circle',
|
||||
hexagon => 'hexagon',
|
||||
trapezium => 'trapeze',
|
||||
invtrapezium => 'uptrapeze',
|
||||
invparallelogram => 'lparallelogram',
|
||||
parallelogram => 'rparallelogram',
|
||||
};
|
||||
|
||||
sub _vcg_remap_shape
|
||||
{
|
||||
my ($self, $name, $shape) = @_;
|
||||
|
||||
return ('invisible','yes') if $shape eq 'invisible';
|
||||
|
||||
('shape', $vcg_shapes->{$shape} || 'box');
|
||||
}
|
||||
|
||||
sub _vcg_remap_align
|
||||
{
|
||||
my ($self, $name, $style) = @_;
|
||||
|
||||
# center => center, left => left_justify, right => right_justify
|
||||
$style .= '_justify' unless $style eq 'center';
|
||||
|
||||
('textmode', $style);
|
||||
}
|
||||
|
||||
my $vcg_flow = {
|
||||
'south' => 'top_to_bottom',
|
||||
'north' => 'bottom_to_top',
|
||||
'down' => 'top_to_bottom',
|
||||
'up' => 'bottom_to_top',
|
||||
'east' => 'left_to_right',
|
||||
'west' => 'right_to_left',
|
||||
'right' => 'left_to_right',
|
||||
'left' => 'right_to_left',
|
||||
};
|
||||
|
||||
sub _vcg_remap_flow
|
||||
{
|
||||
my ($self, $name, $style) = @_;
|
||||
|
||||
('orientation', $vcg_flow->{$style} || 'top_to_bottom');
|
||||
}
|
||||
|
||||
sub _class_attributes_as_vcg
|
||||
{
|
||||
# convert a hash with attribute => value mappings to a string
|
||||
my ($self, $a, $class) = @_;
|
||||
|
||||
|
||||
my $att = '';
|
||||
$class = '' if $class eq 'graph';
|
||||
$class .= '.' if $class ne '';
|
||||
|
||||
# create the attributes as text:
|
||||
for my $atr (sort keys %$a)
|
||||
{
|
||||
my $v = $a->{$atr};
|
||||
$v =~ s/"/\\"/g; # '2"' => '2\"'
|
||||
$v = '"' . $v . '"' unless $v =~ /^[0-9]+\z/; # 1, "1a"
|
||||
$att .= " $class$atr: $v\n";
|
||||
}
|
||||
$att =~ s/,\s$//; # remove last ","
|
||||
|
||||
$att = "\n$att" unless $att eq '';
|
||||
$att;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _generate_vcg_edge
|
||||
{
|
||||
# Given an edge, generate the VCG code for it
|
||||
my ($self, $e, $indent) = @_;
|
||||
|
||||
# skip links from/to groups, these will be done later
|
||||
return '' if
|
||||
$e->{from}->isa('Graph::Easy::Group') ||
|
||||
$e->{to}->isa('Graph::Easy::Group');
|
||||
|
||||
my $edge_att = $e->attributes_as_vcg();
|
||||
|
||||
$e->{_p} = undef; # mark as processed
|
||||
" edge:$edge_att\n"; # return edge text
|
||||
}
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
sub _as_vcg
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
# convert the graph to a textual representation
|
||||
# does not need a layout() beforehand!
|
||||
|
||||
# gather all edge classes to build the classname attribute from them:
|
||||
$self->{_vcg_edge_classes} = {};
|
||||
for my $e (ord_values ( $self->{edges} ))
|
||||
{
|
||||
my $class = $e->sub_class();
|
||||
$self->{_vcg_edge_classes}->{$class} = undef if defined $class && $class ne '';
|
||||
}
|
||||
# sort gathered class names and map them to integers
|
||||
my $class_names = '';
|
||||
if (keys %{$self->{_vcg_edge_classes}} > 0)
|
||||
{
|
||||
my $i = 1;
|
||||
$class_names = "\n";
|
||||
for my $ec (sort keys %{$self->{_vcg_edge_classes}})
|
||||
{
|
||||
$self->{_vcg_edge_classes}->{$ec} = $i; # remember mapping
|
||||
$class_names .= " classname $i: \"$ec\"\n";
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
# generate the class attributes first
|
||||
my $label = $self->label();
|
||||
my $t = ''; $t = "\n title: \"$label\"" if $label ne '';
|
||||
|
||||
my $txt = "graph: {$t\n\n" .
|
||||
" // Generated by Graph::Easy $Graph::Easy::VERSION" .
|
||||
" at " . scalar localtime() . "\n" .
|
||||
$class_names;
|
||||
|
||||
my $groups = $self->groups();
|
||||
|
||||
# to keep track of invisible helper nodes
|
||||
$self->{_vcg_invis} = {};
|
||||
# name for invisible helper nodes
|
||||
$self->{_vcg_invis_id} = 'joint0';
|
||||
|
||||
my $atts = $self->{att};
|
||||
# insert the class attributes
|
||||
for my $class (qw/edge graph node/)
|
||||
{
|
||||
next if $class =~ /\./; # skip subclasses
|
||||
|
||||
my $out = $self->_remap_attributes( $class, $atts->{$class}, $vcg_remap, 'noquote');
|
||||
$txt .= $self->_class_attributes_as_vcg($out, $class);
|
||||
}
|
||||
|
||||
$txt .= "\n" if $txt ne ''; # insert newline
|
||||
|
||||
###########################################################################
|
||||
# output groups as subgraphs
|
||||
|
||||
# insert the edges into the proper group
|
||||
$self->_edges_into_groups() if $groups > 0;
|
||||
|
||||
# output the groups (aka subclusters)
|
||||
my $indent = ' ';
|
||||
for my $group (sort { $a->{name} cmp $b->{name} } values %{$self->{groups}})
|
||||
{
|
||||
# quote special chars in group name
|
||||
my $name = $group->{name}; $name =~ s/([\[\]\(\)\{\}\#"])/\\$1/g;
|
||||
|
||||
# # output group attributes first
|
||||
# $txt .= " subgraph \"cluster$group->{id}\" {\n${indent}label=\"$name\";\n";
|
||||
|
||||
# Make a copy of the attributes, including our class attributes:
|
||||
my $copy = {};
|
||||
my $attribs = $group->get_attributes();
|
||||
|
||||
for my $a (keys %$attribs)
|
||||
{
|
||||
$copy->{$a} = $attribs->{$a};
|
||||
}
|
||||
# # set some defaults
|
||||
# $copy->{'borderstyle'} = 'solid' unless defined $copy->{'borderstyle'};
|
||||
|
||||
my $out = {};
|
||||
# my $out = $self->_remap_attributes( $group->class(), $copy, $vcg_remap, 'noquote');
|
||||
|
||||
# Set some defaults:
|
||||
$out->{fillcolor} = '#a0d0ff' unless defined $out->{fillcolor};
|
||||
# $out->{labeljust} = 'l' unless defined $out->{labeljust};
|
||||
|
||||
my $att = '';
|
||||
# we need to output style first ("filled" and "color" need come later)
|
||||
for my $atr (reverse sort keys %$out)
|
||||
{
|
||||
my $v = $out->{$atr};
|
||||
$v = '"' . $v . '"';
|
||||
$att .= " $atr: $v\n";
|
||||
}
|
||||
$txt .= $att . "\n" if $att ne '';
|
||||
|
||||
# # output nodes (w/ or w/o attributes) in that group
|
||||
# for my $n ($group->sorted_nodes())
|
||||
# {
|
||||
# my $att = $n->attributes_as_vcg();
|
||||
# $n->{_p} = undef; # mark as processed
|
||||
# $txt .= $indent . $n->as_graphviz_txt() . $att . "\n";
|
||||
# }
|
||||
|
||||
# # output node connections in this group
|
||||
# for my $e (ord_values ( $group->{edges} ))
|
||||
# {
|
||||
# next if exists $e->{_p};
|
||||
# $txt .= $self->_generate_edge($e, $indent);
|
||||
# }
|
||||
|
||||
$txt .= " }\n";
|
||||
}
|
||||
|
||||
my $root = $self->attribute('root');
|
||||
$root = '' unless defined $root;
|
||||
|
||||
my $count = 0;
|
||||
# output nodes with attributes first, sorted by their name
|
||||
for my $n (sort { $a->{name} cmp $b->{name} } values %{$self->{nodes}})
|
||||
{
|
||||
next if exists $n->{_p};
|
||||
my $att = $n->attributes_as_vcg($root);
|
||||
if ($att ne '')
|
||||
{
|
||||
$n->{_p} = undef; # mark as processed
|
||||
$count++;
|
||||
$txt .= " node:" . $att . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$txt .= "\n" if $count > 0; # insert a newline
|
||||
|
||||
my @nodes = $self->sorted_nodes();
|
||||
|
||||
foreach my $n (@nodes)
|
||||
{
|
||||
my @out = $n->successors();
|
||||
my $first = $n->as_vcg_txt();
|
||||
if ((@out == 0) && ( (scalar $n->predecessors() || 0) == 0))
|
||||
{
|
||||
# single node without any connections (unless already output)
|
||||
$txt .= " node: { title: " . $first . " }\n" unless exists $n->{_p};
|
||||
}
|
||||
# for all outgoing connections
|
||||
foreach my $other (reverse @out)
|
||||
{
|
||||
# in case there is more than one edge going from N to O
|
||||
my @edges = $n->edges_to($other);
|
||||
foreach my $e (@edges)
|
||||
{
|
||||
next if exists $e->{_p};
|
||||
$txt .= $self->_generate_vcg_edge($e, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# insert now edges between groups (clusters/subgraphs)
|
||||
|
||||
# foreach my $e (ord_values ( $self->{edges} ))
|
||||
# {
|
||||
# $txt .= $self->_generate_group_edge($e, ' ')
|
||||
# if $e->{from}->isa('Graph::Easy::Group') ||
|
||||
# $e->{to}->isa('Graph::Easy::Group');
|
||||
# }
|
||||
|
||||
# clean up
|
||||
for my $n ( ord_values ( $self->{nodes} ), ord_values ( $self->{edges} ))
|
||||
{
|
||||
delete $n->{_p};
|
||||
}
|
||||
delete $self->{_vcg_invis}; # invisible helper nodes for joints
|
||||
delete $self->{_vcg_invis_id}; # invisible helper node name
|
||||
delete $self->{_vcg_edge_classes};
|
||||
|
||||
$txt . "\n}\n"; # close the graph
|
||||
}
|
||||
|
||||
package Graph::Easy::Node;
|
||||
|
||||
sub attributes_as_vcg
|
||||
{
|
||||
# return the attributes of this node as text description
|
||||
my ($self, $root) = @_;
|
||||
$root = '' unless defined $root;
|
||||
|
||||
my $att = '';
|
||||
my $class = $self->class();
|
||||
|
||||
return '' unless ref $self->{graph};
|
||||
|
||||
my $g = $self->{graph};
|
||||
|
||||
# get all attributes, excluding the class attributes
|
||||
my $a = $self->raw_attributes();
|
||||
|
||||
# add the attributes that are listed under "always":
|
||||
my $attr = $self->{att};
|
||||
my $base_class = $class; $base_class =~ s/\..*//;
|
||||
my $list = $vcg_remap->{always}->{$class} || $vcg_remap->{always}->{$base_class};
|
||||
|
||||
for my $name (@$list)
|
||||
{
|
||||
# for speed, try to look it up directly
|
||||
|
||||
# look if we have a code ref, if yes, simple set the value to undef
|
||||
# and let the coderef handle it later:
|
||||
if ( ref($vcg_remap->{$base_class}->{$name}) ||
|
||||
ref($vcg_remap->{all}->{$name}) )
|
||||
{
|
||||
$a->{$name} = $attr->{$name};
|
||||
}
|
||||
else
|
||||
{
|
||||
$a->{$name} = $attr->{$name};
|
||||
$a->{$name} = $self->attribute($name) unless defined $a->{$name} && $a->{$name} ne 'inherit';
|
||||
}
|
||||
}
|
||||
|
||||
$a = $g->_remap_attributes( $self, $a, $vcg_remap, 'noquote');
|
||||
|
||||
if ($self->isa('Graph::Easy::Edge'))
|
||||
{
|
||||
$a->{sourcename} = $self->{from}->{name};
|
||||
$a->{targetname} = $self->{to}->{name};
|
||||
my $class = $self->sub_class();
|
||||
$a->{class} = $self->{graph}->{_vcg_edge_classes}->{ $class } if defined $class && $class ne '';
|
||||
}
|
||||
else
|
||||
{
|
||||
# title: "Bonn"
|
||||
$a->{title} = $self->{name};
|
||||
}
|
||||
|
||||
# do not needlessly output labels:
|
||||
delete $a->{label} if !$self->isa('Graph::Easy::Edge') && # not an edge
|
||||
exists $a->{label} && $a->{label} eq $self->{name};
|
||||
|
||||
# bidirectional and undirected edges
|
||||
if ($self->{bidirectional})
|
||||
{
|
||||
delete $a->{dir};
|
||||
my ($n,$s) = Graph::Easy::_graphviz_remap_arrow_style(
|
||||
$self,'', $self->attribute('arrowstyle'));
|
||||
$a->{arrowhead} = $s;
|
||||
$a->{arrowtail} = $s;
|
||||
}
|
||||
if ($self->{undirected})
|
||||
{
|
||||
delete $a->{dir};
|
||||
$a->{arrowhead} = 'none';
|
||||
$a->{arrowtail} = 'none';
|
||||
}
|
||||
|
||||
# borderstyle: double:
|
||||
if (!$self->isa('Graph::Easy::Edge'))
|
||||
{
|
||||
my $style = $self->attribute('borderstyle');
|
||||
$a->{peripheries} = 2 if $style =~ /^double/;
|
||||
}
|
||||
|
||||
# For nodes with shape plaintext, set the fillcolor to the background of
|
||||
# the graph/group
|
||||
my $shape = $a->{shape} || 'rect';
|
||||
if ($class =~ /node/ && $shape eq 'plaintext')
|
||||
{
|
||||
my $p = $self->parent();
|
||||
$a->{fillcolor} = $p->attribute('fill');
|
||||
$a->{fillcolor} = 'white' if $a->{fillcolor} eq 'inherit';
|
||||
}
|
||||
|
||||
$shape = $self->attribute('shape') unless $self->isa_cell();
|
||||
|
||||
# for point-shaped nodes, include the point as label and set width/height
|
||||
if ($shape eq 'point')
|
||||
{
|
||||
require Graph::Easy::As_ascii; # for _u8 and point-style
|
||||
|
||||
my $style = $self->_point_style( $self->attribute('pointstyle') );
|
||||
|
||||
$a->{label} = $style;
|
||||
# for point-shaped invisible nodes, set height/width = 0
|
||||
$a->{width} = 0, $a->{height} = 0 if $style eq '';
|
||||
}
|
||||
if ($shape eq 'invisible')
|
||||
{
|
||||
$a->{label} = ' ';
|
||||
}
|
||||
|
||||
$a->{rank} = '0' if $root ne '' && $root eq $self->{name};
|
||||
|
||||
# create the attributes as text:
|
||||
for my $atr (sort keys %$a)
|
||||
{
|
||||
my $v = $a->{$atr};
|
||||
$v =~ s/"/\\"/g; # '2"' => '2\"'
|
||||
$v = '"' . $v . '"' unless $v =~ /^[0-9]+\z/; # 1, "1a"
|
||||
$att .= "$atr: $v ";
|
||||
}
|
||||
$att =~ s/,\s$//; # remove last ","
|
||||
|
||||
# generate attribute text if nec.
|
||||
$att = ' { ' . $att . '}' if $att ne '';
|
||||
|
||||
$att;
|
||||
}
|
||||
|
||||
sub as_vcg_txt
|
||||
{
|
||||
# return the node itself (w/o attributes) as VCG representation
|
||||
my $self = shift;
|
||||
|
||||
my $name = $self->{name};
|
||||
|
||||
# escape special chars in name (including doublequote!)
|
||||
$name =~ s/([\[\]\(\)\{\}"])/\\$1/g;
|
||||
|
||||
# quote:
|
||||
'"' . $name . '"';
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::As_vcg - Generate VCG/GDL text from Graph::Easy object
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
my $bonn = Graph::Easy::Node->new(
|
||||
name => 'Bonn',
|
||||
);
|
||||
my $berlin = Graph::Easy::Node->new(
|
||||
name => 'Berlin',
|
||||
);
|
||||
|
||||
$graph->add_edge ($bonn, $berlin);
|
||||
|
||||
print $graph->as_vcg();
|
||||
|
||||
|
||||
This prints something like this:
|
||||
|
||||
graph: {
|
||||
node: { title: "Bonn" }
|
||||
node: { title: "Berlin" }
|
||||
edge: { sourcename: "Bonn" targetname: "Berlin" }
|
||||
}
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<Graph::Easy::As_vcg> contains just the code for converting a
|
||||
L<Graph::Easy|Graph::Easy> object to either a VCG
|
||||
or GDL textual description.
|
||||
|
||||
Note that the generated format is compatible to C<GDL> aka I<Graph
|
||||
Description Language>.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
Exports nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>, L<http://rw4.cs.uni-sb.de/~sander/html/gsvcg1.html>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004-2008 by Tels L<http://bloodgate.com>
|
||||
|
||||
See the LICENSE file for information.
|
||||
|
||||
=cut
|
||||
4182
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Attributes.pm
Normal file
4182
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Attributes.pm
Normal file
File diff suppressed because it is too large
Load Diff
486
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Base.pm
Normal file
486
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Base.pm
Normal file
@@ -0,0 +1,486 @@
|
||||
#############################################################################
|
||||
# A baseclass for Graph::Easy objects like nodes, edges etc.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Base;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
#############################################################################
|
||||
|
||||
{
|
||||
# protected vars
|
||||
my $id = 0;
|
||||
sub _new_id { $id++; }
|
||||
sub _reset_id { $id = 0; }
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub new
|
||||
{
|
||||
# Create a new object. This is a generic routine that is inherited
|
||||
# by many other things like Edge, Cell etc.
|
||||
my $self = bless { id => _new_id() }, shift;
|
||||
|
||||
my $args = $_[0];
|
||||
$args = { name => $_[0] } if ref($args) ne 'HASH' && @_ == 1;
|
||||
$args = { @_ } if ref($args) ne 'HASH' && @_ > 1;
|
||||
|
||||
$self->_init($args);
|
||||
}
|
||||
|
||||
sub _init
|
||||
{
|
||||
# Generic init routine, to be overriden in subclasses.
|
||||
my ($self,$args) = @_;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub self
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub no_fatal_errors
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{fatal_errors} = ($_[1] ? 1 : 0) if @_ > 0;
|
||||
|
||||
~ ($self->{fatal_errors} || 0);
|
||||
}
|
||||
|
||||
sub fatal_errors
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{fatal_errors} = ($_[1] ? 0 : 1) if @_ > 0;
|
||||
|
||||
$self->{fatal_errors} || 0;
|
||||
}
|
||||
|
||||
sub error
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# If we switched to a temp. Graphviz parser, then set the error on the
|
||||
# original parser object, too:
|
||||
$self->{_old_self}->error(@_) if ref($self->{_old_self});
|
||||
|
||||
# if called on a member on a graph, call error() on the graph itself:
|
||||
return $self->{graph}->error(@_) if ref($self->{graph});
|
||||
|
||||
if (defined $_[0])
|
||||
{
|
||||
$self->{error} = $_[0];
|
||||
if ($self->{_catch_errors})
|
||||
{
|
||||
push @{$self->{_errors}}, $self->{error};
|
||||
}
|
||||
else
|
||||
{
|
||||
$self->_croak($self->{error}, 2)
|
||||
if ($self->{fatal_errors}) && $self->{error} ne '';
|
||||
}
|
||||
}
|
||||
$self->{error} || '';
|
||||
}
|
||||
|
||||
sub error_as_html
|
||||
{
|
||||
# return error() properly escaped
|
||||
my $self = shift;
|
||||
|
||||
my $msg = $self->{error};
|
||||
|
||||
$msg =~ s/&/&/g;
|
||||
$msg =~ s/</</g;
|
||||
$msg =~ s/>/>/g;
|
||||
$msg =~ s/"/"/g;
|
||||
|
||||
$msg;
|
||||
}
|
||||
|
||||
sub catch_messages
|
||||
{
|
||||
# Catch all warnings (and errors if no_fatal_errors() was used)
|
||||
# these can later be retrieved with warnings() and errors():
|
||||
my $self = shift;
|
||||
|
||||
if (@_ > 0)
|
||||
{
|
||||
if ($_[0])
|
||||
{
|
||||
$self->{_catch_warnings} = 1;
|
||||
$self->{_catch_errors} = 1;
|
||||
$self->{_warnings} = [];
|
||||
$self->{_errors} = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
$self->{_catch_warnings} = 0;
|
||||
$self->{_catch_errors} = 0;
|
||||
}
|
||||
}
|
||||
$self;
|
||||
}
|
||||
|
||||
sub catch_warnings
|
||||
{
|
||||
# Catch all warnings
|
||||
# these can later be retrieved with warnings():
|
||||
my $self = shift;
|
||||
|
||||
if (@_ > 0)
|
||||
{
|
||||
if ($_[0])
|
||||
{
|
||||
$self->{_catch_warnings} = 1;
|
||||
$self->{_warnings} = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
$self->{_catch_warnings} = 0;
|
||||
}
|
||||
}
|
||||
$self->{_catch_warnings};
|
||||
}
|
||||
|
||||
sub catch_errors
|
||||
{
|
||||
# Catch all errors
|
||||
# these can later be retrieved with errors():
|
||||
my $self = shift;
|
||||
|
||||
if (@_ > 0)
|
||||
{
|
||||
if ($_[0])
|
||||
{
|
||||
$self->{_catch_errors} = 1;
|
||||
$self->{_errors} = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
$self->{_catch_errors} = 0;
|
||||
}
|
||||
}
|
||||
$self->{_catch_errors};
|
||||
}
|
||||
|
||||
sub warnings
|
||||
{
|
||||
# return all warnings that occurred after catch_messages(1)
|
||||
my $self = shift;
|
||||
|
||||
@{$self->{_warnings}};
|
||||
}
|
||||
|
||||
sub errors
|
||||
{
|
||||
# return all errors that occurred after catch_messages(1)
|
||||
my $self = shift;
|
||||
|
||||
@{$self->{_errors}};
|
||||
}
|
||||
|
||||
sub warn
|
||||
{
|
||||
my ($self, $msg) = @_;
|
||||
|
||||
if ($self->{_catch_warnings})
|
||||
{
|
||||
push @{$self->{_warnings}}, $msg;
|
||||
}
|
||||
else
|
||||
{
|
||||
require Carp;
|
||||
Carp::carp('Warning: ' . $msg);
|
||||
}
|
||||
}
|
||||
|
||||
sub _croak
|
||||
{
|
||||
my ($self, $msg, $level) = @_;
|
||||
$level = 1 unless defined $level;
|
||||
|
||||
require Carp;
|
||||
if (ref($self) && $self->{debug})
|
||||
{
|
||||
$Carp::CarpLevel = $level; # don't report Base itself
|
||||
Carp::confess($msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
Carp::croak($msg);
|
||||
}
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# class management
|
||||
|
||||
sub sub_class
|
||||
{
|
||||
# get/set the subclass
|
||||
my $self = shift;
|
||||
|
||||
if (defined $_[0])
|
||||
{
|
||||
$self->{class} =~ s/\..*//; # nix subclass
|
||||
$self->{class} .= '.' . $_[0]; # append new one
|
||||
delete $self->{cache};
|
||||
$self->{cache}->{subclass} = $_[0];
|
||||
$self->{cache}->{class} = $self->{class};
|
||||
return;
|
||||
}
|
||||
$self->{class} =~ /\.(.*)/;
|
||||
|
||||
return $1 if defined $1;
|
||||
|
||||
return $self->{cache}->{subclass} if defined $self->{cache}->{subclass};
|
||||
|
||||
# Subclass not defined, so check our base class for a possible set class
|
||||
# attribute and return this:
|
||||
|
||||
# take a shortcut
|
||||
my $g = $self->{graph};
|
||||
if (defined $g)
|
||||
{
|
||||
my $subclass = $g->{att}->{$self->{class}}->{class};
|
||||
$subclass = '' unless defined $subclass;
|
||||
$self->{cache}->{subclass} = $subclass;
|
||||
$self->{cache}->{class} = $self->{class};
|
||||
return $subclass;
|
||||
}
|
||||
|
||||
# not part of a graph?
|
||||
$self->{cache}->{subclass} = $self->attribute('class');
|
||||
}
|
||||
|
||||
sub class
|
||||
{
|
||||
# return our full class name like "node.subclass" or "node"
|
||||
my $self = shift;
|
||||
|
||||
$self->error("class() method does not take arguments") if @_ > 0;
|
||||
|
||||
$self->{class} =~ /\.(.*)/;
|
||||
|
||||
return $self->{class} if defined $1;
|
||||
|
||||
return $self->{cache}->{class} if defined $self->{cache}->{class};
|
||||
|
||||
# Subclass not defined, so check our base class for a possible set class
|
||||
# attribute and return this:
|
||||
|
||||
my $subclass;
|
||||
# take a shortcut:
|
||||
my $g = $self->{graph};
|
||||
if (defined $g)
|
||||
{
|
||||
$subclass = $g->{att}->{$self->{class}}->{class};
|
||||
$subclass = '' unless defined $subclass;
|
||||
}
|
||||
|
||||
$subclass = $self->{att}->{class} unless defined $subclass;
|
||||
$subclass = '' unless defined $subclass;
|
||||
$self->{cache}->{subclass} = $subclass;
|
||||
$subclass = '.' . $subclass if $subclass ne '';
|
||||
|
||||
$self->{cache}->{class} = $self->{class} . $subclass;
|
||||
}
|
||||
|
||||
sub main_class
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{class} =~ /^(.+?)(\.|\z)/; # extract first part
|
||||
|
||||
$1;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Base - base class for Graph::Easy objects like nodes, edges etc
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
package Graph::Easy::My::Node;
|
||||
use Graph::Easy::Base;
|
||||
@ISA = qw/Graph::Easy::Base/;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Used automatically and internally by L<Graph::Easy> - should not be used
|
||||
directly.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 new()
|
||||
|
||||
my $object = Graph::Easy::Base->new();
|
||||
|
||||
Create a new object, and call C<_init()> on it.
|
||||
|
||||
=head2 error()
|
||||
|
||||
$last_error = $object->error();
|
||||
|
||||
$object->error($error); # set new messages
|
||||
$object->error(''); # clear the error
|
||||
|
||||
Returns the last error message, or '' for no error.
|
||||
|
||||
When setting a new error message, C<< $self->_croak($error) >> will be called
|
||||
unless C<< $object->no_fatal_errors() >> is true.
|
||||
|
||||
=head2 error_as_html()
|
||||
|
||||
my $error = $object->error_as_html();
|
||||
|
||||
Returns the same error message as L<error()>, but properly escaped
|
||||
as HTML so it is safe to output to the client.
|
||||
|
||||
=head2 warn()
|
||||
|
||||
$object->warn('Warning!');
|
||||
|
||||
Warn on STDERR with the given message.
|
||||
|
||||
=head2 no_fatal_errors()
|
||||
|
||||
$object->no_fatal_errors(1);
|
||||
|
||||
Set the flag that determines whether setting an error message
|
||||
via C<error()> is fatal, e.g. results in a call to C<_croak()>.
|
||||
|
||||
A true value will make errors non-fatal. See also L<fatal_errors>.
|
||||
|
||||
=head2 fatal_errors()
|
||||
|
||||
$fatal = $object->fatal_errors();
|
||||
$object->fatal_errors(0); # turn off
|
||||
$object->fatal_errors(1); # turn on
|
||||
|
||||
Set/get the flag that determines whether setting an error message
|
||||
via C<error()> is fatal, e.g. results in a call to C<_croak()>.
|
||||
|
||||
A true value makes errors fatal.
|
||||
|
||||
=head2 catch_errors()
|
||||
|
||||
my $catch_errors = $object->catch_errors(); # query
|
||||
$object->catch_errors(1); # enable
|
||||
|
||||
$object->...(); # some error
|
||||
if ($object->error())
|
||||
{
|
||||
my @errors = $object->errors(); # retrieve
|
||||
}
|
||||
|
||||
Enable/disable catching of all error messages. When enabled,
|
||||
all previously caught error messages are thrown away, and from this
|
||||
poin on new errors are non-fatal and stored internally. You can
|
||||
retrieve these errors later with the errors() method.
|
||||
|
||||
=head2 catch_warnings()
|
||||
|
||||
my $catch_warns = $object->catch_warnings(); # query
|
||||
$object->catch_warnings(1); # enable
|
||||
|
||||
$object->...(); # some error
|
||||
if ($object->warning())
|
||||
{
|
||||
my @warnings = $object->warnings(); # retrieve
|
||||
}
|
||||
|
||||
Enable/disable catching of all warnings. When enabled, all previously
|
||||
caught warning messages are thrown away, and from this poin on new
|
||||
warnings are stored internally. You can retrieve these errors later
|
||||
with the errors() method.
|
||||
|
||||
=head2 catch_messages()
|
||||
|
||||
# catch errors and warnings
|
||||
$object->catch_messages(1);
|
||||
# stop catching errors and warnings
|
||||
$object->catch_messages(0);
|
||||
|
||||
A true parameter is equivalent to:
|
||||
|
||||
$object->catch_warnings(1);
|
||||
$object->catch_errors(1);
|
||||
|
||||
See also: L<catch_warnings()> and L<catch_errors()> as well as
|
||||
L<errors()> and L<warnings()>.
|
||||
|
||||
=head2 errors()
|
||||
|
||||
my @errors = $object->errors();
|
||||
|
||||
Return all error messages that occurred after L<catch_messages()> was
|
||||
called.
|
||||
|
||||
=head2 warnings()
|
||||
|
||||
my @warnings = $object->warnings();
|
||||
|
||||
Return all warning messages that occurred after L<catch_messages()>
|
||||
or L<catch_errors()> was called.
|
||||
|
||||
=head2 self()
|
||||
|
||||
my $self = $object->self();
|
||||
|
||||
Returns the object itself.
|
||||
|
||||
=head2 class()
|
||||
|
||||
my $class = $object->class();
|
||||
|
||||
Returns the full class name like C<node.cities>. See also C<sub_class>.
|
||||
|
||||
=head2 sub_class()
|
||||
|
||||
my $sub_class = $object->sub_class();
|
||||
|
||||
Returns the sub class name like C<cities>. See also C<class>.
|
||||
|
||||
=head2 main_class()
|
||||
|
||||
my $main_class = $object->main_class();
|
||||
|
||||
Returns the main class name like C<node>. See also C<sub_class>.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2008 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
X<tels>
|
||||
X<bloodgate>
|
||||
X<license>
|
||||
X<gpl>
|
||||
|
||||
=cut
|
||||
751
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Edge.pm
Normal file
751
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Edge.pm
Normal file
@@ -0,0 +1,751 @@
|
||||
#############################################################################
|
||||
# An edge connecting two nodes in Graph::Easy.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Edge;
|
||||
|
||||
use Graph::Easy::Node;
|
||||
@ISA = qw/Graph::Easy::Node/; # an edge is just a special node
|
||||
$VERSION = '0.76';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use constant isa_cell => 1;
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _init
|
||||
{
|
||||
# generic init, override in subclasses
|
||||
my ($self,$args) = @_;
|
||||
|
||||
$self->{class} = 'edge';
|
||||
|
||||
# leave this unitialized until we need it
|
||||
# $self->{cells} = [ ];
|
||||
|
||||
foreach my $k (sort keys %$args)
|
||||
{
|
||||
if ($k !~ /^(label|name|style)\z/)
|
||||
{
|
||||
require Carp;
|
||||
Carp::confess ("Invalid argument '$k' passed to Graph::Easy::Node->new()");
|
||||
}
|
||||
my $n = $k; $n = 'label' if $k eq 'name';
|
||||
|
||||
$self->{att}->{$n} = $args->{$k};
|
||||
}
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# accessor methods
|
||||
|
||||
sub bidirectional
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
if (@_ > 0)
|
||||
{
|
||||
my $old = $self->{bidirectional} || 0;
|
||||
$self->{bidirectional} = $_[0] ? 1 : 0;
|
||||
|
||||
# invalidate layout?
|
||||
$self->{graph}->{score} = undef if $old != $self->{bidirectional} && ref($self->{graph});
|
||||
}
|
||||
|
||||
$self->{bidirectional};
|
||||
}
|
||||
|
||||
sub undirected
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
if (@_ > 0)
|
||||
{
|
||||
my $old = $self->{undirected} || 0;
|
||||
$self->{undirected} = $_[0] ? 1 : 0;
|
||||
|
||||
# invalidate layout?
|
||||
$self->{graph}->{score} = undef if $old != $self->{undirected} && ref($self->{graph});
|
||||
}
|
||||
|
||||
$self->{undirected};
|
||||
}
|
||||
|
||||
sub has_ports
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
my $s_port = $self->{att}->{start} || $self->attribute('start');
|
||||
|
||||
return 1 if $s_port ne '';
|
||||
|
||||
my $e_port = $self->{att}->{end} || $self->attribute('end');
|
||||
|
||||
return 1 if $e_port ne '';
|
||||
|
||||
0;
|
||||
}
|
||||
|
||||
sub start_port
|
||||
{
|
||||
# return the side and portnumber if the edge has a shared source port
|
||||
# undef for none
|
||||
my $self = shift;
|
||||
|
||||
my $s = $self->{att}->{start} || $self->attribute('start');
|
||||
return undef if !defined $s || $s !~ /,/; # "south, 0" => ok, "south" => no
|
||||
|
||||
return (split /\s*,\s*/, $s) if wantarray;
|
||||
|
||||
$s =~ s/\s+//g; # remove spaces to normalize "south, 0" to "south,0"
|
||||
$s;
|
||||
}
|
||||
|
||||
sub end_port
|
||||
{
|
||||
# return the side and portnumber if the edge has a shared source port
|
||||
# undef for none
|
||||
my $self = shift;
|
||||
|
||||
my $s = $self->{att}->{end} || $self->attribute('end');
|
||||
return undef if !defined $s || $s !~ /,/; # "south, 0" => ok, "south" => no
|
||||
|
||||
return split /\s*,\s*/, $s if wantarray;
|
||||
|
||||
$s =~ s/\s+//g; # remove spaces to normalize "south, 0" to "south,0"
|
||||
$s;
|
||||
}
|
||||
|
||||
sub style
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{att}->{style} || $self->attribute('style');
|
||||
}
|
||||
|
||||
sub name
|
||||
{
|
||||
# returns actually the label
|
||||
my $self = shift;
|
||||
|
||||
$self->{att}->{label} || '';
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# cell management - used by the cell-based layouter
|
||||
|
||||
sub _cells
|
||||
{
|
||||
# return all the cells this edge currently occupies
|
||||
my $self = shift;
|
||||
|
||||
$self->{cells} = [] unless defined $self->{cells};
|
||||
|
||||
@{$self->{cells}};
|
||||
}
|
||||
|
||||
sub _clear_cells
|
||||
{
|
||||
# remove all belonging cells
|
||||
my $self = shift;
|
||||
|
||||
$self->{cells} = [];
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _unplace
|
||||
{
|
||||
# Take an edge, and remove all the cells it covers from the cells area
|
||||
my ($self, $cells) = @_;
|
||||
|
||||
print STDERR "# clearing path from $self->{from}->{name} to $self->{to}->{name}\n" if $self->{debug};
|
||||
|
||||
for my $key (@{$self->{cells}})
|
||||
{
|
||||
# XXX TODO: handle crossed edges differently (from CROSS => HOR or VER)
|
||||
# free in our cells area
|
||||
delete $cells->{$key};
|
||||
}
|
||||
|
||||
$self->clear_cells();
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _distance
|
||||
{
|
||||
# estimate the distance from SRC to DST node
|
||||
my ($self) = @_;
|
||||
|
||||
my $src = $self->{from};
|
||||
my $dst = $self->{to};
|
||||
|
||||
# one of them not yet placed?
|
||||
return 100000 unless defined $src->{x} && defined $dst->{x};
|
||||
|
||||
my $cells = $self->{graph}->{cells};
|
||||
|
||||
# get all the starting positions
|
||||
# distance = 1: slots, generate starting types, the direction is shifted
|
||||
# by 90° counter-clockwise
|
||||
|
||||
my @start = $src->_near_places($cells, 1, undef, undef, $src->_shift(-90) );
|
||||
|
||||
# potential stop positions
|
||||
my @stop = $dst->_near_places($cells, 1); # distance = 1: slots
|
||||
|
||||
my ($s_p,@ss_p) = $self->port('start');
|
||||
my ($e_p,@ee_p) = $self->port('end');
|
||||
|
||||
# the edge has a port description, limiting the start places
|
||||
@start = $src->_allowed_places( \@start, $src->_allow( $s_p, @ss_p ), 3)
|
||||
if defined $s_p;
|
||||
|
||||
# the edge has a port description, limiting the stop places
|
||||
@stop = $dst->_allowed_places( \@stop, $dst->_allow( $e_p, @ee_p ), 3)
|
||||
if defined $e_p;
|
||||
|
||||
my $stop = scalar @stop;
|
||||
|
||||
return 0 unless @stop > 0 && @start > 0; # no free slots on one node?
|
||||
|
||||
my $lowest;
|
||||
|
||||
my $i = 0;
|
||||
while ($i < scalar @start)
|
||||
{
|
||||
my $sx = $start[$i]; my $sy = $start[$i+1]; $i += 2;
|
||||
|
||||
# for each start point, calculate the distance to each stop point, then use
|
||||
# the smallest as value
|
||||
|
||||
for (my $u = 0; $u < $stop; $u += 2)
|
||||
{
|
||||
my $dist = Graph::Easy::_astar_distance($sx,$sy, $stop[$u], $stop[$u+1]);
|
||||
$lowest = $dist if !defined $lowest || $dist < $lowest;
|
||||
}
|
||||
}
|
||||
|
||||
$lowest;
|
||||
}
|
||||
|
||||
sub _add_cell
|
||||
{
|
||||
# add a cell to the list of cells this edge covers. If $after is a ref
|
||||
# to a cell, then the new cell will be inserted right after this cell.
|
||||
# if after is defined, but not a ref, the new cell will be inserted
|
||||
# at the specified position.
|
||||
my ($self, $cell, $after, $before) = @_;
|
||||
|
||||
$self->{cells} = [] unless defined $self->{cells};
|
||||
my $cells = $self->{cells};
|
||||
|
||||
# if both are defined, but belong to different edges, just ignore $before:
|
||||
$before = undef if ref($before) && $before->{edge} != $self;
|
||||
$after = undef if ref($after) && $after->{edge} != $self;
|
||||
if (!defined $after && ref($before))
|
||||
{
|
||||
$after = $before; $before = undef;
|
||||
}
|
||||
|
||||
if (defined $after)
|
||||
{
|
||||
# insert the new cell right after $after
|
||||
my $ofs = $after;
|
||||
if (ref($after) && !ref($before))
|
||||
{
|
||||
# insert after $after
|
||||
$ofs = 1;
|
||||
for my $cell (@$cells)
|
||||
{
|
||||
last if $cell == $after;
|
||||
$ofs++;
|
||||
}
|
||||
}
|
||||
elsif (ref($after) && ref($before))
|
||||
{
|
||||
# insert between after and before (or before/after for "reversed edges)
|
||||
$ofs = 0;
|
||||
my $found = 0;
|
||||
while ($ofs < scalar @$cells - 1) # 0,1,2,3 => 0 .. 2
|
||||
{
|
||||
my $c1 = $cells->[$ofs];
|
||||
my $c2 = $cells->[$ofs+1];
|
||||
$ofs++;
|
||||
$found++, last if (($c1 == $after && $c2 == $before) ||
|
||||
($c1 == $before && $c2 == $after));
|
||||
}
|
||||
if (!$found)
|
||||
{
|
||||
# XXX TODO: last effort
|
||||
|
||||
# insert after $after
|
||||
$ofs = 1;
|
||||
for my $cell (@$cells)
|
||||
{
|
||||
last if $cell == $after;
|
||||
$ofs++;
|
||||
}
|
||||
$found++;
|
||||
}
|
||||
$self->_croak("Could not find $after and $before") unless $found;
|
||||
}
|
||||
splice (@$cells, $ofs, 0, $cell);
|
||||
}
|
||||
else
|
||||
{
|
||||
# insert new cell at the end
|
||||
push @$cells, $cell;
|
||||
}
|
||||
|
||||
$cell->_update_boundaries();
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub from
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{from};
|
||||
}
|
||||
|
||||
sub to
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{to};
|
||||
}
|
||||
|
||||
sub nodes
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
($self->{from}, $self->{to});
|
||||
}
|
||||
|
||||
sub start_at
|
||||
{
|
||||
# move the edge's start point from the current node to the given node
|
||||
my ($self, $node) = @_;
|
||||
|
||||
# if not a node yet, or not part of this graph, make into one proper node
|
||||
$node = $self->{graph}->add_node($node);
|
||||
|
||||
$self->_croak("start_at() needs a node object, but got $node")
|
||||
unless ref($node) && $node->isa('Graph::Easy::Node');
|
||||
|
||||
# A => A => nothing to do
|
||||
return $node if $self->{from} == $node;
|
||||
|
||||
# delete self at A
|
||||
delete $self->{from}->{edges}->{ $self->{id} };
|
||||
|
||||
# set "from" to B
|
||||
$self->{from} = $node;
|
||||
|
||||
# add to B
|
||||
$self->{from}->{edges}->{ $self->{id} } = $self;
|
||||
|
||||
# invalidate layout
|
||||
$self->{graph}->{score} = undef if ref($self->{graph});
|
||||
|
||||
# return new start point
|
||||
$node;
|
||||
}
|
||||
|
||||
sub end_at
|
||||
{
|
||||
# move the edge's end point from the current node to the given node
|
||||
my ($self, $node) = @_;
|
||||
|
||||
# if not a node yet, or not part of this graph, make into one proper node
|
||||
$node = $self->{graph}->add_node($node);
|
||||
|
||||
$self->_croak("start_at() needs a node object, but got $node")
|
||||
unless ref($node) && $node->isa('Graph::Easy::Node');
|
||||
|
||||
# A => A => nothing to do
|
||||
return $node if $self->{to} == $node;
|
||||
|
||||
# delete self at A
|
||||
delete $self->{to}->{edges}->{ $self->{id} };
|
||||
|
||||
# set "to" to B
|
||||
$self->{to} = $node;
|
||||
|
||||
# add to node B
|
||||
$self->{to}->{edges}->{ $self->{id} } = $self;
|
||||
|
||||
# invalidate layout
|
||||
$self->{graph}->{score} = undef if ref($self->{graph});
|
||||
|
||||
# return new end point
|
||||
$node;
|
||||
}
|
||||
|
||||
sub edge_flow
|
||||
{
|
||||
# return the flow at this edge or '' if the edge itself doesn't have a flow
|
||||
my $self = shift;
|
||||
|
||||
# our flow comes from ourselves
|
||||
my $flow = $self->{att}->{flow};
|
||||
$flow = $self->raw_attribute('flow') unless defined $flow;
|
||||
|
||||
$flow;
|
||||
}
|
||||
|
||||
sub flow
|
||||
{
|
||||
# return the flow at this edge (including inheriting flow from node)
|
||||
my ($self) = @_;
|
||||
|
||||
# print STDERR "# flow from $self->{from}->{name} to $self->{to}->{name}\n";
|
||||
|
||||
# our flow comes from ourselves
|
||||
my $flow = $self->{att}->{flow};
|
||||
# or maybe our class
|
||||
$flow = $self->raw_attribute('flow') unless defined $flow;
|
||||
|
||||
# if the edge doesn't have a flow, maybe the node has a default out flow
|
||||
$flow = $self->{from}->{att}->{flow} if !defined $flow;
|
||||
|
||||
# if that didn't work out either, use the parents flows
|
||||
$flow = $self->parent()->attribute('flow') if !defined $flow;
|
||||
# or finally, the default "east":
|
||||
$flow = 90 if !defined $flow;
|
||||
|
||||
# absolute flow does not depend on the in-flow, so can return early
|
||||
return $flow if $flow =~ /^(0|90|180|270)\z/;
|
||||
|
||||
# in-flow comes from our "from" node
|
||||
my $in = $self->{from}->flow();
|
||||
|
||||
# print STDERR "# in: $self->{from}->{name} = $in\n";
|
||||
|
||||
my $out = $self->{graph}->_flow_as_direction($in,$flow);
|
||||
$out;
|
||||
}
|
||||
|
||||
sub port
|
||||
{
|
||||
my ($self, $which) = @_;
|
||||
|
||||
$self->_croak("'$which' must be one of 'start' or 'end' in port()") unless $which =~ /^(start|end)/;
|
||||
|
||||
# our flow comes from ourselves
|
||||
my $sp = $self->attribute($which);
|
||||
|
||||
return (undef,undef) unless defined $sp && $sp ne '';
|
||||
|
||||
my ($side, $port) = split /\s*,\s*/, $sp;
|
||||
|
||||
# if absolut direction, return as is
|
||||
my $s = Graph::Easy->_direction_as_side($side);
|
||||
|
||||
if (defined $s)
|
||||
{
|
||||
my @rc = ($s); push @rc, $port if defined $port;
|
||||
return @rc;
|
||||
}
|
||||
|
||||
# in_flow comes from our "from" node
|
||||
my $in = 90; $in = $self->{from}->flow() if ref($self->{from});
|
||||
|
||||
# turn left in "south" etc:
|
||||
$s = Graph::Easy->_flow_as_side($in,$side);
|
||||
|
||||
my @rc = ($s); push @rc, $port if defined $port;
|
||||
@rc;
|
||||
}
|
||||
|
||||
sub flip
|
||||
{
|
||||
# swap from and to for this edge
|
||||
my ($self) = @_;
|
||||
|
||||
($self->{from}, $self->{to}) = ($self->{to}, $self->{from});
|
||||
|
||||
# invalidate layout
|
||||
$self->{graph}->{score} = undef if ref($self->{graph});
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub as_ascii
|
||||
{
|
||||
my ($self, $x,$y) = @_;
|
||||
|
||||
# invisible nodes, or very small ones
|
||||
return '' if $self->{w} == 0 || $self->{h} == 0;
|
||||
|
||||
my $fb = $self->_framebuffer($self->{w}, $self->{h});
|
||||
|
||||
###########################################################################
|
||||
# "draw" the label into the framebuffer (e.g. the edge and the text)
|
||||
$self->_draw_label($fb, $x, $y, '');
|
||||
|
||||
join ("\n", @$fb);
|
||||
}
|
||||
|
||||
sub as_txt
|
||||
{
|
||||
require Graph::Easy::As_ascii;
|
||||
|
||||
_as_txt(@_);
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Edge - An edge (a path connecting one ore more nodes)
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $ssl = Graph::Easy::Edge->new(
|
||||
label => 'encrypted connection',
|
||||
style => 'solid',
|
||||
);
|
||||
$ssl->set_attribute('color', 'red');
|
||||
|
||||
my $src = Graph::Easy::Node->new('source');
|
||||
|
||||
my $dst = Graph::Easy::Node->new('destination');
|
||||
|
||||
$graph = Graph::Easy->new();
|
||||
|
||||
$graph->add_edge($src, $dst, $ssl);
|
||||
|
||||
print $graph->as_ascii();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Edge> represents an edge between two (or more) nodes in a
|
||||
simple graph.
|
||||
|
||||
Each edge has a direction (from source to destination, or back and forth),
|
||||
plus a style (line width and style), colors etc. It can also have a label,
|
||||
e.g. a text associated with it.
|
||||
|
||||
During the layout phase, each edge also contains a list of path-elements
|
||||
(also called cells), which make up the path from source to destination.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 error()
|
||||
|
||||
$last_error = $edge->error();
|
||||
|
||||
$cvt->error($error); # set new messages
|
||||
$cvt->error(''); # clear error
|
||||
|
||||
Returns the last error message, or '' for no error.
|
||||
|
||||
=head2 as_ascii()
|
||||
|
||||
my $ascii = $edge->as_ascii();
|
||||
|
||||
Returns the edge as a little ascii representation.
|
||||
|
||||
=head2 as_txt()
|
||||
|
||||
my $txt = $edge->as_txt();
|
||||
|
||||
Returns the edge as a little Graph::Easy textual representation.
|
||||
|
||||
=head2 label()
|
||||
|
||||
my $label = $edge->label();
|
||||
|
||||
Returns the label (also known as 'name') of the edge.
|
||||
|
||||
=head2 name()
|
||||
|
||||
my $label = $edge->name();
|
||||
|
||||
To make the interface more consistent, the C<name()> method of
|
||||
an edge can also be called, and it will returned either the edge
|
||||
label, or the empty string if the edge doesn't have a label.
|
||||
|
||||
=head2 style()
|
||||
|
||||
my $style = $edge->style();
|
||||
|
||||
Returns the style of the edge, like 'solid', 'dotted', 'double', etc.
|
||||
|
||||
=head2 nodes()
|
||||
|
||||
my @nodes = $edge->nodes();
|
||||
|
||||
Returns the source and target node that this edges connects as objects.
|
||||
|
||||
=head2 bidirectional()
|
||||
|
||||
$edge->bidirectional(1);
|
||||
if ($edge->bidirectional())
|
||||
{
|
||||
}
|
||||
|
||||
Returns true if the edge is bidirectional, aka has arrow heads on both ends.
|
||||
An optional parameter will set the bidirectional status of the edge.
|
||||
|
||||
=head2 undirected()
|
||||
|
||||
$edge->undirected(1);
|
||||
if ($edge->undirected())
|
||||
{
|
||||
}
|
||||
|
||||
Returns true if the edge is undirected, aka has now arrow at all.
|
||||
An optional parameter will set the undirected status of the edge.
|
||||
|
||||
=head2 has_ports()
|
||||
|
||||
if ($edge->has_ports())
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
Return true if the edge has restriction on the starting or ending
|
||||
port, e.g. either the C<start> or C<end> attribute is set on
|
||||
this edge.
|
||||
|
||||
=head2 start_port()
|
||||
|
||||
my $port = $edge->start_port();
|
||||
|
||||
Return undef if the edge does not have a fixed start port, otherwise
|
||||
returns the port as "side, number", for example "south, 0".
|
||||
|
||||
=head2 end_port()
|
||||
|
||||
my $port = $edge->end_port();
|
||||
|
||||
Return undef if the edge does not have a fixed end port, otherwise
|
||||
returns the port as "side, number", for example "south, 0".
|
||||
|
||||
=head2 from()
|
||||
|
||||
my $from = $edge->from();
|
||||
|
||||
Returns the node that this edge starts at. See also C<to()>.
|
||||
|
||||
=head2 to()
|
||||
|
||||
my $to = $edge->to();
|
||||
|
||||
Returns the node that this edge leads to. See also C<from()>.
|
||||
|
||||
=head2 start_at()
|
||||
|
||||
$edge->start_at($other);
|
||||
my $other = $edge->start_at('some node');
|
||||
|
||||
Set the edge's start point to the given node. If given a node name,
|
||||
will add that node to the graph first.
|
||||
|
||||
Returns the new edge start point node.
|
||||
|
||||
=head2 end_at()
|
||||
|
||||
$edge->end_at($other);
|
||||
my $other = $edge->end_at('some other node');
|
||||
|
||||
Set the edge's end point to the given node. If given a node name,
|
||||
will add that node to the graph first.
|
||||
|
||||
Returns the new edge end point node.
|
||||
|
||||
=head2 flip()
|
||||
|
||||
$edge->flip();
|
||||
|
||||
Swaps the C<start> and C<end> nodes on this edge, e.g. reverses the direction
|
||||
of the edge.
|
||||
|
||||
X<transpose>
|
||||
|
||||
=head2 flow()
|
||||
|
||||
my $flow = $edge->flow();
|
||||
|
||||
Returns the flow for this edge, honoring inheritance. An edge without
|
||||
a specific flow set will inherit the flow from the node it comes from.
|
||||
|
||||
=head2 edge_flow()
|
||||
|
||||
my $flow = $edge->edge_flow();
|
||||
|
||||
Returns the flow for this edge, or undef if it has none set on either
|
||||
the object itself or its class.
|
||||
|
||||
=head2 port()
|
||||
|
||||
my ($side, $number) = $edge->port('start');
|
||||
my ($side, $number) = $edge->port('end');
|
||||
|
||||
Return the side and port number where this edge starts or ends.
|
||||
|
||||
Returns undef for $side if the edge has no port restriction. The
|
||||
returned side will be one absolute direction of C<east>, C<west>,
|
||||
C<north> or C<south>, depending on the port restriction and
|
||||
flow at that edge.
|
||||
|
||||
=head2 get_attributes()
|
||||
|
||||
my $att = $object->get_attributes();
|
||||
|
||||
Return all effective attributes on this object (graph/node/group/edge) as
|
||||
an anonymous hash ref. This respects inheritance and default values.
|
||||
|
||||
See also L<raw_attributes()>.
|
||||
|
||||
=head2 raw_attributes()
|
||||
|
||||
my $att = $object->get_attributes();
|
||||
|
||||
Return all set attributes on this object (graph/node/group/edge) as
|
||||
an anonymous hash ref. This respects inheritance, but does not include
|
||||
default values for unset attributes.
|
||||
|
||||
See also L<get_attributes()>.
|
||||
|
||||
=head2 attribute related methods
|
||||
|
||||
You can call all the various attribute related methods like C<set_attribute()>,
|
||||
C<get_attribute()>, etc. on an edge, too. For example:
|
||||
|
||||
$edge->set_attribute('label', 'by train');
|
||||
my $attr = $edge->get_attributes();
|
||||
my $raw_attr = $edge->raw_attributes();
|
||||
|
||||
You can find more documentation in L<Graph::Easy>.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2008 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
1464
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Edge/Cell.pm
Normal file
1464
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Edge/Cell.pm
Normal file
File diff suppressed because it is too large
Load Diff
828
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Group.pm
Normal file
828
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Group.pm
Normal file
@@ -0,0 +1,828 @@
|
||||
#############################################################################
|
||||
# A group of nodes. Part of Graph::Easy.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Group;
|
||||
|
||||
use Graph::Easy::Group::Cell;
|
||||
use Graph::Easy;
|
||||
use Scalar::Util qw/weaken/;
|
||||
|
||||
@ISA = qw/Graph::Easy::Node Graph::Easy/;
|
||||
$VERSION = '0.76';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _init
|
||||
{
|
||||
# generic init, override in subclasses
|
||||
my ($self,$args) = @_;
|
||||
|
||||
$self->{name} = 'Group #'. $self->{id};
|
||||
$self->{class} = 'group';
|
||||
$self->{_cells} = {}; # the Group::Cell objects
|
||||
# $self->{cx} = 1;
|
||||
# $self->{cy} = 1;
|
||||
|
||||
foreach my $k (sort keys %$args)
|
||||
{
|
||||
if ($k !~ /^(graph|name)\z/)
|
||||
{
|
||||
require Carp;
|
||||
Carp::confess ("Invalid argument '$k' passed to Graph::Easy::Group->new()");
|
||||
}
|
||||
$self->{$k} = $args->{$k};
|
||||
}
|
||||
|
||||
$self->{nodes} = {};
|
||||
$self->{groups} = {};
|
||||
$self->{att} = {};
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# accessor methods
|
||||
|
||||
sub nodes
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
wantarray ? ( ord_values ( $self->{nodes} ) ) : scalar keys %{$self->{nodes}};
|
||||
}
|
||||
|
||||
sub edges
|
||||
{
|
||||
# edges leading from/to this group
|
||||
my $self = shift;
|
||||
|
||||
wantarray ? ( ord_values ( $self->{edges} ) ) : scalar keys %{$self->{edges}};
|
||||
}
|
||||
|
||||
sub edges_within
|
||||
{
|
||||
# edges between nodes inside this group
|
||||
my $self = shift;
|
||||
|
||||
wantarray ? ( ord_values ( $self->{edges_within} ) ) :
|
||||
scalar keys %{$self->{edges_within}};
|
||||
}
|
||||
|
||||
sub _groups_within
|
||||
{
|
||||
my ($self, $level, $max_level, $cur) = @_;
|
||||
|
||||
no warnings 'recursion';
|
||||
|
||||
push @$cur, ord_values ( $self->{groups} );
|
||||
|
||||
return if $level >= $max_level;
|
||||
|
||||
for my $g (ord_values ( $self->{groups} ))
|
||||
{
|
||||
$g->_groups_within($level+1,$max_level, $cur) if scalar keys %{$g->{groups}} > 0;
|
||||
}
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub set_attribute
|
||||
{
|
||||
my ($self, $name, $val, $class) = @_;
|
||||
|
||||
$self->SUPER::set_attribute($name, $val, $class);
|
||||
|
||||
# if defined attribute "nodeclass", put our nodes into that class
|
||||
if ($name eq 'nodeclass')
|
||||
{
|
||||
my $class = $self->{att}->{nodeclass};
|
||||
for my $node (ord_values ( $self->{nodes} ) )
|
||||
{
|
||||
$node->sub_class($class);
|
||||
}
|
||||
}
|
||||
$self;
|
||||
}
|
||||
|
||||
sub shape
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
# $self->{att}->{shape} || $self->attribute('shape');
|
||||
'';
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# node handling
|
||||
|
||||
sub add_node
|
||||
{
|
||||
# add a node to this group
|
||||
my ($self,$n) = @_;
|
||||
|
||||
if (!ref($n) || !$n->isa("Graph::Easy::Node"))
|
||||
{
|
||||
if (!ref($self->{graph}))
|
||||
{
|
||||
return $self->error("Cannot add non node-object $n to group '$self->{name}'");
|
||||
}
|
||||
$n = $self->{graph}->add_node($n);
|
||||
}
|
||||
$self->{nodes}->{ $n->{name} } = $n;
|
||||
|
||||
# if defined attribute "nodeclass", put our nodes into that class
|
||||
$n->sub_class($self->{att}->{nodeclass}) if exists $self->{att}->{nodeclass};
|
||||
|
||||
# register ourselves with the member
|
||||
$n->{group} = $self;
|
||||
|
||||
# set the proper attribute (for layout)
|
||||
$n->{att}->{group} = $self->{name};
|
||||
|
||||
# Register the nodes and the edge with our graph object
|
||||
# and weaken the references. Be careful to not needlessly
|
||||
# override and weaken again an already existing reference, this
|
||||
# is an O(N) operation in most Perl versions, and thus very slow.
|
||||
|
||||
# If the node does not belong to a graph yet or belongs to another
|
||||
# graph, add it to our own graph:
|
||||
weaken($n->{graph} = $self->{graph}) unless
|
||||
$n->{graph} && $self->{graph} && $n->{graph} == $self->{graph};
|
||||
|
||||
$n;
|
||||
}
|
||||
|
||||
sub add_member
|
||||
{
|
||||
# add a node or group to this group
|
||||
my ($self,$n) = @_;
|
||||
|
||||
if (!ref($n) || !$n->isa("Graph::Easy::Node"))
|
||||
{
|
||||
if (!ref($self->{graph}))
|
||||
{
|
||||
return $self->error("Cannot add non node-object $n to group '$self->{name}'");
|
||||
}
|
||||
$n = $self->{graph}->add_node($n);
|
||||
}
|
||||
return $self->_add_edge($n) if $n->isa("Graph::Easy::Edge");
|
||||
return $self->add_group($n) if $n->isa('Graph::Easy::Group');
|
||||
|
||||
$self->{nodes}->{ $n->{name} } = $n;
|
||||
|
||||
# if defined attribute "nodeclass", put our nodes into that class
|
||||
my $cl = $self->attribute('nodeclass');
|
||||
$n->sub_class($cl) if $cl ne '';
|
||||
|
||||
# register ourselves with the member
|
||||
$n->{group} = $self;
|
||||
|
||||
# set the proper attribute (for layout)
|
||||
$n->{att}->{group} = $self->{name};
|
||||
|
||||
# Register the nodes and the edge with our graph object
|
||||
# and weaken the references. Be careful to not needlessly
|
||||
# override and weaken again an already existing reference, this
|
||||
# is an O(N) operation in most Perl versions, and thus very slow.
|
||||
|
||||
# If the node does not belong to a graph yet or belongs to another
|
||||
# graph, add it to our own graph:
|
||||
weaken($n->{graph} = $self->{graph}) unless
|
||||
$n->{graph} && $self->{graph} && $n->{graph} == $self->{graph};
|
||||
|
||||
$n;
|
||||
}
|
||||
|
||||
sub del_member
|
||||
{
|
||||
# delete a node or group from this group
|
||||
my ($self,$n) = @_;
|
||||
|
||||
# XXX TOOD: groups vs. nodes
|
||||
my $class = 'nodes'; my $key = 'name';
|
||||
if ($n->isa('Graph::Easy::Group'))
|
||||
{
|
||||
# XXX TOOD: groups vs. nodes
|
||||
$class = 'groups'; $key = 'id';
|
||||
}
|
||||
delete $self->{$class}->{ $n->{$key} };
|
||||
delete $n->{group}; # unregister us
|
||||
|
||||
if ($n->isa('Graph::Easy::Node'))
|
||||
{
|
||||
# find all edges that mention this node and drop them from the group
|
||||
my $edges = $self->{edges_within};
|
||||
for my $e (ord_values ( $edges))
|
||||
{
|
||||
delete $edges->{ $e->{id} } if $e->{from} == $n || $e->{to} == $n;
|
||||
}
|
||||
}
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub del_node
|
||||
{
|
||||
# delete a node from this group
|
||||
my ($self,$n) = @_;
|
||||
|
||||
delete $self->{nodes}->{ $n->{name} };
|
||||
delete $n->{group}; # unregister us
|
||||
delete $n->{att}->{group}; # delete the group attribute
|
||||
|
||||
# find all edges that mention this node and drop them from the group
|
||||
my $edges = $self->{edges_within};
|
||||
for my $e (ord_values ( $edges))
|
||||
{
|
||||
delete $edges->{ $e->{id} } if $e->{from} == $n || $e->{to} == $n;
|
||||
}
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub add_nodes
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# make a copy in case of scalars
|
||||
my @arg = @_;
|
||||
foreach my $n (@arg)
|
||||
{
|
||||
if (!ref($n) && !ref($self->{graph}))
|
||||
{
|
||||
return $self->error("Cannot add non node-object $n to group '$self->{name}'");
|
||||
}
|
||||
return $self->error("Cannot add group-object $n to group '$self->{name}'")
|
||||
if $n->isa('Graph::Easy::Group');
|
||||
|
||||
$n = $self->{graph}->add_node($n) unless ref($n);
|
||||
|
||||
$self->{nodes}->{ $n->{name} } = $n;
|
||||
|
||||
# set the proper attribute (for layout)
|
||||
$n->{att}->{group} = $self->{name};
|
||||
|
||||
# XXX TODO TEST!
|
||||
# # if defined attribute "nodeclass", put our nodes into that class
|
||||
# $n->sub_class($self->{att}->{nodeclass}) if exists $self->{att}->{nodeclass};
|
||||
|
||||
# register ourselves with the member
|
||||
$n->{group} = $self;
|
||||
|
||||
# Register the nodes and the edge with our graph object
|
||||
# and weaken the references. Be careful to not needlessly
|
||||
# override and weaken again an already existing reference, this
|
||||
# is an O(N) operation in most Perl versions, and thus very slow.
|
||||
|
||||
# If the node does not belong to a graph yet or belongs to another
|
||||
# graph, add it to our own graph:
|
||||
weaken($n->{graph} = $self->{graph}) unless
|
||||
$n->{graph} && $self->{graph} && $n->{graph} == $self->{graph};
|
||||
|
||||
}
|
||||
|
||||
@arg;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _del_edge
|
||||
{
|
||||
# delete an edge from this group
|
||||
my ($self,$e) = @_;
|
||||
|
||||
delete $self->{edges_within}->{ $e->{id} };
|
||||
delete $e->{group}; # unregister us
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _add_edge
|
||||
{
|
||||
# add an edge to this group (e.g. when both from/to of this edge belong
|
||||
# to this group)
|
||||
my ($self,$e) = @_;
|
||||
|
||||
if (!ref($e) || !$e->isa("Graph::Easy::Edge"))
|
||||
{
|
||||
return $self->error("Cannot add non edge-object $e to group '$self->{name}'");
|
||||
}
|
||||
$self->{edges_within}->{ $e->{id} } = $e;
|
||||
|
||||
# if defined attribute "edgeclass", put our edges into that class
|
||||
my $edge_class = $self->attribute('edgeclass');
|
||||
$e->sub_class($edge_class) if $edge_class ne '';
|
||||
|
||||
# XXX TODO: inline
|
||||
$self->add_node($e->{from});
|
||||
$self->add_node($e->{to});
|
||||
|
||||
# register us, but don't do weaken() if the ref was already set
|
||||
weaken($e->{group} = $self) unless defined $e->{group} && $e->{group} == $self;
|
||||
|
||||
$e;
|
||||
}
|
||||
|
||||
sub add_edge
|
||||
{
|
||||
# Add an edge to the graph of this group, then register it with this group.
|
||||
my ($self,$from,$to) = @_;
|
||||
|
||||
my $g = $self->{graph};
|
||||
return $self->error("Cannot add edge to group '$self->{name}' without graph")
|
||||
unless defined $g;
|
||||
|
||||
my $edge = $g->add_edge($from,$to);
|
||||
|
||||
$self->_add_edge($edge);
|
||||
}
|
||||
|
||||
sub add_edge_once
|
||||
{
|
||||
# Add an edge to the graph of this group, then register it with this group.
|
||||
my ($self,$from,$to) = @_;
|
||||
|
||||
my $g = $self->{graph};
|
||||
return $self->error("Cannot non edge to group '$self->{name}' without graph")
|
||||
unless defined $g;
|
||||
|
||||
my $edge = $g->add_edge_once($from,$to);
|
||||
# edge already exists => so fetch it
|
||||
$edge = $g->edge($from,$to) unless defined $edge;
|
||||
|
||||
$self->_add_edge($edge);
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub add_group
|
||||
{
|
||||
# add a group to us
|
||||
my ($self,$group) = @_;
|
||||
|
||||
# group with that name already exists?
|
||||
my $name = $group;
|
||||
$group = $self->{groups}->{ $group } unless ref $group;
|
||||
|
||||
# group with that name doesn't exist, so create new one
|
||||
$group = $self->{graph}->add_group($name) unless ref $group;
|
||||
|
||||
# index under the group name for easier lookup
|
||||
$self->{groups}->{ $group->{name} } = $group;
|
||||
|
||||
# make attribute->('group') work
|
||||
$group->{att}->{group} = $self->{name};
|
||||
|
||||
# register group with the graph and ourself
|
||||
$group->{graph} = $self->{graph};
|
||||
$group->{group} = $self;
|
||||
{
|
||||
no warnings; # don't warn on already weak references
|
||||
weaken($group->{graph});
|
||||
weaken($group->{group});
|
||||
}
|
||||
$self->{graph}->{score} = undef; # invalidate last layout
|
||||
|
||||
$group;
|
||||
}
|
||||
|
||||
# cell management - used by the layouter
|
||||
|
||||
sub _cells
|
||||
{
|
||||
# return all the cells this group currently occupies
|
||||
my $self = shift;
|
||||
|
||||
$self->{_cells};
|
||||
}
|
||||
|
||||
sub _clear_cells
|
||||
{
|
||||
# remove all belonging cells
|
||||
my $self = shift;
|
||||
|
||||
$self->{_cells} = {};
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _add_cell
|
||||
{
|
||||
# add a cell to the list of cells this group covers
|
||||
my ($self,$cell) = @_;
|
||||
|
||||
$cell->_update_boundaries();
|
||||
$self->{_cells}->{"$cell->{x},$cell->{y}"} = $cell;
|
||||
$cell;
|
||||
}
|
||||
|
||||
sub _del_cell
|
||||
{
|
||||
# delete a cell from the list of cells this group covers
|
||||
my ($self,$cell) = @_;
|
||||
|
||||
delete $self->{_cells}->{"$cell->{x},$cell->{y}"};
|
||||
delete $cell->{group};
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _find_label_cell
|
||||
{
|
||||
# go through all cells of this group and find one where to attach the label
|
||||
my $self = shift;
|
||||
|
||||
my $g = $self->{graph};
|
||||
|
||||
my $align = $self->attribute('align');
|
||||
my $loc = $self->attribute('labelpos');
|
||||
|
||||
# depending on whether the label should be on top or bottom:
|
||||
my $match = qr/^\s*gt\s*\z/;
|
||||
$match = qr/^\s*gb\s*\z/ if $loc eq 'bottom';
|
||||
|
||||
my $lc; # the label cell
|
||||
|
||||
for my $c (ord_values ( $self->{_cells} ))
|
||||
{
|
||||
# find a cell where to put the label
|
||||
next unless $c->{cell_class} =~ $match;
|
||||
|
||||
if (defined $lc)
|
||||
{
|
||||
if ($align eq 'left')
|
||||
{
|
||||
# find top-most, left-most cell
|
||||
next if $lc->{x} < $c->{x} || $lc->{y} < $c->{y};
|
||||
}
|
||||
elsif ($align eq 'center')
|
||||
{
|
||||
# just find any top-most cell
|
||||
next if $lc->{y} < $c->{y};
|
||||
}
|
||||
elsif ($align eq 'right')
|
||||
{
|
||||
# find top-most, right-most cell
|
||||
next if $lc->{x} > $c->{x} || $lc->{y} < $c->{y};
|
||||
}
|
||||
}
|
||||
$lc = $c;
|
||||
}
|
||||
|
||||
# find the cell mostly near the center in the found top-row
|
||||
if (ref($lc) && $align eq 'center')
|
||||
{
|
||||
my ($left, $right);
|
||||
# find left/right most coordinates
|
||||
for my $c (ord_values ( $self->{_cells} ))
|
||||
{
|
||||
next if $c->{y} != $lc->{y};
|
||||
$left = $c->{x} if !defined $left || $left > $c->{x};
|
||||
$right = $c->{x} if !defined $right || $right < $c->{x};
|
||||
}
|
||||
my $center = int(($right - $left) / 2 + $left);
|
||||
my $min_dist;
|
||||
# find the cell mostly near the center in the found top-row
|
||||
for my $c (ord_values ( $self->{_cells} ))
|
||||
{
|
||||
next if $c->{y} != $lc->{y};
|
||||
# squared to get rid of sign
|
||||
my $dist = ($center - $c->{x}); $dist *= $dist;
|
||||
next if defined $min_dist && $dist > $min_dist;
|
||||
$min_dist = $dist; $lc = $c;
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR "# Setting label for group '$self->{name}' at $lc->{x},$lc->{y}\n"
|
||||
if $self->{debug};
|
||||
|
||||
$lc->_set_label() if ref($lc);
|
||||
}
|
||||
|
||||
sub layout
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->_croak('Cannot call layout() on a Graph::Easy::Group directly.');
|
||||
}
|
||||
|
||||
sub _layout
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
###########################################################################
|
||||
# set local {debug} for groups
|
||||
local $self->{debug} = $self->{graph}->{debug};
|
||||
|
||||
$self->SUPER::_layout();
|
||||
}
|
||||
|
||||
sub _set_cell_types
|
||||
{
|
||||
my ($self, $cells) = @_;
|
||||
|
||||
# Set the right cell class for all of our cells:
|
||||
for my $cell (ord_values ( $self->{_cells} ))
|
||||
{
|
||||
$cell->_set_type($cells);
|
||||
}
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Group - A group of nodes (aka subgraph) in Graph::Easy
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $bonn = Graph::Easy::Node->new('Bonn');
|
||||
|
||||
$bonn->set_attribute('border', 'solid 1px black');
|
||||
|
||||
my $berlin = Graph::Easy::Node->new( name => 'Berlin' );
|
||||
|
||||
my $cities = Graph::Easy::Group->new(
|
||||
name => 'Cities',
|
||||
);
|
||||
$cities->set_attribute('border', 'dashed 1px blue');
|
||||
|
||||
$cities->add_nodes ($bonn);
|
||||
# $bonn will be ONCE in the group
|
||||
$cities->add_nodes ($bonn, $berlin);
|
||||
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Group> represents a group of nodes in an C<Graph::Easy>
|
||||
object. These nodes are grouped together on output.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 new()
|
||||
|
||||
my $group = Graph::Easy::Group->new( $options );
|
||||
|
||||
Create a new, empty group. C<$options> are the possible options, see
|
||||
L<Graph::Easy::Node> for a list.
|
||||
|
||||
=head2 error()
|
||||
|
||||
$last_error = $group->error();
|
||||
|
||||
$group->error($error); # set new messages
|
||||
$group->error(''); # clear error
|
||||
|
||||
Returns the last error message, or '' for no error.
|
||||
|
||||
=head2 as_ascii()
|
||||
|
||||
my $ascii = $group->as_ascii();
|
||||
|
||||
Return the group as a little box drawn in ASCII art as a string.
|
||||
|
||||
=head2 name()
|
||||
|
||||
my $name = $group->name();
|
||||
|
||||
Return the name of the group.
|
||||
|
||||
=head2 id()
|
||||
|
||||
my $id = $group->id();
|
||||
|
||||
Returns the group's unique ID number.
|
||||
|
||||
=head2 set_attribute()
|
||||
|
||||
$group->set_attribute('border-style', 'none');
|
||||
|
||||
Sets the specified attribute of this (and only this!) group to the
|
||||
specified value.
|
||||
|
||||
=head2 add_member()
|
||||
|
||||
$group->add_member($node);
|
||||
$group->add_member($group);
|
||||
|
||||
Add the specified object to this group and returns this member. If the
|
||||
passed argument is a scalar, will treat it as a node name.
|
||||
|
||||
Note that each object can only be a member of one group at a time.
|
||||
|
||||
=head2 add_node()
|
||||
|
||||
$group->add_node($node);
|
||||
|
||||
Add the specified node to this group and returns this node.
|
||||
|
||||
Note that each object can only be a member of one group at a time.
|
||||
|
||||
=head2 add_edge(), add_edge_once()
|
||||
|
||||
$group->add_edge($edge); # Graph::Easy::Edge
|
||||
$group->add_edge($from, $to); # Graph::Easy::Node or
|
||||
# Graph::Easy::Group
|
||||
$group->add_edge('From', 'To'); # Scalars
|
||||
|
||||
If passed an Graph::Easy::Edge object, moves the nodes involved in
|
||||
this edge to the group.
|
||||
|
||||
if passed two nodes, adds these nodes to the graph (unless they already
|
||||
exist) and adds an edge between these two nodes. See L<add_edge_once()>
|
||||
to avoid creating multiple edges.
|
||||
|
||||
This method works only on groups that are part of a graph.
|
||||
|
||||
Note that each object can only be a member of one group at a time,
|
||||
and edges are automatically a member of a group if and only if both
|
||||
the target and the destination node are a member of the same group.
|
||||
|
||||
=head2 add_group()
|
||||
|
||||
my $inner = $group->add_group('Group name');
|
||||
my $nested = $group->add_group($group);
|
||||
|
||||
Add a group as subgroup to this group and returns this group.
|
||||
|
||||
=head2 del_member()
|
||||
|
||||
$group->del_member($node);
|
||||
$group->del_member($group);
|
||||
|
||||
Delete the specified object from this group.
|
||||
|
||||
=head2 del_node()
|
||||
|
||||
$group->del_node($node);
|
||||
|
||||
Delete the specified node from this group.
|
||||
|
||||
=head2 del_edge()
|
||||
|
||||
$group->del_edge($edge);
|
||||
|
||||
Delete the specified edge from this group.
|
||||
|
||||
=head2 add_nodes()
|
||||
|
||||
$group->add_nodes($node, $node2, ... );
|
||||
|
||||
Add all the specified nodes to this group and returns them as a list.
|
||||
|
||||
=head2 nodes()
|
||||
|
||||
my @nodes = $group->nodes();
|
||||
|
||||
Returns a list of all node objects that belong to this group.
|
||||
|
||||
=head2 edges()
|
||||
|
||||
my @edges = $group->edges();
|
||||
|
||||
Returns a list of all edge objects that lead to or from this group.
|
||||
|
||||
Note: This does B<not> return edges between nodes that are inside the group,
|
||||
for this see L<edges_within()>.
|
||||
|
||||
=head2 edges_within()
|
||||
|
||||
my @edges_within = $group->edges_within();
|
||||
|
||||
Returns a list of all edge objects that are I<inside> this group, in arbitrary
|
||||
order. Edges are automatically considered I<inside> a group if their starting
|
||||
and ending node both are in the same group.
|
||||
|
||||
Note: This does B<not> return edges between this group and other groups,
|
||||
nor edges between this group and nodes outside this group, for this see
|
||||
L<edges()>.
|
||||
|
||||
=head2 groups()
|
||||
|
||||
my @groups = $group->groups();
|
||||
|
||||
Returns the contained groups of this group as L<Graph::Easy::Group> objects,
|
||||
in arbitrary order.
|
||||
|
||||
=head2 groups_within()
|
||||
|
||||
# equivalent to $group->groups():
|
||||
my @groups = $group->groups_within(); # all
|
||||
my @toplevel_groups = $group->groups_within(0); # level 0 only
|
||||
|
||||
Return the groups that are inside this group, up to the specified level,
|
||||
in arbitrary order.
|
||||
|
||||
The default level is -1, indicating no bounds and thus all contained
|
||||
groups are returned.
|
||||
|
||||
A level of 0 means only the direct children, and hence only the toplevel
|
||||
groups will be returned. A level 1 means the toplevel groups and their
|
||||
toplevel children, and so on.
|
||||
|
||||
=head2 as_txt()
|
||||
|
||||
my $txt = $group->as_txt();
|
||||
|
||||
Returns the group as Graph::Easy textual description.
|
||||
|
||||
=head2 _find_label_cell()
|
||||
|
||||
$group->_find_label_cell();
|
||||
|
||||
Called by the layouter once for each group. Goes through all cells of this
|
||||
group and finds one where to attach the label to. Internal usage only.
|
||||
|
||||
=head2 get_attributes()
|
||||
|
||||
my $att = $object->get_attributes();
|
||||
|
||||
Return all effective attributes on this object (graph/node/group/edge) as
|
||||
an anonymous hash ref. This respects inheritance and default values.
|
||||
|
||||
See also L<raw_attributes()>.
|
||||
|
||||
=head2 raw_attributes()
|
||||
|
||||
my $att = $object->get_attributes();
|
||||
|
||||
Return all set attributes on this object (graph/node/group/edge) as
|
||||
an anonymous hash ref. This respects inheritance, but does not include
|
||||
default values for unset attributes.
|
||||
|
||||
See also L<get_attributes()>.
|
||||
|
||||
=head2 attribute related methods
|
||||
|
||||
You can call all the various attribute related methods like C<set_attribute()>,
|
||||
C<get_attribute()>, etc. on a group, too. For example:
|
||||
|
||||
$group->set_attribute('label', 'by train');
|
||||
my $attr = $group->get_attributes();
|
||||
|
||||
You can find more documentation in L<Graph::Easy>.
|
||||
|
||||
=head2 layout()
|
||||
|
||||
This routine should not be called on groups, it only works on the graph
|
||||
itself.
|
||||
|
||||
=head2 shape()
|
||||
|
||||
my $shape = $group->shape();
|
||||
|
||||
Returns the shape of the group as string.
|
||||
|
||||
=head2 has_as_successor()
|
||||
|
||||
if ($group->has_as_successor($other))
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
Returns true if C<$other> (a node or group) is a successor of this group, e.g.
|
||||
if there is an edge leading from this group to C<$other>.
|
||||
|
||||
=head2 has_as_predecessor()
|
||||
|
||||
if ($group->has_as_predecessor($other))
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
Returns true if the group has C<$other> (a group or node) as predecessor, that
|
||||
is if there is an edge leading from C<$other> to this group.
|
||||
|
||||
=head2 root_node()
|
||||
|
||||
my $root = $group->root_node();
|
||||
|
||||
Return the root node as L<Graph::Easy::Node> object, if it was
|
||||
set with the 'root' attribute.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>, L<Graph::Easy::Node>, L<Graph::Easy::Manual>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2008 by Tels L<http://bloodgate.com>
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
124
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Group/Anon.pm
Normal file
124
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Group/Anon.pm
Normal file
@@ -0,0 +1,124 @@
|
||||
#############################################################################
|
||||
# (c) by Tels 2004. Part of Graph::Easy. An anonymous group.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Group::Anon;
|
||||
|
||||
use Graph::Easy::Group;
|
||||
use warnings;
|
||||
|
||||
@ISA = qw/Graph::Easy::Group/;
|
||||
$VERSION = '0.76';
|
||||
|
||||
use strict;
|
||||
|
||||
sub _init
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->SUPER::_init(@_);
|
||||
|
||||
$self->{name} = 'Group #' . $self->{id};
|
||||
$self->{class} = 'group.anon';
|
||||
|
||||
$self->{att}->{label} = '';
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _correct_size
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{w} = 3;
|
||||
$self->{h} = 3;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub attributes_as_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->SUPER::attributes_as_txt( {
|
||||
node => {
|
||||
label => undef,
|
||||
shape => undef,
|
||||
class => undef,
|
||||
} } );
|
||||
}
|
||||
|
||||
sub as_pure_txt
|
||||
{
|
||||
'( )';
|
||||
}
|
||||
|
||||
sub _as_part_txt
|
||||
{
|
||||
'( )';
|
||||
}
|
||||
|
||||
sub as_graphviz_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
my $name = $self->{name};
|
||||
|
||||
# quote special chars in name
|
||||
$name =~ s/([\[\]\(\)\{\}\#])/\\$1/g;
|
||||
|
||||
'"' . $name . '"';
|
||||
}
|
||||
|
||||
sub text_styles_as_css
|
||||
{
|
||||
'';
|
||||
}
|
||||
|
||||
sub is_anon
|
||||
{
|
||||
# is an anon group
|
||||
1;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Group::Anon - An anonymous group of nodes in Graph::Easy
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy::Group::Anon;
|
||||
|
||||
my $anon = Graph::Easy::Group::Anon->new();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Group::Anon> represents an anonymous group of nodes,
|
||||
e.g. a group without a name.
|
||||
|
||||
The syntax in the Graph::Easy textual description language looks like this:
|
||||
|
||||
( [ Bonn ] -> [ Berlin ] )
|
||||
|
||||
This module is loaded and used automatically by Graph::Easy, so there is
|
||||
no need to use it manually.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy::Group>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2006 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
401
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Group/Cell.pm
Normal file
401
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Group/Cell.pm
Normal file
@@ -0,0 +1,401 @@
|
||||
#############################################################################
|
||||
# A cell of a group during layout. Part of Graph::Easy.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Group::Cell;
|
||||
|
||||
use Graph::Easy::Node;
|
||||
|
||||
@ISA = qw/Graph::Easy::Node/;
|
||||
$VERSION = '0.76';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN
|
||||
{
|
||||
*get_attribute = \&attribute;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
# The different types for a group-cell:
|
||||
use constant {
|
||||
GROUP_INNER => 0, # completely sourounded by group cells
|
||||
GROUP_RIGHT => 1, # right border only
|
||||
GROUP_LEFT => 2, # left border only
|
||||
GROUP_TOP => 3, # top border only
|
||||
GROUP_BOTTOM => 4, # bottom border only
|
||||
GROUP_ALL => 5, # completely sourounded by non-group cells
|
||||
|
||||
GROUP_BOTTOM_RIGHT => 6, # bottom and right border
|
||||
GROUP_BOTTOM_LEFT => 7, # bottom and left border
|
||||
GROUP_TOP_RIGHT => 8, # top and right border
|
||||
GROUP_TOP_LEFT => 9, # top and left order
|
||||
|
||||
GROUP_MAX => 5, # max number
|
||||
};
|
||||
|
||||
my $border_styles =
|
||||
{
|
||||
# type top, bottom, left, right, class
|
||||
GROUP_INNER() => [ 0, 0, 0, 0, ['gi'] ],
|
||||
GROUP_RIGHT() => [ 0, 0, 0, 1, ['gr'] ],
|
||||
GROUP_LEFT() => [ 0, 0, 1, 0, ['gl'] ],
|
||||
GROUP_TOP() => [ 1, 0, 0, 0, ['gt'] ],
|
||||
GROUP_BOTTOM() => [ 0, 1, 0, 0, ['gb'] ],
|
||||
GROUP_ALL() => [ 0, 0, 0, 0, ['ga'] ],
|
||||
GROUP_BOTTOM_RIGHT() => [ 0, 1, 0, 1, ['gb','gr'] ],
|
||||
GROUP_BOTTOM_LEFT() => [ 0, 1, 1, 0, ['gb','gl'] ],
|
||||
GROUP_TOP_RIGHT() => [ 1, 0, 0, 1, ['gt','gr'] ],
|
||||
GROUP_TOP_LEFT() => [ 1, 0, 1, 0, ['gt','gl'] ],
|
||||
};
|
||||
|
||||
my $border_name = [ 'top', 'bottom', 'left', 'right' ];
|
||||
|
||||
sub _css
|
||||
{
|
||||
my ($c, $id, $group, $border) = @_;
|
||||
|
||||
my $css = '';
|
||||
|
||||
for my $type (0 .. 5)
|
||||
{
|
||||
my $b = $border_styles->{$type};
|
||||
|
||||
# If border eq 'none', this would needlessly repeat the "border: none"
|
||||
# from the general group class.
|
||||
next if $border eq 'none';
|
||||
|
||||
my $cl = '.' . $b->[4]->[0]; # $cl .= "-$group" unless $group eq '';
|
||||
|
||||
$css .= "table.graph$id $cl {";
|
||||
if ($type == GROUP_INNER)
|
||||
{
|
||||
$css .= " border: none;"; # shorter CSS
|
||||
}
|
||||
elsif ($type == GROUP_ALL)
|
||||
{
|
||||
$css .= " border-style: $border;"; # shorter CSS
|
||||
}
|
||||
else
|
||||
{
|
||||
for (my $i = 0; $i < 4; $i++)
|
||||
{
|
||||
$css .= ' border-' . $border_name->[$i] . "-style: $border;" if $b->[$i];
|
||||
}
|
||||
}
|
||||
$css .= "}\n";
|
||||
}
|
||||
|
||||
$css;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _init
|
||||
{
|
||||
# generic init, override in subclasses
|
||||
my ($self,$args) = @_;
|
||||
|
||||
$self->{class} = 'group';
|
||||
$self->{cell_class} = ' gi';
|
||||
$self->{name} = '';
|
||||
|
||||
$self->{'x'} = 0;
|
||||
$self->{'y'} = 0;
|
||||
|
||||
# XXX TODO check arguments
|
||||
foreach my $k (sort keys %$args)
|
||||
{
|
||||
$self->{$k} = $args->{$k};
|
||||
}
|
||||
|
||||
if (defined $self->{group})
|
||||
{
|
||||
# register ourselves at this group
|
||||
$self->{group}->_add_cell ($self);
|
||||
# XXX CHECK also implement sub_class()
|
||||
$self->{class} = $self->{group}->{class};
|
||||
$self->{class} = 'group' unless defined $self->{class};
|
||||
}
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _set_type
|
||||
{
|
||||
# set the proper type of this cell based on the sourrounding cells
|
||||
my ($self, $cells) = @_;
|
||||
|
||||
# +------+--------+-------+
|
||||
# | LT TOP RU |
|
||||
# + + + +
|
||||
# | LEFT INNER Right |
|
||||
# + + + +
|
||||
# | LB BOTTOM RB |
|
||||
# +------+--------+-------+
|
||||
|
||||
my @coord = (
|
||||
[ 0, -1, ' gt' ],
|
||||
[ +1, 0, ' gr' ],
|
||||
[ 0, +1, ' gb' ],
|
||||
[ -1, 0, ' gl' ],
|
||||
);
|
||||
|
||||
my ($sx,$sy) = ($self->{x},$self->{y});
|
||||
|
||||
my $class = '';
|
||||
my $gr = $self->{group};
|
||||
foreach my $co (@coord)
|
||||
{
|
||||
my ($x,$y,$c) = @$co; $x += $sx; $y += $sy;
|
||||
my $cell = $cells->{"$x,$y"};
|
||||
|
||||
# belongs to the same group?
|
||||
my $go = 0; $go = $cell->group() if UNIVERSAL::can($cell, 'group');
|
||||
|
||||
$class .= $c unless defined $go && $gr == $go;
|
||||
}
|
||||
|
||||
$class = ' ga' if $class eq ' gt gr gb gl';
|
||||
|
||||
$self->{cell_class} = $class;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _set_label
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{has_label} = 1;
|
||||
|
||||
$self->{name} = $self->{group}->label();
|
||||
}
|
||||
|
||||
sub shape
|
||||
{
|
||||
'rect';
|
||||
}
|
||||
|
||||
sub attribute
|
||||
{
|
||||
my ($self, $name) = @_;
|
||||
|
||||
# print STDERR "called attribute($name)\n";
|
||||
# return $self->{group}->attribute($name);
|
||||
|
||||
my $group = $self->{group};
|
||||
|
||||
return $group->{att}->{$name} if exists $group->{att}->{$name};
|
||||
|
||||
$group->{cache} = {} unless exists $group->{cache};
|
||||
$group->{cache}->{att} = {} unless exists $group->{cache}->{att};
|
||||
|
||||
my $cache = $group->{cache}->{att};
|
||||
return $cache->{$name} if exists $cache->{$name};
|
||||
|
||||
$cache->{$name} = $group->attribute($name);
|
||||
}
|
||||
|
||||
use constant isa_cell => 1;
|
||||
|
||||
#############################################################################
|
||||
# conversion to ASCII or HTML
|
||||
|
||||
sub as_ascii
|
||||
{
|
||||
my ($self, $x,$y) = @_;
|
||||
|
||||
my $fb = $self->_framebuffer($self->{w}, $self->{h});
|
||||
|
||||
my $border_style = $self->attribute('borderstyle');
|
||||
my $EM = 14;
|
||||
# use $self here and not $self->{group} to engage attribute cache:
|
||||
my $border_width = Graph::Easy::_border_width_in_pixels($self,$EM);
|
||||
|
||||
# convert overly broad borders to the correct style
|
||||
$border_style = 'bold' if $border_width > 2;
|
||||
$border_style = 'broad' if $border_width > $EM * 0.2 && $border_width < $EM * 0.75;
|
||||
$border_style = 'wide' if $border_width >= $EM * 0.75;
|
||||
|
||||
if ($border_style ne 'none')
|
||||
{
|
||||
|
||||
#########################################################################
|
||||
# draw our border into the framebuffer
|
||||
|
||||
my $c = $self->{cell_class};
|
||||
|
||||
my $b_top = $border_style;
|
||||
my $b_left = $border_style;
|
||||
my $b_right = $border_style;
|
||||
my $b_bottom = $border_style;
|
||||
if ($c !~ 'ga')
|
||||
{
|
||||
$b_top = 'none' unless $c =~ /gt/;
|
||||
$b_left = 'none' unless $c =~ /gl/;
|
||||
$b_right = 'none' unless $c =~ /gr/;
|
||||
$b_bottom = 'none' unless $c =~ /gb/;
|
||||
}
|
||||
|
||||
$self->_draw_border($fb, $b_right, $b_bottom, $b_left, $b_top, $x, $y);
|
||||
}
|
||||
|
||||
if ($self->{has_label})
|
||||
{
|
||||
# include our label
|
||||
|
||||
my $align = $self->attribute('align');
|
||||
# the default label cell as a top border, but no left/right border
|
||||
my $ys = 0.5;
|
||||
$ys = 0 if $border_style eq 'none';
|
||||
my $h = $self->{h} - 1; $h ++ if $border_style eq 'none';
|
||||
|
||||
$self->_printfb_aligned ($fb, 0, $ys, $self->{w}, $h,
|
||||
$self->_aligned_label($align), 'middle');
|
||||
}
|
||||
|
||||
join ("\n", @$fb);
|
||||
}
|
||||
|
||||
sub class
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{class} . $self->{cell_class};
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
# for rendering this cell as ASCII/Boxart, we need to correct our width based
|
||||
# on whether we have a border or not. But this is only known after parsing is
|
||||
# complete.
|
||||
|
||||
sub _correct_size
|
||||
{
|
||||
my ($self,$format) = @_;
|
||||
|
||||
if (!defined $self->{w})
|
||||
{
|
||||
my $border = $self->attribute('borderstyle');
|
||||
$self->{w} = 0;
|
||||
$self->{h} = 0;
|
||||
# label needs space
|
||||
$self->{h} = 1 if $self->{has_label};
|
||||
if ($border ne 'none')
|
||||
{
|
||||
# class "gt", "gb", "gr" or "gr" will be compressed away
|
||||
# (e.g. only edge cells will be existent)
|
||||
if ($self->{has_label} || ($self->{cell_class} =~ /g[rltb] /))
|
||||
{
|
||||
$self->{w} = 2;
|
||||
$self->{h} = 2;
|
||||
}
|
||||
elsif ($self->{cell_class} =~ /^ g[rl]\z/)
|
||||
{
|
||||
$self->{w} = 2;
|
||||
}
|
||||
elsif ($self->{cell_class} =~ /^ g[bt]\z/)
|
||||
{
|
||||
$self->{h} = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($self->{has_label})
|
||||
{
|
||||
my ($w,$h) = $self->dimensions();
|
||||
$self->{h} += $h;
|
||||
$self->{w} += $w;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Group::Cell - A cell in a group
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $ssl = Graph::Easy::Edge->new( );
|
||||
|
||||
$ssl->set_attributes(
|
||||
label => 'encrypted connection',
|
||||
style => '-->',
|
||||
color => 'red',
|
||||
);
|
||||
|
||||
$graph = Graph::Easy->new();
|
||||
|
||||
$graph->add_edge('source', 'destination', $ssl);
|
||||
|
||||
print $graph->as_ascii();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Group::Cell> represents a cell of a group.
|
||||
|
||||
Group cells can have a background and, if they are on the outside, a border.
|
||||
|
||||
There should be no need to use this package directly.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 error()
|
||||
|
||||
$last_error = $group->error();
|
||||
|
||||
$group->error($error); # set new messages
|
||||
$group->error(''); # clear error
|
||||
|
||||
Returns the last error message, or '' for no error.
|
||||
|
||||
=head2 as_ascii()
|
||||
|
||||
my $ascii = $cell->as_ascii();
|
||||
|
||||
Returns the cell as a little ascii representation.
|
||||
|
||||
=head2 as_html()
|
||||
|
||||
my $html = $cell->as_html($tag,$id);
|
||||
|
||||
Returns the cell as HTML code.
|
||||
|
||||
=head2 label()
|
||||
|
||||
my $label = $cell->label();
|
||||
|
||||
Returns the name (also known as 'label') of the cell.
|
||||
|
||||
=head2 class()
|
||||
|
||||
my $class = $cell->class();
|
||||
|
||||
Returns the classname(s) of this cell, like:
|
||||
|
||||
group_cities gr gb
|
||||
|
||||
for a cell with a bottom (gb) and right (gr) border in the class C<cities>.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
None.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2007 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
1071
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Layout.pm
Normal file
1071
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Layout.pm
Normal file
File diff suppressed because it is too large
Load Diff
570
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Layout/Chain.pm
Normal file
570
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Layout/Chain.pm
Normal file
@@ -0,0 +1,570 @@
|
||||
#############################################################################
|
||||
# One chain of nodes in a Graph::Easy - used internally for layouts.
|
||||
#
|
||||
# (c) by Tels 2004-2006. Part of Graph::Easy
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Layout::Chain;
|
||||
|
||||
use Graph::Easy::Base;
|
||||
$VERSION = '0.76';
|
||||
@ISA = qw/Graph::Easy::Base/;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
use constant {
|
||||
_ACTION_NODE => 0, # place node somewhere
|
||||
_ACTION_TRACE => 1, # trace path from src to dest
|
||||
_ACTION_CHAIN => 2, # place node in chain (with parent)
|
||||
_ACTION_EDGES => 3, # trace all edges (shortes connect. first)
|
||||
};
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _init
|
||||
{
|
||||
# Generic init routine, to be overriden in subclasses.
|
||||
my ($self,$args) = @_;
|
||||
|
||||
foreach my $k (sort keys %$args)
|
||||
{
|
||||
if ($k !~ /^(start|graph)\z/)
|
||||
{
|
||||
require Carp;
|
||||
Carp::confess ("Invalid argument '$k' passed to __PACKAGE__->new()");
|
||||
}
|
||||
$self->{$k} = $args->{$k};
|
||||
}
|
||||
|
||||
$self->{end} = $self->{start};
|
||||
|
||||
# store chain at node (to lookup node => chain info)
|
||||
$self->{start}->{_chain} = $self;
|
||||
$self->{start}->{_next} = undef;
|
||||
|
||||
$self->{len} = 1;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub start
|
||||
{
|
||||
# return first node in the chain
|
||||
my $self = shift;
|
||||
|
||||
$self->{start};
|
||||
}
|
||||
|
||||
sub end
|
||||
{
|
||||
# return last node in the chain
|
||||
my $self = shift;
|
||||
|
||||
$self->{end};
|
||||
}
|
||||
|
||||
sub add_node
|
||||
{
|
||||
# add a node at the end of the chain
|
||||
my ($self, $node) = @_;
|
||||
|
||||
# store at end
|
||||
$self->{end}->{_next} = $node;
|
||||
$self->{end} = $node;
|
||||
|
||||
# store chain at node (to lookup node => chain info)
|
||||
$node->{_chain} = $self;
|
||||
$node->{_next} = undef;
|
||||
|
||||
$self->{len} ++;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub length
|
||||
{
|
||||
# Return the length of the chain in nodes. Takes optional
|
||||
# node from where to calculate length.
|
||||
my ($self, $node) = @_;
|
||||
|
||||
return $self->{len} unless defined $node;
|
||||
|
||||
my $len = 0;
|
||||
while (defined $node)
|
||||
{
|
||||
$len++; $node = $node->{_next};
|
||||
}
|
||||
|
||||
$len;
|
||||
}
|
||||
|
||||
sub nodes
|
||||
{
|
||||
# return all the nodes in the chain as a list, in order.
|
||||
my $self = shift;
|
||||
|
||||
my @nodes = ();
|
||||
my $n = $self->{start};
|
||||
while (defined $n)
|
||||
{
|
||||
push @nodes, $n;
|
||||
$n = $n->{_next};
|
||||
}
|
||||
|
||||
@nodes;
|
||||
}
|
||||
|
||||
sub layout
|
||||
{
|
||||
# Return an action stack containing the nec. actions to
|
||||
# lay out the nodes in the chain, plus any connections between
|
||||
# them.
|
||||
my ($self, $edge) = @_;
|
||||
|
||||
# prevent doing it twice
|
||||
return [] if $self->{_done}; $self->{_done} = 1;
|
||||
|
||||
my @TODO = ();
|
||||
|
||||
my $g = $self->{graph};
|
||||
|
||||
# first, layout all the nodes in the chain:
|
||||
|
||||
# start with first node
|
||||
my $pre = $self->{start}; my $n = $pre->{_next};
|
||||
if (exists $pre->{_todo})
|
||||
{
|
||||
# edges with a flow attribute must be handled differently
|
||||
# XXX TODO: the test for attribute('flow') might be wrong (raw_attribute()?)
|
||||
if ($edge && ($edge->{to} == $pre) && ($edge->attribute('flow') || $edge->has_ports()))
|
||||
{
|
||||
push @TODO, $g->_action( _ACTION_CHAIN, $pre, 0, $edge->{from}, $edge);
|
||||
}
|
||||
else
|
||||
{
|
||||
push @TODO, $g->_action( _ACTION_NODE, $pre, 0, $edge );
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR "# Stack after first:\n" if $g->{debug};
|
||||
$g->_dump_stack(@TODO) if $g->{debug};
|
||||
|
||||
while (defined $n)
|
||||
{
|
||||
if (exists $n->{_todo})
|
||||
{
|
||||
# CHAIN means if $n isn't placed yet, it will be done with
|
||||
# $pre as parent:
|
||||
|
||||
# in case there are multiple edges to the target node, use the first
|
||||
# one to determine the flow:
|
||||
my @edges = $g->edge($pre,$n);
|
||||
|
||||
push @TODO, $g->_action( _ACTION_CHAIN, $n, 0, $pre, $edges[0] );
|
||||
}
|
||||
$pre = $n;
|
||||
$n = $n->{_next};
|
||||
}
|
||||
|
||||
print STDERR "# Stack after chaining:\n" if $g->{debug};
|
||||
$g->_dump_stack(@TODO) if $g->{debug};
|
||||
|
||||
# link from each node to the next
|
||||
$pre = $self->{start}; $n = $pre->{_next};
|
||||
while (defined $n)
|
||||
{
|
||||
# first do edges going from P to N
|
||||
#for my $e (sort { $a->{to}->{name} cmp $b->{to}->{name} } values %{$pre->{edges}})
|
||||
for my $e (ord_values ( $pre->{edges}))
|
||||
{
|
||||
# skip selfloops and backward links, these will be done later
|
||||
next if $e->{to} != $n;
|
||||
|
||||
next unless exists $e->{_todo};
|
||||
|
||||
# skip links from/to groups
|
||||
next if $e->{to}->isa('Graph::Easy::Group') ||
|
||||
$e->{from}->isa('Graph::Easy::Group');
|
||||
|
||||
# # skip edges with a flow
|
||||
# next if exists $e->{att}->{start} || exist $e->{att}->{end};
|
||||
|
||||
push @TODO, [ _ACTION_TRACE, $e ];
|
||||
delete $e->{_todo};
|
||||
}
|
||||
|
||||
} continue { $pre = $n; $n = $n->{_next}; }
|
||||
|
||||
print STDERR "# Stack after chain-linking:\n" if $g->{debug};
|
||||
$g->_dump_stack(@TODO) if $g->{debug};
|
||||
|
||||
# Do all other links inside the chain (backwards, going forward more than
|
||||
# one node etc)
|
||||
|
||||
$n = $self->{start};
|
||||
while (defined $n)
|
||||
{
|
||||
my @edges;
|
||||
|
||||
my @count;
|
||||
|
||||
print STDERR "# inter-chain link from $n->{name}\n" if $g->{debug};
|
||||
|
||||
# gather all edges starting at $n, but do the ones with a flow first
|
||||
# for my $e (sort { $a->{to}->{name} cmp $b->{to}->{name} } values %{$n->{edges}})
|
||||
for my $e (ord_values ( $n->{edges}))
|
||||
{
|
||||
# skip selfloops, these will be done later
|
||||
next if $e->{to} == $n;
|
||||
|
||||
next if !ref($e->{to}->{_chain});
|
||||
next if !ref($e->{from}->{_chain});
|
||||
|
||||
next if $e->has_ports();
|
||||
|
||||
# skip links from/to groups
|
||||
next if $e->{to}->isa('Graph::Easy::Group') ||
|
||||
$e->{from}->isa('Graph::Easy::Group');
|
||||
|
||||
print STDERR "# inter-chain link from $n->{name} to $e->{to}->{name}\n" if $g->{debug};
|
||||
|
||||
# leaving the chain?
|
||||
next if $e->{to}->{_chain} != $self;
|
||||
|
||||
# print STDERR "# trying for $n->{name}:\t $e->{from}->{name} to $e->{to}->{name}\n";
|
||||
next unless exists $e->{_todo};
|
||||
|
||||
# calculate for this edge, how far it goes
|
||||
my $count = 0;
|
||||
my $curr = $n;
|
||||
while (defined $curr && $curr != $e->{to})
|
||||
{
|
||||
$curr = $curr->{_next}; $count ++;
|
||||
}
|
||||
if (!defined $curr)
|
||||
{
|
||||
# edge goes backward
|
||||
|
||||
# start at $to
|
||||
$curr = $e->{to};
|
||||
$count = 0;
|
||||
while (defined $curr && $curr != $e->{from})
|
||||
{
|
||||
$curr = $curr->{_next}; $count ++;
|
||||
}
|
||||
$count = 100000 if !defined $curr; # should not happen
|
||||
}
|
||||
push @edges, [ $count, $e ];
|
||||
push @count, [ $count, $e->{from}->{name}, $e->{to}->{name} ];
|
||||
}
|
||||
|
||||
# use Data::Dumper; print STDERR "count\n", Dumper(@count);
|
||||
|
||||
# do edges, shortest first
|
||||
for my $e (sort { $a->[0] <=> $b->[0] } @edges)
|
||||
{
|
||||
push @TODO, [ _ACTION_TRACE, $e->[1] ];
|
||||
delete $e->[1]->{_todo};
|
||||
}
|
||||
|
||||
$n = $n->{_next};
|
||||
}
|
||||
|
||||
# also do all selfloops on $n
|
||||
$n = $self->{start};
|
||||
while (defined $n)
|
||||
{
|
||||
# for my $e (sort { $a->{to}->{name} cmp $b->{to}->{name} } values %{$n->{edges}})
|
||||
for my $e (ord_values $n->{edges})
|
||||
{
|
||||
next unless exists $e->{_todo};
|
||||
|
||||
# print STDERR "# $e->{from}->{name} to $e->{to}->{name} on $n->{name}\n";
|
||||
# print STDERR "# ne $e->{to} $n $e->{id}\n"
|
||||
# if $e->{from} != $n || $e->{to} != $n; # no selfloop?
|
||||
|
||||
next if $e->{from} != $n || $e->{to} != $n; # no selfloop?
|
||||
|
||||
push @TODO, [ _ACTION_TRACE, $e ];
|
||||
delete $e->{_todo};
|
||||
}
|
||||
$n = $n->{_next};
|
||||
}
|
||||
|
||||
print STDERR "# Stack after self-loops:\n" if $g->{debug};
|
||||
$g->_dump_stack(@TODO) if $g->{debug};
|
||||
|
||||
# XXX TODO
|
||||
# now we should do any links that start or end at this chain, recursively
|
||||
|
||||
$n = $self->{start};
|
||||
while (defined $n)
|
||||
{
|
||||
|
||||
# all chains that start at this node
|
||||
for my $e (sort { $a->{to}->{name} cmp $b->{to}->{name} } values %{$n->{edges}})
|
||||
{
|
||||
my $to = $e->{to};
|
||||
|
||||
# skip links to groups
|
||||
next if $to->isa('Graph::Easy::Group');
|
||||
|
||||
# print STDERR "# chain-tracking to: $to->{name} $to->{_chain}\n";
|
||||
|
||||
next unless exists $to->{_chain} && ref($to->{_chain}) =~ /Chain/;
|
||||
my $chain = $to->{_chain};
|
||||
next if $chain->{_done};
|
||||
|
||||
# print STDERR "# chain-tracking to: $to->{name}\n";
|
||||
|
||||
# pass the edge along, in case it has a flow
|
||||
# my @pass = ();
|
||||
# push @pass, $e if $chain->{_first} && $e->{to} == $chain->{_first};
|
||||
push @TODO, @{ $chain->layout($e) } unless $chain->{_done};
|
||||
|
||||
# link the edges to $to
|
||||
next unless exists $e->{_todo}; # was already done above?
|
||||
|
||||
# next if $e->has_ports();
|
||||
|
||||
push @TODO, [ _ACTION_TRACE, $e ];
|
||||
delete $e->{_todo};
|
||||
}
|
||||
$n = $n->{_next};
|
||||
}
|
||||
|
||||
\@TODO;
|
||||
}
|
||||
|
||||
sub dump
|
||||
{
|
||||
# dump the chain to STDERR
|
||||
my ($self, $indent) = @_;
|
||||
|
||||
$indent = '' unless defined $indent;
|
||||
|
||||
print STDERR "#$indent chain id $self->{id} (len $self->{len}):\n";
|
||||
print STDERR "#$indent is empty\n" and return if $self->{len} == 0;
|
||||
|
||||
my $n = $self->{start};
|
||||
while (defined $n)
|
||||
{
|
||||
print STDERR "#$indent $n->{name} (chain id: $n->{_chain}->{id})\n";
|
||||
$n = $n->{_next};
|
||||
}
|
||||
$self;
|
||||
}
|
||||
|
||||
sub merge
|
||||
{
|
||||
# take another chain, and merge it into ourselves. If $where is defined,
|
||||
# absorb only the nodes from $where onwards (instead of all of them).
|
||||
my ($self, $other, $where) = @_;
|
||||
|
||||
my $g = $self->{graph};
|
||||
|
||||
print STDERR "# panik: ", join(" \n",caller()),"\n" if !defined $other;
|
||||
|
||||
print STDERR
|
||||
"# Merging chain $other->{id} (len $other->{len}) into $self->{id} (len $self->{len})\n"
|
||||
if $g->{debug};
|
||||
|
||||
print STDERR
|
||||
"# Merging from $where->{name} onwards\n"
|
||||
if $g->{debug} && ref($where);
|
||||
|
||||
# cannot merge myself into myself (without allocating infinitely memory)
|
||||
return if $self == $other;
|
||||
|
||||
# start at start as default
|
||||
$where = undef unless ref($where) && exists $where->{_chain} && $where->{_chain} == $other;
|
||||
|
||||
$where = $other->{start} unless defined $where;
|
||||
|
||||
# make all nodes from chain #1 belong to it (to detect loops)
|
||||
my $n = $self->{start};
|
||||
while (defined $n)
|
||||
{
|
||||
$n->{_chain} = $self;
|
||||
$n = $n->{_next};
|
||||
}
|
||||
|
||||
print STDERR "# changed nodes\n" if $g->{debug};
|
||||
$self->dump() if $g->{debug};
|
||||
|
||||
# terminate at $where
|
||||
$self->{end}->{_next} = $where;
|
||||
$self->{end} = $other->{end};
|
||||
|
||||
# start at joiner
|
||||
$n = $where;
|
||||
while (ref($n))
|
||||
{
|
||||
$n->{_chain} = $self;
|
||||
my $pre = $n;
|
||||
$n = $n->{_next};
|
||||
|
||||
# sleep(1);
|
||||
# print "# at $n->{name} $n->{_chain}\n" if ref($n);
|
||||
if (ref($n) && defined $n->{_chain} && $n->{_chain} == $self) # already points into ourself?
|
||||
{
|
||||
# sleep(1);
|
||||
# print "# pre $pre->{name} $pre->{_chain}\n";
|
||||
$pre->{_next} = undef; # terminate
|
||||
$self->{end} = $pre;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
# could speed this up
|
||||
$self->{len} = 0; $n = $self->{start};
|
||||
while (defined $n)
|
||||
{
|
||||
$self->{len}++; $n = $n->{_next};
|
||||
}
|
||||
|
||||
# print "done merging, dumping result:\n";
|
||||
# $self->dump(); sleep(10);
|
||||
|
||||
if (defined $other->{start} && $where == $other->{start})
|
||||
{
|
||||
# we absorbed the other chain completely, so drop it
|
||||
$other->{end} = undef;
|
||||
$other->{start} = undef;
|
||||
$other->{len} = 0;
|
||||
# caller is responsible for cleaning it up
|
||||
}
|
||||
|
||||
print STDERR "# after merging\n" if $g->{debug};
|
||||
$self->dump() if $g->{debug};
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Layout::Chain - Chain of nodes for layouter
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
# used internally, do not use directly
|
||||
|
||||
use Graph::Easy;
|
||||
use Graph::Easy::Layout::Chain;
|
||||
|
||||
my $graph = Graph::Easy->new( );
|
||||
my ($node, $node2) = $graph->add_edge( 'A', 'B' );
|
||||
|
||||
my $chain = Graph::Easy::Layout::Chain->new(
|
||||
start => $node,
|
||||
graph => $graph, );
|
||||
|
||||
$chain->add_node( $node2 );
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Layout::Chain> object represents a chain of nodes
|
||||
for the layouter.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 new()
|
||||
|
||||
my $chain = Graph::Easy::Layout::Chain->new( start => $node );
|
||||
|
||||
Create a new chain and set its starting node to C<$node>.
|
||||
|
||||
=head2 length()
|
||||
|
||||
my $len = $chain->length();
|
||||
|
||||
Return the length of the chain, in nodes.
|
||||
|
||||
my $len = $chain->length( $node );
|
||||
|
||||
Given an optional C<$node> as argument, returns the length
|
||||
from that node onwards. For the chain with the three nodes
|
||||
A, B and C would return 3, 2, and 1 for A, B and C, respectively.
|
||||
|
||||
Returns 0 if the passed node is not part of this chain.
|
||||
|
||||
=head2 nodes()
|
||||
|
||||
my @nodes = $chain->nodes();
|
||||
|
||||
Return all the node objects in the chain as list, in order.
|
||||
|
||||
=head2 add_node()
|
||||
|
||||
$chain->add_node( $node );
|
||||
|
||||
Add C<$node> to the end of the chain.
|
||||
|
||||
=head2 start()
|
||||
|
||||
my $node = $chain->start();
|
||||
|
||||
Return first node in the chain.
|
||||
|
||||
=head2 end()
|
||||
|
||||
my $node = $chain->end();
|
||||
|
||||
Return last node in the chain.
|
||||
|
||||
=head2 layout()
|
||||
|
||||
my $todo = $chain->layout();
|
||||
|
||||
Return an action stack as array ref, containing the nec. actions to
|
||||
layout the chain (nodes, plus interlinks in the chain).
|
||||
|
||||
Will recursively traverse all chains linked to this chain.
|
||||
|
||||
=head2 merge()
|
||||
|
||||
my $chain->merge ( $other_chain );
|
||||
my $chain->merge ( $other_chain, $where );
|
||||
|
||||
Merge the other chain into ourselves, adding its nodes at our end.
|
||||
The other chain is emptied and must be deleted by the caller.
|
||||
|
||||
If C<$where> is defined and a member of C<$other_chain>, absorb only the
|
||||
nodes from C<$where> onwards, instead of all of them.
|
||||
|
||||
=head2 error()
|
||||
|
||||
$last_error = $node->error();
|
||||
|
||||
$node->error($error); # set new messages
|
||||
$node->error(''); # clear error
|
||||
|
||||
Returns the last error message, or '' for no error.
|
||||
|
||||
=head2 dump()
|
||||
|
||||
$chain->dump();
|
||||
|
||||
Dump the chain to STDERR, to aid debugging.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>, L<Graph::Easy::Layout>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2006 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
251
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Layout/Force.pm
Normal file
251
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Layout/Force.pm
Normal file
@@ -0,0 +1,251 @@
|
||||
#############################################################################
|
||||
# Force-based layouter for Graph::Easy.
|
||||
#
|
||||
# (c) by Tels 2004-2007.
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Layout::Force;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
sub _layout_force
|
||||
{
|
||||
# Calculate for each node the force on it, then move them accordingly.
|
||||
# When things have settled, stop.
|
||||
my ($self) = @_;
|
||||
|
||||
# For each node, calculate the force acting on it, separated into two
|
||||
# components along the X and Y axis:
|
||||
|
||||
# XXX TODO: replace with all contained nodes + groups
|
||||
my @nodes = $self->nodes();
|
||||
|
||||
return if @nodes == 0;
|
||||
|
||||
my $root = $self->root_node();
|
||||
|
||||
if (!defined $root)
|
||||
{
|
||||
# find a suitable root node
|
||||
$root = $nodes[0];
|
||||
}
|
||||
|
||||
# this node never moves
|
||||
$root->{_pinned} = undef;
|
||||
$root->{x} = 0;
|
||||
$root->{y} = 0;
|
||||
|
||||
# get the "gravity" force
|
||||
my $gx = 0; my $gy = 0;
|
||||
|
||||
my $flow = $self->flow();
|
||||
if ($flow == 0)
|
||||
{
|
||||
$gx = 1;
|
||||
}
|
||||
elsif ($flow == 90)
|
||||
{
|
||||
$gy = -1;
|
||||
}
|
||||
elsif ($flow == 270)
|
||||
{
|
||||
$gy = 1;
|
||||
}
|
||||
else # ($flow == 180)
|
||||
{
|
||||
$gx = -1;
|
||||
}
|
||||
|
||||
my @particles;
|
||||
# set initial positions
|
||||
for my $n (@nodes)
|
||||
{
|
||||
# the net force on this node is the gravity
|
||||
$n->{_x_force} = $gx;
|
||||
$n->{_y_force} = $gy;
|
||||
if ($root == $n || defined $n->{origin})
|
||||
{
|
||||
# nodes that are relative to another are "pinned"
|
||||
$n->{_pinned} = undef;
|
||||
}
|
||||
else
|
||||
{
|
||||
$n->{x} = rand(100);
|
||||
$n->{y} = rand(100);
|
||||
push @particles, $n;
|
||||
}
|
||||
}
|
||||
|
||||
my $energy = 1;
|
||||
while ($energy > 0.1)
|
||||
{
|
||||
$energy = 0;
|
||||
for my $n (@particles)
|
||||
{
|
||||
# reset forces on this node
|
||||
$n->{_x_force} = 0;
|
||||
$n->{_y_force} = 0;
|
||||
|
||||
# Add forces of all other nodes. We need to include pinned nodes here,
|
||||
# too, since a moving node might get near a pinned one and get repelled.
|
||||
for my $n2 (@nodes)
|
||||
{
|
||||
next if $n2 == $n; # don't repel yourself
|
||||
|
||||
my $dx = ($n->{x} - $n2->{x});
|
||||
my $dy = ($n->{y} - $n2->{y});
|
||||
|
||||
my $r = $dx * $dx + $dy * $dy;
|
||||
|
||||
$r = 0.01 if $r < 0.01; # too small?
|
||||
if ($r < 4)
|
||||
{
|
||||
# not too big
|
||||
$n->{_x_force} += 1 / $dx * $dx;
|
||||
$n->{_y_force} += 1 / $dy * $dy;
|
||||
|
||||
my $dx2 = 1 / $dx * $dx;
|
||||
my $dy2 = 1 / $dy * $dy;
|
||||
|
||||
print STDERR "# Force between $n->{name} and $n2->{name}: fx $dx2, fy $dy2\n";
|
||||
}
|
||||
}
|
||||
|
||||
# for all edges connected at this node
|
||||
for my $e (ord_values ( $n->{edges} ))
|
||||
{
|
||||
# exclude self-loops
|
||||
next if $e->{from} == $n && $e->{to} == $n;
|
||||
|
||||
# get the other end-point of this edge
|
||||
my $n2 = $e->{from}; $n2 = $e->{to} if $n2 == $n;
|
||||
|
||||
# XXX TODO
|
||||
# we should "connect" the edges to the appropriate port so that
|
||||
# they excert an off-center force
|
||||
|
||||
my $dx = -($n->{x} - $n2->{x}) / 2;
|
||||
my $dy = -($n->{y} - $n2->{y}) / 2;
|
||||
|
||||
print STDERR "# Spring force between $n->{name} and $n2->{name}: fx $dx, fy $dy\n";
|
||||
$n->{_x_force} += $dx;
|
||||
$n->{_y_force} += $dy;
|
||||
}
|
||||
|
||||
print STDERR "# $n->{name}: Summed force: fx $n->{_x_force}, fy $n->{_y_force}\n";
|
||||
|
||||
# for grid-like layouts, add a small force drawing this node to the gridpoint
|
||||
# 0.7 => 1 - 0.7 => 0.3
|
||||
# 1.2 => 1 - 1.2 => -0.2
|
||||
|
||||
my $dx = int($n->{x} + 0.5) - $n->{x};
|
||||
$n->{_x_force} += $dx;
|
||||
my $dy = int($n->{y} + 0.5) - $n->{y};
|
||||
$n->{_y_force} += $dy;
|
||||
|
||||
print STDERR "# $n->{name}: Final force: fx $n->{_x_force}, fy $n->{_y_force}\n";
|
||||
|
||||
$energy += $n->{_x_force} * $n->{_x_force} + $n->{_x_force} * $n->{_y_force};
|
||||
|
||||
print STDERR "# Net energy: $energy\n";
|
||||
}
|
||||
|
||||
# after having calculated all forces, move the nodes
|
||||
for my $n (@particles)
|
||||
{
|
||||
my $dx = $n->{_x_force};
|
||||
$dx = 5 if $dx > 5; # limit it
|
||||
$n->{x} += $dx;
|
||||
|
||||
my $dy = $n->{_y_force};
|
||||
$dy = 5 if $dy > 5; # limit it
|
||||
$n->{y} += $dy;
|
||||
|
||||
print STDERR "# $n->{name}: Position $n->{x}, $n->{y}\n";
|
||||
}
|
||||
|
||||
sleep(1); print STDERR "\n";
|
||||
}
|
||||
|
||||
for my $n (@nodes)
|
||||
{
|
||||
delete $n->{_x_force};
|
||||
delete $n->{_y_force};
|
||||
}
|
||||
$self;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Layout::Force - Force-based layouter for Graph::Easy
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
$graph->add_edge ('Bonn', 'Berlin');
|
||||
$graph->add_edge ('Bonn', 'Ulm');
|
||||
$graph->add_edge ('Ulm', 'Berlin');
|
||||
|
||||
$graph->layout( type => 'force' );
|
||||
|
||||
print $graph->as_ascii( );
|
||||
|
||||
# prints:
|
||||
|
||||
# +------------------------+
|
||||
# | v
|
||||
# +------+ +-----+ +--------+
|
||||
# | Bonn | --> | Ulm | --> | Berlin |
|
||||
# +------+ +-----+ +--------+
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<Graph::Easy::Layout::Force> contains routines that calculate a
|
||||
force-based layout for a graph.
|
||||
|
||||
Nodes repell each other, while edges connecting them draw them together.
|
||||
|
||||
The layouter calculates the forces on each node, then moves them around
|
||||
according to these forces until things have settled down.
|
||||
|
||||
Used automatically by Graph::Easy.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
Exports nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
This module injects the following methods into Graph::Easy:
|
||||
|
||||
=head2 _layout_force()
|
||||
|
||||
Calculates the node position with a force-based method.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2007 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for information.
|
||||
|
||||
=cut
|
||||
348
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Layout/Grid.pm
Normal file
348
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Layout/Grid.pm
Normal file
@@ -0,0 +1,348 @@
|
||||
#############################################################################
|
||||
# Grid-management and layout preparation.
|
||||
#
|
||||
# (c) by Tels 2004-2006.
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Layout::Grid;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
sub _balance_sizes
|
||||
{
|
||||
# Given a list of column/row sizes and a minimum size that their sum must
|
||||
# be, will grow individual sizes until the constraint (sum) is met.
|
||||
my ($self, $sizes, $need) = @_;
|
||||
|
||||
# XXX TODO: we can abort the loop and distribute the remaining nec. size
|
||||
# once all elements in $sizes are equal.
|
||||
|
||||
return if $need < 1;
|
||||
|
||||
# if there is only one element, return it immediately
|
||||
if (@$sizes == 1)
|
||||
{
|
||||
$sizes->[0] = $need if $sizes->[0] < $need;
|
||||
return;
|
||||
}
|
||||
|
||||
# endless loop until constraint is met
|
||||
while (1)
|
||||
{
|
||||
|
||||
# find the smallest size, and also compute their sum
|
||||
my $sum = 0; my $i = 0;
|
||||
my $sm = $need + 1; # start with an arbitrary size
|
||||
my $sm_i = 0; # if none is != 0, then use the first
|
||||
for my $s (@$sizes)
|
||||
{
|
||||
$sum += $s;
|
||||
next if $s == 0;
|
||||
if ($s < $sm)
|
||||
{
|
||||
$sm = $s; $sm_i = $i;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
# their sum is already equal or bigger than what we need?
|
||||
last if $sum >= $need;
|
||||
|
||||
# increase the smallest size by one, then try again
|
||||
$sizes->[$sm_i]++;
|
||||
}
|
||||
|
||||
# use Data::Dumper; print STDERR "# " . Dumper($sizes),"\n";
|
||||
|
||||
undef;
|
||||
}
|
||||
|
||||
sub _prepare_layout
|
||||
{
|
||||
# this method is used by as_ascii() and as_svg() to find out the
|
||||
# sizes and placement of the different cells (edges, nodes etc).
|
||||
my ($self,$format) = @_;
|
||||
|
||||
# Find out for each row and column how big they are:
|
||||
# +--------+-----+------+
|
||||
# | Berlin | --> | Bonn |
|
||||
# +--------+-----+------+
|
||||
# results in:
|
||||
# w, h, x, y
|
||||
# 0,0 => 10, 3, 0, 0
|
||||
# 1,0 => 7, 3, 10, 0
|
||||
# 2,0 => 8, 3, 16, 0
|
||||
|
||||
# Technically, we also need to "compress" away non-existent columns/rows.
|
||||
# We achieve that by simply rendering them with size 0, so they become
|
||||
# practically invisible.
|
||||
|
||||
my $cells = $self->{cells};
|
||||
my $rows = {};
|
||||
my $cols = {};
|
||||
|
||||
# the last column/row (highest X,Y pair)
|
||||
my $mx = -1000000; my $my = -1000000;
|
||||
|
||||
# We need to do this twice, once for single-cell objects, and again for
|
||||
# objects covering multiple cells. The single-cell objects can be solved
|
||||
# first:
|
||||
|
||||
# find all x and y occurrences to sort them by row/columns
|
||||
for my $cell (ord_values $cells)
|
||||
{
|
||||
my ($x,$y) = ($cell->{x}, $cell->{y});
|
||||
|
||||
{
|
||||
no strict 'refs';
|
||||
|
||||
my $method = '_correct_size_' . $format;
|
||||
$method = '_correct_size' unless $cell->can($method);
|
||||
$cell->$method();
|
||||
}
|
||||
|
||||
my $w = $cell->{w} || 0;
|
||||
my $h = $cell->{h} || 0;
|
||||
|
||||
# Set the minimum cell size only for single-celled objects:
|
||||
if ( (($cell->{cx}||1) + ($cell->{cy}||1)) == 2)
|
||||
{
|
||||
# record maximum size for that col/row
|
||||
$rows->{$y} = $h if $h >= ($rows->{$y} || 0);
|
||||
$cols->{$x} = $w if $w >= ($cols->{$x} || 0);
|
||||
}
|
||||
|
||||
# Find highest X,Y pair. Always use x,y, and not x+cx,y+cy, because
|
||||
# a multi-celled object "sticking" out will not count unless there
|
||||
# is another object in the same row/column.
|
||||
$mx = $x if $x > $mx;
|
||||
$my = $y if $y > $my;
|
||||
}
|
||||
|
||||
# insert a dummy row/column with size=0 as last
|
||||
$rows->{$my+1} = 0;
|
||||
$cols->{$mx+1} = 0;
|
||||
|
||||
# do the last step again, but for multi-celled objects
|
||||
for my $cell (ord_values $cells)
|
||||
{
|
||||
my ($x,$y) = ($cell->{x}, $cell->{y});
|
||||
|
||||
my $w = $cell->{w} || 0;
|
||||
my $h = $cell->{h} || 0;
|
||||
|
||||
# Set the minimum cell size only for multi-celled objects:
|
||||
if ( (($cell->{cx} || 1) + ($cell->{cy}||1)) > 2)
|
||||
{
|
||||
$cell->{cx} ||= 1;
|
||||
$cell->{cy} ||= 1;
|
||||
|
||||
# do this twice, for X and Y:
|
||||
|
||||
# print STDERR "\n# ", $cell->{name} || $cell->{id}, " cx=$cell->{cx} cy=$cell->{cy} $cell->{w},$cell->{h}:\n";
|
||||
|
||||
# create an array with the current sizes for the affacted rows/columns
|
||||
my @sizes;
|
||||
|
||||
# print STDERR "# $cell->{cx} $cell->{cy} at cx:\n";
|
||||
|
||||
# XXX TODO: no need to do this for empty/zero cols
|
||||
for (my $i = 0; $i < $cell->{cx}; $i++)
|
||||
{
|
||||
push @sizes, $cols->{$i+$x} || 0;
|
||||
}
|
||||
$self->_balance_sizes(\@sizes, $cell->{w});
|
||||
# store the result back
|
||||
for (my $i = 0; $i < $cell->{cx}; $i++)
|
||||
{
|
||||
# print STDERR "# store back $sizes[$i] to col ", $i+$x,"\n";
|
||||
$cols->{$i+$x} = $sizes[$i];
|
||||
}
|
||||
|
||||
@sizes = ();
|
||||
|
||||
# print STDERR "# $cell->{cx} $cell->{cy} at cy:\n";
|
||||
|
||||
# XXX TODO: no need to do this for empty/zero cols
|
||||
for (my $i = 0; $i < $cell->{cy}; $i++)
|
||||
{
|
||||
push @sizes, $rows->{$i+$y} || 0;
|
||||
}
|
||||
$self->_balance_sizes(\@sizes, $cell->{h});
|
||||
# store the result back
|
||||
for (my $i = 0; $i < $cell->{cy}; $i++)
|
||||
{
|
||||
# print STDERR "# store back $sizes[$i] to row ", $i+$y,"\n";
|
||||
$rows->{$i+$y} = $sizes[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR "# Calculating absolute positions for rows/columns\n" if $self->{debug};
|
||||
|
||||
# Now run through all rows/columns and get their absolute pos by taking all
|
||||
# previous ones into account.
|
||||
my $pos = 0;
|
||||
for my $y (sort { $a <=> $b } keys %$rows)
|
||||
{
|
||||
my $s = $rows->{$y};
|
||||
$rows->{$y} = $pos; # first is 0, second is $rows[1] etc
|
||||
$pos += $s;
|
||||
}
|
||||
$pos = 0;
|
||||
for my $x (sort { $a <=> $b } keys %$cols)
|
||||
{
|
||||
my $s = $cols->{$x};
|
||||
$cols->{$x} = $pos;
|
||||
$pos += $s;
|
||||
}
|
||||
|
||||
# find out max. dimensions for framebuffer
|
||||
print STDERR "# Finding max. dimensions for framebuffer\n" if $self->{debug};
|
||||
my $max_y = 0; my $max_x = 0;
|
||||
|
||||
for my $v (ord_values $cells)
|
||||
{
|
||||
# Skip multi-celled nodes for later.
|
||||
next if ($v->{cx}||1) + ($v->{cy}||1) != 2;
|
||||
|
||||
# X and Y are col/row, so translate them to real pos
|
||||
my $x = $cols->{ $v->{x} };
|
||||
my $y = $rows->{ $v->{y} };
|
||||
|
||||
# Also set correct the width/height of each cell to be the maximum
|
||||
# width/height of that row/column and store the previous size in 'minw'
|
||||
# and 'minh', respectively.
|
||||
|
||||
$v->{minw} = $v->{w};
|
||||
$v->{minh} = $v->{h};
|
||||
|
||||
# find next col/row
|
||||
my $nx = $v->{x} + 1;
|
||||
my $next_col = $cols->{ $nx };
|
||||
my $ny = $v->{y} + 1;
|
||||
my $next_row = $rows->{ $ny };
|
||||
|
||||
$next_col = $cols->{ ++$nx } while (!defined $next_col);
|
||||
$next_row = $rows->{ ++$ny } while (!defined $next_row);
|
||||
|
||||
$v->{w} = $next_col - $x;
|
||||
$v->{h} = $next_row - $y;
|
||||
|
||||
my $m = $y + $v->{h} - 1;
|
||||
$max_y = $m if $m > $max_y;
|
||||
$m = $x + $v->{w} - 1;
|
||||
$max_x = $m if $m > $max_x;
|
||||
}
|
||||
|
||||
# repeat the previous step, now for multi-celled objects
|
||||
foreach my $v (ord_values ( $self->{cells} ))
|
||||
{
|
||||
next unless defined $v->{x} && (($v->{cx}||1) + ($v->{cy}||1) > 2);
|
||||
|
||||
# X and Y are col/row, so translate them to real pos
|
||||
my $x = $cols->{ $v->{x} };
|
||||
my $y = $rows->{ $v->{y} };
|
||||
|
||||
$v->{minw} = $v->{w};
|
||||
$v->{minh} = $v->{h};
|
||||
|
||||
# find next col/row
|
||||
my $nx = $v->{x} + ($v->{cx} || 1);
|
||||
my $next_col = $cols->{ $nx };
|
||||
my $ny = $v->{y} + ($v->{cy} || 1);
|
||||
my $next_row = $rows->{ $ny };
|
||||
|
||||
$next_col = $cols->{ ++$nx } while (!defined $next_col);
|
||||
$next_row = $rows->{ ++$ny } while (!defined $next_row);
|
||||
|
||||
$v->{w} = $next_col - $x;
|
||||
$v->{h} = $next_row - $y;
|
||||
|
||||
my $m = $y + $v->{h} - 1;
|
||||
$max_y = $m if $m > $max_y;
|
||||
$m = $x + $v->{w} - 1;
|
||||
$max_x = $m if $m > $max_x;
|
||||
}
|
||||
|
||||
# return what we found out:
|
||||
($rows,$cols,$max_x,$max_y);
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Layout::Grid - Grid management and size calculation
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
my $bonn = Graph::Easy::Node->new(
|
||||
name => 'Bonn',
|
||||
);
|
||||
my $berlin = Graph::Easy::Node->new(
|
||||
name => 'Berlin',
|
||||
);
|
||||
|
||||
$graph->add_edge ($bonn, $berlin);
|
||||
|
||||
$graph->layout();
|
||||
|
||||
print $graph->as_ascii( );
|
||||
|
||||
# prints:
|
||||
|
||||
# +------+ +--------+
|
||||
# | Bonn | --> | Berlin |
|
||||
# +------+ +--------+
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<Graph::Easy::Layout::Grid> contains routines that calculate cell sizes
|
||||
on the grid, which is necessary for ASCII, boxart and SVG output.
|
||||
|
||||
Used automatically by Graph::Easy.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
Exports nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
This module injects the following methods into Graph::Easy:
|
||||
|
||||
=head2 _prepare_layout()
|
||||
|
||||
my ($rows,$cols,$max_x,$max_y, \@V) = $graph->_prepare_layout();
|
||||
|
||||
Returns two hashes (C<$rows> and C<$cols>), containing the columns and rows
|
||||
of the layout with their nec. sizes (in chars) plus the maximum
|
||||
framebuffer size nec. for this layout. Also returns reference of
|
||||
a list of all cells to be rendered.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2006 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for information.
|
||||
|
||||
=cut
|
||||
916
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Layout/Path.pm
Normal file
916
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Layout/Path.pm
Normal file
@@ -0,0 +1,916 @@
|
||||
#############################################################################
|
||||
# Path and cell management for Graph::Easy.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Layout::Path;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Node;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Graph::Easy::Edge::Cell qw/
|
||||
EDGE_END_E EDGE_END_N EDGE_END_S EDGE_END_W
|
||||
/;
|
||||
|
||||
sub _shuffle_dir
|
||||
{
|
||||
# take a list with four entries and shuffle them around according to $dir
|
||||
my ($self, $e, $dir) = @_;
|
||||
|
||||
# $dir: 0 => north, 90 => east, 180 => south, 270 => west
|
||||
|
||||
$dir = 90 unless defined $dir; # default is east
|
||||
|
||||
return [ @$e ] if $dir == 90; # default is no shuffling
|
||||
|
||||
my @shuffle = (0,1,2,3); # the default
|
||||
@shuffle = (1,2,0,3) if $dir == 180; # south
|
||||
@shuffle = (2,3,1,0) if $dir == 270; # west
|
||||
@shuffle = (3,0,2,1) if $dir == 0; # north
|
||||
|
||||
[
|
||||
$e->[ $shuffle[0] ],
|
||||
$e->[ $shuffle[1] ],
|
||||
$e->[ $shuffle[2] ],
|
||||
$e->[ $shuffle[3] ],
|
||||
];
|
||||
}
|
||||
|
||||
sub _shift
|
||||
{
|
||||
# get a flow shifted by X° to $dir
|
||||
my ($self, $turn) = @_;
|
||||
|
||||
my $dir = $self->flow();
|
||||
|
||||
$dir += $turn;
|
||||
$dir += 360 if $dir < 0;
|
||||
$dir -= 360 if $dir > 360;
|
||||
$dir;
|
||||
}
|
||||
|
||||
sub _near_places
|
||||
{
|
||||
# Take a node and return a list of possible placements around it and
|
||||
# prune out already occupied cells. $d is the distance from the node
|
||||
# border and defaults to two (for placements). Set it to one for
|
||||
# adjacent cells.
|
||||
|
||||
# If defined, $type contains four flags for each direction. If undef,
|
||||
# two entries (x,y) will be returned for each pos, instead of (x,y,type).
|
||||
|
||||
# If $loose is true, no checking whether the returned fields are free
|
||||
# is done.
|
||||
|
||||
my ($n, $cells, $d, $type, $loose, $dir) = @_;
|
||||
|
||||
my $cx = $n->{cx} || 1;
|
||||
my $cy = $n->{cy} || 1;
|
||||
|
||||
$d = 2 unless defined $d; # default is distance = 2
|
||||
|
||||
my $flags = $type;
|
||||
|
||||
if (ref($flags) ne 'ARRAY')
|
||||
{
|
||||
$flags = [
|
||||
EDGE_END_W,
|
||||
EDGE_END_N,
|
||||
EDGE_END_E,
|
||||
EDGE_END_S,
|
||||
];
|
||||
}
|
||||
$dir = $n->flow() unless defined $dir;
|
||||
|
||||
my $index = $n->_shuffle_dir( [ 0,3,6,9], $dir);
|
||||
|
||||
my @places = ();
|
||||
|
||||
# single-celled node
|
||||
if ($cx + $cy == 2)
|
||||
{
|
||||
my @tries = (
|
||||
$n->{x} + $d, $n->{y}, $flags->[0], # right
|
||||
$n->{x}, $n->{y} + $d, $flags->[1], # down
|
||||
$n->{x} - $d, $n->{y}, $flags->[2], # left
|
||||
$n->{x}, $n->{y} - $d, $flags->[3], # up
|
||||
);
|
||||
|
||||
for my $i (0..3)
|
||||
{
|
||||
my $idx = $index->[$i];
|
||||
my ($x,$y,$t) = ($tries[$idx], $tries[$idx+1], $tries[$idx+2]);
|
||||
|
||||
# print STDERR "# Considering place $x, $y \n";
|
||||
|
||||
# This quick check does not take node clusters or multi-celled nodes
|
||||
# into account. These are handled in $node->_do_place() later.
|
||||
next if !$loose && exists $cells->{"$x,$y"};
|
||||
push @places, $x, $y;
|
||||
push @places, $t if defined $type;
|
||||
}
|
||||
return @places;
|
||||
}
|
||||
|
||||
# Handle a multi-celled node. For a 3x2 node:
|
||||
# A B C
|
||||
# J [00][10][20] D
|
||||
# I [10][11][21] E
|
||||
# H G F
|
||||
# we have 10 (3 * 2 + 2 * 2) places to consider
|
||||
|
||||
my $nx = $n->{x};
|
||||
my $ny = $n->{y};
|
||||
my ($px,$py);
|
||||
|
||||
my $idx = 0;
|
||||
my @results = ( [], [], [], [] );
|
||||
|
||||
$cy--; $cx--;
|
||||
my $t = $flags->[$idx++];
|
||||
# right
|
||||
$px = $nx + $cx + $d;
|
||||
for my $y (0 .. $cy)
|
||||
{
|
||||
$py = $y + $ny;
|
||||
next if exists $cells->{"$px,$py"} && !$loose;
|
||||
push @{$results[0]}, $px, $py;
|
||||
push @{$results[0]}, $t if defined $type;
|
||||
}
|
||||
|
||||
# below
|
||||
$py = $ny + $cy + $d;
|
||||
$t = $flags->[$idx++];
|
||||
for my $x (0 .. $cx)
|
||||
{
|
||||
$px = $x + $nx;
|
||||
next if exists $cells->{"$px,$py"} && !$loose;
|
||||
push @{$results[1]}, $px, $py;
|
||||
push @{$results[1]}, $t if defined $type;
|
||||
}
|
||||
|
||||
# left
|
||||
$px = $nx - $d;
|
||||
$t = $flags->[$idx++];
|
||||
for my $y (0 .. $cy)
|
||||
{
|
||||
$py = $y + $ny;
|
||||
next if exists $cells->{"$px,$py"} && !$loose;
|
||||
push @{$results[2]}, $px, $py;
|
||||
push @{$results[2]}, $t if defined $type;
|
||||
}
|
||||
|
||||
# top
|
||||
$py = $ny - $d;
|
||||
$t = $flags->[$idx];
|
||||
for my $x (0 .. $cx)
|
||||
{
|
||||
$px = $x + $nx;
|
||||
next if exists $cells->{"$px,$py"} && !$loose;
|
||||
push @{$results[3]}, $px, $py;
|
||||
push @{$results[3]}, $t if defined $type;
|
||||
}
|
||||
|
||||
# accumulate the results in the requested, shuffled order
|
||||
for my $i (0..3)
|
||||
{
|
||||
my $idx = $index->[$i] / 3;
|
||||
push @places, @{$results[$idx]};
|
||||
}
|
||||
|
||||
@places;
|
||||
}
|
||||
|
||||
sub _allowed_places
|
||||
{
|
||||
# given a list of potential positions, and a list of allowed positions,
|
||||
# return the valid ones (e.g. that are in both lists)
|
||||
my ($self, $places, $allowed, $step) = @_;
|
||||
|
||||
print STDERR
|
||||
"# calculating allowed places for $self->{name} from " . @$places .
|
||||
" positions and " . scalar @$allowed . " allowed ones:\n"
|
||||
if $self->{graph}->{debug};
|
||||
|
||||
$step ||= 2; # default: "x,y"
|
||||
|
||||
my @good;
|
||||
my $i = 0;
|
||||
while ($i < @$places)
|
||||
{
|
||||
my ($x,$y) = ($places->[$i], $places->[$i+1]);
|
||||
my $allow = 0;
|
||||
my $j = 0;
|
||||
while ($j < @$allowed)
|
||||
{
|
||||
my ($m,$n) = ($allowed->[$j], $allowed->[$j+1]);
|
||||
$allow++ and last if ($m == $x && $n == $y);
|
||||
} continue { $j += 2; }
|
||||
next unless $allow;
|
||||
push @good, $places->[$i + $_ -1] for (1..$step);
|
||||
} continue { $i += $step; }
|
||||
|
||||
print STDERR "# left with " . ((scalar @good) / $step) . " position(s)\n" if $self->{graph}->{debug};
|
||||
@good;
|
||||
}
|
||||
|
||||
sub _allow
|
||||
{
|
||||
# return a list of places, depending on the start/end attribute:
|
||||
# "south" - any place south
|
||||
# "south,0" - first place south
|
||||
# "south,-1" - last place south
|
||||
# XXX TODO:
|
||||
# "south,0..2" - first three places south
|
||||
# "south,0,1,-1" - first, second and last place south
|
||||
|
||||
my ($self, $dir, @pos) = @_;
|
||||
|
||||
# for relative direction, get the absolute flow from the node
|
||||
if ($dir =~ /^(front|forward|back|left|right)\z/)
|
||||
{
|
||||
# get the flow at the node
|
||||
$dir = $self->flow();
|
||||
}
|
||||
|
||||
my $place = {
|
||||
'south' => [ 0,0, 0,1, 'cx', 1,0 ],
|
||||
'north' => [ 0,-1, 0,0, 'cx', 1,0 ],
|
||||
'east' => [ 0,0, 1,0, 'cy', 0,1 ],
|
||||
'west' => [ -1,0, 0,0, 'cy', 0,1 ] ,
|
||||
180 => [ 0,0, 0,1, 'cx', 1,0 ],
|
||||
0 => [ 0,-1, 0,0, 'cx', 1,0 ],
|
||||
90 => [ 0,0, 1,0, 'cy', 0,1 ],
|
||||
270 => [ -1,0, 0,0, 'cy', 0,1 ] ,
|
||||
};
|
||||
|
||||
my $p = $place->{$dir};
|
||||
|
||||
return [] unless defined $p;
|
||||
|
||||
# start pos
|
||||
my $x = $p->[0] + $self->{x} + $p->[2] * $self->{cx};
|
||||
my $y = $p->[1] + $self->{y} + $p->[3] * $self->{cy};
|
||||
|
||||
my @allowed;
|
||||
push @pos, '' if @pos == 0;
|
||||
|
||||
my $c = $p->[4];
|
||||
if (@pos == 1 && $pos[0] eq '')
|
||||
{
|
||||
# allow all of them
|
||||
for (1 .. $self->{$c})
|
||||
{
|
||||
push @allowed, $x, $y;
|
||||
$x += $p->[5];
|
||||
$y += $p->[6];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
# allow only the given position
|
||||
my $ps = $pos[0];
|
||||
# limit to 0..$self->{cx}-1
|
||||
$ps = $self->{$c} + $ps if $ps < 0;
|
||||
$ps = 0 if $ps < 0;
|
||||
$ps = $self->{$c} - 1 if $ps >= $self->{$c};
|
||||
$x += $p->[5] * $ps;
|
||||
$y += $p->[6] * $ps;
|
||||
push @allowed, $x, $y;
|
||||
}
|
||||
|
||||
\@allowed;
|
||||
}
|
||||
|
||||
package Graph::Easy;
|
||||
use strict;
|
||||
use Graph::Easy::Node::Cell;
|
||||
|
||||
use Graph::Easy::Edge::Cell qw/
|
||||
EDGE_HOR EDGE_VER EDGE_CROSS
|
||||
EDGE_TYPE_MASK
|
||||
EDGE_HOLE
|
||||
/;
|
||||
|
||||
sub _clear_tries
|
||||
{
|
||||
# Take a list of potential positions for a node, and then remove the
|
||||
# ones that are immediately near any other node.
|
||||
# Returns a list of "good" positions. Afterwards $node->{x} is undef.
|
||||
my ($self, $node, $cells, $tries) = @_;
|
||||
|
||||
my $src = 0; my @new;
|
||||
|
||||
print STDERR "# clearing ", scalar @$tries / 2, " tries for $node->{name}\n" if $self->{debug};
|
||||
|
||||
my $node_grandpa = $node->find_grandparent();
|
||||
|
||||
while ($src < scalar @$tries)
|
||||
{
|
||||
# check the current position
|
||||
|
||||
# temporary place node here
|
||||
my $x = $tries->[$src];
|
||||
my $y = $tries->[$src+1];
|
||||
|
||||
# print STDERR "# checking $x,$y\n" if $self->{debug};
|
||||
|
||||
$node->{x} = $x;
|
||||
$node->{y} = $y;
|
||||
|
||||
my @near = $node->_near_places($cells, 1, undef, 1);
|
||||
|
||||
# push also the four corner cells to avoid placing nodes corner-to-corner
|
||||
push @near, $x-1, $y-1, # upperleft corner
|
||||
$x-1, $y+($node->{cy}||1), # lowerleft corner
|
||||
$x+($node->{cx}||1), $y+($node->{cy}||1), # lowerright corner
|
||||
$x+($node->{cx}||1), $y-1; # upperright corner
|
||||
|
||||
# check all near places to be free from nodes (except our children)
|
||||
my $j = 0; my $g = 0;
|
||||
while ($j < @near)
|
||||
{
|
||||
my $xy = $near[$j]. ',' . $near[$j+1];
|
||||
|
||||
# print STDERR "# checking near-place: $xy: " . ref($cells->{$xy}) . "\n" if $self->{debug};
|
||||
|
||||
my $cell = $cells->{$xy};
|
||||
|
||||
# skip, unless we are a children of node, or the cell is our children
|
||||
next unless ref($cell) && $cell->isa('Graph::Easy::Node');
|
||||
|
||||
my $grandpa = $cell->find_grandparent();
|
||||
|
||||
# this cell is our children
|
||||
# this cell is our grandpa
|
||||
# has the same grandpa as node
|
||||
next if $grandpa == $node || $cell == $node_grandpa || $grandpa == $node_grandpa;
|
||||
|
||||
$g++; last;
|
||||
|
||||
} continue { $j += 2; }
|
||||
|
||||
if ($g == 0)
|
||||
{
|
||||
push @new, $tries->[$src], $tries->[$src+1];
|
||||
}
|
||||
$src += 2;
|
||||
}
|
||||
|
||||
$node->{x} = undef;
|
||||
|
||||
@new;
|
||||
}
|
||||
|
||||
my $flow_shift = {
|
||||
270 => [ 0, -1 ],
|
||||
90 => [ 0, 1 ],
|
||||
0 => [ 1, 0 ],
|
||||
180 => [ -1, 0 ],
|
||||
};
|
||||
|
||||
sub _placed_shared
|
||||
{
|
||||
# check whether one of the nodes from the list of shared was already placed
|
||||
my ($self) = shift;
|
||||
|
||||
my $placed;
|
||||
for my $n (@_)
|
||||
{
|
||||
$placed = [$n->{x}, $n->{y}] and last if defined $n->{x};
|
||||
}
|
||||
$placed;
|
||||
}
|
||||
|
||||
use Graph::Easy::Util qw(first_kv);
|
||||
|
||||
sub _find_node_place
|
||||
{
|
||||
# Try to place a node (or node cluster). Return score (usually 0).
|
||||
my ($self, $node, $try, $parent, $edge) = @_;
|
||||
|
||||
$try ||= 0;
|
||||
|
||||
print STDERR "# Finding place for $node->{name}, try #$try\n" if $self->{debug};
|
||||
print STDERR "# Parent node is '$parent->{name}'\n" if $self->{debug} && ref $parent;
|
||||
|
||||
print STDERR "# called from ". join (" ", caller) . "\n" if $self->{debug};
|
||||
|
||||
# If the node has a user-set rank, see if we already placed another node in that
|
||||
# row/column
|
||||
if ($node->{rank} >= 0)
|
||||
{
|
||||
my $r = abs($node->{rank});
|
||||
# print STDERR "# User-set rank for $node->{name} (rank $r)\n";
|
||||
my $c = $self->{_rank_coord};
|
||||
# use Data::Dumper; print STDERR "# rank_pos: \n", Dumper($self->{_rank_pos});
|
||||
if (exists $self->{_rank_pos}->{ $r })
|
||||
{
|
||||
my $co = { x => 0, y => 0 };
|
||||
$co->{$c} = $self->{_rank_pos}->{ $r };
|
||||
while (1 < 3)
|
||||
{
|
||||
# print STDERR "# trying to force placement of '$node->{name}' at $co->{x} $co->{y}\n";
|
||||
return 0 if $node->_do_place($co->{x},$co->{y},$self);
|
||||
$co->{$c} += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $cells = $self->{cells};
|
||||
|
||||
# local $self->{debug} = 1;
|
||||
|
||||
my $min_dist = 2;
|
||||
# minlen = 0 => min_dist = 2,
|
||||
# minlen = 1 => min_dist = 2,
|
||||
# minlen = 2 => min_dist = 3, etc
|
||||
$min_dist = $edge->attribute('minlen') + 1 if ref($edge);
|
||||
|
||||
# if the node has outgoing edges (which might be shared)
|
||||
if (!ref($edge))
|
||||
{
|
||||
(undef,$edge) = first_kv($node->{edges}) if keys %{$node->{edges}} > 0;
|
||||
}
|
||||
|
||||
my $dir = undef; $dir = $edge->flow() if ref($edge);
|
||||
|
||||
my @tries;
|
||||
# if (ref($parent) && defined $parent->{x})
|
||||
if (keys %{$node->{edges}} > 0)
|
||||
{
|
||||
my $src_node = $parent; $src_node = $edge->{from} if ref($edge) && !ref($parent);
|
||||
print STDERR "# from $src_node->{name} to $node->{name}: edge $edge dir $dir\n" if $self->{debug};
|
||||
|
||||
# if there are more than one edge to this node, and they share a start point,
|
||||
# move the node at least 3 cells away to create space for the joints
|
||||
|
||||
my ($s_p, @ss_p);
|
||||
($s_p, @ss_p) = $edge->port('start') if ref($edge);
|
||||
|
||||
my ($from,$to);
|
||||
if (ref($edge))
|
||||
{
|
||||
$from = $edge->{from}; $to = $edge->{to};
|
||||
}
|
||||
|
||||
my @shared_nodes;
|
||||
@shared_nodes = $from->nodes_sharing_start($s_p,@ss_p) if defined $s_p && @ss_p > 0;
|
||||
|
||||
print STDERR "# Edge from '$src_node->{name}' shares an edge start with ", scalar @shared_nodes, " other nodes\n"
|
||||
if $self->{debug};
|
||||
|
||||
if (@shared_nodes > 1)
|
||||
{
|
||||
$min_dist = 3 if $min_dist < 3; # make space
|
||||
$min_dist++ if $edge->label() ne ''; # make more space for the label
|
||||
|
||||
# if we are the first shared node to be placed
|
||||
my $placed = $self->_placed_shared(@shared_nodes);
|
||||
|
||||
if (defined $placed)
|
||||
{
|
||||
# we are not the first, so skip the placement below
|
||||
# instead place on the same column/row as already placed node(s)
|
||||
my ($bx, $by) = @$placed;
|
||||
|
||||
my $flow = $node->flow();
|
||||
|
||||
print STDERR "# One of the shared nodes was already placed at ($bx,$by) with flow $flow\n"
|
||||
if $self->{debug};
|
||||
|
||||
my $ofs = 2; # start with a distance of 2
|
||||
my ($mx, $my) = @{ ($flow_shift->{$flow} || [ 0, 1 ]) };
|
||||
|
||||
while (1)
|
||||
{
|
||||
my $x = $bx + $mx * $ofs; my $y = $by + $my * $ofs;
|
||||
|
||||
print STDERR "# Trying to place $node->{name} at ($x,$y)\n"
|
||||
if $self->{debug};
|
||||
|
||||
next if $self->_clear_tries($node, $cells, [ $x,$y ]) == 0;
|
||||
last if $node->_do_place($x,$y,$self);
|
||||
}
|
||||
continue {
|
||||
$ofs += 2;
|
||||
}
|
||||
return 0; # found place already
|
||||
} # end we-are-the-first-to-be-placed
|
||||
}
|
||||
|
||||
# shared end point?
|
||||
($s_p, @ss_p) = $edge->port('end') if ref($edge);
|
||||
|
||||
@shared_nodes = $to->nodes_sharing_end($s_p,@ss_p) if defined $s_p && @ss_p > 0;
|
||||
|
||||
print STDERR "# Edge from '$src_node->{name}' shares an edge end with ", scalar @shared_nodes, " other nodes\n"
|
||||
if $self->{debug};
|
||||
|
||||
if (@shared_nodes > 1)
|
||||
{
|
||||
$min_dist = 3 if $min_dist < 3;
|
||||
$min_dist++ if $edge->label() ne ''; # make more space for the label
|
||||
|
||||
# if the node to be placed is not in the list to be placed, it is the end-point
|
||||
|
||||
# see if we are the first shared node to be placed
|
||||
my $placed = $self->_placed_shared(@shared_nodes);
|
||||
|
||||
# print STDERR "# "; for (@shared_nodes) { print $_->{name}, " "; } print "\n";
|
||||
|
||||
if ((grep( $_ == $node, @shared_nodes)) && defined $placed)
|
||||
{
|
||||
# we are not the first, so skip the placement below
|
||||
# instead place on the same column/row as already placed node(s)
|
||||
my ($bx, $by) = @$placed;
|
||||
|
||||
my $flow = $node->flow();
|
||||
|
||||
print STDERR "# One of the shared nodes was already placed at ($bx,$by) with flow $flow\n"
|
||||
if $self->{debug};
|
||||
|
||||
my $ofs = 2; # start with a distance of 2
|
||||
my ($mx, $my) = @{ ($flow_shift->{$flow} || [ 0, 1 ]) };
|
||||
|
||||
while (1)
|
||||
{
|
||||
my $x = $bx + $mx * $ofs; my $y = $by + $my * $ofs;
|
||||
|
||||
print STDERR "# Trying to place $node->{name} at ($x,$y)\n"
|
||||
if $self->{debug};
|
||||
|
||||
next if $self->_clear_tries($node, $cells, [ $x,$y ]) == 0;
|
||||
last if $node->_do_place($x,$y,$self);
|
||||
}
|
||||
continue {
|
||||
$ofs += 2;
|
||||
}
|
||||
return 0; # found place already
|
||||
} # end we-are-the-first-to-be-placed
|
||||
}
|
||||
}
|
||||
|
||||
if (ref($parent) && defined $parent->{x})
|
||||
{
|
||||
@tries = $parent->_near_places($cells, $min_dist, undef, 0, $dir);
|
||||
|
||||
print STDERR
|
||||
"# Trying chained placement of $node->{name} with min distance $min_dist from parent $parent->{name}\n"
|
||||
if $self->{debug};
|
||||
|
||||
# weed out positions that are unsuitable
|
||||
@tries = $self->_clear_tries($node, $cells, \@tries);
|
||||
|
||||
splice (@tries,0,$try) if $try > 0; # remove the first N tries
|
||||
print STDERR "# Left with " . scalar @tries . " tries for node $node->{name}\n" if $self->{debug};
|
||||
|
||||
while (@tries > 0)
|
||||
{
|
||||
my $x = shift @tries;
|
||||
my $y = shift @tries;
|
||||
|
||||
print STDERR "# Trying to place $node->{name} at $x,$y\n" if $self->{debug};
|
||||
return 0 if $node->_do_place($x,$y,$self);
|
||||
} # for all trial positions
|
||||
}
|
||||
|
||||
print STDERR "# Trying to place $node->{name} at 0,0\n" if $try == 0 && $self->{debug};
|
||||
# Try to place node at upper left corner (the very first node to be
|
||||
# placed will usually end up there).
|
||||
return 0 if $try == 0 && $node->_do_place(0,0,$self);
|
||||
|
||||
# try to place node near the predecessor(s)
|
||||
my @pre_all = $node->predecessors();
|
||||
|
||||
print STDERR "# Predecessors of $node->{name} " . scalar @pre_all . "\n" if $self->{debug};
|
||||
|
||||
# find all already placed predecessors
|
||||
my @pre;
|
||||
for my $p (@pre_all)
|
||||
{
|
||||
push @pre, $p if defined $p->{x};
|
||||
print STDERR "# Placed predecessors of $node->{name}: $p->{name} at $p->{x},$p->{y}\n" if $self->{debug} && defined $p->{x};
|
||||
}
|
||||
|
||||
# sort predecessors on their rank (to try first the higher ranking ones on placement)
|
||||
@pre = sort { $b->{rank} <=> $a->{rank} } @pre;
|
||||
|
||||
print STDERR "# Number of placed predecessors of $node->{name}: " . scalar @pre . "\n" if $self->{debug};
|
||||
|
||||
if (@pre <= 2 && @pre > 0)
|
||||
{
|
||||
|
||||
if (@pre == 1)
|
||||
{
|
||||
# only one placed predecessor, so place $node near it
|
||||
print STDERR "# placing $node->{name} near predecessor\n" if $self->{debug};
|
||||
@tries = ( $pre[0]->_near_places($cells, $min_dist), $pre[0]->_near_places($cells,$min_dist+2) );
|
||||
}
|
||||
else
|
||||
{
|
||||
# two placed predecessors, so place at crossing point of both of them
|
||||
# compute difference between the two nodes
|
||||
|
||||
my $dx = ($pre[0]->{x} - $pre[1]->{x});
|
||||
my $dy = ($pre[0]->{y} - $pre[1]->{y});
|
||||
|
||||
# are both nodes NOT on a straight line?
|
||||
if ($dx != 0 && $dy != 0)
|
||||
{
|
||||
# ok, so try to place at the crossing point
|
||||
@tries = (
|
||||
$pre[0]->{x}, $pre[1]->{y},
|
||||
$pre[0]->{y}, $pre[1]->{x},
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
# two nodes on a line, try to place node in the middle
|
||||
if ($dx == 0)
|
||||
{
|
||||
@tries = ( $pre[1]->{x}, $pre[1]->{y} + int($dy / 2) );
|
||||
}
|
||||
else
|
||||
{
|
||||
@tries = ( $pre[1]->{x} + int($dx / 2), $pre[1]->{y} );
|
||||
}
|
||||
}
|
||||
# XXX TODO BUG: shouldn't we also try this if we have more than 2
|
||||
# placed predecessors?
|
||||
|
||||
# In addition, we can also try to place the node around the
|
||||
# different nodes:
|
||||
foreach my $n (@pre)
|
||||
{
|
||||
push @tries, $n->_near_places($cells, $min_dist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my @suc_all = $node->successors();
|
||||
|
||||
# find all already placed successors
|
||||
my @suc;
|
||||
for my $s (@suc_all)
|
||||
{
|
||||
push @suc, $s if defined $s->{x};
|
||||
}
|
||||
print STDERR "# Number of placed successors of $node->{name}: " . scalar @suc . "\n" if $self->{debug};
|
||||
foreach my $s (@suc)
|
||||
{
|
||||
# for each successors (especially if there is only one), try to place near
|
||||
push @tries, $s->_near_places($cells, $min_dist);
|
||||
push @tries, $s->_near_places($cells, $min_dist + 2);
|
||||
}
|
||||
|
||||
# weed out positions that are unsuitable
|
||||
@tries = $self->_clear_tries($node, $cells, \@tries);
|
||||
|
||||
print STDERR "# Left with " . scalar @tries . " for node $node->{name}\n" if $self->{debug};
|
||||
|
||||
splice (@tries,0,$try) if $try > 0; # remove the first N tries
|
||||
|
||||
while (@tries > 0)
|
||||
{
|
||||
my $x = shift @tries;
|
||||
my $y = shift @tries;
|
||||
|
||||
print STDERR "# Trying to place $node->{name} at $x,$y\n" if $self->{debug};
|
||||
return 0 if $node->_do_place($x,$y,$self);
|
||||
|
||||
} # for all trial positions
|
||||
|
||||
##############################################################################
|
||||
# all simple possibilities exhausted, try a generic approach
|
||||
|
||||
print STDERR "# No more simple possibilities for node $node->{name}\n" if $self->{debug};
|
||||
|
||||
# XXX TODO:
|
||||
# find out which sides of the node predecessor node(s) still have free
|
||||
# ports/slots. With increasing distances, try to place the node around these.
|
||||
|
||||
# If no predecessors/incoming edges, try to place in column 0, otherwise
|
||||
# considered the node's rank, too
|
||||
|
||||
my $col = 0; $col = $node->{rank} * 2 if @pre > 0;
|
||||
|
||||
$col = $pre[0]->{x} if @pre > 0;
|
||||
|
||||
# find the first free row
|
||||
my $y = 0;
|
||||
$y +=2 while (exists $cells->{"$col,$y"});
|
||||
$y += 1 if exists $cells->{"$col," . ($y-1)}; # leave one cell spacing
|
||||
|
||||
# now try to place node (or node cluster)
|
||||
while (1)
|
||||
{
|
||||
next if $self->_clear_tries($node, $cells, [ $col,$y ]) == 0;
|
||||
last if $node->_do_place($col,$y,$self);
|
||||
}
|
||||
continue {
|
||||
$y += 2;
|
||||
}
|
||||
|
||||
$node->{x} = $col;
|
||||
|
||||
0; # success, score 0
|
||||
}
|
||||
|
||||
sub _trace_path
|
||||
{
|
||||
# find a free way from $src to $dst (both need to be placed beforehand)
|
||||
my ($self, $src, $dst, $edge) = @_;
|
||||
|
||||
print STDERR "# Finding path from '$src->{name}' to '$dst->{name}'\n" if $self->{debug};
|
||||
print STDERR "# src: $src->{x}, $src->{y} dst: $dst->{x}, $dst->{y}\n" if $self->{debug};
|
||||
|
||||
my $coords = $self->_find_path ($src, $dst, $edge);
|
||||
|
||||
# found no path?
|
||||
if (!defined $coords)
|
||||
{
|
||||
print STDERR "# Unable to find path from $src->{name} ($src->{x},$src->{y}) to $dst->{name} ($dst->{x},$dst->{y})\n" if $self->{debug};
|
||||
return undef;
|
||||
}
|
||||
|
||||
# path is empty, happens for sharing edges with only a joint
|
||||
return 1 if scalar @$coords == 0;
|
||||
|
||||
# Create all cells from the returned list and score path (lower score: better)
|
||||
my $i = 0;
|
||||
my $score = 0;
|
||||
while ($i < scalar @$coords)
|
||||
{
|
||||
my $type = $coords->[$i+2];
|
||||
$self->_create_cell($edge,$coords->[$i],$coords->[$i+1],$type);
|
||||
$score ++; # each element: one point
|
||||
$type &= EDGE_TYPE_MASK; # mask flags
|
||||
# edge bend or cross: one point extra
|
||||
$score ++ if $type != EDGE_HOR && $type != EDGE_VER;
|
||||
$score += 3 if $type == EDGE_CROSS; # crossings are doubleplusungood
|
||||
$i += 3;
|
||||
}
|
||||
|
||||
$score;
|
||||
}
|
||||
|
||||
sub _create_cell
|
||||
{
|
||||
my ($self,$edge,$x,$y,$type) = @_;
|
||||
|
||||
my $cells = $self->{cells}; my $xy = "$x,$y";
|
||||
|
||||
if (ref($cells->{$xy}) && $cells->{$xy}->isa('Graph::Easy::Edge'))
|
||||
{
|
||||
$cells->{$xy}->_make_cross($edge,$type & EDGE_FLAG_MASK);
|
||||
# insert a EDGE_HOLE into the cells of the edge (but not into the list of
|
||||
# to-be-rendered cells). This cell will be removed by the optimizer later on.
|
||||
Graph::Easy::Edge::Cell->new( type => EDGE_HOLE, edge => $edge, x => $x, y => $y );
|
||||
return;
|
||||
}
|
||||
|
||||
my $path = Graph::Easy::Edge::Cell->new( type => $type, edge => $edge, x => $x, y => $y );
|
||||
$cells->{$xy} = $path; # store in cells
|
||||
}
|
||||
|
||||
sub _path_is_clear
|
||||
{
|
||||
# For all points (x,y pairs) in the path, check that the cell is still free
|
||||
# $path points to a list of [ x,y,type, x,y,type, ...]
|
||||
my ($self,$path) = @_;
|
||||
|
||||
my $cells = $self->{cells};
|
||||
my $i = 0;
|
||||
while ($i < scalar @$path)
|
||||
{
|
||||
my $x = $path->[$i];
|
||||
my $y = $path->[$i+1];
|
||||
# my $t = $path->[$i+2];
|
||||
$i += 3;
|
||||
|
||||
return 0 if exists $cells->{"$x,$y"}; # obstacle hit
|
||||
}
|
||||
1; # path is clear
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Layout::Path - Path management for Manhattan-style grids
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
my $bonn = Graph::Easy::Node->new(
|
||||
name => 'Bonn',
|
||||
);
|
||||
my $berlin = Graph::Easy::Node->new(
|
||||
name => 'Berlin',
|
||||
);
|
||||
|
||||
$graph->add_edge ($bonn, $berlin);
|
||||
|
||||
$graph->layout();
|
||||
|
||||
print $graph->as_ascii( );
|
||||
|
||||
# prints:
|
||||
|
||||
# +------+ +--------+
|
||||
# | Bonn | --> | Berlin |
|
||||
# +------+ +--------+
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<Graph::Easy::Layout::Scout> contains just the actual path-managing code for
|
||||
L<Graph::Easy|Graph::Easy>, e.g. to create/destroy/maintain paths, node
|
||||
placement etc.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
Exports nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 METHODS into Graph::Easy
|
||||
|
||||
This module injects the following methods into C<Graph::Easy>:
|
||||
|
||||
=head2 _path_is_clear()
|
||||
|
||||
$graph->_path_is_clear($path);
|
||||
|
||||
For all points (x,y pairs) in the path, check that the cell is still free.
|
||||
C<$path> points to a list x,y,type pairs as in C<< [ [x,y,type], [x,y,type], ...] >>.
|
||||
|
||||
=head2 _create_cell()
|
||||
|
||||
my $cell = $graph->($edge,$x,$y,$type);
|
||||
|
||||
Create a cell at C<$x,$y> coordinates with type C<$type> for the specified
|
||||
edge.
|
||||
|
||||
=head2 _path_is_clear()
|
||||
|
||||
$graph->_path_is_clear();
|
||||
|
||||
For all points (x,y pairs) in the path, check that the cell is still free.
|
||||
C<$path> points to a list of C<[ x,y,type, x,y,type, ...]>.
|
||||
|
||||
Returns true when the path is clear, false otherwise.
|
||||
|
||||
=head2 _trace_path()
|
||||
|
||||
my $path = my $graph->_trace_path($src,$dst,$edge);
|
||||
|
||||
Find a free way from source node/group to destination node/group for the
|
||||
specified edge. Both source and destination need to be placed beforehand.
|
||||
|
||||
=head1 METHODS in Graph::Easy::Node
|
||||
|
||||
This module injects the following methods into C<Graph::Easy::Node>:
|
||||
|
||||
=head2 _near_places()
|
||||
|
||||
my $node->_near_places();
|
||||
|
||||
Take a node and return a list of possible placements around it and
|
||||
prune out already occupied cells. $d is the distance from the node
|
||||
border and defaults to two (for placements). Set it to one for
|
||||
adjacent cells.
|
||||
|
||||
=head2 _shuffle_dir()
|
||||
|
||||
my $dirs = $node->_shuffle_dir( [ 0,1,2,3 ], $dir);
|
||||
|
||||
Take a ref to an array with four entries and shuffle them around according to
|
||||
C<$dir>.
|
||||
|
||||
=head2 _shift()
|
||||
|
||||
my $dir = $node->_shift($degrees);
|
||||
|
||||
Return a the C<flow()> direction shifted by X degrees to C<$dir>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2007 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for information.
|
||||
|
||||
=cut
|
||||
649
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Layout/Repair.pm
Normal file
649
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Layout/Repair.pm
Normal file
@@ -0,0 +1,649 @@
|
||||
#############################################################################
|
||||
# Layout directed graphs on a flat plane. Part of Graph::Easy.
|
||||
#
|
||||
# Code to repair spliced layouts (after group cells have been inserted).
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Layout::Repair;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
# for layouts with groups:
|
||||
|
||||
package Graph::Easy;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
sub _edges_into_groups
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Put all edges between two nodes with the same group in the group as well
|
||||
for my $edge (ord_values $self->{edges})
|
||||
{
|
||||
my $gf = $edge->{from}->group();
|
||||
my $gt = $edge->{to}->group();
|
||||
|
||||
$gf->_add_edge($edge) if defined $gf && defined $gt && $gf == $gt;
|
||||
}
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _repair_nodes
|
||||
{
|
||||
# Splicing the rows/columns to add filler cells will have torn holes into
|
||||
# multi-edges nodes, so we insert additional filler cells.
|
||||
my ($self) = @_;
|
||||
my $cells = $self->{cells};
|
||||
|
||||
# Make multi-celled nodes occupy the proper double space due to splicing
|
||||
# in group cell has doubled the layout in each direction:
|
||||
for my $n ($self->nodes())
|
||||
{
|
||||
# 1 => 1, 2 => 3, 3 => 5, 4 => 7 etc
|
||||
$n->{cx} = $n->{cx} * 2 - 1;
|
||||
$n->{cy} = $n->{cy} * 2 - 1;
|
||||
}
|
||||
|
||||
# We might get away with not inserting filler cells if we just mark the
|
||||
# cells as used (e.g. use only one global filler cell) since filler cells
|
||||
# aren't actually rendered, anyway.
|
||||
|
||||
for my $cell (ord_values $cells)
|
||||
{
|
||||
next unless $cell->isa('Graph::Easy::Node::Cell');
|
||||
|
||||
# we have "[ empty ] [ filler ]" (unless cell is on the same column as node)
|
||||
if ($cell->{x} > $cell->{node}->{x})
|
||||
{
|
||||
my $x = $cell->{x} - 1; my $y = $cell->{y};
|
||||
|
||||
# print STDERR "# inserting filler at $x,$y for $cell->{node}->{name}\n";
|
||||
$cells->{"$x,$y"} =
|
||||
Graph::Easy::Node::Cell->new(node => $cell->{node}, x => $x, y => $y );
|
||||
}
|
||||
|
||||
# we have " [ empty ] "
|
||||
# " [ filler ] " (unless cell is on the same row as node)
|
||||
if ($cell->{y} > $cell->{node}->{y})
|
||||
{
|
||||
my $x = $cell->{x}; my $y = $cell->{y} - 1;
|
||||
|
||||
# print STDERR "# inserting filler at $x,$y for $cell->{node}->{name}\n";
|
||||
$cells->{"$x,$y"} =
|
||||
Graph::Easy::Node::Cell->new(node => $cell->{node}, x => $x, y => $y );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub _repair_cell
|
||||
{
|
||||
my ($self, $type, $edge, $x, $y, $after, $before) = @_;
|
||||
|
||||
# already repaired?
|
||||
return if exists $self->{cells}->{"$x,$y"};
|
||||
|
||||
# print STDERR "# Insert edge cell at $x,$y (type $type) for edge $edge->{from}->{name} --> $edge->{to}->{name}\n";
|
||||
|
||||
$self->{cells}->{"$x,$y"} =
|
||||
Graph::Easy::Edge::Cell->new(
|
||||
type => $type,
|
||||
edge => $edge, x => $x, y => $y, before => $before, after => $after );
|
||||
|
||||
}
|
||||
|
||||
sub _splice_edges
|
||||
{
|
||||
# Splicing the rows/columns to add filler cells might have torn holes into
|
||||
# edges, so we splice these together again.
|
||||
my ($self) = @_;
|
||||
|
||||
my $cells = $self->{cells};
|
||||
|
||||
print STDERR "# Reparing spliced layout\n" if $self->{debug};
|
||||
|
||||
# Edge end/start points inside groups are not handled here, but in
|
||||
# _repair_group_edge()
|
||||
|
||||
# go over the old layout, because the new cells were inserted into odd
|
||||
# rows/columns and we do not care for these:
|
||||
for my $cell (sort { $a->{x} <=> $b->{x} || $a->{y} <=> $b->{y} } values %$cells)
|
||||
{
|
||||
next unless $cell->isa('Graph::Easy::Edge::Cell');
|
||||
|
||||
my $edge = $cell->{edge};
|
||||
|
||||
#########################################################################
|
||||
# check for "[ JOINT ] [ empty ] [ edge ]"
|
||||
|
||||
my $x = $cell->{x} + 2; my $y = $cell->{y};
|
||||
|
||||
my $type = $cell->{type} & EDGE_TYPE_MASK;
|
||||
|
||||
# left is a joint and right exists
|
||||
if ( ($type == EDGE_S_E_W || $type == EDGE_N_E_W || $type == EDGE_E_N_S)
|
||||
&& exists $cells->{"$x,$y"})
|
||||
{
|
||||
my $right = $cells->{"$x,$y"};
|
||||
|
||||
# print STDERR "# at $x,$y\n";
|
||||
|
||||
# |-> [ empty ] [ node ]
|
||||
if ($right->isa('Graph::Easy::Edge::Cell'))
|
||||
{
|
||||
# when the left one is a joint, the right one must be an edge
|
||||
$self->error("Found non-edge piece ($right->{type} $right) right to a joint ($type)")
|
||||
unless $right->isa('Graph::Easy::Edge::Cell');
|
||||
|
||||
# print STDERR "splicing in HOR piece to the right of joint at $x, $y ($edge $right $right->{edge})\n";
|
||||
|
||||
# insert the new piece before the first part of the edge after the joint
|
||||
$self->_repair_cell(EDGE_HOR(), $right->{edge},$cell->{x}+1,$y,0)
|
||||
if $edge != $right->{edge};
|
||||
}
|
||||
}
|
||||
|
||||
#########################################################################
|
||||
# check for "[ edge ] [ empty ] [ joint ]"
|
||||
|
||||
$x = $cell->{x} - 2; $y = $cell->{y};
|
||||
|
||||
# right is a joint and left exists
|
||||
if ( ($type == EDGE_S_E_W || $type == EDGE_N_E_W || $type == EDGE_W_N_S)
|
||||
&& exists $cells->{"$x,$y"})
|
||||
{
|
||||
my $left = $cells->{"$x,$y"};
|
||||
|
||||
# [ node ] [ empty ] [ <-| ]
|
||||
if (!$left->isa('Graph::Easy::Node'))
|
||||
{
|
||||
# when the left one is a joint, the right one must be an edge
|
||||
$self->error('Found non-edge piece right to a joint')
|
||||
unless $left->isa('Graph::Easy::Edge::Cell');
|
||||
|
||||
# insert the new piece before the joint
|
||||
$self->_repair_cell(EDGE_HOR(), $edge, $cell->{x}+1,$y,0) # $left,$cell)
|
||||
if $edge != $left->{edge};
|
||||
}
|
||||
}
|
||||
|
||||
#########################################################################
|
||||
# check for " [ joint ]
|
||||
# [ empty ]
|
||||
# [ edge ]"
|
||||
|
||||
$x = $cell->{x}; $y = $cell->{y} + 2;
|
||||
|
||||
# top is a joint and down exists
|
||||
if ( ($type == EDGE_S_E_W || $type == EDGE_E_N_S || $type == EDGE_W_N_S)
|
||||
&& exists $cells->{"$x,$y"})
|
||||
{
|
||||
my $bottom = $cells->{"$x,$y"};
|
||||
|
||||
# when top is a joint, the bottom one must be an edge
|
||||
$self->error('Found non-edge piece below a joint')
|
||||
unless $bottom->isa('Graph::Easy::Edge::Cell');
|
||||
|
||||
# print STDERR "splicing in VER piece below joint at $x, $y\n";
|
||||
|
||||
# XXX TODO
|
||||
# insert the new piece after the joint
|
||||
$self->_repair_cell(EDGE_VER(), $bottom->{edge},$x,$cell->{y}+1,0)
|
||||
if $edge != $bottom->{edge};
|
||||
}
|
||||
|
||||
#########################################################################
|
||||
# check for "[ --- ] [ empty ] [ ---> ]"
|
||||
|
||||
$x = $cell->{x} + 2; $y = $cell->{y};
|
||||
|
||||
if (exists $cells->{"$x,$y"})
|
||||
{
|
||||
my $right = $cells->{"$x,$y"};
|
||||
|
||||
$self->_repair_cell(EDGE_HOR(), $edge, $cell->{x}+1,$y,$cell,$right)
|
||||
if $right->isa('Graph::Easy::Edge::Cell') &&
|
||||
defined $right->{edge} && defined $right->{type} &&
|
||||
# check that both cells belong to the same edge
|
||||
( $edge == $right->{edge} ||
|
||||
# or the right part is a cross
|
||||
$right->{type} == EDGE_CROSS ||
|
||||
# or the left part is a cross
|
||||
$cell->{type} == EDGE_CROSS );
|
||||
}
|
||||
|
||||
#########################################################################
|
||||
# check for [ | ]
|
||||
# [ empty ]
|
||||
# [ | ]
|
||||
$x = $cell->{x}; $y = $cell->{y}+2;
|
||||
|
||||
if (exists $cells->{"$x,$y"})
|
||||
{
|
||||
my $below = $cells->{"$x,$y"};
|
||||
|
||||
$self->_repair_cell(EDGE_VER(),$edge,$x,$cell->{y}+1,$cell,$below)
|
||||
if $below->isa('Graph::Easy::Edge::Cell') &&
|
||||
# check that both cells belong to the same edge
|
||||
( $edge == $below->{edge} ||
|
||||
# or the lower part is a cross
|
||||
$below->{type} == EDGE_CROSS ||
|
||||
# or the upper part is a cross
|
||||
$cell->{type} == EDGE_CROSS );
|
||||
}
|
||||
|
||||
} # end for all cells
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _new_edge_cell
|
||||
{
|
||||
# create a new edge cell to be spliced into the layout for repairs
|
||||
my ($self, $cells, $group, $edge, $x, $y, $after, $type) = @_;
|
||||
|
||||
$type += EDGE_SHORT_CELL() if defined $group;
|
||||
|
||||
my $e_cell = Graph::Easy::Edge::Cell->new(
|
||||
type => $type, edge => $edge, x => $x, y => $y, after => $after);
|
||||
$group->_del_cell($e_cell) if defined $group;
|
||||
$cells->{"$x,$y"} = $e_cell;
|
||||
}
|
||||
|
||||
sub _check_edge_cell
|
||||
{
|
||||
# check a start/end edge cell and if nec. repair it
|
||||
my ($self, $cell, $x, $y, $flag, $type, $match, $check, $where) = @_;
|
||||
|
||||
my $edge = $cell->{edge};
|
||||
if (grep { exists $_->{cell_class} && $_->{cell_class} =~ $match } ord_values ($check))
|
||||
{
|
||||
$cell->{type} &= ~ $flag; # delete the flag
|
||||
|
||||
$self->_new_edge_cell(
|
||||
$self->{cells}, $edge->{group}, $edge, $x, $y, $where, $type + $flag);
|
||||
}
|
||||
}
|
||||
|
||||
sub _repair_group_edge
|
||||
{
|
||||
# repair an edges inside a group
|
||||
my ($self, $cell, $rows, $cols, $group) = @_;
|
||||
|
||||
my $cells = $self->{cells};
|
||||
my ($x,$y,$doit);
|
||||
|
||||
my $type = $cell->{type};
|
||||
|
||||
#########################################################################
|
||||
# check for " [ empty ] [ |---> ]"
|
||||
$x = $cell->{x} - 1; $y = $cell->{y};
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_START_W, EDGE_HOR, qr/g[rl]/, $cols->{$x}, 0)
|
||||
if (($type & EDGE_START_MASK) == EDGE_START_W);
|
||||
|
||||
#########################################################################
|
||||
# check for " [ <--- ] [ empty ]"
|
||||
$x = $cell->{x} + 1;
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_START_E, EDGE_HOR, qr/g[rl]/, $cols->{$x}, 0)
|
||||
if (($type & EDGE_START_MASK) == EDGE_START_E);
|
||||
|
||||
#########################################################################
|
||||
# check for " [ --> ] [ empty ]"
|
||||
$x = $cell->{x} + 1;
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_END_E, EDGE_HOR, qr/g[rl]/, $cols->{$x}, -1)
|
||||
if (($type & EDGE_END_MASK) == EDGE_END_E);
|
||||
|
||||
# $self->_check_edge_cell($cell, $x, $y, EDGE_END_E, EDGE_E_N_S, qr/g[rl]/, $cols->{$x}, -1)
|
||||
# if (($type & EDGE_END_MASK) == EDGE_END_E);
|
||||
|
||||
#########################################################################
|
||||
# check for " [ empty ] [ <-- ]"
|
||||
$x = $cell->{x} - 1;
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_END_W, EDGE_HOR, qr/g[rl]/, $cols->{$x}, -1)
|
||||
if (($type & EDGE_END_MASK) == EDGE_END_W);
|
||||
|
||||
#########################################################################
|
||||
#########################################################################
|
||||
# vertical cases
|
||||
|
||||
#########################################################################
|
||||
# check for [empty]
|
||||
# [ | ]
|
||||
$x = $cell->{x}; $y = $cell->{y} - 1;
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_START_N, EDGE_VER, qr/g[tb]/, $rows->{$y}, 0)
|
||||
if (($type & EDGE_START_MASK) == EDGE_START_N);
|
||||
|
||||
#########################################################################
|
||||
# check for [ |]
|
||||
# [ empty ]
|
||||
$y = $cell->{y} + 1;
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_START_S, EDGE_VER, qr/g[tb]/, $rows->{$y}, 0)
|
||||
if (($type & EDGE_START_MASK) == EDGE_START_S);
|
||||
|
||||
#########################################################################
|
||||
# check for [ v ]
|
||||
# [empty]
|
||||
$y = $cell->{y} + 1;
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_END_S, EDGE_VER, qr/g[tb]/, $rows->{$y}, -1)
|
||||
if (($type & EDGE_END_MASK) == EDGE_END_S);
|
||||
|
||||
#########################################################################
|
||||
# check for [ empty ]
|
||||
# [ ^ ]
|
||||
$y = $cell->{y} - 1;
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_END_N, EDGE_VER, qr/g[tb]/, $rows->{$y}, -1)
|
||||
if (($type & EDGE_END_MASK) == EDGE_END_N);
|
||||
}
|
||||
|
||||
sub _repair_edge
|
||||
{
|
||||
# repair an edge outside a group
|
||||
my ($self, $cell, $rows, $cols) = @_;
|
||||
|
||||
my $cells = $self->{cells};
|
||||
|
||||
#########################################################################
|
||||
# check for [ |\n|\nv ]
|
||||
# [empty] ... [non-empty]
|
||||
# [node]
|
||||
|
||||
my $x = $cell->{x}; my $y = $cell->{y} + 1;
|
||||
|
||||
my $below = $cells->{"$x,$y"}; # must be empty
|
||||
|
||||
if (!ref($below) && (($cell->{type} & EDGE_END_MASK) == EDGE_END_S))
|
||||
{
|
||||
if (grep { exists $_->{cell_class} && $_->{cell_class} =~ /g[tb]/ } ord_values $rows->{$y})
|
||||
{
|
||||
# delete the start flag
|
||||
$cell->{type} &= ~ EDGE_END_S;
|
||||
|
||||
$self->_new_edge_cell($cells, undef, $cell->{edge}, $x, $y, -1,
|
||||
EDGE_VER() + EDGE_END_S() );
|
||||
}
|
||||
}
|
||||
# XXX TODO: do the other ends (END_N, END_W, END_E), too
|
||||
|
||||
}
|
||||
|
||||
sub _repair_edges
|
||||
{
|
||||
# fix edge end/start cells to be closer to the node cell they point at
|
||||
my ($self, $rows, $cols) = @_;
|
||||
|
||||
my $cells = $self->{cells};
|
||||
|
||||
# go over all existing cells
|
||||
for my $cell (sort { $a->{x} <=> $b->{x} || $a->{y} <=> $b->{y} } values %$cells)
|
||||
{
|
||||
next unless $cell->isa('Graph::Easy::Edge::Cell');
|
||||
|
||||
# skip odd positions
|
||||
next unless ($cell->{x} & 1) == 0 && ($cell->{y} & 1) == 0;
|
||||
|
||||
my $group = $cell->group();
|
||||
|
||||
$self->_repair_edge($cell,$rows,$cols) unless $group;
|
||||
$self->_repair_group_edge($cell,$rows,$cols,$group) if $group;
|
||||
|
||||
} # end for all cells
|
||||
}
|
||||
|
||||
sub _fill_group_cells
|
||||
{
|
||||
# after doing a layout(), we need to add the group to each cell based on
|
||||
# what group the nearest node is in.
|
||||
my ($self, $cells_layout) = @_;
|
||||
|
||||
print STDERR "\n# Padding with fill cells, have ",
|
||||
scalar $self->groups(), " groups.\n" if $self->{debug};
|
||||
|
||||
# take a shortcut if we do not have groups
|
||||
return $self if $self->groups == 0;
|
||||
|
||||
$self->{padding_cells} = 1; # set to true
|
||||
|
||||
# We need to insert "filler" cells around each node/edge/cell:
|
||||
|
||||
# To "insert" the filler cells, we simple multiply each X and Y by 2, this
|
||||
# is O(N) where N is the number of actually existing cells. Otherwise we
|
||||
# would have to create the full table-layout, and then insert rows/columns.
|
||||
my $cells = {};
|
||||
for my $key (sort keys %$cells_layout)
|
||||
{
|
||||
my ($x,$y) = split /,/, $key;
|
||||
my $cell = $cells_layout->{$key};
|
||||
|
||||
$x *= 2;
|
||||
$y *= 2;
|
||||
$cell->{x} = $x;
|
||||
$cell->{y} = $y;
|
||||
|
||||
$cells->{"$x,$y"} = $cell;
|
||||
}
|
||||
|
||||
$self->{cells} = $cells; # override with new cell layout
|
||||
|
||||
$self->_splice_edges(); # repair edges
|
||||
$self->_repair_nodes(); # repair multi-celled nodes
|
||||
|
||||
my $c = 'Graph::Easy::Group::Cell';
|
||||
for my $cell (ord_values $self->{cells})
|
||||
{
|
||||
# DO NOT MODIFY $cell IN THE LOOP BODY!
|
||||
|
||||
my ($x,$y) = ($cell->{x},$cell->{y});
|
||||
|
||||
# find the primary node for node cells, for group check
|
||||
my $group = $cell->group();
|
||||
|
||||
# not part of group, so no group-cells nec.
|
||||
next unless $group;
|
||||
|
||||
# now insert up to 8 filler cells around this cell
|
||||
my $ofs = [ -1, 0,
|
||||
0, -1,
|
||||
+1, 0,
|
||||
+1, 0,
|
||||
0, +1,
|
||||
0, +1,
|
||||
-1, 0,
|
||||
-1, 0, ];
|
||||
while (@$ofs > 0)
|
||||
{
|
||||
$x += shift @$ofs;
|
||||
$y += shift @$ofs;
|
||||
|
||||
$cells->{"$x,$y"} = $c->new ( graph => $self, group => $group, x => $x, y => $y )
|
||||
unless exists $cells->{"$x,$y"};
|
||||
}
|
||||
}
|
||||
|
||||
# Nodes positioned two cols/rows apart (f.i. y == 0 and y == 2) will be
|
||||
# three cells apart (y == 0 and y == 4) after the splicing, the step above
|
||||
# will not be able to close that hole - it will create fillers at y == 1 and
|
||||
# y == 3. So we close these holes now with an extra step.
|
||||
for my $cell (ord_values ( $self->{cells} ))
|
||||
{
|
||||
# only for filler cells
|
||||
next unless $cell->isa('Graph::Easy::Group::Cell');
|
||||
|
||||
my ($sx,$sy) = ($cell->{x},$cell->{y});
|
||||
my $group = $cell->{group};
|
||||
|
||||
my $x = $sx; my $y2 = $sy + 2; my $y = $sy + 1;
|
||||
# look for:
|
||||
# [ group ]
|
||||
# [ empty ]
|
||||
# [ group ]
|
||||
if (exists $cells->{"$x,$y2"} && !exists $cells->{"$x,$y"})
|
||||
{
|
||||
my $down = $cells->{"$x,$y2"};
|
||||
if ($down->isa('Graph::Easy::Group::Cell') && $down->{group} == $group)
|
||||
{
|
||||
$cells->{"$x,$y"} = $c->new ( graph => $self, group => $group, x => $x, y => $y );
|
||||
}
|
||||
}
|
||||
$x = $sx+1; my $x2 = $sx + 2; $y = $sy;
|
||||
# look for:
|
||||
# [ group ] [ empty ] [ group ]
|
||||
if (exists $cells->{"$x2,$y"} && !exists $cells->{"$x,$y"})
|
||||
{
|
||||
my $right = $cells->{"$x2,$y"};
|
||||
if ($right->isa('Graph::Easy::Group::Cell') && $right->{group} == $group)
|
||||
{
|
||||
$cells->{"$x,$y"} = $c->new ( graph => $self, group => $group, x => $x, y => $y );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# XXX TODO
|
||||
# we should "grow" the group area to close holes
|
||||
|
||||
for my $group (ord_values ( $self->{groups} ))
|
||||
{
|
||||
$group->_set_cell_types($cells);
|
||||
}
|
||||
|
||||
# create a mapping for each row/column so that we can repair edge starts/ends
|
||||
my $rows = {};
|
||||
my $cols = {};
|
||||
for my $cell (ord_values ($cells))
|
||||
{
|
||||
$rows->{$cell->{y}}->{$cell->{x}} = $cell;
|
||||
$cols->{$cell->{x}}->{$cell->{y}} = $cell;
|
||||
}
|
||||
$self->_repair_edges($rows,$cols); # insert short edge cells on group
|
||||
# border rows/columns
|
||||
|
||||
# for all groups, set the cell carrying the label (top-left-most cell)
|
||||
for my $group (ord_values ( $self->{groups} ))
|
||||
{
|
||||
$group->_find_label_cell();
|
||||
}
|
||||
|
||||
# DEBUG:
|
||||
# for my $cell (ord_values $cells)
|
||||
# {
|
||||
# $cell->_correct_size();
|
||||
# }
|
||||
#
|
||||
# my $y = 0;
|
||||
# for my $cell (sort { $a->{y} <=> $b->{y} || $a->{x} <=> $b->{x} } values %$cells)
|
||||
# {
|
||||
# print STDERR "\n" if $y != $cell->{y};
|
||||
# print STDERR "$cell->{x},$cell->{y}, $cell->{w},$cell->{h}, ", $cell->{group}->{name} || 'none', "\t";
|
||||
# $y = $cell->{y};
|
||||
# }
|
||||
# print STDERR "\n";
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Layout::Repair - Repair spliced layout with group cells
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
my $bonn = Graph::Easy::Node->new(
|
||||
name => 'Bonn',
|
||||
);
|
||||
my $berlin = Graph::Easy::Node->new(
|
||||
name => 'Berlin',
|
||||
);
|
||||
|
||||
$graph->add_edge ($bonn, $berlin);
|
||||
|
||||
$graph->layout();
|
||||
|
||||
print $graph->as_ascii( );
|
||||
|
||||
# prints:
|
||||
|
||||
# +------+ +--------+
|
||||
# | Bonn | --> | Berlin |
|
||||
# +------+ +--------+
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<Graph::Easy::Layout::Repair> contains code that can splice in
|
||||
group cells into a layout, as well as repair the layout after that step.
|
||||
|
||||
It is part of L<Graph::Easy|Graph::Easy> and used automatically.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
C<Graph::Easy::Layout> injects the following methods into the C<Graph::Easy>
|
||||
namespace:
|
||||
|
||||
=head2 _edges_into_groups()
|
||||
|
||||
Put the edges into the appropriate group and class.
|
||||
|
||||
=head2 _assign_ranks()
|
||||
|
||||
$graph->_assign_ranks();
|
||||
|
||||
=head2 _repair_nodes()
|
||||
|
||||
Splicing the rows/columns to add filler cells will have torn holes into
|
||||
multi-edges nodes, so we insert additional filler cells to repair this.
|
||||
|
||||
=head2 _splice_edges()
|
||||
|
||||
Splicing the rows/columns to add filler cells might have torn holes into
|
||||
multi-celled edges, so we splice these together again.
|
||||
|
||||
=head2 _repair_edges()
|
||||
|
||||
Splicing the rows/columns to add filler cells might have put "holes"
|
||||
between an edge start/end and the node cell it points to. This
|
||||
routine fixes this problem by extending the edge by one cell if
|
||||
necessary.
|
||||
|
||||
=head2 _fill_group_cells()
|
||||
|
||||
After doing a C<layout()>, we need to add the group to each cell based on
|
||||
what group the nearest node is in.
|
||||
|
||||
This routine will also find the label cell for each group, and repair
|
||||
edge/node damage done by the splicing.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
Exports nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2007 by Tels L<http://bloodgate.com>
|
||||
|
||||
See the LICENSE file for information.
|
||||
|
||||
=cut
|
||||
1717
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Layout/Scout.pm
Normal file
1717
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Layout/Scout.pm
Normal file
File diff suppressed because it is too large
Load Diff
2865
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Node.pm
Normal file
2865
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Node.pm
Normal file
File diff suppressed because it is too large
Load Diff
116
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Node/Anon.pm
Normal file
116
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Node/Anon.pm
Normal file
@@ -0,0 +1,116 @@
|
||||
#############################################################################
|
||||
# (c) by Tels 2004. Part of Graph::Easy. An anonymous (invisible) node.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Node::Anon;
|
||||
|
||||
use Graph::Easy::Node;
|
||||
|
||||
@ISA = qw/Graph::Easy::Node/;
|
||||
$VERSION = '0.76';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub _init
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->SUPER::_init(@_);
|
||||
|
||||
$self->{name} = '#' . $self->{id};
|
||||
$self->{class} = 'node.anon';
|
||||
|
||||
$self->{att}->{label} = ' ';
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _correct_size
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{w} = 3;
|
||||
$self->{h} = 3;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub attributes_as_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->SUPER::attributes_as_txt( {
|
||||
node => {
|
||||
label => undef,
|
||||
shape => undef,
|
||||
class => undef,
|
||||
} } );
|
||||
}
|
||||
|
||||
sub as_pure_txt
|
||||
{
|
||||
'[ ]';
|
||||
}
|
||||
|
||||
sub _as_part_txt
|
||||
{
|
||||
'[ ]';
|
||||
}
|
||||
|
||||
sub as_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
'[ ]' . $self->attributes_as_txt();
|
||||
}
|
||||
|
||||
sub text_styles_as_css
|
||||
{
|
||||
'';
|
||||
}
|
||||
|
||||
sub is_anon
|
||||
{
|
||||
# is an anon node
|
||||
1;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Node::Anon - An anonymous, invisible node in Graph::Easy
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy::Node::Anon;
|
||||
|
||||
my $anon = Graph::Easy::Node::Anon->new();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Node::Anon> represents an anonymous, invisible node.
|
||||
These can be used to let edges start and end "nowhere".
|
||||
|
||||
The syntax in the Graph::Easy textual description language looks like this:
|
||||
|
||||
[ ] -> [ Bonn ] -> [ ]
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy::Node>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2006 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
140
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Node/Cell.pm
Normal file
140
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Node/Cell.pm
Normal file
@@ -0,0 +1,140 @@
|
||||
#############################################################################
|
||||
# (c) by Tels 2004 - 2005. An empty filler cell. Part of Graph::Easy.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Node::Cell;
|
||||
|
||||
use Graph::Easy::Node;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw(@ISA $VERSION);
|
||||
|
||||
@ISA = qw/Graph::Easy::Node/;
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _init
|
||||
{
|
||||
# generic init, override in subclasses
|
||||
my ($self,$args) = @_;
|
||||
|
||||
$self->{class} = '';
|
||||
$self->{name} = '';
|
||||
|
||||
$self->{'x'} = 0;
|
||||
$self->{'y'} = 0;
|
||||
|
||||
# default: belongs to no node
|
||||
$self->{node} = undef;
|
||||
|
||||
foreach my $k (sort keys %$args)
|
||||
{
|
||||
if ($k !~ /^(node|graph|x|y)\z/)
|
||||
{
|
||||
require Carp;
|
||||
Carp::confess ("Invalid argument '$k' passed to Graph::Easy::Node::Cell->new()");
|
||||
}
|
||||
$self->{$k} = $args->{$k};
|
||||
}
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _correct_size
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{w} = 0;
|
||||
$self->{h} = 0;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub node
|
||||
{
|
||||
# return the node this cell belongs to
|
||||
my $self = shift;
|
||||
|
||||
$self->{node};
|
||||
}
|
||||
|
||||
sub as_ascii
|
||||
{
|
||||
'';
|
||||
}
|
||||
|
||||
sub as_html
|
||||
{
|
||||
'';
|
||||
}
|
||||
|
||||
sub group
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{node}->group();
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Node::Cell - An empty filler cell
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
use Graph::Easy::Edge;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
my $node = $graph->add_node('A');
|
||||
|
||||
my $path = Graph::Easy::Node::Cell->new(
|
||||
graph => $graph, node => $node,
|
||||
);
|
||||
|
||||
...
|
||||
|
||||
print $graph->as_ascii();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Node::Cell> is used to reserve a cell in the grid for nodes
|
||||
that occupy more than one cell.
|
||||
|
||||
You should not need to use this class directly.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 error()
|
||||
|
||||
$last_error = $cell->error();
|
||||
|
||||
$cvt->error($error); # set new messages
|
||||
$cvt->error(''); # clear error
|
||||
|
||||
Returns the last error message, or '' for no error.
|
||||
|
||||
=head2 node()
|
||||
|
||||
my $node = $cell->node();
|
||||
|
||||
Returns the node this filler cell belongs to.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2005 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
69
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Node/Empty.pm
Normal file
69
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Node/Empty.pm
Normal file
@@ -0,0 +1,69 @@
|
||||
#############################################################################
|
||||
# An empty, borderless cell. Part of Graph::Easy.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Node::Empty;
|
||||
|
||||
use Graph::Easy::Node;
|
||||
|
||||
@ISA = qw/Graph::Easy::Node/;
|
||||
$VERSION = '0.76';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _init
|
||||
{
|
||||
# generic init, override in subclasses
|
||||
my ($self,$args) = @_;
|
||||
|
||||
$self->SUPER::_init($args);
|
||||
|
||||
$self->{class} = 'node.empty';
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _correct_size
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{w} = 3;
|
||||
$self->{h} = 3;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Node::Empty - An empty, borderless cell in a node cluster
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $cell = Graph::Easy::Node::Empty->new();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Node::Empty> represents a borderless, empty cell in
|
||||
a node cluster. It is mainly used to have an object to render collapsed
|
||||
borders in ASCII output.
|
||||
|
||||
You should not need to use this class directly.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy::Node>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2007 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
1778
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Parser.pm
Normal file
1778
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Parser.pm
Normal file
File diff suppressed because it is too large
Load Diff
2231
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Parser/Graphviz.pm
Normal file
2231
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Parser/Graphviz.pm
Normal file
File diff suppressed because it is too large
Load Diff
1168
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Parser/VCG.pm
Normal file
1168
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Parser/VCG.pm
Normal file
File diff suppressed because it is too large
Load Diff
51
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Util.pm
Normal file
51
perl/lib/Graph-Easy-0.76/blib/lib/Graph/Easy/Util.pm
Normal file
@@ -0,0 +1,51 @@
|
||||
package Graph::Easy::Util;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'Exporter';
|
||||
|
||||
our @EXPORT_OK = (qw(first_kv ord_values));
|
||||
|
||||
use List::Util qw(minstr);
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=head2 first_kv($hash_ref)
|
||||
|
||||
The first key value pair from a hash reference - lexicographically.
|
||||
|
||||
=cut
|
||||
|
||||
sub first_kv
|
||||
{
|
||||
my $href = shift;
|
||||
|
||||
my $n = minstr( keys(%$href) );
|
||||
my $v = $href->{$n};
|
||||
|
||||
return ($n, $v);
|
||||
}
|
||||
|
||||
=head2 ord_values($hash_ref)
|
||||
|
||||
The values of the hash ordered by a lexicographical keyname.
|
||||
|
||||
=cut
|
||||
|
||||
sub ord_values
|
||||
{
|
||||
my $href = shift;
|
||||
|
||||
if ((!defined $href) || (! %$href))
|
||||
{
|
||||
return (wantarray ? () : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (wantarray ? @{$href}{sort keys( %$href )} : scalar(keys(%$href)));
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
0
perl/lib/Graph-Easy-0.76/blib/man1/.exists
Normal file
0
perl/lib/Graph-Easy-0.76/blib/man1/.exists
Normal file
0
perl/lib/Graph-Easy-0.76/blib/man3/.exists
Normal file
0
perl/lib/Graph-Easy-0.76/blib/man3/.exists
Normal file
0
perl/lib/Graph-Easy-0.76/blib/script/.exists
Normal file
0
perl/lib/Graph-Easy-0.76/blib/script/.exists
Normal file
712
perl/lib/Graph-Easy-0.76/blib/script/graph-easy
Normal file
712
perl/lib/Graph-Easy-0.76/blib/script/graph-easy
Normal file
@@ -0,0 +1,712 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
use strict;
|
||||
use Graph::Easy 0.63;
|
||||
use Graph::Easy::Parser;
|
||||
|
||||
my $help_requested = 0;
|
||||
|
||||
# echo "[A]" | graph-easy # should work
|
||||
# graph-easy # need help
|
||||
$help_requested = 1 if @ARGV == 0 && -t STDIN;
|
||||
|
||||
# list of supported output formats for external renderers like dot:
|
||||
my @external = qw/png bmp gif jpg pdf ps ps2 tif tga pcl hpgl/;
|
||||
my $external = join('|',@external);
|
||||
my $qr_ext = qr/^($external)\z/;
|
||||
|
||||
my $OUT = \*STDERR;
|
||||
my $opt = get_options();
|
||||
|
||||
# error?
|
||||
$help_requested = 1 if !ref($opt);
|
||||
|
||||
# no error and --help was specified
|
||||
$help_requested = 2 if ref($opt) && $opt->{help} ne '';
|
||||
|
||||
my $copyright = "Graph::Easy v$Graph::Easy::VERSION (c) by Tels 2004-2008. "
|
||||
."Released under the GPL 2.0 or later.\n\n";
|
||||
|
||||
if (ref($opt) && $opt->{version} != 0)
|
||||
{
|
||||
print $copyright;
|
||||
print "Running under Perl v$]";
|
||||
eval { require Graph::Easy::As_svg; };
|
||||
if (defined $Graph::Easy::As_svg::VERSION)
|
||||
{
|
||||
print " and using Graph::Easy::As_svg v$Graph::Easy::As_svg::VERSION";
|
||||
}
|
||||
print ".\n\n";
|
||||
exit 2;
|
||||
}
|
||||
|
||||
if ($help_requested > 0)
|
||||
{
|
||||
print STDERR $copyright;
|
||||
require Pod::Usage;
|
||||
if ($help_requested > 1 && $Pod::Usage::VERSION < 1.35)
|
||||
{
|
||||
# The way old Pod::Usage executes "perldoc" might fail:
|
||||
system('perldoc', $0);
|
||||
exit 2;
|
||||
}
|
||||
Pod::Usage::pod2usage( { -exitval => 2, -verbose => $help_requested } );
|
||||
}
|
||||
|
||||
my $verbose = $opt->{verbose};
|
||||
|
||||
print $OUT $copyright if $verbose;
|
||||
|
||||
#############################################################################
|
||||
# Create the parser object
|
||||
|
||||
my $parser_class = 'Graph::Easy::Parser';
|
||||
if ($opt->{from} eq 'graphviz')
|
||||
{
|
||||
require Graph::Easy::Parser::Graphviz;
|
||||
$parser_class = 'Graph::Easy::Parser::Graphviz';
|
||||
}
|
||||
elsif ($opt->{from} =~ /^(vcg|gdl)\z/)
|
||||
{
|
||||
require Graph::Easy::Parser::VCG;
|
||||
$parser_class = 'Graph::Easy::Parser::VCG';
|
||||
}
|
||||
|
||||
print $OUT "Creating $parser_class object.\n" if $verbose;
|
||||
|
||||
my $parser = $parser_class->new( debug => $opt->{debug} );
|
||||
|
||||
#############################################################################
|
||||
# parse the input file
|
||||
|
||||
print $OUT "Parsing input in $opt->{from} from $opt->{inputname}.\n" if $verbose;
|
||||
|
||||
my $graph = $parser->from_file($opt->{input});
|
||||
|
||||
my $error = '';
|
||||
$error = $parser->error() if !$graph || $parser->error();
|
||||
$error = $graph->error() if $graph && $graph->error();
|
||||
|
||||
die ($error) if $error;
|
||||
|
||||
#############################################################################
|
||||
# If wanted, generate the statistics:
|
||||
|
||||
if ($opt->{stats})
|
||||
{
|
||||
print STDERR "\nInput is a ",
|
||||
$graph->is_simple() ? 'simple' : 'multi-edged',
|
||||
", ",
|
||||
$graph->is_undirected() ? 'undirected' : 'directed',
|
||||
" graph with:\n";
|
||||
|
||||
my $nodes = $graph->nodes();
|
||||
my $edges = $graph->edges();
|
||||
my $groups = $graph->groups();
|
||||
|
||||
print STDERR " $nodes node" . ($nodes != 1 ? 's' : '') .
|
||||
", $edges edge" . ($edges != 1 ? 's' : '') .
|
||||
" and $groups group" . ($groups != 1 ? 's' : '') . "\n\n";
|
||||
|
||||
for my $g ($graph->groups())
|
||||
{
|
||||
my $nodes = $g->nodes();
|
||||
my $edges = $g->edges();
|
||||
my $groups = $g->groups();
|
||||
|
||||
print STDERR " Group '$g->{name}':\n";
|
||||
print STDERR " $nodes node" . ($nodes != 1 ? 's' : '') .
|
||||
", $edges edge" . ($edges != 1 ? 's' : '') .
|
||||
" and $groups group" . ($groups != 1 ? 's' : '') . "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# Generate the wanted output format and write it to the output:
|
||||
|
||||
if (! $opt->{parse})
|
||||
{
|
||||
my $method = 'as_' . $opt->{as} . '_file';
|
||||
if ($verbose)
|
||||
{
|
||||
if ($opt->{outputname} =~ /\.$external\z/)
|
||||
{
|
||||
print $OUT "Piping output to '$opt->{renderer} -T$opt->{ext} -o \"$opt->{outputname}\"'.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
print $OUT "Writing output as $opt->{as} to $opt->{outputname}.\n";
|
||||
}
|
||||
}
|
||||
|
||||
$graph->timeout(abs($opt->{timeout} || 240));
|
||||
my $FILE = $opt->{output};
|
||||
print $FILE $graph->$method();
|
||||
|
||||
print $OUT "Everything done. Have fun!\n\n" if $verbose;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# Everything done
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
|
||||
sub get_options
|
||||
{
|
||||
# set the defaults
|
||||
my $opt = {
|
||||
input => undef,
|
||||
output => undef,
|
||||
as => '',
|
||||
from => 'txt',
|
||||
help => '',
|
||||
as_ascii => '',
|
||||
as_boxart => '',
|
||||
as_html => '',
|
||||
as_svg => '',
|
||||
as_graphviz => '',
|
||||
as_txt => '',
|
||||
as_vcg => '',
|
||||
as_gdl => '',
|
||||
as_graphml => '',
|
||||
debug => 0,
|
||||
from_txt => '',
|
||||
from_vcg => '',
|
||||
from_gdl => '',
|
||||
from_graphviz => '',
|
||||
verbose => 0,
|
||||
version => 0,
|
||||
parse => 0,
|
||||
stats => 0,
|
||||
timeout => 240,
|
||||
renderer => 'dot',
|
||||
};
|
||||
# insert the ones from @external
|
||||
for my $e (@external) { $opt->{$e} = ''; }
|
||||
|
||||
# map the output format to the method to generate the output:
|
||||
my $formats = {
|
||||
html => 'html',
|
||||
txt => 'ascii',
|
||||
svg => 'svg',
|
||||
dot => 'graphviz',
|
||||
vcg => 'vcg',
|
||||
gdl => 'gdl',
|
||||
graphml => 'graphml',
|
||||
};
|
||||
# insert the ones from @external
|
||||
for my $e (@external) { $formats->{$e} = 'graphviz'; }
|
||||
|
||||
# do we have some options?
|
||||
if (@ARGV > 0)
|
||||
{
|
||||
require Getopt::Long;
|
||||
|
||||
my @o = (
|
||||
"input=s" => \$opt->{input},
|
||||
"output=s" => \$opt->{output},
|
||||
"as=s" => \$opt->{as},
|
||||
"from=s" => \$opt->{from},
|
||||
"help|?" => \$opt->{help},
|
||||
"version" => \$opt->{version},
|
||||
"verbose" => \$opt->{verbose},
|
||||
"debug=i" => \$opt->{debug},
|
||||
"parse" => \$opt->{parse},
|
||||
"as_ascii|ascii" => \$opt->{as_ascii},
|
||||
"as_html|html" => \$opt->{as_html},
|
||||
"as_svg|svg" => \$opt->{as_svg},
|
||||
"as_txt|txt" => \$opt->{as_txt},
|
||||
"as_vcg|vcg" => \$opt->{as_vcg},
|
||||
"as_gdl|gdl" => \$opt->{as_gdl},
|
||||
"as_graphml|graphml" => \$opt->{as_graphml},
|
||||
"as_graphviz|graphviz|as_dot|dot" => \$opt->{as_graphviz},
|
||||
"as_boxart|boxart" => \$opt->{as_boxart},
|
||||
"timeout=i" => \$opt->{timeout},
|
||||
"renderer=s" => \$opt->{renderer},
|
||||
"stats" => \$opt->{stats},
|
||||
"from_txt" => \$opt->{from_txt},
|
||||
"from_vcg" => \$opt->{from_vcg},
|
||||
"from_gdl" => \$opt->{from_gdl},
|
||||
"from_graphviz" => \$opt->{from_graphviz},
|
||||
);
|
||||
# insert the ones from @external
|
||||
for my $e (@external) { push @o, "as_$e|$e" => \$opt->{"as_$e"}; }
|
||||
|
||||
return unless Getopt::Long::GetOptions (@o);
|
||||
}
|
||||
|
||||
# allow "as=dot" for easier usage:
|
||||
$opt->{as} = 'graphviz' if $opt->{as} eq 'dot';
|
||||
|
||||
# make the renderer argument sane to avoid --renderer=';rm -fR *':
|
||||
$opt->{renderer} =~ s/[^a-zA-Z0-9_\\\/\:\.-]//g;
|
||||
|
||||
# if there are arguments left, they are input and possible output
|
||||
$opt->{input} = shift @ARGV if @ARGV;
|
||||
$opt->{output} = shift @ARGV if @ARGV;
|
||||
|
||||
if (!defined $opt->{input})
|
||||
{
|
||||
$opt->{input} = \*STDIN;
|
||||
$opt->{inputname} = 'STDIN';
|
||||
}
|
||||
else
|
||||
{
|
||||
$opt->{inputname} = $opt->{input};
|
||||
}
|
||||
|
||||
# This code gets confused if the user specified multiple options. Not much
|
||||
# can be done about that except whack the user with something heavy:
|
||||
for my $format (qw/ascii boxart html svg txt graphviz vcg gdl graphml/, @external )
|
||||
{
|
||||
warn ("Warning: Output format '$format' overrides specified '$opt->{as}'")
|
||||
if $opt->{"as_$format"} && $opt->{as};
|
||||
$opt->{as} = $format if $opt->{"as_$format"};
|
||||
delete $opt->{"as_$format"};
|
||||
}
|
||||
|
||||
if ($opt->{as} =~ $qr_ext)
|
||||
{
|
||||
$opt->{output} = $opt->{input} unless defined $opt->{output};
|
||||
# set some default output name, so the replace works correctly
|
||||
$opt->{output} = 'graph.txt' if ref($opt->{input});
|
||||
# two-step process to fix bug #37534 - overwrites input with no extension
|
||||
# example.txt => example
|
||||
$opt->{output} =~ s/\.(txt|dot|vcg|gdl|graphml|$external)\z//;
|
||||
# example => example.png
|
||||
$opt->{output} .= ".$opt->{as}";
|
||||
}
|
||||
if (!defined $opt->{output})
|
||||
{
|
||||
$opt->{outputname} = 'STDOUT';
|
||||
$opt->{output} = \*STDOUT;
|
||||
# default to ASCII if nothing is known
|
||||
$opt->{as} = 'ascii' if $opt->{as} eq '';
|
||||
}
|
||||
else
|
||||
{
|
||||
my $file = $opt->{output};
|
||||
$opt->{outputname} = $opt->{output};
|
||||
if ($opt->{as} eq '')
|
||||
{
|
||||
$opt->{as} = 'ascii'; # default
|
||||
$opt->{as} = $formats->{$1} if $file =~ /\.(html|svg|txt|dot|vcg|gdl|graphml|$external)\z/;
|
||||
}
|
||||
$opt->{output} = undef;
|
||||
if ($opt->{as} !~ $qr_ext)
|
||||
{
|
||||
# do not clobber the output file if we cannot read the input
|
||||
return unless ref $opt->{input} || -R $opt->{input};
|
||||
|
||||
open $opt->{output}, ">", $file or die ("Cannot write to $file: $!");
|
||||
}
|
||||
else
|
||||
{
|
||||
# open a pipe to dot/neato etc.
|
||||
my $file_save = $file;
|
||||
$file_save =~ s/["'\|;]//g; # remove potentially unsafe characters
|
||||
open $opt->{output}, "|$opt->{renderer} -T$opt->{as} -o \"$file_save\"" or die ("Cannot open pipe to dot: $!");
|
||||
binmode $opt->{output}, ':utf8';
|
||||
}
|
||||
}
|
||||
|
||||
if ($opt->{as} !~ $qr_ext)
|
||||
{
|
||||
binmode ($opt->{output}, ':utf8') or die ("Cannot do binmode(output,':utf8')");
|
||||
}
|
||||
else
|
||||
{
|
||||
$opt->{ext} = $opt->{as};
|
||||
$opt->{as} = 'graphviz';
|
||||
}
|
||||
|
||||
# convert "from_vcg" to "from=vcg"
|
||||
for my $format (qw/txt graphviz dot vcg gdl/)
|
||||
{
|
||||
$opt->{from} = $format if $opt->{"from_$format"};
|
||||
delete $opt->{"from_$format"};
|
||||
}
|
||||
$opt->{from} = 'graphviz' if $opt->{from} eq 'dot';
|
||||
|
||||
die ("Unknown input format '$opt->{from}'")
|
||||
unless $opt->{from} =~ /^(vcg|gdl|graphviz|txt)\z/;
|
||||
$opt;
|
||||
}
|
||||
|
||||
__END__
|
||||
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
graph-easy - render/convert graphs in/from various formats
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Convert between graph formats and layout/render graphs:
|
||||
|
||||
graph-easy [options] [inputfile [outputfile]]
|
||||
|
||||
echo "[ Bonn ] - car -> [ Berlin ]" | graph-easy
|
||||
graph-easy --input=graph.dot --as_ascii
|
||||
graph-easy --html --output=mygraph.html graph.txt
|
||||
graph-easy graph.txt graph.svg
|
||||
graph-easy graph.txt --as_dot | dot -Tpng -o graph.png
|
||||
graph-easy graph.txt --png
|
||||
graph-easy graph.vcg --dot
|
||||
graph-easy graph.dot --gdl
|
||||
graph-easy graph.dot --graphml
|
||||
|
||||
=head1 ARGUMENTS
|
||||
|
||||
Here are the most important options, more are listed in the full
|
||||
documentation:
|
||||
|
||||
=over 10
|
||||
|
||||
=item --help
|
||||
|
||||
Print the full documentation, not just this short overview.
|
||||
|
||||
=item --input
|
||||
|
||||
Specify the input file name. Example:
|
||||
|
||||
graph-easy --input=input.txt
|
||||
|
||||
The format will be auto-detected, override it with L<--from>.
|
||||
|
||||
=item --output
|
||||
|
||||
Specify the output file name. Example:
|
||||
|
||||
graph-easy --output=output.txt input.txt
|
||||
|
||||
=item --as
|
||||
|
||||
Specify the output format. Example:
|
||||
|
||||
graph-easy --as=ascii input.txt
|
||||
|
||||
Valid formats are:
|
||||
|
||||
ascii ASCII art rendering
|
||||
boxart Unicode Boxart rendering
|
||||
html HTML
|
||||
svg Scalable Vector Graphics
|
||||
graphviz the DOT language
|
||||
dot alias for "graphviz"
|
||||
txt Graph::Easy text
|
||||
vcg VCG (Visualizing Compiler Graphs - a subset of GDL) text
|
||||
gdl GDL (Graph Description Language) text
|
||||
graphml GraphML
|
||||
|
||||
In addition, the following formats are understood and piped through the program
|
||||
specified with the --renderer option (default: dot):
|
||||
|
||||
bmp Windows bitmap
|
||||
gif GIF
|
||||
hpgl HP-GL/2 vector graphic
|
||||
jpg JPEG
|
||||
pcl PCL printer language
|
||||
pdf PDF
|
||||
png PNG
|
||||
ps Postscript
|
||||
ps2 Postscript with PDF notations (see graphviz documentation)
|
||||
tga Targa bitmap
|
||||
tif TIFF bitmap
|
||||
|
||||
The default format will be determined by the output filename extension,
|
||||
and is C<ascii>, if the output filename was not set.
|
||||
|
||||
You can also use B<ONE> argument of the form C<--as_ascii> or C<--ascii>.
|
||||
|
||||
=item --from
|
||||
|
||||
Specify the input format. Valid formats are:
|
||||
|
||||
graphviz the DOT language
|
||||
txt Graph::Easy text
|
||||
vcg VCG text
|
||||
gdl GDL (Graph Description Language) text
|
||||
|
||||
If not specified, the input format is auto-detected.
|
||||
|
||||
You can also use B<ONE> argument of the form C<--from_dot>, etc.
|
||||
|
||||
=item --renderer
|
||||
|
||||
The external program (default: "dot") used to render the output
|
||||
formats like C<png>, C<jpg> etc. Some choices are "neato", "twopi", "fdp" or "circo".
|
||||
|
||||
=item --parse
|
||||
|
||||
Input will only be parsed, without any output generation.
|
||||
Useful in combination with C<--debug=1> or C<--stats>. Example:
|
||||
|
||||
graph-easy input.txt --parse --debug=1
|
||||
|
||||
=item --stats
|
||||
|
||||
Write various statistics about the input graph to STDERR. Best used in
|
||||
combination with C<--parse>:
|
||||
|
||||
graph-easy input.txt --parse --stats
|
||||
|
||||
=item --timeout
|
||||
|
||||
Set the timeout B<in seconds> for the Graph::Easy layouter that generates
|
||||
ASCII, HTML, SVG or boxart output. If the layout does not
|
||||
finish in this time, it will be aborted. Example:
|
||||
|
||||
graph-easy input.txt --timeout=500
|
||||
|
||||
Conversion to DOT, VCG/GDL, GraphML or plain text ignores the timeout.
|
||||
|
||||
The default is 240 seconds (4 minutes).
|
||||
|
||||
=item --verbose
|
||||
|
||||
Write info regarding the conversion process to STDERR.
|
||||
|
||||
=back
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<graph-easy> reads a description of a graph (a connected network of
|
||||
nodes and edges, not a pie chart :-) and then converts this to the desired
|
||||
output format.
|
||||
|
||||
By default, the input will be read from STDIN, and the output will go to
|
||||
STDOUT. The input is expected to be encoded in UTF-8, the output will
|
||||
also be UTF-8.
|
||||
|
||||
It understands the following formats as input:
|
||||
|
||||
Graph::Easy http://bloodgate.com/perl/graph/manual/
|
||||
DOT http://www.graphviz.org/
|
||||
VCG http://rw4.cs.uni-sb.de/~sander/html/gsvcg1.html
|
||||
GDL http://www.aisee.com/
|
||||
|
||||
The formats are automatically detected, regardless of the input file name,
|
||||
but you can also explicitly declare your input to be in one specific
|
||||
format.
|
||||
|
||||
The output can be a dump of the graph in one of the following formats:
|
||||
|
||||
Graph::Easy http://bloodgate.com/perl/graph/manual/
|
||||
DOT http://www.graphviz.org/
|
||||
VCG http://rw4.cs.uni-sb.de/~sander/html/gsvcg1.html
|
||||
GDL http://www.aisee.com/
|
||||
GraphML http://graphml.graphdrawing.org/
|
||||
|
||||
In addition, C<Graph::Easy> can also create layouts of graphs in
|
||||
one of the following output formats:
|
||||
|
||||
HTML SVG ASCII BOXART
|
||||
|
||||
Note that for SVG output, you need to install the module
|
||||
L<Graph::Easy::As_svg> first.
|
||||
|
||||
As a shortcut, you can also specify the output format as 'png', this will
|
||||
cause C<graph-easy> to pipe the input in graphviz format to the C<dot> program
|
||||
to create a PNG file in one step. The following two examples are equivalent:
|
||||
|
||||
graph-easy graph.txt --dot | dot -Tpng -o graph.png
|
||||
graph-easy graph.txt --png
|
||||
|
||||
X<svg>
|
||||
X<html>
|
||||
X<ascii>
|
||||
X<boxart>
|
||||
X<png>
|
||||
X<dot>
|
||||
X<graphviz>
|
||||
X<vcg>
|
||||
X<gdl>
|
||||
X<graph description language>
|
||||
X<unicode>
|
||||
|
||||
=head1 OTHER ARGUMENTS
|
||||
|
||||
C<graph-easy> supports a few more arguments in addition to the ones from above:
|
||||
|
||||
=over 10
|
||||
|
||||
=item --version
|
||||
|
||||
Write version info and exit.
|
||||
|
||||
=item --debug=N
|
||||
|
||||
Set the debug level (1..3). Warning, this will generate huge
|
||||
amounts of hard to understand output on STDERR. Example:
|
||||
|
||||
graph-easy input.txt --output=test.html --debug=1
|
||||
|
||||
=item --png, --dot, --vcg, --gdl, --txt, --ascii, --boxart, --html, --svg
|
||||
|
||||
Given exactly one of these options, produces the desired output format.
|
||||
|
||||
=back
|
||||
|
||||
=head1 EXAMPLES
|
||||
|
||||
=head2 ASCII output
|
||||
|
||||
echo "[ Bonn ] -- car --> [ Berlin ], [ Ulm ]" | graph-easy
|
||||
|
||||
+--------+ car +-----+
|
||||
| Bonn | -----> | Ulm |
|
||||
+--------+ +-----+
|
||||
|
|
||||
| car
|
||||
v
|
||||
+--------+
|
||||
| Berlin |
|
||||
+--------+
|
||||
|
||||
=head2 Graphviz example output
|
||||
|
||||
echo "[ Bonn ] -- car --> [ Berlin ], [ Ulm ]" | graph-easy --dot
|
||||
digraph GRAPH_0 {
|
||||
|
||||
edge [ arrowhead=open ];
|
||||
graph [ rankdir=LR ];
|
||||
node [
|
||||
fontsize=11,
|
||||
fillcolor=white,
|
||||
style=filled,
|
||||
shape=box ];
|
||||
|
||||
Bonn -> Ulm [ label=car ]
|
||||
Bonn -> Berlin [ label=car ]
|
||||
|
||||
}
|
||||
|
||||
=head2 VCG example output
|
||||
|
||||
echo "[ Bonn ] -- car --> [ Berlin ], [ Ulm ]" | graph-easy --vcg
|
||||
graph: {
|
||||
title: "Untitled graph"
|
||||
|
||||
node: { title: "Berlin" }
|
||||
node: { title: "Bonn" }
|
||||
node: { title: "Ulm" }
|
||||
|
||||
edge: { label: "car" sourcename: "Bonn" targetname: "Ulm" }
|
||||
edge: { label: "car" sourcename: "Bonn" targetname: "Berlin" }
|
||||
|
||||
}
|
||||
|
||||
=head2 GDL example output
|
||||
|
||||
GDL (Graph Description Language) is a superset of VCG, and thus the output will
|
||||
look almost the same as VCG:
|
||||
|
||||
echo "[ Bonn ] -- car --> [ Berlin ], [ Ulm ]" | graph-easy --gdl
|
||||
graph: {
|
||||
title: "Untitled graph"
|
||||
|
||||
node: { title: "Berlin" }
|
||||
node: { title: "Bonn" }
|
||||
node: { title: "Ulm" }
|
||||
|
||||
edge: { label: "car" source: "Bonn" target: "Ulm" }
|
||||
edge: { label: "car" source: "Bonn" target: "Berlin" }
|
||||
|
||||
}
|
||||
|
||||
=head2 GraphML example output
|
||||
|
||||
GraphML is XML:
|
||||
|
||||
echo "[ Bonn ] -- car --> [ Berlin ], [ Ulm ]" | graph-easy --graphml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
|
||||
http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
|
||||
|
||||
<!-- Created by Graph::Easy v0.58 at Mon Aug 20 00:01:25 2007 -->
|
||||
|
||||
<key id="d0" for="edge" attr.name="label" attr.type="string"/>
|
||||
|
||||
<graph id="G" edgedefault="directed">
|
||||
<node id="Berlin">
|
||||
</node>
|
||||
<node id="Bonn">
|
||||
</node>
|
||||
<node id="Ulm">
|
||||
</node>
|
||||
<edge source="Bonn" target="Berlin">
|
||||
<data key="d0">car</data>
|
||||
</edge>
|
||||
<edge source="Bonn" target="Ulm">
|
||||
<data key="d0">car</data>
|
||||
</edge>
|
||||
</graph>
|
||||
<graphml>
|
||||
|
||||
=head1 CAVEATS
|
||||
|
||||
Please note that it is impossible to convert 100% from one format to another
|
||||
format since every graph language out there has features that are unique to
|
||||
only this language.
|
||||
|
||||
In addition, the conversion process always converts the input first into an
|
||||
L<Graph::Easy> graph, and then to the desired output format.
|
||||
|
||||
This means that only features and attributes that are actually valid in
|
||||
Graph::Easy are supported yet. Work in making Graph::Easy an universal
|
||||
format supporting as much as possible is still in progress.
|
||||
|
||||
Attributes that are not yet supported natively by Graph::Easy are converted
|
||||
to custom attributes with a prefixed C<x-format->, f.i. C<x-dot->. Upon output
|
||||
to the same format, these are converted back, but conversion to a different
|
||||
format will lose these attributes.
|
||||
|
||||
For a list of what problems still remain, please see the TODO
|
||||
file in the C<Graph::Easy> distribution on CPAN:
|
||||
|
||||
L<http://search.cpan.org/~tels/Graph-Easy/>
|
||||
|
||||
If you notice anything wrong, or miss attributes, please file a bug report on
|
||||
|
||||
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Graph-Easy>
|
||||
|
||||
so we can fix it and include the missing things into Graph::Easy!
|
||||
|
||||
X<bugreport>
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
This library is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GPL.
|
||||
|
||||
See the LICENSE file of Graph::Easy for a copy of the GPL.
|
||||
|
||||
This product includes color specifications and designs developed by Cynthia
|
||||
Brewer (L<http://colorbrewer.org/>). See the LICENSE file for the full license
|
||||
text that applies to these color schemes.
|
||||
X<gpl>
|
||||
X<apache-style>
|
||||
X<cynthia>
|
||||
X<brewer>
|
||||
X<colorscheme>
|
||||
X<license>
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2008 by Tels L<http://bloodgate.com>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
More information can be found in the online manual of Graph::Easy:
|
||||
|
||||
L<http://bloodgate.com/perl/graph/manual/>
|
||||
|
||||
See also: L<Graph::Easy>, L<Graph::Easy::Manual>
|
||||
|
||||
=cut
|
||||
728
perl/lib/Graph-Easy-0.76/blib/script/graph-easy.bat
Normal file
728
perl/lib/Graph-Easy-0.76/blib/script/graph-easy.bat
Normal file
@@ -0,0 +1,728 @@
|
||||
@rem = '--*-Perl-*--
|
||||
@echo off
|
||||
if "%OS%" == "Windows_NT" goto WinNT
|
||||
perl -x -S "%0" %1 %2 %3 %4 %5 %6 %7 %8 %9
|
||||
goto endofperl
|
||||
:WinNT
|
||||
perl -x -S %0 %*
|
||||
if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" goto endofperl
|
||||
if %errorlevel% == 9009 echo You do not have Perl in your PATH.
|
||||
if errorlevel 1 goto script_failed_so_exit_with_non_zero_val 2>nul
|
||||
goto endofperl
|
||||
@rem ';
|
||||
#!/usr/bin/perl -w
|
||||
#line 15
|
||||
|
||||
use strict;
|
||||
use Graph::Easy 0.63;
|
||||
use Graph::Easy::Parser;
|
||||
|
||||
my $help_requested = 0;
|
||||
|
||||
# echo "[A]" | graph-easy # should work
|
||||
# graph-easy # need help
|
||||
$help_requested = 1 if @ARGV == 0 && -t STDIN;
|
||||
|
||||
# list of supported output formats for external renderers like dot:
|
||||
my @external = qw/png bmp gif jpg pdf ps ps2 tif tga pcl hpgl/;
|
||||
my $external = join('|',@external);
|
||||
my $qr_ext = qr/^($external)\z/;
|
||||
|
||||
my $OUT = \*STDERR;
|
||||
my $opt = get_options();
|
||||
|
||||
# error?
|
||||
$help_requested = 1 if !ref($opt);
|
||||
|
||||
# no error and --help was specified
|
||||
$help_requested = 2 if ref($opt) && $opt->{help} ne '';
|
||||
|
||||
my $copyright = "Graph::Easy v$Graph::Easy::VERSION (c) by Tels 2004-2008. "
|
||||
."Released under the GPL 2.0 or later.\n\n";
|
||||
|
||||
if (ref($opt) && $opt->{version} != 0)
|
||||
{
|
||||
print $copyright;
|
||||
print "Running under Perl v$]";
|
||||
eval { require Graph::Easy::As_svg; };
|
||||
if (defined $Graph::Easy::As_svg::VERSION)
|
||||
{
|
||||
print " and using Graph::Easy::As_svg v$Graph::Easy::As_svg::VERSION";
|
||||
}
|
||||
print ".\n\n";
|
||||
exit 2;
|
||||
}
|
||||
|
||||
if ($help_requested > 0)
|
||||
{
|
||||
print STDERR $copyright;
|
||||
require Pod::Usage;
|
||||
if ($help_requested > 1 && $Pod::Usage::VERSION < 1.35)
|
||||
{
|
||||
# The way old Pod::Usage executes "perldoc" might fail:
|
||||
system('perldoc', $0);
|
||||
exit 2;
|
||||
}
|
||||
Pod::Usage::pod2usage( { -exitval => 2, -verbose => $help_requested } );
|
||||
}
|
||||
|
||||
my $verbose = $opt->{verbose};
|
||||
|
||||
print $OUT $copyright if $verbose;
|
||||
|
||||
#############################################################################
|
||||
# Create the parser object
|
||||
|
||||
my $parser_class = 'Graph::Easy::Parser';
|
||||
if ($opt->{from} eq 'graphviz')
|
||||
{
|
||||
require Graph::Easy::Parser::Graphviz;
|
||||
$parser_class = 'Graph::Easy::Parser::Graphviz';
|
||||
}
|
||||
elsif ($opt->{from} =~ /^(vcg|gdl)\z/)
|
||||
{
|
||||
require Graph::Easy::Parser::VCG;
|
||||
$parser_class = 'Graph::Easy::Parser::VCG';
|
||||
}
|
||||
|
||||
print $OUT "Creating $parser_class object.\n" if $verbose;
|
||||
|
||||
my $parser = $parser_class->new( debug => $opt->{debug} );
|
||||
|
||||
#############################################################################
|
||||
# parse the input file
|
||||
|
||||
print $OUT "Parsing input in $opt->{from} from $opt->{inputname}.\n" if $verbose;
|
||||
|
||||
my $graph = $parser->from_file($opt->{input});
|
||||
|
||||
my $error = '';
|
||||
$error = $parser->error() if !$graph || $parser->error();
|
||||
$error = $graph->error() if $graph && $graph->error();
|
||||
|
||||
die ($error) if $error;
|
||||
|
||||
#############################################################################
|
||||
# If wanted, generate the statistics:
|
||||
|
||||
if ($opt->{stats})
|
||||
{
|
||||
print STDERR "\nInput is a ",
|
||||
$graph->is_simple() ? 'simple' : 'multi-edged',
|
||||
", ",
|
||||
$graph->is_undirected() ? 'undirected' : 'directed',
|
||||
" graph with:\n";
|
||||
|
||||
my $nodes = $graph->nodes();
|
||||
my $edges = $graph->edges();
|
||||
my $groups = $graph->groups();
|
||||
|
||||
print STDERR " $nodes node" . ($nodes != 1 ? 's' : '') .
|
||||
", $edges edge" . ($edges != 1 ? 's' : '') .
|
||||
" and $groups group" . ($groups != 1 ? 's' : '') . "\n\n";
|
||||
|
||||
for my $g ($graph->groups())
|
||||
{
|
||||
my $nodes = $g->nodes();
|
||||
my $edges = $g->edges();
|
||||
my $groups = $g->groups();
|
||||
|
||||
print STDERR " Group '$g->{name}':\n";
|
||||
print STDERR " $nodes node" . ($nodes != 1 ? 's' : '') .
|
||||
", $edges edge" . ($edges != 1 ? 's' : '') .
|
||||
" and $groups group" . ($groups != 1 ? 's' : '') . "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# Generate the wanted output format and write it to the output:
|
||||
|
||||
if (! $opt->{parse})
|
||||
{
|
||||
my $method = 'as_' . $opt->{as} . '_file';
|
||||
if ($verbose)
|
||||
{
|
||||
if ($opt->{outputname} =~ /\.$external\z/)
|
||||
{
|
||||
print $OUT "Piping output to '$opt->{renderer} -T$opt->{ext} -o \"$opt->{outputname}\"'.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
print $OUT "Writing output as $opt->{as} to $opt->{outputname}.\n";
|
||||
}
|
||||
}
|
||||
|
||||
$graph->timeout(abs($opt->{timeout} || 240));
|
||||
my $FILE = $opt->{output};
|
||||
print $FILE $graph->$method();
|
||||
|
||||
print $OUT "Everything done. Have fun!\n\n" if $verbose;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# Everything done
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
|
||||
sub get_options
|
||||
{
|
||||
# set the defaults
|
||||
my $opt = {
|
||||
input => undef,
|
||||
output => undef,
|
||||
as => '',
|
||||
from => 'txt',
|
||||
help => '',
|
||||
as_ascii => '',
|
||||
as_boxart => '',
|
||||
as_html => '',
|
||||
as_svg => '',
|
||||
as_graphviz => '',
|
||||
as_txt => '',
|
||||
as_vcg => '',
|
||||
as_gdl => '',
|
||||
as_graphml => '',
|
||||
debug => 0,
|
||||
from_txt => '',
|
||||
from_vcg => '',
|
||||
from_gdl => '',
|
||||
from_graphviz => '',
|
||||
verbose => 0,
|
||||
version => 0,
|
||||
parse => 0,
|
||||
stats => 0,
|
||||
timeout => 240,
|
||||
renderer => 'dot',
|
||||
};
|
||||
# insert the ones from @external
|
||||
for my $e (@external) { $opt->{$e} = ''; }
|
||||
|
||||
# map the output format to the method to generate the output:
|
||||
my $formats = {
|
||||
html => 'html',
|
||||
txt => 'ascii',
|
||||
svg => 'svg',
|
||||
dot => 'graphviz',
|
||||
vcg => 'vcg',
|
||||
gdl => 'gdl',
|
||||
graphml => 'graphml',
|
||||
};
|
||||
# insert the ones from @external
|
||||
for my $e (@external) { $formats->{$e} = 'graphviz'; }
|
||||
|
||||
# do we have some options?
|
||||
if (@ARGV > 0)
|
||||
{
|
||||
require Getopt::Long;
|
||||
|
||||
my @o = (
|
||||
"input=s" => \$opt->{input},
|
||||
"output=s" => \$opt->{output},
|
||||
"as=s" => \$opt->{as},
|
||||
"from=s" => \$opt->{from},
|
||||
"help|?" => \$opt->{help},
|
||||
"version" => \$opt->{version},
|
||||
"verbose" => \$opt->{verbose},
|
||||
"debug=i" => \$opt->{debug},
|
||||
"parse" => \$opt->{parse},
|
||||
"as_ascii|ascii" => \$opt->{as_ascii},
|
||||
"as_html|html" => \$opt->{as_html},
|
||||
"as_svg|svg" => \$opt->{as_svg},
|
||||
"as_txt|txt" => \$opt->{as_txt},
|
||||
"as_vcg|vcg" => \$opt->{as_vcg},
|
||||
"as_gdl|gdl" => \$opt->{as_gdl},
|
||||
"as_graphml|graphml" => \$opt->{as_graphml},
|
||||
"as_graphviz|graphviz|as_dot|dot" => \$opt->{as_graphviz},
|
||||
"as_boxart|boxart" => \$opt->{as_boxart},
|
||||
"timeout=i" => \$opt->{timeout},
|
||||
"renderer=s" => \$opt->{renderer},
|
||||
"stats" => \$opt->{stats},
|
||||
"from_txt" => \$opt->{from_txt},
|
||||
"from_vcg" => \$opt->{from_vcg},
|
||||
"from_gdl" => \$opt->{from_gdl},
|
||||
"from_graphviz" => \$opt->{from_graphviz},
|
||||
);
|
||||
# insert the ones from @external
|
||||
for my $e (@external) { push @o, "as_$e|$e" => \$opt->{"as_$e"}; }
|
||||
|
||||
return unless Getopt::Long::GetOptions (@o);
|
||||
}
|
||||
|
||||
# allow "as=dot" for easier usage:
|
||||
$opt->{as} = 'graphviz' if $opt->{as} eq 'dot';
|
||||
|
||||
# make the renderer argument sane to avoid --renderer=';rm -fR *':
|
||||
$opt->{renderer} =~ s/[^a-zA-Z0-9_\\\/\:\.-]//g;
|
||||
|
||||
# if there are arguments left, they are input and possible output
|
||||
$opt->{input} = shift @ARGV if @ARGV;
|
||||
$opt->{output} = shift @ARGV if @ARGV;
|
||||
|
||||
if (!defined $opt->{input})
|
||||
{
|
||||
$opt->{input} = \*STDIN;
|
||||
$opt->{inputname} = 'STDIN';
|
||||
}
|
||||
else
|
||||
{
|
||||
$opt->{inputname} = $opt->{input};
|
||||
}
|
||||
|
||||
# This code gets confused if the user specified multiple options. Not much
|
||||
# can be done about that except whack the user with something heavy:
|
||||
for my $format (qw/ascii boxart html svg txt graphviz vcg gdl graphml/, @external )
|
||||
{
|
||||
warn ("Warning: Output format '$format' overrides specified '$opt->{as}'")
|
||||
if $opt->{"as_$format"} && $opt->{as};
|
||||
$opt->{as} = $format if $opt->{"as_$format"};
|
||||
delete $opt->{"as_$format"};
|
||||
}
|
||||
|
||||
if ($opt->{as} =~ $qr_ext)
|
||||
{
|
||||
$opt->{output} = $opt->{input} unless defined $opt->{output};
|
||||
# set some default output name, so the replace works correctly
|
||||
$opt->{output} = 'graph.txt' if ref($opt->{input});
|
||||
# two-step process to fix bug #37534 - overwrites input with no extension
|
||||
# example.txt => example
|
||||
$opt->{output} =~ s/\.(txt|dot|vcg|gdl|graphml|$external)\z//;
|
||||
# example => example.png
|
||||
$opt->{output} .= ".$opt->{as}";
|
||||
}
|
||||
if (!defined $opt->{output})
|
||||
{
|
||||
$opt->{outputname} = 'STDOUT';
|
||||
$opt->{output} = \*STDOUT;
|
||||
# default to ASCII if nothing is known
|
||||
$opt->{as} = 'ascii' if $opt->{as} eq '';
|
||||
}
|
||||
else
|
||||
{
|
||||
my $file = $opt->{output};
|
||||
$opt->{outputname} = $opt->{output};
|
||||
if ($opt->{as} eq '')
|
||||
{
|
||||
$opt->{as} = 'ascii'; # default
|
||||
$opt->{as} = $formats->{$1} if $file =~ /\.(html|svg|txt|dot|vcg|gdl|graphml|$external)\z/;
|
||||
}
|
||||
$opt->{output} = undef;
|
||||
if ($opt->{as} !~ $qr_ext)
|
||||
{
|
||||
# do not clobber the output file if we cannot read the input
|
||||
return unless ref $opt->{input} || -R $opt->{input};
|
||||
|
||||
open $opt->{output}, ">", $file or die ("Cannot write to $file: $!");
|
||||
}
|
||||
else
|
||||
{
|
||||
# open a pipe to dot/neato etc.
|
||||
my $file_save = $file;
|
||||
$file_save =~ s/["'\|;]//g; # remove potentially unsafe characters
|
||||
open $opt->{output}, "|$opt->{renderer} -T$opt->{as} -o \"$file_save\"" or die ("Cannot open pipe to dot: $!");
|
||||
binmode $opt->{output}, ':utf8';
|
||||
}
|
||||
}
|
||||
|
||||
if ($opt->{as} !~ $qr_ext)
|
||||
{
|
||||
binmode ($opt->{output}, ':utf8') or die ("Cannot do binmode(output,':utf8')");
|
||||
}
|
||||
else
|
||||
{
|
||||
$opt->{ext} = $opt->{as};
|
||||
$opt->{as} = 'graphviz';
|
||||
}
|
||||
|
||||
# convert "from_vcg" to "from=vcg"
|
||||
for my $format (qw/txt graphviz dot vcg gdl/)
|
||||
{
|
||||
$opt->{from} = $format if $opt->{"from_$format"};
|
||||
delete $opt->{"from_$format"};
|
||||
}
|
||||
$opt->{from} = 'graphviz' if $opt->{from} eq 'dot';
|
||||
|
||||
die ("Unknown input format '$opt->{from}'")
|
||||
unless $opt->{from} =~ /^(vcg|gdl|graphviz|txt)\z/;
|
||||
$opt;
|
||||
}
|
||||
|
||||
__END__
|
||||
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
graph-easy - render/convert graphs in/from various formats
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Convert between graph formats and layout/render graphs:
|
||||
|
||||
graph-easy [options] [inputfile [outputfile]]
|
||||
|
||||
echo "[ Bonn ] - car -> [ Berlin ]" | graph-easy
|
||||
graph-easy --input=graph.dot --as_ascii
|
||||
graph-easy --html --output=mygraph.html graph.txt
|
||||
graph-easy graph.txt graph.svg
|
||||
graph-easy graph.txt --as_dot | dot -Tpng -o graph.png
|
||||
graph-easy graph.txt --png
|
||||
graph-easy graph.vcg --dot
|
||||
graph-easy graph.dot --gdl
|
||||
graph-easy graph.dot --graphml
|
||||
|
||||
=head1 ARGUMENTS
|
||||
|
||||
Here are the most important options, more are listed in the full
|
||||
documentation:
|
||||
|
||||
=over 10
|
||||
|
||||
=item --help
|
||||
|
||||
Print the full documentation, not just this short overview.
|
||||
|
||||
=item --input
|
||||
|
||||
Specify the input file name. Example:
|
||||
|
||||
graph-easy --input=input.txt
|
||||
|
||||
The format will be auto-detected, override it with L<--from>.
|
||||
|
||||
=item --output
|
||||
|
||||
Specify the output file name. Example:
|
||||
|
||||
graph-easy --output=output.txt input.txt
|
||||
|
||||
=item --as
|
||||
|
||||
Specify the output format. Example:
|
||||
|
||||
graph-easy --as=ascii input.txt
|
||||
|
||||
Valid formats are:
|
||||
|
||||
ascii ASCII art rendering
|
||||
boxart Unicode Boxart rendering
|
||||
html HTML
|
||||
svg Scalable Vector Graphics
|
||||
graphviz the DOT language
|
||||
dot alias for "graphviz"
|
||||
txt Graph::Easy text
|
||||
vcg VCG (Visualizing Compiler Graphs - a subset of GDL) text
|
||||
gdl GDL (Graph Description Language) text
|
||||
graphml GraphML
|
||||
|
||||
In addition, the following formats are understood and piped through the program
|
||||
specified with the --renderer option (default: dot):
|
||||
|
||||
bmp Windows bitmap
|
||||
gif GIF
|
||||
hpgl HP-GL/2 vector graphic
|
||||
jpg JPEG
|
||||
pcl PCL printer language
|
||||
pdf PDF
|
||||
png PNG
|
||||
ps Postscript
|
||||
ps2 Postscript with PDF notations (see graphviz documentation)
|
||||
tga Targa bitmap
|
||||
tif TIFF bitmap
|
||||
|
||||
The default format will be determined by the output filename extension,
|
||||
and is C<ascii>, if the output filename was not set.
|
||||
|
||||
You can also use B<ONE> argument of the form C<--as_ascii> or C<--ascii>.
|
||||
|
||||
=item --from
|
||||
|
||||
Specify the input format. Valid formats are:
|
||||
|
||||
graphviz the DOT language
|
||||
txt Graph::Easy text
|
||||
vcg VCG text
|
||||
gdl GDL (Graph Description Language) text
|
||||
|
||||
If not specified, the input format is auto-detected.
|
||||
|
||||
You can also use B<ONE> argument of the form C<--from_dot>, etc.
|
||||
|
||||
=item --renderer
|
||||
|
||||
The external program (default: "dot") used to render the output
|
||||
formats like C<png>, C<jpg> etc. Some choices are "neato", "twopi", "fdp" or "circo".
|
||||
|
||||
=item --parse
|
||||
|
||||
Input will only be parsed, without any output generation.
|
||||
Useful in combination with C<--debug=1> or C<--stats>. Example:
|
||||
|
||||
graph-easy input.txt --parse --debug=1
|
||||
|
||||
=item --stats
|
||||
|
||||
Write various statistics about the input graph to STDERR. Best used in
|
||||
combination with C<--parse>:
|
||||
|
||||
graph-easy input.txt --parse --stats
|
||||
|
||||
=item --timeout
|
||||
|
||||
Set the timeout B<in seconds> for the Graph::Easy layouter that generates
|
||||
ASCII, HTML, SVG or boxart output. If the layout does not
|
||||
finish in this time, it will be aborted. Example:
|
||||
|
||||
graph-easy input.txt --timeout=500
|
||||
|
||||
Conversion to DOT, VCG/GDL, GraphML or plain text ignores the timeout.
|
||||
|
||||
The default is 240 seconds (4 minutes).
|
||||
|
||||
=item --verbose
|
||||
|
||||
Write info regarding the conversion process to STDERR.
|
||||
|
||||
=back
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<graph-easy> reads a description of a graph (a connected network of
|
||||
nodes and edges, not a pie chart :-) and then converts this to the desired
|
||||
output format.
|
||||
|
||||
By default, the input will be read from STDIN, and the output will go to
|
||||
STDOUT. The input is expected to be encoded in UTF-8, the output will
|
||||
also be UTF-8.
|
||||
|
||||
It understands the following formats as input:
|
||||
|
||||
Graph::Easy http://bloodgate.com/perl/graph/manual/
|
||||
DOT http://www.graphviz.org/
|
||||
VCG http://rw4.cs.uni-sb.de/~sander/html/gsvcg1.html
|
||||
GDL http://www.aisee.com/
|
||||
|
||||
The formats are automatically detected, regardless of the input file name,
|
||||
but you can also explicitly declare your input to be in one specific
|
||||
format.
|
||||
|
||||
The output can be a dump of the graph in one of the following formats:
|
||||
|
||||
Graph::Easy http://bloodgate.com/perl/graph/manual/
|
||||
DOT http://www.graphviz.org/
|
||||
VCG http://rw4.cs.uni-sb.de/~sander/html/gsvcg1.html
|
||||
GDL http://www.aisee.com/
|
||||
GraphML http://graphml.graphdrawing.org/
|
||||
|
||||
In addition, C<Graph::Easy> can also create layouts of graphs in
|
||||
one of the following output formats:
|
||||
|
||||
HTML SVG ASCII BOXART
|
||||
|
||||
Note that for SVG output, you need to install the module
|
||||
L<Graph::Easy::As_svg> first.
|
||||
|
||||
As a shortcut, you can also specify the output format as 'png', this will
|
||||
cause C<graph-easy> to pipe the input in graphviz format to the C<dot> program
|
||||
to create a PNG file in one step. The following two examples are equivalent:
|
||||
|
||||
graph-easy graph.txt --dot | dot -Tpng -o graph.png
|
||||
graph-easy graph.txt --png
|
||||
|
||||
X<svg>
|
||||
X<html>
|
||||
X<ascii>
|
||||
X<boxart>
|
||||
X<png>
|
||||
X<dot>
|
||||
X<graphviz>
|
||||
X<vcg>
|
||||
X<gdl>
|
||||
X<graph description language>
|
||||
X<unicode>
|
||||
|
||||
=head1 OTHER ARGUMENTS
|
||||
|
||||
C<graph-easy> supports a few more arguments in addition to the ones from above:
|
||||
|
||||
=over 10
|
||||
|
||||
=item --version
|
||||
|
||||
Write version info and exit.
|
||||
|
||||
=item --debug=N
|
||||
|
||||
Set the debug level (1..3). Warning, this will generate huge
|
||||
amounts of hard to understand output on STDERR. Example:
|
||||
|
||||
graph-easy input.txt --output=test.html --debug=1
|
||||
|
||||
=item --png, --dot, --vcg, --gdl, --txt, --ascii, --boxart, --html, --svg
|
||||
|
||||
Given exactly one of these options, produces the desired output format.
|
||||
|
||||
=back
|
||||
|
||||
=head1 EXAMPLES
|
||||
|
||||
=head2 ASCII output
|
||||
|
||||
echo "[ Bonn ] -- car --> [ Berlin ], [ Ulm ]" | graph-easy
|
||||
|
||||
+--------+ car +-----+
|
||||
| Bonn | -----> | Ulm |
|
||||
+--------+ +-----+
|
||||
|
|
||||
| car
|
||||
v
|
||||
+--------+
|
||||
| Berlin |
|
||||
+--------+
|
||||
|
||||
=head2 Graphviz example output
|
||||
|
||||
echo "[ Bonn ] -- car --> [ Berlin ], [ Ulm ]" | graph-easy --dot
|
||||
digraph GRAPH_0 {
|
||||
|
||||
edge [ arrowhead=open ];
|
||||
graph [ rankdir=LR ];
|
||||
node [
|
||||
fontsize=11,
|
||||
fillcolor=white,
|
||||
style=filled,
|
||||
shape=box ];
|
||||
|
||||
Bonn -> Ulm [ label=car ]
|
||||
Bonn -> Berlin [ label=car ]
|
||||
|
||||
}
|
||||
|
||||
=head2 VCG example output
|
||||
|
||||
echo "[ Bonn ] -- car --> [ Berlin ], [ Ulm ]" | graph-easy --vcg
|
||||
graph: {
|
||||
title: "Untitled graph"
|
||||
|
||||
node: { title: "Berlin" }
|
||||
node: { title: "Bonn" }
|
||||
node: { title: "Ulm" }
|
||||
|
||||
edge: { label: "car" sourcename: "Bonn" targetname: "Ulm" }
|
||||
edge: { label: "car" sourcename: "Bonn" targetname: "Berlin" }
|
||||
|
||||
}
|
||||
|
||||
=head2 GDL example output
|
||||
|
||||
GDL (Graph Description Language) is a superset of VCG, and thus the output will
|
||||
look almost the same as VCG:
|
||||
|
||||
echo "[ Bonn ] -- car --> [ Berlin ], [ Ulm ]" | graph-easy --gdl
|
||||
graph: {
|
||||
title: "Untitled graph"
|
||||
|
||||
node: { title: "Berlin" }
|
||||
node: { title: "Bonn" }
|
||||
node: { title: "Ulm" }
|
||||
|
||||
edge: { label: "car" source: "Bonn" target: "Ulm" }
|
||||
edge: { label: "car" source: "Bonn" target: "Berlin" }
|
||||
|
||||
}
|
||||
|
||||
=head2 GraphML example output
|
||||
|
||||
GraphML is XML:
|
||||
|
||||
echo "[ Bonn ] -- car --> [ Berlin ], [ Ulm ]" | graph-easy --graphml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
|
||||
http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
|
||||
|
||||
<!-- Created by Graph::Easy v0.58 at Mon Aug 20 00:01:25 2007 -->
|
||||
|
||||
<key id="d0" for="edge" attr.name="label" attr.type="string"/>
|
||||
|
||||
<graph id="G" edgedefault="directed">
|
||||
<node id="Berlin">
|
||||
</node>
|
||||
<node id="Bonn">
|
||||
</node>
|
||||
<node id="Ulm">
|
||||
</node>
|
||||
<edge source="Bonn" target="Berlin">
|
||||
<data key="d0">car</data>
|
||||
</edge>
|
||||
<edge source="Bonn" target="Ulm">
|
||||
<data key="d0">car</data>
|
||||
</edge>
|
||||
</graph>
|
||||
<graphml>
|
||||
|
||||
=head1 CAVEATS
|
||||
|
||||
Please note that it is impossible to convert 100% from one format to another
|
||||
format since every graph language out there has features that are unique to
|
||||
only this language.
|
||||
|
||||
In addition, the conversion process always converts the input first into an
|
||||
L<Graph::Easy> graph, and then to the desired output format.
|
||||
|
||||
This means that only features and attributes that are actually valid in
|
||||
Graph::Easy are supported yet. Work in making Graph::Easy an universal
|
||||
format supporting as much as possible is still in progress.
|
||||
|
||||
Attributes that are not yet supported natively by Graph::Easy are converted
|
||||
to custom attributes with a prefixed C<x-format->, f.i. C<x-dot->. Upon output
|
||||
to the same format, these are converted back, but conversion to a different
|
||||
format will lose these attributes.
|
||||
|
||||
For a list of what problems still remain, please see the TODO
|
||||
file in the C<Graph::Easy> distribution on CPAN:
|
||||
|
||||
L<http://search.cpan.org/~tels/Graph-Easy/>
|
||||
|
||||
If you notice anything wrong, or miss attributes, please file a bug report on
|
||||
|
||||
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Graph-Easy>
|
||||
|
||||
so we can fix it and include the missing things into Graph::Easy!
|
||||
|
||||
X<bugreport>
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
This library is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GPL.
|
||||
|
||||
See the LICENSE file of Graph::Easy for a copy of the GPL.
|
||||
|
||||
This product includes color specifications and designs developed by Cynthia
|
||||
Brewer (L<http://colorbrewer.org/>). See the LICENSE file for the full license
|
||||
text that applies to these color schemes.
|
||||
X<gpl>
|
||||
X<apache-style>
|
||||
X<cynthia>
|
||||
X<brewer>
|
||||
X<colorscheme>
|
||||
X<license>
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2008 by Tels L<http://bloodgate.com>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
More information can be found in the online manual of Graph::Easy:
|
||||
|
||||
L<http://bloodgate.com/perl/graph/manual/>
|
||||
|
||||
See also: L<Graph::Easy>, L<Graph::Easy::Manual>
|
||||
|
||||
=cut
|
||||
|
||||
__END__
|
||||
:endofperl
|
||||
43
perl/lib/Graph-Easy-0.76/examples/as_ascii
Normal file
43
perl/lib/Graph-Easy-0.76/examples/as_ascii
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
#############################################################################
|
||||
# This example is a bit outdated, please use the new bin/graph-easy script -
|
||||
# which is after "make install" available on any command line in your system.
|
||||
|
||||
# Convert an input file containing a Graph::Easy description to
|
||||
# ASCII art.
|
||||
|
||||
# Example usage:
|
||||
# examples/as_ascii t/in/2nodes.txt
|
||||
# echo "[ A ] -> [ B ]" | examples/as_ascii
|
||||
|
||||
BEGIN { $|++; }
|
||||
|
||||
use lib 'lib';
|
||||
use Graph::Easy::Parser;
|
||||
|
||||
my $file = shift;
|
||||
my $id = shift || '';
|
||||
my $debug = shift;
|
||||
|
||||
my $parser = Graph::Easy::Parser->new( debug => $debug );
|
||||
|
||||
if (!defined $file)
|
||||
{
|
||||
$file = \*STDIN;
|
||||
binmode STDIN, ':utf8' or die ("binmode STDIN, ':utf8' failed: $!");
|
||||
}
|
||||
binmode STDERR, ':utf8' or die ("binmode STDERR, ':utf8' failed: $!");
|
||||
my $graph = $parser->from_file( $file );
|
||||
|
||||
die ($parser->error()) unless defined $graph;
|
||||
|
||||
$graph->id($id);
|
||||
$graph->timeout(360);
|
||||
$graph->layout();
|
||||
|
||||
warn($graph->error()) if $graph->error();
|
||||
|
||||
binmode STDOUT, ':utf8' or die ("binmode STDOUT, ':utf8' failed: $!");
|
||||
print $graph->as_ascii();
|
||||
|
||||
43
perl/lib/Graph-Easy-0.76/examples/as_boxart
Normal file
43
perl/lib/Graph-Easy-0.76/examples/as_boxart
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
#############################################################################
|
||||
# This example is a bit outdated, please use the new bin/graph-easy script -
|
||||
# which is after "make install" available on any command line in your system.
|
||||
|
||||
# Convert an input file containing a Graph::Easy description to
|
||||
# ASCII art using "box drawing" Unicode characters.
|
||||
|
||||
# Example usage:
|
||||
# examples/as_boxart t/in/2nodes.txt
|
||||
# echo "[ A ] -> [ B ]" | examples/as_boxart
|
||||
|
||||
BEGIN { $|++; }
|
||||
|
||||
use lib 'lib';
|
||||
use Graph::Easy::Parser;
|
||||
|
||||
my $file = shift;
|
||||
my $id = shift || '';
|
||||
my $debug = shift;
|
||||
|
||||
my $parser = Graph::Easy::Parser->new( debug => $debug );
|
||||
|
||||
if (!defined $file)
|
||||
{
|
||||
$file = \*STDIN;
|
||||
binmode STDIN, ':utf8' or die ("binmode STDIN, ':utf8' failed: $!");
|
||||
}
|
||||
my $graph = $parser->from_file( $file );
|
||||
|
||||
die ($parser->error()) unless defined $graph;
|
||||
|
||||
$graph->id($id);
|
||||
$graph->timeout(360);
|
||||
$graph->layout();
|
||||
|
||||
warn($graph->error()) if $graph->error();
|
||||
|
||||
binmode STDOUT, ':utf8' or die ("binmode STDOUT, ':utf8' failed: $!");
|
||||
|
||||
print $graph->as_boxart();
|
||||
|
||||
44
perl/lib/Graph-Easy-0.76/examples/as_boxart_html
Normal file
44
perl/lib/Graph-Easy-0.76/examples/as_boxart_html
Normal file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
#############################################################################
|
||||
# This example is a bit outdated, please use the new bin/graph-easy script -
|
||||
# which is after "make install" available on any command line in your system.
|
||||
|
||||
# Convert an input file containing a Graph::Easy description to
|
||||
# ASCII art using "box drawing" Unicode characters.
|
||||
|
||||
# Example usage:
|
||||
# examples/as_boxart t/in/2nodes.txt
|
||||
# echo "[ A ] -> [ B ]" | examples/as_boxart
|
||||
|
||||
BEGIN { $|++; }
|
||||
|
||||
use lib 'lib';
|
||||
use Graph::Easy::Parser;
|
||||
|
||||
my $file = shift;
|
||||
my $id = shift || '';
|
||||
my $debug = shift;
|
||||
|
||||
my $parser = Graph::Easy::Parser->new( debug => $debug );
|
||||
|
||||
if (!defined $file)
|
||||
{
|
||||
$file = \*STDIN;
|
||||
binmode STDIN, ':utf8' or die ("binmode STDIN, ':utf8' failed: $!");
|
||||
}
|
||||
my $graph = $parser->from_file( $file );
|
||||
|
||||
die ($parser->error()) unless defined $graph;
|
||||
|
||||
$graph->id($id);
|
||||
$graph->timeout(360);
|
||||
$graph->layout();
|
||||
|
||||
warn($graph->error()) if $graph->error();
|
||||
|
||||
binmode STDOUT, ':utf8' or die ("binmode STDOUT, ':utf8' failed: $!");
|
||||
|
||||
#print $graph->as_boxart();
|
||||
print $graph->as_boxart_html_file();
|
||||
|
||||
35
perl/lib/Graph-Easy-0.76/examples/as_graphviz
Normal file
35
perl/lib/Graph-Easy-0.76/examples/as_graphviz
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
#############################################################################
|
||||
# This example is a bit outdated, please use the new bin/graph-easy script -
|
||||
# which is after "make install" available on any command line in your system.
|
||||
|
||||
# Convert an input file containing a Graph::Easy description to
|
||||
# graphviz output that can be feed to dot etc.
|
||||
|
||||
# Example usage:
|
||||
# examples/as_graphviz t/in/2nodes.txt | dot -Tpng >test.png
|
||||
# echo "[ A ] -> [ B ]" | examples/as_graphviz | dot -Tpng >test.png
|
||||
|
||||
BEGIN { $|++; }
|
||||
|
||||
use strict;
|
||||
use lib 'lib';
|
||||
use Graph::Easy::Parser;
|
||||
|
||||
my $file = shift;
|
||||
|
||||
my $parser = Graph::Easy::Parser->new( debug => 0 );
|
||||
|
||||
if (!defined $file)
|
||||
{
|
||||
$file = \*STDIN;
|
||||
binmode STDIN, ':utf8' or die ("binmode STDIN, ':utf8' failed: $!");
|
||||
}
|
||||
binmode STDERR, ':utf8' or die ("binmode STDERR, ':utf8' failed: $!");
|
||||
my $graph = $parser->from_file( $file );
|
||||
|
||||
die ($parser->error()) unless defined $graph;
|
||||
binmode STDOUT, ':utf8' or die ("binmode STDOUT, ':utf8' failed: $!");
|
||||
print $graph->as_graphviz();
|
||||
|
||||
43
perl/lib/Graph-Easy-0.76/examples/as_html
Normal file
43
perl/lib/Graph-Easy-0.76/examples/as_html
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
#############################################################################
|
||||
# This example is a bit outdated, please use the new bin/graph-easy script -
|
||||
# which is after "make install" available on any command line in your system.
|
||||
|
||||
# Convert an input file containing a Graph::Easy description to
|
||||
# an HTML page.
|
||||
|
||||
# Example usage:
|
||||
# examples/as_html t/in/2nodes.txt >test.html
|
||||
# echo "[ A ] -> [ B ]" | examples/as_ascii
|
||||
|
||||
BEGIN { $|++; }
|
||||
|
||||
use strict;
|
||||
use lib 'lib';
|
||||
use Graph::Easy::Parser;
|
||||
|
||||
my $file = shift;
|
||||
my $id = shift || '';
|
||||
my $debug = shift || 0;
|
||||
|
||||
my $parser = Graph::Easy::Parser->new( debug => $debug );
|
||||
|
||||
if (!defined $file)
|
||||
{
|
||||
$file = \*STDIN;
|
||||
binmode STDIN, ':utf8' or die ("binmode STDIN, ':utf8' failed: $!");
|
||||
}
|
||||
binmode STDERR, ':utf8' or die ("binmode STDERR, ':utf8' failed: $!");
|
||||
my $graph = $parser->from_file( $file );
|
||||
|
||||
die ($parser->error()) unless defined $graph;
|
||||
|
||||
$graph->id($id);
|
||||
$graph->timeout(360);
|
||||
$graph->layout();
|
||||
|
||||
warn ($graph->error()) if $graph->error();
|
||||
binmode STDOUT, ':utf8' or die ("binmode STDOUT, ':utf8' failed: $!");
|
||||
print $graph->as_html_page();
|
||||
|
||||
40
perl/lib/Graph-Easy-0.76/examples/as_svg
Normal file
40
perl/lib/Graph-Easy-0.76/examples/as_svg
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
#############################################################################
|
||||
# This example is a bit outdated, please use the new bin/graph-easy script -
|
||||
# which is after "make install" available on any command line in your system.
|
||||
|
||||
# Convert an input file containing a Graph::Easy description to
|
||||
# standalone SVG file
|
||||
|
||||
# Example usage:
|
||||
# examples/as_svg t/in/2nodes.txt >test.svg
|
||||
# echo "[ A ] -> [ B ]" | examples/as_svg
|
||||
|
||||
BEGIN { $|++; }
|
||||
|
||||
use strict;
|
||||
use lib 'lib';
|
||||
use Graph::Easy::Parser;
|
||||
|
||||
my $file = shift;
|
||||
my $debug = shift;
|
||||
|
||||
my $parser = Graph::Easy::Parser->new( debug => $debug );
|
||||
|
||||
if (!defined $file)
|
||||
{
|
||||
$file = \*STDIN;
|
||||
binmode STDIN, ':utf8' or die ("binmode STDIN, ':utf8' failed: $!");
|
||||
}
|
||||
binmode STDERR, ':utf8' or die ("binmode STDERR, ':utf8' failed: $!");
|
||||
my $graph = $parser->from_file( $file );
|
||||
|
||||
die ($parser->error()) unless defined $graph;
|
||||
$graph->timeout(360);
|
||||
$graph->layout();
|
||||
warn ($graph->error()) if $graph->error();
|
||||
|
||||
binmode STDOUT, ':utf8' or die ("binmode STDOUT, ':utf8' failed: $!");
|
||||
print $graph->as_svg_file();
|
||||
|
||||
39
perl/lib/Graph-Easy-0.76/examples/as_txt
Normal file
39
perl/lib/Graph-Easy-0.76/examples/as_txt
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
#############################################################################
|
||||
# This example is a bit outdated, please use the new bin/graph-easy script -
|
||||
# which is after "make install" available on any command line in your system.
|
||||
|
||||
# Convert an input file containing a Graph::Easy object, then dump
|
||||
# it again as textual description.
|
||||
|
||||
# Example usage:
|
||||
# examples/as_txt t/in/2nodes.txt
|
||||
# echo "[ A ] -> [ B ]" | examples/as_txt
|
||||
|
||||
BEGIN { $|++; }
|
||||
|
||||
use lib 'lib';
|
||||
use Graph::Easy::Parser;
|
||||
|
||||
my $file = shift;
|
||||
my $id = shift || '';
|
||||
my $debug = shift;
|
||||
|
||||
my $parser = Graph::Easy::Parser->new( debug => $debug );
|
||||
|
||||
if (!defined $file)
|
||||
{
|
||||
$file = \*STDIN;
|
||||
binmode STDIN, ':utf8' or die ("binmode STDIN, ':utf8' failed: $!");
|
||||
}
|
||||
my $graph = $parser->from_file( $file );
|
||||
|
||||
die ($parser->error()) unless defined $graph;
|
||||
|
||||
$graph->id($id);
|
||||
|
||||
warn($graph->error()) if $graph->error();
|
||||
binmode STDOUT, ':utf8' or die ("binmode STDOUT, ':utf8' failed: $!");
|
||||
print $graph->as_txt();
|
||||
|
||||
28
perl/lib/Graph-Easy-0.76/examples/ascii.pl
Normal file
28
perl/lib/Graph-Easy-0.76/examples/ascii.pl
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
#############################################################################
|
||||
# This example is a bit outdated, please use the new bin/grapheasy script -
|
||||
# which is after "make install" available in your system as simple as
|
||||
# "grapheasy" on any command line prompt.
|
||||
|
||||
#############################################################################
|
||||
# This script uses examples/common.pl to generate some example graphs and
|
||||
# displays them in ASCII.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN { chdir 'examples' if -d 'examples'; }
|
||||
|
||||
require "common.pl";
|
||||
|
||||
sub out
|
||||
{
|
||||
my ($graph,$method) = @_;
|
||||
|
||||
$method = 'as_' . $method;
|
||||
print $graph->$method(), "\n";
|
||||
}
|
||||
|
||||
gen_graphs ();
|
||||
|
||||
179
perl/lib/Graph-Easy-0.76/examples/base.css
Normal file
179
perl/lib/Graph-Easy-0.76/examples/base.css
Normal file
@@ -0,0 +1,179 @@
|
||||
h1
|
||||
{
|
||||
border: 1px solid black;
|
||||
padding: 0.2em;
|
||||
background: #fff0f0;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
h2
|
||||
{
|
||||
border: 1px solid gray;
|
||||
border-bottom: none;
|
||||
padding: 0.2em;
|
||||
padding-left: 0.5em;
|
||||
background: #e0e0f0;
|
||||
margin-top: 0.8em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
div.h3
|
||||
{
|
||||
border-bottom: 1px solid gray;
|
||||
padding: 0.2em;
|
||||
padding-left: 0.1em;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
h2.green { background: #e0f0e0; }
|
||||
h2.coral { background: #e0f0f0; }
|
||||
h2.purple { background: #f0e0f0; }
|
||||
h2.orange { background: #fff0d0; }
|
||||
h2.brown { background: #e0b090; }
|
||||
h2.lime { background: #e0f090; }
|
||||
h2.honey { background: #f0f0a0; }
|
||||
h2.mint { background: #c0ffe0; }
|
||||
div.footer
|
||||
{
|
||||
background: #f0f0f0;
|
||||
border: 1px solid gray;
|
||||
padding: 0.6em;
|
||||
padding-left: 1.6em;
|
||||
font-size: small;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
p.hr
|
||||
{
|
||||
padding-top: 0.3em;
|
||||
border: none;
|
||||
border-top: 1px solid gray;
|
||||
}
|
||||
div.right
|
||||
{
|
||||
margin-left: 8.2em;
|
||||
}
|
||||
div.text
|
||||
{
|
||||
border: 1px solid gray;
|
||||
padding: 0.5em;
|
||||
padding-left: 1.5em;
|
||||
background: #e8e8e8;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.clear { clear: both; }
|
||||
a.top
|
||||
{
|
||||
font-size: 0.8em;
|
||||
float: right;
|
||||
position: relative;
|
||||
top: -2.5em;
|
||||
right: 0.5em;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
padding: 0.2em;
|
||||
}
|
||||
a.top:hover
|
||||
{
|
||||
color: white;
|
||||
background: black;
|
||||
padding: 0.2em;
|
||||
}
|
||||
|
||||
.menubck, .menuext, .menucur, .menuadd, .menuind, .menuinc, .menucin
|
||||
{
|
||||
display: block;
|
||||
border: 1px solid gray;
|
||||
padding: 0.1em;
|
||||
padding-left: 0.5em;
|
||||
margin: 0;
|
||||
margin-bottom: 0.4em;
|
||||
min-width: 7em;
|
||||
font-size: 0.75em;
|
||||
text-decoration: none;
|
||||
background: #e0e0ff;
|
||||
color: black;
|
||||
}
|
||||
.menuind, .menuinc, .menucin
|
||||
{
|
||||
min-width: 6em;
|
||||
margin-left: 1em;
|
||||
background: #e0e0ff;
|
||||
}
|
||||
.menu
|
||||
{
|
||||
background: white;
|
||||
padding: 0em;
|
||||
margin: 0;
|
||||
border: none;
|
||||
width: 7em;
|
||||
margin-right: 0.2em;
|
||||
position: fixed;
|
||||
}
|
||||
.menucur, .menucin { border-color: #404040; }
|
||||
.menucin { background: #a0a0ff; }
|
||||
.menucur { background: #a0a0ff; }
|
||||
.menuadd { background: #f0a0a0; }
|
||||
.menuind { background: #d0d0ff; }
|
||||
.menubck { background: #f0b0b0; }
|
||||
|
||||
:hover
|
||||
{
|
||||
color: #ffffff;
|
||||
background: #000000;
|
||||
}
|
||||
.menubck:hover, .menuadd:hover { background: #a03030; }
|
||||
.menucur:hover { background: #000080; }
|
||||
.menuind:hover, .menucin:hover { background: #3030a0; }
|
||||
|
||||
img.i
|
||||
{
|
||||
border: none;
|
||||
}
|
||||
img
|
||||
{
|
||||
border: 1px solid gray;
|
||||
margin-top: 0.7em;
|
||||
margin-bottom: 0.7em;
|
||||
}
|
||||
p, li
|
||||
{
|
||||
max-width: 50em;
|
||||
}
|
||||
p {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0.4em;
|
||||
margin-top: 0.4em;
|
||||
}
|
||||
ul
|
||||
{
|
||||
list-style: square;
|
||||
}
|
||||
li
|
||||
{
|
||||
font-size: 0.9em;
|
||||
}
|
||||
tr.odd td
|
||||
{
|
||||
background: #ffdead;
|
||||
}
|
||||
code
|
||||
{
|
||||
background: #ffffff;
|
||||
color: black;
|
||||
padding: 2px;
|
||||
}
|
||||
pre
|
||||
{
|
||||
background: #d0d0d0;
|
||||
color: black;
|
||||
padding: 0.8em;
|
||||
margin-left: 1em;
|
||||
margin-bottom: 2.5em;
|
||||
border: 1px solid black;
|
||||
max-width: 40em;
|
||||
}
|
||||
74
perl/lib/Graph-Easy-0.76/examples/common.pl
Normal file
74
perl/lib/Graph-Easy-0.76/examples/common.pl
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
#############################################################################
|
||||
# This script is used by both examples/ascii.pl and examples/html.pl to
|
||||
# generate some sample graphs and then outputting them in the desired format.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN
|
||||
{
|
||||
use lib '../lib';
|
||||
}
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
sub gen_graphs
|
||||
{
|
||||
my $graph = shift || Graph::Easy->new();
|
||||
my $method = shift || 'ascii';
|
||||
|
||||
###########################################################################
|
||||
|
||||
my $node = $graph->add_node( 'Bonn' );
|
||||
my $node2 = $graph->add_node( 'Berlin' );
|
||||
|
||||
$graph->add_edge( $node, $node2 );
|
||||
|
||||
out ($graph, $method);
|
||||
|
||||
###########################################################################
|
||||
$graph->{debug} = 0;
|
||||
|
||||
my $node3 = $graph->add_node( 'Frankfurt' );
|
||||
$node3->set_attribute('border-style', 'dotted');
|
||||
|
||||
my $edge3 = Graph::Easy::Edge->new( style => 'double' );
|
||||
|
||||
$graph->add_edge( $node2, $node3, $edge3 );
|
||||
|
||||
out ($graph, $method);
|
||||
|
||||
###########################################################################
|
||||
|
||||
$graph->add_edge( $node3, 'Dresden' );
|
||||
|
||||
out ($graph, $method);
|
||||
|
||||
###########################################################################
|
||||
|
||||
$graph->add_edge( $node2, 'Potsdam' );
|
||||
|
||||
out ($graph, $method);
|
||||
|
||||
###########################################################################
|
||||
my $node6 = $graph->add_node( 'Cottbus',);
|
||||
$node6->set_attribute('border', '1px red dashed');
|
||||
|
||||
my $edge5 = $graph->add_edge( 'Potsdam', $node6 );
|
||||
|
||||
out ($graph, $method);
|
||||
|
||||
###########################################################################
|
||||
$graph->add_edge( $node6, $node3 );
|
||||
|
||||
out ($graph, $method);
|
||||
|
||||
$graph->add_edge( $node6, $node3 );
|
||||
|
||||
out ($graph, $method);
|
||||
|
||||
}
|
||||
|
||||
1;
|
||||
22
perl/lib/Graph-Easy-0.76/examples/complex.txt
Normal file
22
perl/lib/Graph-Easy-0.76/examples/complex.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
graph {
|
||||
border: 1px solid black;
|
||||
fill: oldlace;
|
||||
background: goldenrod;
|
||||
label: My sample graph;
|
||||
}
|
||||
edge { label-color: green; color: blue; }
|
||||
|
||||
[ One ] { fill: seagreen; color: white; } -- label --> [ Two ] { shape: triangle; }
|
||||
[ One ] => { arrow-style: closed; } [ Three ]
|
||||
[ Five ] { fill: maroon; color: yellow; } <=> [ Three ]
|
||||
[ One ] .. Test\n label ..> [ Four ]
|
||||
[ Three ] { border-style: dashed; }
|
||||
.. Test\n label ..> { arrow-style: closed; } [ Six ] { label: Sixty\n Six\nand\nsix; }
|
||||
[ Five ] - Test label - > { label-color: darkslategrey; color: red; } [ Seven ]
|
||||
[ Seven ] -- [ Eight ]
|
||||
[ Five ] --> [ Eight ]
|
||||
[ Five ] --> [ Seven ]
|
||||
[ Two ] -> [ Four ]
|
||||
[ Three ] <-- Test label --> { arrow-style: closed; } [ Six ]
|
||||
[ Eight ] .. [ None ] { shape: none; fill: red; color: brown; }
|
||||
|
||||
59
perl/lib/Graph-Easy-0.76/examples/fun.tpl
Normal file
59
perl/lib/Graph-Easy-0.76/examples/fun.tpl
Normal file
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title><graph>-Plugin for Mediawiki - Syntax</title>
|
||||
<meta name="MSSmartTagsPreventParsing" content="TRUE">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta http-equiv="imagetoolbar" content="no">
|
||||
<link rel="stylesheet" type="text/css" href="base.css">
|
||||
<style type="text/css">
|
||||
<!--
|
||||
.graph { margin-left: 2em; }
|
||||
pre { margin-right: 1.5em; }
|
||||
h3 { border-bottom: 1px solid #404040; padding-bottom: 0.3em; }
|
||||
a.top { top: -3.5em; }
|
||||
-->
|
||||
</style>
|
||||
</head>
|
||||
<body bgcolor=white text=black>
|
||||
|
||||
<a name="top"></a>
|
||||
|
||||
<div class="menu">
|
||||
<a class="menubck" href="index.html" title="Back to the main page">Main</a>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
|
||||
<h1><graph>-Plugin for Mediawiki</h1>
|
||||
|
||||
<h2>Table of Contents:</h2>
|
||||
|
||||
<div class="text">
|
||||
##TOC##
|
||||
|
||||
<p>
|
||||
To see the input text for each graph, follow the <font color="red">Source</font> link in each section.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<h2>Fun with Graphs</h2>
|
||||
|
||||
<div class="text">
|
||||
##HTML##
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
|
||||
<p>
|
||||
This page was automatically created at <strong><small>##time##</small></strong> by <code>examples/syntax.pl</code> running
|
||||
<a href="http://search.cpan.org/~tels/Graph-Simple/" title="Get it from search.cpan.org">Graph::Easy</a> v##version##.
|
||||
Contact <a href="/mail.html">Tels</a> for help.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</div> <!-- right cell ends here -->
|
||||
|
||||
</body></html>
|
||||
6
perl/lib/Graph-Easy-0.76/examples/history.txt
Normal file
6
perl/lib/Graph-Easy-0.76/examples/history.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
[ Bonn ] -> [ Berlin ]
|
||||
[ Berlin ] -> [ Frankfurt ]
|
||||
[ Frankfurt ] -> [ Dresden ]
|
||||
[ Berlin ] -> [ Potsdam ]
|
||||
[ Potsdam ] -> [ Cottbus ] { border-color: red; }
|
||||
[ Cottbus ] -> [ Frankfurt ]
|
||||
113
perl/lib/Graph-Easy-0.76/examples/html.pl
Normal file
113
perl/lib/Graph-Easy-0.76/examples/html.pl
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
#############################################################################
|
||||
# This example is a bit outdated, please use the new bin/grapheasy script -
|
||||
# which is after "make install" available in your system as simple as
|
||||
# "grapheasy" on any command line prompt.
|
||||
|
||||
#############################################################################
|
||||
# This script uses examples/common.pl to generate some example graphs and
|
||||
# prints them as HTML page. Use it like:
|
||||
|
||||
# ewxamples/html.pl >test.html
|
||||
|
||||
# and then open test.html in your favourite browser.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN { chdir 'examples' if -d 'examples'; }
|
||||
|
||||
require "common.pl";
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
my @toc = ();
|
||||
my $html = $graph->html_page_header();
|
||||
|
||||
$html .= <<HTML
|
||||
|
||||
<style type="text/css">
|
||||
h1 { border-bottom: 1px solid black; padding-bottom: 0.2em; }
|
||||
h2 { border-bottom: 1px solid grey; padding-bottom: 0.2em; margin-bottom: 0em; }
|
||||
div { margin-left: 2em; }
|
||||
.graph { margin-left: 2em; }
|
||||
</style>
|
||||
|
||||
<h1>Graph-Simple Test page</h1>
|
||||
|
||||
<p>
|
||||
This page was automatically created at <small>##time##</small> by <code>examples/html.pl</code> running
|
||||
<a href="http://search.cpan.org/~tels/Graph-Simple/" title="Get it from search.cpan.org">Graph::Easy</a> v##version##.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
On each of the following testcases you will see a text representation of the graph on the left side,
|
||||
and on the right side the automatically generated HTML+CSS code.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Notes:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>The text representation does not yet carry node attributes, like colors or border style.
|
||||
<li>The HTML does not yet have "pretty" edges. This will be fixed later.
|
||||
<li>The limitations in <a href="http://search.cpan.org/~tels/Graph-Simple/lib/Graph/Simple.pm#LIMITATIONS">Graph::Easy</a> apply.
|
||||
</ul>
|
||||
|
||||
<h2>Testcases:</h2>
|
||||
|
||||
##TOC##
|
||||
|
||||
HTML
|
||||
;
|
||||
|
||||
# generate the parts and push their names into @toc
|
||||
gen_graphs($graph, 'html');
|
||||
|
||||
$html .= $graph->html_page_footer();
|
||||
|
||||
my $toc = '<ul>';
|
||||
|
||||
for my $t (@toc)
|
||||
{
|
||||
my $n = $t; $n =~ s/\s/_/;
|
||||
$toc .= " <li><a href=\"#$n\">" . $t . "</a>\n";
|
||||
}
|
||||
$toc .= "</ul>\n";
|
||||
|
||||
# insert the TOC
|
||||
$html =~ s/##TOC##/ $toc /;
|
||||
$html =~ s/##time##/ scalar localtime() /e;
|
||||
$html =~ s/##version##/$Graph::Easy::VERSION/e;
|
||||
|
||||
print $html;
|
||||
|
||||
# all done;
|
||||
|
||||
1;
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub out
|
||||
{
|
||||
my ($graph,$method) = @_;
|
||||
|
||||
$method = 'as_' . $method;
|
||||
|
||||
my $t = $graph->nodes() . ' Nodes, ' . $graph->edges . ' Edges';
|
||||
my $n = $t; $n =~ s/\s/_/;
|
||||
|
||||
$html .= "<a name=\"$n\"><h2>$t</h2></a>\n" .
|
||||
"<div style='float: left; min-widht: 30%'>\n" .
|
||||
"<h3>As Text</h3>\n" .
|
||||
"<pre>" . $graph->as_txt() . "</pre></div>" .
|
||||
"<div style='float: left;'>\n" .
|
||||
"<h3>As HTML:</h3>\n" .
|
||||
$graph->$method() . "</div>\n" .
|
||||
"<div style='clear: both;'> </div>\n\n";
|
||||
|
||||
push @toc, $t;
|
||||
}
|
||||
|
||||
39
perl/lib/Graph-Easy-0.76/examples/parse
Normal file
39
perl/lib/Graph-Easy-0.76/examples/parse
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
BEGIN { $|++; }
|
||||
|
||||
use strict;
|
||||
use lib 'lib';
|
||||
use Graph::Easy::Parser;
|
||||
|
||||
print "# Graph::Easy v$Graph::Easy::VERSION\n";
|
||||
|
||||
my $file = shift;
|
||||
$file = \*STDIN unless defined $file;
|
||||
my $id = shift || '';
|
||||
my $debug = shift || 0;
|
||||
|
||||
my $parser = Graph::Easy::Parser->new( debug => $debug );
|
||||
|
||||
my $graph = $parser->from_file( $file );
|
||||
|
||||
print "# input: '$file'\n";
|
||||
|
||||
die ($parser->error()) unless defined $graph;
|
||||
|
||||
print "# Graph has ", scalar $graph->nodes(),
|
||||
" nodes and ", scalar $graph->edges()," edges.\n";
|
||||
|
||||
$graph->id($id);
|
||||
$graph->timeout(240);
|
||||
$graph->layout();
|
||||
|
||||
warn ($graph->error()) if $graph->error();
|
||||
|
||||
print $graph->as_txt();
|
||||
|
||||
print $graph->as_ascii(), "\n";
|
||||
|
||||
print "<style type='text/css'>\n<!--\n",
|
||||
$graph->css(), "--></style>\n", $graph->as_html();
|
||||
|
||||
211
perl/lib/Graph-Easy-0.76/examples/syntax.pl
Normal file
211
perl/lib/Graph-Easy-0.76/examples/syntax.pl
Normal file
@@ -0,0 +1,211 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
#############################################################################
|
||||
# This example is a bit outdated, please use the new bin/grapheasy script -
|
||||
# which is after "make install" available in your system as simple as
|
||||
# "grapheasy" on any command line prompt.
|
||||
|
||||
#############################################################################
|
||||
# This script tries to generate graphs from all the files in t/syntax/
|
||||
# and outputs the result as an HTML page.
|
||||
# Use it like:
|
||||
|
||||
# examples/syntax.pl >test.html
|
||||
|
||||
# and then open test.html in your favourite browser.
|
||||
|
||||
BEGIN
|
||||
{
|
||||
chdir 'examples' if -d 'examples';
|
||||
use lib '../lib';
|
||||
}
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Graph::Easy::Parser;
|
||||
|
||||
my $parser = Graph::Easy::Parser->new( debug => 0);
|
||||
|
||||
my ($name, $template, $sep, @dirs) = @ARGV;
|
||||
|
||||
$name = 'Graph::Easy Test page' unless $name;
|
||||
$template = 'syntax.tpl' unless $template;
|
||||
|
||||
my @toc = ();
|
||||
|
||||
open FILE, $template or die ("Cannot read 'syntax.tpl': $!");
|
||||
local $/ = undef;
|
||||
my $html = <FILE>;
|
||||
close FILE;
|
||||
|
||||
my $output = ''; my $ID = '0';
|
||||
|
||||
# generate the parts and push their names into @toc
|
||||
gen_graphs($parser, @dirs);
|
||||
|
||||
my $toc = '<ul>';
|
||||
for my $t (@toc)
|
||||
{
|
||||
$toc .= " <li><a href='#$t->[0]'>$t->[1]</a>\n";
|
||||
}
|
||||
$toc .= "</ul>\n";
|
||||
|
||||
# insert the TOC
|
||||
$html =~ s/##TOC##/ $toc /;
|
||||
$html =~ s/##NAME##/ $name /;
|
||||
$html =~ s/##HTML##/ $output /;
|
||||
$html =~ s/##time##/ scalar localtime() /eg;
|
||||
$html =~ s/##version##/$Graph::Easy::VERSION/eg;
|
||||
|
||||
print $html;
|
||||
|
||||
# all done;
|
||||
|
||||
1;
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub gen_graphs
|
||||
{
|
||||
# for all files in a dir, generate a graph from it
|
||||
my $parser = shift;
|
||||
|
||||
@dirs = qw/syntax stress/ unless @dirs;
|
||||
|
||||
foreach my $dir (@dirs)
|
||||
{
|
||||
_for_all_files($parser, $dir);
|
||||
}
|
||||
}
|
||||
|
||||
sub _for_all_files
|
||||
{
|
||||
my ($parser, $dir) = @_;
|
||||
|
||||
opendir DIR, "../t/$dir" or die ("Cannot read dir '../t/$dir': $!");
|
||||
my @files = readdir DIR;
|
||||
closedir DIR;
|
||||
|
||||
foreach my $file (sort @files)
|
||||
{
|
||||
my $f = "../t/$dir/" . $file;
|
||||
next unless -f $f; # not a file?
|
||||
|
||||
print STDERR "# at file $f\n";
|
||||
|
||||
open FILE, "$f" or die ("Cannot read '$f': $!");
|
||||
local $/ = undef;
|
||||
my $input = <FILE>;
|
||||
close FILE;
|
||||
my $graph = $parser->from_text( $input );
|
||||
|
||||
if (!defined $graph)
|
||||
{
|
||||
my $error = $parser->error();
|
||||
$output .=
|
||||
"<h2>$dir/$file</h2>" .
|
||||
"<a class='top' href='#top' title='Go to the top'>Top -^</a>\n".
|
||||
"<div class='text'>\n".
|
||||
"Error: Could not parse input from $file: <b style='color: red;'>$error</b>".
|
||||
"<br>Input was:\n" .
|
||||
"<pre>$input</pre>\n".
|
||||
"</div>\n";
|
||||
next;
|
||||
}
|
||||
|
||||
$graph->timeout(100);
|
||||
$graph->layout();
|
||||
|
||||
if ($graph->error())
|
||||
{
|
||||
my $error = $graph->error();
|
||||
$output .=
|
||||
"<h2>$dir/$file</h2>" .
|
||||
"<a class='top' href='#top' title='Go to the top'>Top -^</a>\n".
|
||||
"<div class='text'>\n".
|
||||
"Error: $error</b>".
|
||||
"<br>Input was:\n" .
|
||||
"<pre>$input</pre>\n".
|
||||
"</div>\n";
|
||||
next;
|
||||
}
|
||||
|
||||
$output .= out ($input, $graph, 'html', $dir, $file);
|
||||
}
|
||||
}
|
||||
|
||||
sub out
|
||||
{
|
||||
my ($txt,$graph,$method,$dir, $file) = @_;
|
||||
|
||||
$method = 'as_' . $method;
|
||||
|
||||
# set unique ID for CSS
|
||||
$graph->id($ID++);
|
||||
|
||||
my $t = $graph->nodes() . ' Nodes, ' . $graph->edges . ' Edges';
|
||||
my $n = $dir."_$file";
|
||||
|
||||
$dir = ucfirst($dir);
|
||||
|
||||
# get comment
|
||||
$txt =~ /^\s*#\s*(.*)/;
|
||||
my $comment = ucfirst($1 || '');
|
||||
my $link;
|
||||
$link = $1 if $txt =~ /\n#\s*(http.*)/;
|
||||
|
||||
my $name = $comment || $t;
|
||||
push @toc, [ $n, $name ];
|
||||
|
||||
my $out =
|
||||
"<style type='text/css'>\n" .
|
||||
"<!--\n" .
|
||||
$graph->css() .
|
||||
"-->\n" .
|
||||
"</style>\n";
|
||||
|
||||
if (!$sep)
|
||||
{
|
||||
$out .=
|
||||
"<a name=\"$n\"></a><h2>$dir: $name</h2>\n" .
|
||||
"<a class='top' href='#top' title='Go to the top'>Top -^</a>\n".
|
||||
"<div class='text'>\n";
|
||||
|
||||
$out .= "<span style='color: red; font-weight: bold;'>Error: </span>" .
|
||||
$graph->error() if $graph->error();
|
||||
|
||||
my $input =
|
||||
"<div style='float: left;'>\n" .
|
||||
" <h3>Input</h3>\n" .
|
||||
" <pre>$txt</pre>\n</div>" .
|
||||
"<div style='float: left;'>\n" .
|
||||
" <h3>As Text</h3>\n" .
|
||||
"<pre>" . $graph->as_txt() . "</pre>\n</div>";
|
||||
|
||||
$out .= $input .
|
||||
"<div style='float: left;'>\n" .
|
||||
"<h3>As HTML:</h3>\n" .
|
||||
$graph->$method() . "\n</div>\n";
|
||||
$out .= "<div class='clear'> </div></div>\n\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
$out .=
|
||||
"<a name=\"$n\"></a><h3>$name</h3>\n";
|
||||
|
||||
$out .= "<a class='top' href='#top' title='Go to the top'>Top -^</a>\n";
|
||||
$out .= "<a class='top' href='$link' style='color: red;'>Source</a>\n" if $link;
|
||||
|
||||
$out .= "<span style='color: red; font-weight: bold;'>Error: </span> " .
|
||||
$graph->error() if $graph->error();
|
||||
|
||||
$out .= $graph->$method() . "\n" .
|
||||
"<div class='clear'></div>\n\n";
|
||||
# write out the input/text
|
||||
}
|
||||
|
||||
$out;
|
||||
}
|
||||
|
||||
|
||||
72
perl/lib/Graph-Easy-0.76/examples/syntax.tpl
Normal file
72
perl/lib/Graph-Easy-0.76/examples/syntax.tpl
Normal file
@@ -0,0 +1,72 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title><graph>-Plugin for Mediawiki - Syntax</title>
|
||||
<meta name="MSSmartTagsPreventParsing" content="TRUE">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta http-equiv="imagetoolbar" content="no">
|
||||
<link rel="stylesheet" type="text/css" href="base.css">
|
||||
<style type="text/css">
|
||||
.graph { margin-left: 2em; }
|
||||
pre { margin-right: 1.5em; }
|
||||
</style>
|
||||
</head>
|
||||
<body bgcolor=white text=black>
|
||||
|
||||
<a name="top"></a>
|
||||
|
||||
<div class="menu">
|
||||
<a class="menubck" href="index.html" title="Back to the main page">Main</a>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
|
||||
<h1><graph>-Plugin for Mediawiki</h1>
|
||||
|
||||
<h2>##NAME##</h2>
|
||||
|
||||
<div class="text">
|
||||
|
||||
<p>
|
||||
This page was automatically created at <strong><small>##time##</small></strong> by <code>examples/syntax.pl</code> running
|
||||
<a href="http://search.cpan.org/~tels/Graph-Simple/" title="Get it from search.cpan.org">Graph::Easy</a> v##version##.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
On each of the following testcases you will see the original text
|
||||
representation of the graph, a text representation created automatically
|
||||
from the parsed input, as well the automatically generated HTML+CSS code.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Notes:</strong>
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>The limitations in
|
||||
<a href="http://search.cpan.org/~tels/Graph-Simple/lib/Graph/Simple.pm#LIMITATIONS">Graph::Easy</a> apply.
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<h2>Table of Contents:</h2>
|
||||
|
||||
<div class="text">
|
||||
##TOC##
|
||||
</div>
|
||||
|
||||
##HTML##
|
||||
|
||||
<div class="footer">
|
||||
|
||||
<p>
|
||||
This page was automatically created at <strong><small>##time##</small></strong> by <code>examples/syntax.pl</code> running
|
||||
<a href="http://search.cpan.org/~tels/Graph-Simple/" title="Get it from search.cpan.org">Graph::Easy</a> v##version##.
|
||||
Contact <a href="/mail.html">Tels</a> for help.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</div> <!-- right cell ends here -->
|
||||
|
||||
</body></html>
|
||||
318
perl/lib/Graph-Easy-0.76/examples/wikicrawl.pl
Normal file
318
perl/lib/Graph-Easy-0.76/examples/wikicrawl.pl
Normal file
@@ -0,0 +1,318 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use Graph::Easy;
|
||||
use LWP;
|
||||
use HTML::TokeParser;
|
||||
use utf8;
|
||||
use Getopt::Long;
|
||||
use Encode;
|
||||
use Data::Dumper;
|
||||
|
||||
my $VERSION = 0.03;
|
||||
|
||||
# things that shouldn't be looked at
|
||||
my %bad = map { $_ => 1 } qw/
|
||||
Wikipedia Image Talk Help Template Portal Special User Category
|
||||
Wikipedia Bild Diskussion Hilfe Vorlage Portal Spezial Benutzer Kategorie
|
||||
Wikipédia Image Discuter Modèle Mod%C3%A9le Aide Utilisateur Catégorie Cat%C3%A9gorie
|
||||
/;
|
||||
# do not crawl these:
|
||||
my $skip = qr/\((disambiguation|Begriffsklärung|Homonymie)\)/i;
|
||||
# to figure out redirections
|
||||
my $redir = qr/(Weitergeleitet von|Redirected from|Redirig. depuis).*?title="(.*?)"/i;
|
||||
|
||||
# the default settings are defined in get_options()
|
||||
# option handling
|
||||
my $help_requested = 0; $help_requested = 1 if @ARGV == 0;
|
||||
|
||||
my $opt = get_options();
|
||||
|
||||
# error?
|
||||
$help_requested = 1 if !ref($opt);
|
||||
|
||||
# no error and --help was specified
|
||||
$help_requested = 2 if ref($opt) && $opt->{help} ne '';
|
||||
|
||||
my $copyright = "wikicrawl v$VERSION (c) by Tels 2008. "
|
||||
."Released under the GPL 2.0 or later.\n\n"
|
||||
."After a very cool idea by 'integral' on forum.xkcd.com. Thanx! :)\n\n";
|
||||
|
||||
if (ref($opt) && $opt->{version} != 0)
|
||||
{
|
||||
print $copyright;
|
||||
print "Running under Perl v$].\n\n";
|
||||
exit 2;
|
||||
}
|
||||
|
||||
if ($help_requested > 0)
|
||||
{
|
||||
print STDERR $copyright;
|
||||
require Pod::Usage;
|
||||
if ($help_requested > 1 && $Pod::Usage::VERSION < 1.35)
|
||||
{
|
||||
# The way old Pod::Usage executes "perldoc" might fail:
|
||||
system('perldoc', $0);
|
||||
exit 2;
|
||||
}
|
||||
Pod::Usage::pod2usage( { -exitval => 2, -verbose => $help_requested } );
|
||||
}
|
||||
|
||||
my $verbose = $opt->{verbose};
|
||||
|
||||
output ($copyright);
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
# set some default attributes on the graph
|
||||
$graph->set_attribute('node','shape',$opt->{nodeshape});
|
||||
$graph->set_attribute('node','font-size','80%');
|
||||
$graph->set_attribute('edge','arrowstyle','filled');
|
||||
$graph->set_attribute('graph','label',"Wikipedia map for $opt->{root}");
|
||||
$graph->set_attribute('graph','font-size', '200%');
|
||||
$graph->set_attribute('graph','comment', "Created with wikicrawl.pl v$VERSION");
|
||||
|
||||
output ("Using the following settings:\n");
|
||||
print Data::Dumper->Dump([$opt], ['opt']);
|
||||
|
||||
# don't crawl stuff twice
|
||||
my %visitedLinks;
|
||||
# re-use the UserAgent object
|
||||
my $ua = LWP::UserAgent->new();
|
||||
#$ua->agent("WikiCrawl/$VERSION - " . $ua->_agent . " - vGraph::Easy $Graph::Easy::VERSION");
|
||||
|
||||
# count how many we have done
|
||||
my $nodes = 0;
|
||||
|
||||
# enable UTF-8 output
|
||||
binmode STDERR, ':utf8';
|
||||
binmode STDOUT, ':utf8';
|
||||
|
||||
# push the first node on the stack
|
||||
my @todo = [$opt->{root},0];
|
||||
# and work on it (this will take one off and then push more nodes on it)
|
||||
while (@todo && crawl()) { };
|
||||
|
||||
my $file = "wikicrawl-$opt->{lang}.txt";
|
||||
output ("Generating $file:\n");
|
||||
open(my $DATA, ">", "$file") or die("Could not write to '$file': $!");
|
||||
binmode ($DATA,':utf8');
|
||||
print $DATA $graph->as_txt();
|
||||
close $DATA;
|
||||
output ("All done.\n");
|
||||
|
||||
my $png = $file; $png =~ s/.txt/.png/;
|
||||
|
||||
output ("Generating $png:\n");
|
||||
`perl -Ilib bin/graph-easy --png --renderer=$opt->{renderer} $file`;
|
||||
|
||||
output ("All done.\n");
|
||||
|
||||
########################################################################################
|
||||
|
||||
# main crawl routine
|
||||
sub crawl {
|
||||
no warnings 'recursion';
|
||||
|
||||
# all done?
|
||||
return if @todo == 0;
|
||||
my ($name,$depth) = ($todo[0]->[0],$todo[0]->[1]);
|
||||
shift @todo;
|
||||
|
||||
my $page = "http://$opt->{lang}.wikipedia.org/wiki/$name";
|
||||
|
||||
# limit depth
|
||||
return if $depth + 1 > $opt->{maxdepth};
|
||||
# already did as many nodes?
|
||||
return if $opt->{maxnodes} > 0 && $nodes > $opt->{maxnodes};
|
||||
# skip this page
|
||||
return 1 if exists $visitedLinks{$page};
|
||||
|
||||
# crawl page
|
||||
my $res = $ua->request(HTTP::Request->new(GET => $page));
|
||||
return 1 unless $res->is_success();
|
||||
|
||||
# remove the " - Wikipedia" (en) or " – Wikipedia" (de) from the title
|
||||
my $title = decode('utf8',$res->title); # convert to UTF-8
|
||||
$title =~ s/ [–-] Wikip[ée]dia.*//;
|
||||
return 1 if $title =~ $skip; # no disambiguation pages
|
||||
|
||||
# tels: not sure when/why these happen:
|
||||
print STDERR "# $title ",$res->title()," $page\n" if $title eq '';
|
||||
|
||||
output ("Crawling node #$nodes '$title' at depth $depth\n"); $nodes++;
|
||||
|
||||
# set flag
|
||||
$visitedLinks{$page} = undef;
|
||||
my $content = $res->content;
|
||||
|
||||
# parse anchors
|
||||
my $parser = HTML::TokeParser->new(\$content) or die("Could not parse page.");
|
||||
|
||||
# handle redirects:
|
||||
$content = decode('utf-8', $content);
|
||||
$content =~ $redir; my $old = $2;
|
||||
|
||||
if ($old)
|
||||
{
|
||||
output (" Redirected to '$title' from '$old'\n");
|
||||
# find the node named "$old" (at the same time adding it if it didn't exist yet)
|
||||
my $source = $graph->add_node($old);
|
||||
# and mention the redirect in the label
|
||||
$source->set_attribute('label', "$old\\n($title)");
|
||||
# now force edges to come from that node
|
||||
$title = $old;
|
||||
}
|
||||
|
||||
# iterate over all links
|
||||
for(my $i = 0; (my $token = $parser->get_tag("a")) && ($i < $opt->{maxspread} || $opt->{maxspread} == 0);)
|
||||
{
|
||||
my $url = $token->[1]{href};
|
||||
my $alt = $token->[1]{title};
|
||||
|
||||
next unless defined $url;
|
||||
# we do not crawl these:
|
||||
next if $url !~ m/^\/wiki\//; # no pages outside of wikipedia
|
||||
next if $alt =~ $skip; # no disambiguation pages
|
||||
next if $alt =~ m/\[/; # no brackets
|
||||
|
||||
my @chunks = split ":", substr(decode('utf-8',$url), 6); # extract special pages, if any
|
||||
next if exists $bad{$chunks[0]}; # no bad pages
|
||||
|
||||
$i++;
|
||||
if ($title ne $alt)
|
||||
{
|
||||
output (" Adding link from '$title' to '$alt'\n", 1);
|
||||
my ($from,$to,$edge) = $graph->add_edge_once($title,$alt);
|
||||
if (defined $to)
|
||||
{
|
||||
my $old_depth = $to->raw_attribute('rank');
|
||||
if (!$old_depth)
|
||||
{
|
||||
my $color = sprintf("%i", (360 / $opt->{maxdepth}) * ($depth));
|
||||
$to->set_attribute('fill', 'hsl(' .$color.',1,0.7)');
|
||||
# store rank
|
||||
$to->set_attribute('rank', $depth+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
my $u = $url; $u =~ s/^\/wiki\///;
|
||||
push @todo, [$u,$depth+1];
|
||||
}
|
||||
|
||||
# continue
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub get_options
|
||||
{
|
||||
my $opt = {};
|
||||
$opt->{help} = '';
|
||||
$opt->{version} = 0;
|
||||
# max depth to crawl
|
||||
$opt->{maxdepth} = 4;
|
||||
# max number of links per node
|
||||
$opt->{maxspread} = 5;
|
||||
# stop after so many nodes, -1 to disable
|
||||
$opt->{maxnodes} = -1;
|
||||
# language
|
||||
$opt->{lang} = 'en';
|
||||
# root node
|
||||
$opt->{root} = 'Xkcd';
|
||||
$opt->{renderer} = 'neato';
|
||||
$opt->{nodeshape} = 'rect';
|
||||
my @o = (
|
||||
"language=s" => \$opt->{lang},
|
||||
"root=s" => \$opt->{root},
|
||||
"maxdepth=i" => \$opt->{maxdepth},
|
||||
"maxspread=i" => \$opt->{maxspread},
|
||||
"maxnodes=i" => \$opt->{maxnodes},
|
||||
"version" => \$opt->{version},
|
||||
"help|?" => \$opt->{help},
|
||||
"verbose" => \$opt->{verbose},
|
||||
"nodeshape" => \$opt->{nodeshape},
|
||||
);
|
||||
return unless Getopt::Long::GetOptions (@o);
|
||||
$opt;
|
||||
}
|
||||
|
||||
sub output
|
||||
{
|
||||
my ($txt, $level) = @_;
|
||||
|
||||
$level |= 0;
|
||||
|
||||
print STDERR $txt if $opt->{verbose} || $level == 0;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
wikicrawl - crawl Wikipedia to generate graph from the found article links
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Crawl wikipedia and create a L<Graph::Easy> text describing the inter-article links
|
||||
that were found during the crawl.
|
||||
|
||||
At least one argument must be given to start:
|
||||
|
||||
perl examples/wikicrawl.pl --lang=fr
|
||||
|
||||
=head1 ARGUMENTS
|
||||
|
||||
Here are the options:
|
||||
|
||||
=over 12
|
||||
|
||||
=item --help
|
||||
|
||||
Print the full documentation, not just this short overview.
|
||||
|
||||
=item --version
|
||||
|
||||
Write version info and exit.
|
||||
|
||||
=item --language
|
||||
|
||||
Select the language of Wikipedia that we should crawl. Currently supported
|
||||
are 'de', 'en' and 'fr'. Default is 'en'.
|
||||
|
||||
=item --root
|
||||
|
||||
Set the root node where the crawl should start. Default is of course 'Xkcd'.
|
||||
|
||||
=item --maxdepth
|
||||
|
||||
The maximum depth the crawl should go. Please select small values under 10. Default is 4.
|
||||
|
||||
=item --maxspread
|
||||
|
||||
The maximum number of links we follow per article. Please select small values under 10. Default is 5.
|
||||
|
||||
=item --maxnodes
|
||||
|
||||
The maximum number of nodes we crawl. Set to -1 (default) to disable.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<http://forums.xkcd.com/viewtopic.php?f=2&t=21300&p=672184> and
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
This library is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GPL.
|
||||
|
||||
See the LICENSE file of Graph::Easy for a copy of the GPL.
|
||||
|
||||
X<license>
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2008 by integral L<forum.xkcd.com>
|
||||
Copyright (C) 2008 by Tels L<http://bloodgate.com>
|
||||
|
||||
=cut
|
||||
79
perl/lib/Graph-Easy-0.76/inc/Test/Run/Builder.pm
Normal file
79
perl/lib/Graph-Easy-0.76/inc/Test/Run/Builder.pm
Normal file
@@ -0,0 +1,79 @@
|
||||
package Test::Run::Builder;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Module::Build;
|
||||
|
||||
use vars qw(@ISA);
|
||||
|
||||
@ISA = (qw(Module::Build));
|
||||
|
||||
sub ACTION_runtest
|
||||
{
|
||||
my ($self) = @_;
|
||||
my $p = $self->{properties};
|
||||
|
||||
$self->depends_on('code');
|
||||
|
||||
local @INC = @INC;
|
||||
|
||||
# Make sure we test the module in blib/
|
||||
unshift @INC, (File::Spec->catdir($p->{base_dir}, $self->blib, 'lib'),
|
||||
File::Spec->catdir($p->{base_dir}, $self->blib, 'arch'));
|
||||
|
||||
$self->do_test_run_tests;
|
||||
}
|
||||
|
||||
sub ACTION_distruntest {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->depends_on('distdir');
|
||||
|
||||
my $start_dir = $self->cwd;
|
||||
my $dist_dir = $self->dist_dir;
|
||||
chdir $dist_dir or die "Cannot chdir to $dist_dir: $!";
|
||||
# XXX could be different names for scripts
|
||||
|
||||
$self->run_perl_script('Build.PL') # XXX Should this be run w/ --nouse-rcfile
|
||||
or die "Error executing 'Build.PL' in dist directory: $!";
|
||||
$self->run_perl_script('Build')
|
||||
or die "Error executing 'Build' in dist directory: $!";
|
||||
$self->run_perl_script('Build', [], ['runtest'])
|
||||
or die "Error executing 'Build test' in dist directory";
|
||||
chdir $start_dir;
|
||||
}
|
||||
|
||||
sub do_test_run_tests
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
require Test::Run::CmdLine::Iface;
|
||||
|
||||
my $test_run =
|
||||
Test::Run::CmdLine::Iface->new(
|
||||
{
|
||||
'test_files' => [glob("t/*.t")],
|
||||
}
|
||||
# 'backend_params' => $self->_get_backend_params(),
|
||||
);
|
||||
|
||||
return $test_run->run();
|
||||
}
|
||||
|
||||
sub ACTION_tags
|
||||
{
|
||||
my $self = shift;
|
||||
return
|
||||
$self->do_system(
|
||||
"ctags",
|
||||
qw(-f tags --recurse --totals
|
||||
--exclude=blib/** --exclude=t/lib/**
|
||||
--exclude=**/.svn/** --exclude='*~'),
|
||||
"--exclude=".$self->dist_name()."-*/**",
|
||||
qw(--languages=Perl --langmap=Perl:+.t)
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
4203
perl/lib/Graph-Easy-0.76/lib/Graph/Easy.pm
Normal file
4203
perl/lib/Graph-Easy-0.76/lib/Graph/Easy.pm
Normal file
File diff suppressed because it is too large
Load Diff
1428
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/As_ascii.pm
Normal file
1428
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/As_ascii.pm
Normal file
File diff suppressed because it is too large
Load Diff
396
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/As_graphml.pm
Normal file
396
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/As_graphml.pm
Normal file
@@ -0,0 +1,396 @@
|
||||
#############################################################################
|
||||
# Output an Graph::Easy object as GraphML text
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::As_graphml;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Graph::Easy::Attributes;
|
||||
|
||||
# map the Graph::Easy attribute types to a GraphML name:
|
||||
my $attr_type_to_name =
|
||||
{
|
||||
ATTR_STRING() => 'string',
|
||||
ATTR_COLOR() => 'string',
|
||||
ATTR_ANGLE() => 'double',
|
||||
ATTR_PORT() => 'string',
|
||||
ATTR_UINT() => 'integer',
|
||||
ATTR_URL() => 'string',
|
||||
|
||||
ATTR_LIST() => 'string',
|
||||
ATTR_LCTEXT() => 'string',
|
||||
ATTR_TEXT() => 'string',
|
||||
};
|
||||
|
||||
sub _graphml_attr_keys
|
||||
{
|
||||
my ($self, $tpl, $tpl_no_default, $class, $att, $ids, $id) = @_;
|
||||
|
||||
my $base_class = $class; $base_class =~ s/\..*//;
|
||||
$base_class = 'graph' if $base_class =~ /group/;
|
||||
$ids->{$base_class} = {} unless ref $ids->{$base_class};
|
||||
|
||||
my $txt = '';
|
||||
for my $name (sort keys %$att)
|
||||
{
|
||||
my $entry = $self->_attribute_entry($class,$name);
|
||||
# get a fresh template
|
||||
my $t = $tpl;
|
||||
$t = $tpl_no_default unless defined $entry->[ ATTR_DEFAULT_SLOT ];
|
||||
|
||||
# only keep it once
|
||||
next if exists $ids->{$base_class}->{$name};
|
||||
|
||||
$t =~ s/##id##/$$id/;
|
||||
|
||||
# node.foo => node, group.bar => graph
|
||||
$t =~ s/##class##/$base_class/;
|
||||
$t =~ s/##name##/$name/;
|
||||
$t =~ s/##type##/$attr_type_to_name->{ $entry->[ ATTR_TYPE_SLOT ] || ATTR_COLOR }/eg;
|
||||
|
||||
# will only be there and thus replaced if we have a default
|
||||
if ($t =~ /##default##/)
|
||||
{
|
||||
my $def = $entry->[ ATTR_DEFAULT_SLOT ];
|
||||
# not a simple value?
|
||||
$def = $self->default_attribute($name) if ref $def;
|
||||
$t =~ s/##default##/$def/;
|
||||
}
|
||||
|
||||
# remember name => ID
|
||||
$ids->{$base_class}->{$name} = $$id; $$id++;
|
||||
# append the definition
|
||||
$txt .= $t;
|
||||
}
|
||||
$txt;
|
||||
}
|
||||
|
||||
# yED example:
|
||||
|
||||
# <data key="d0">
|
||||
# <y:ShapeNode>
|
||||
# <y:Geometry height="30.0" width="30.0" x="277.0" y="96.0"/>
|
||||
# <y:Fill color="#FFCC00" transparent="false"/>
|
||||
# <y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
# <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="11.0" x="9.5" y="5.6494140625">1</y:NodeLabel>
|
||||
# <y:Shape type="ellipse"/>
|
||||
# </y:ShapeNode>
|
||||
# </data>
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
sub _as_graphml
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
my $args = $_[0];
|
||||
$args = { name => $_[0] } if ref($args) ne 'HASH' && @_ == 1;
|
||||
$args = { @_ } if ref($args) ne 'HASH' && @_ > 1;
|
||||
|
||||
$args->{format} = 'graph-easy' unless defined $args->{format};
|
||||
|
||||
if ($args->{format} !~ /^(graph-easy|Graph::Easy|yED)\z/i)
|
||||
{
|
||||
return $self->error("Format '$args->{format}' not understood by as_graphml.");
|
||||
}
|
||||
my $format = $args->{format};
|
||||
|
||||
# Convert the graph to a textual representation - does not need layout().
|
||||
|
||||
my $schema = "http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd";
|
||||
$schema = "http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd" if $format eq 'yED';
|
||||
my $y_schema = '';
|
||||
$y_schema = "\n xmlns:y=\"http://www.yworks.com/xml/graphml\"" if $format eq 'yED';
|
||||
|
||||
my $txt = <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"##Y##
|
||||
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
|
||||
##SCHEMA##">
|
||||
|
||||
<!-- Created by Graph::Easy v##VERSION## at ##DATE## -->
|
||||
|
||||
EOF
|
||||
;
|
||||
|
||||
$txt =~ s/##DATE##/scalar localtime()/e;
|
||||
$txt =~ s/##VERSION##/$Graph::Easy::VERSION/;
|
||||
$txt =~ s/##SCHEMA##/$schema/;
|
||||
$txt =~ s/##Y##/$y_schema/;
|
||||
|
||||
# <key id="d0" for="node" attr.name="color" attr.type="string">
|
||||
# <default>yellow</default>
|
||||
# </key>
|
||||
# <key id="d1" for="edge" attr.name="weight" attr.type="double"/>
|
||||
|
||||
# First gather all possible attributes, then add defines for them. This
|
||||
# avoids lengthy re-definitions of attributes that aren't used:
|
||||
|
||||
my %keys;
|
||||
|
||||
my $tpl = ' <key id="##id##" for="##class##" attr.name="##name##" attr.type="##type##">'
|
||||
."\n <default>##default##</default>\n"
|
||||
." </key>\n";
|
||||
my $tpl_no_default = ' <key id="##id##" for="##class##" attr.name="##name##" attr.type="##type##"/>'."\n";
|
||||
|
||||
# for yED:
|
||||
# <key for="node" id="d0" yfiles.type="nodegraphics"/>
|
||||
# <key attr.name="description" attr.type="string" for="node" id="d1"/>
|
||||
# <key for="edge" id="d2" yfiles.type="edgegraphics"/>
|
||||
# <key attr.name="description" attr.type="string" for="edge" id="d3"/>
|
||||
# <key for="graphml" id="d4" yfiles.type="resources"/>
|
||||
|
||||
# we need to remember the mapping between attribute name and ID:
|
||||
my $ids = {};
|
||||
my $id = 'd0';
|
||||
|
||||
###########################################################################
|
||||
# first the class attributes
|
||||
for my $class (sort keys %{$self->{att}})
|
||||
{
|
||||
my $att = $self->{att}->{$class};
|
||||
|
||||
$txt .=
|
||||
$self->_graphml_attr_keys( $tpl, $tpl_no_default, $class, $att, $ids, \$id);
|
||||
|
||||
}
|
||||
|
||||
my @nodes = $self->sorted_nodes('name','id');
|
||||
|
||||
###########################################################################
|
||||
# now the attributes on the objects:
|
||||
for my $o (@nodes, ord_values ( $self->{edges} ))
|
||||
{
|
||||
$txt .=
|
||||
$self->_graphml_attr_keys( $tpl, $tpl_no_default, $o->class(),
|
||||
$o->raw_attributes(), $ids, \$id);
|
||||
}
|
||||
$txt .= "\n" unless $id eq 'd0';
|
||||
|
||||
my $indent = ' ';
|
||||
$txt .= $indent . '<graph id="G" edgedefault="' . $self->type() . "\">\n";
|
||||
|
||||
# output graph attributes:
|
||||
$txt .= $self->_attributes_as_graphml($self,' ',$ids->{graph});
|
||||
|
||||
# output groups recursively
|
||||
my @groups = $self->groups_within(0);
|
||||
foreach my $g (@groups)
|
||||
{
|
||||
$txt .= $g->as_graphml($indent.' ',$ids); # marks nodes as processed if nec.
|
||||
}
|
||||
|
||||
$indent = ' ';
|
||||
foreach my $n (@nodes)
|
||||
{
|
||||
next if $n->{group}; # already done in a group
|
||||
$txt .= $n->as_graphml($indent,$ids); # <node id="..." ...>
|
||||
}
|
||||
|
||||
$txt .= "\n";
|
||||
|
||||
foreach my $n (@nodes)
|
||||
{
|
||||
next if $n->{group}; # already done in a group
|
||||
|
||||
my @out = $n->sorted_successors();
|
||||
# for all outgoing connections
|
||||
foreach my $other (@out)
|
||||
{
|
||||
# in case there exists more than one edge from $n --> $other
|
||||
my @edges = $n->edges_to($other);
|
||||
for my $edge (sort { $a->{id} <=> $b->{id} } @edges)
|
||||
{
|
||||
$txt .= $edge->as_graphml($indent,$ids); # <edge id="..." ...>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$txt .= " </graph>\n</graphml>\n";
|
||||
$txt;
|
||||
}
|
||||
|
||||
sub _safe_xml
|
||||
{
|
||||
# make a text XML safe
|
||||
my ($self,$txt) = @_;
|
||||
|
||||
$txt =~ s/&/&/g; # quote &
|
||||
$txt =~ s/>/>/g; # quote >
|
||||
$txt =~ s/</</g; # quote <
|
||||
$txt =~ s/"/"/g; # quote "
|
||||
$txt =~ s/'/'/g; # quote '
|
||||
$txt =~ s/\\\\/\\/g; # "\\" to "\"
|
||||
|
||||
$txt;
|
||||
}
|
||||
|
||||
sub _attributes_as_graphml
|
||||
{
|
||||
# output the attributes of an object
|
||||
my ($graph, $self, $indent, $ids) = @_;
|
||||
|
||||
my $tpl = "$indent <data key=\"##id##\">##value##</data>\n";
|
||||
my $att = $self->get_attributes();
|
||||
my $txt = '';
|
||||
for my $n (sort keys %$att)
|
||||
{
|
||||
next unless exists $ids->{$n};
|
||||
my $def = $self->default_attribute($n);
|
||||
next if defined $def && $def eq $att->{$n};
|
||||
my $t = $tpl;
|
||||
$t =~ s/##id##/$ids->{$n}/;
|
||||
$t =~ s/##value##/$graph->_safe_xml($att->{$n})/e;
|
||||
$txt .= $t;
|
||||
}
|
||||
$txt;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Group;
|
||||
|
||||
use strict;
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
sub as_graphml
|
||||
{
|
||||
my ($self, $indent, $ids) = @_;
|
||||
|
||||
my $txt = $indent . '<graph id="' . $self->_safe_xml($self->{name}) . '" edgedefault="' .
|
||||
$self->{graph}->type() . "\">\n";
|
||||
$txt .= $self->{graph}->_attributes_as_graphml($self, $indent, $ids->{graph});
|
||||
|
||||
foreach my $n (ord_values ( $self->{nodes} ))
|
||||
{
|
||||
my @out = $n->sorted_successors();
|
||||
|
||||
$txt .= $n->as_graphml($indent.' ', $ids); # <node id="..." ...>
|
||||
|
||||
# for all outgoing connections
|
||||
foreach my $other (@out)
|
||||
{
|
||||
# in case there exists more than one edge from $n --> $other
|
||||
my @edges = $n->edges_to($other);
|
||||
for my $edge (sort { $a->{id} <=> $b->{id} } @edges)
|
||||
{
|
||||
$txt .= $edge->as_graphml($indent.' ',$ids);
|
||||
}
|
||||
$txt .= "\n" if @edges > 0;
|
||||
}
|
||||
}
|
||||
|
||||
# output groups recursively
|
||||
my @groups = $self->groups_within(0);
|
||||
foreach my $g (@groups)
|
||||
{
|
||||
$txt .= $g->_as_graphml($indent.' ',$ids); # marks nodes as processed if nec.
|
||||
}
|
||||
|
||||
# XXX TODO: edges from/to this group
|
||||
|
||||
# close this group
|
||||
$txt .= $indent . "</graph>";
|
||||
|
||||
$txt;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Node;
|
||||
|
||||
use strict;
|
||||
|
||||
sub as_graphml
|
||||
{
|
||||
my ($self, $indent, $ids) = @_;
|
||||
|
||||
my $g = $self->{graph};
|
||||
my $txt = $indent . '<node id="' . $g->_safe_xml($self->{name}) . "\">\n";
|
||||
|
||||
$txt .= $g->_attributes_as_graphml($self, $indent, $ids->{node});
|
||||
|
||||
$txt .= "$indent</node>\n";
|
||||
|
||||
return $txt;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Edge;
|
||||
|
||||
use strict;
|
||||
|
||||
sub as_graphml
|
||||
{
|
||||
my ($self, $indent, $ids) = @_;
|
||||
|
||||
my $g = $self->{graph};
|
||||
my $txt = $indent . '<edge source="' . $g->_safe_xml($self->{from}->{name}) .
|
||||
'" target="' . $g->_safe_xml($self->{to}->{name}) . "\">\n";
|
||||
|
||||
$txt .= $g->_attributes_as_graphml($self, $indent, $ids->{edge});
|
||||
|
||||
$txt .= "$indent</edge>\n";
|
||||
|
||||
$txt;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::As_graphml - Generate a GraphML text from a Graph::Easy object
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
$graph->add_edge ('Bonn', 'Berlin');
|
||||
|
||||
print $graph->as_graphml();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<Graph::Easy::As_graphml> contains just the code for converting a
|
||||
L<Graph::Easy|Graph::Easy> object to a GraphML text.
|
||||
|
||||
=head2 Attributes
|
||||
|
||||
Attributes are output in the format that C<Graph::Easy> specifies. More
|
||||
details about the valid attributes and their default values can be found
|
||||
in the Graph::Easy online manual:
|
||||
|
||||
L<http://bloodgate.com/perl/graph/manual/>.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
Exports nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>, L<http://graphml.graphdrawing.org/>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2008 by Tels L<http://bloodgate.com>
|
||||
|
||||
See the LICENSE file for information.
|
||||
|
||||
=cut
|
||||
|
||||
1249
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/As_graphviz.pm
Normal file
1249
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/As_graphviz.pm
Normal file
File diff suppressed because it is too large
Load Diff
487
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/As_txt.pm
Normal file
487
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/As_txt.pm
Normal file
@@ -0,0 +1,487 @@
|
||||
#############################################################################
|
||||
# Output an Graph::Easy object as textual description
|
||||
#
|
||||
|
||||
package Graph::Easy::As_txt;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub _as_txt
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
# Convert the graph to a textual representation - does not need layout().
|
||||
$self->_assign_ranks();
|
||||
|
||||
# generate the class attributes first
|
||||
my $txt = '';
|
||||
my $att = $self->{att};
|
||||
for my $class (sort keys %$att)
|
||||
{
|
||||
|
||||
my $out = $self->_remap_attributes(
|
||||
$class, $att->{$class}, {}, 'noquote', 'encode' );
|
||||
|
||||
my $att = '';
|
||||
for my $atr (sort keys %$out)
|
||||
{
|
||||
# border is handled special below
|
||||
next if $atr =~ /^border/;
|
||||
$att .= " $atr: $out->{$atr};\n";
|
||||
}
|
||||
|
||||
# edges do not have a border
|
||||
if ($class !~ /^edge/)
|
||||
{
|
||||
my $border = $self->border_attribute($class) || '';
|
||||
|
||||
# 'solid 1px #000000' =~ /^solid/;
|
||||
# 'solid 1px #000000' =~ /^solid 1px #000000/;
|
||||
$border = '' if $self->default_attribute($class,'border') =~ /^$border/;
|
||||
|
||||
$att .= " border: $border;\n" if $border ne '';
|
||||
}
|
||||
|
||||
if ($att ne '')
|
||||
{
|
||||
# the following makes short, single definitions to fit on one line
|
||||
if ($att !~ /\n.*\n/ && length($att) < 40)
|
||||
{
|
||||
$att =~ s/\n/ /; $att =~ s/^ / /;
|
||||
}
|
||||
else
|
||||
{
|
||||
$att = "\n$att";
|
||||
}
|
||||
$txt .= "$class {$att}\n";
|
||||
}
|
||||
}
|
||||
|
||||
$txt .= "\n" if $txt ne ''; # insert newline
|
||||
|
||||
my @nodes = $self->sorted_nodes('name','id');
|
||||
|
||||
my $count = 0;
|
||||
# output nodes with attributes first, sorted by their name
|
||||
foreach my $n (@nodes)
|
||||
{
|
||||
$n->{_p} = undef; # mark as not yet processed
|
||||
my $att = $n->attributes_as_txt();
|
||||
if ($att ne '')
|
||||
{
|
||||
$n->{_p} = 1; # mark as processed
|
||||
$count++;
|
||||
$txt .= $n->as_pure_txt() . $att . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$txt .= "\n" if $count > 0; # insert a newline
|
||||
|
||||
# output groups first, with their nodes
|
||||
foreach my $gn (sort keys %{$self->{groups}})
|
||||
{
|
||||
my $group = $self->{groups}->{$gn};
|
||||
$txt .= $group->as_txt(); # marks nodes as processed if nec.
|
||||
$count++;
|
||||
}
|
||||
|
||||
# XXX TODO:
|
||||
# Output all nodes with rank=0 first, and also follow their successors
|
||||
# What is left will then be done next, with rank=1 etc.
|
||||
# This output order let's us output node chains in compact form as:
|
||||
# [A]->[B]->[C]->[D]
|
||||
# [B]->[E]
|
||||
# instead of having:
|
||||
# [A]->[B]
|
||||
# [B]->[E]
|
||||
# [B]->[C] etc
|
||||
|
||||
@nodes = $self->sorted_nodes('rank','name');
|
||||
foreach my $n (@nodes)
|
||||
{
|
||||
my @out = $n->sorted_successors();
|
||||
my $first = $n->as_pure_txt(); # [ A | B ]
|
||||
if ( defined $n->{autosplit} || ((@out == 0) && ( (scalar $n->predecessors() || 0) == 0)))
|
||||
{
|
||||
# single node without any connections (unless already output)
|
||||
next if exists $n->{autosplit} && !defined $n->{autosplit};
|
||||
$txt .= $first . "\n" unless defined $n->{_p};
|
||||
}
|
||||
|
||||
$first = $n->_as_part_txt(); # [ A.0 ]
|
||||
# for all outgoing connections
|
||||
foreach my $other (@out)
|
||||
{
|
||||
# in case there exists more than one edge from $n --> $other
|
||||
my @edges = $n->edges_to($other);
|
||||
for my $edge (sort { $a->{id} <=> $b->{id} } @edges)
|
||||
{
|
||||
$txt .= $first . $edge->as_txt() . $other->_as_part_txt() . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $n (@nodes)
|
||||
{
|
||||
delete $n->{_p}; # clean up
|
||||
}
|
||||
|
||||
$txt;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Group;
|
||||
|
||||
use strict;
|
||||
|
||||
sub as_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
my $n = '';
|
||||
if (!$self->isa('Graph::Easy::Group::Anon'))
|
||||
{
|
||||
$n = $self->{name};
|
||||
# quote special chars in name
|
||||
$n =~ s/([\[\]\(\)\{\}\#])/\\$1/g;
|
||||
$n = ' ' . $n;
|
||||
}
|
||||
|
||||
my $txt = "($n";
|
||||
|
||||
$n = $self->{nodes};
|
||||
|
||||
$txt .= (keys %$n > 0 ? "\n" : ' ');
|
||||
for my $name ( sort keys %$n )
|
||||
{
|
||||
$n->{$name}->{_p} = 1; # mark as processed
|
||||
$txt .= ' ' . $n->{$name}->as_pure_txt() . "\n";
|
||||
}
|
||||
$txt .= ")" . $self->attributes_as_txt() . "\n\n";
|
||||
|
||||
# insert all the edges of the group
|
||||
|
||||
#
|
||||
$txt;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Node;
|
||||
|
||||
use strict;
|
||||
|
||||
sub attributes_as_txt
|
||||
{
|
||||
# return the attributes of this node as text description
|
||||
my ($self, $remap) = @_;
|
||||
|
||||
# nodes that were autosplit
|
||||
if (exists $self->{autosplit})
|
||||
{
|
||||
# other nodes are invisible in as_txt:
|
||||
return '' unless defined $self->{autosplit};
|
||||
# the first one might have had a label set
|
||||
}
|
||||
|
||||
my $att = '';
|
||||
my $class = $self->class();
|
||||
my $g = $self->{graph};
|
||||
|
||||
# XXX TODO: remove atttributes that are simple the default attributes
|
||||
|
||||
my $attributes = $self->{att};
|
||||
if (exists $self->{autosplit})
|
||||
{
|
||||
# for the first node in a row of autosplit nodes, we need to create
|
||||
# the correct attributes, e.g. "silver|red|" instead of just silver:
|
||||
my $basename = $self->{autosplit_basename};
|
||||
$attributes = { };
|
||||
|
||||
my $parts = $self->{autosplit_parts};
|
||||
# gather all possible attribute names, otherwise an attribute set
|
||||
# on only one part (like via "color: |red;" would not show up:
|
||||
my $names = {};
|
||||
for my $child ($self, @$parts)
|
||||
{
|
||||
for my $k (sort keys %{$child->{att}})
|
||||
{
|
||||
$names->{$k} = undef;
|
||||
}
|
||||
}
|
||||
|
||||
for my $k (sort keys %$names)
|
||||
{
|
||||
next if $k eq 'basename';
|
||||
my $val = $self->{att}->{$k};
|
||||
$val = '' unless defined $val;
|
||||
my $first = $val; my $not_equal = 0;
|
||||
$val .= '|';
|
||||
for my $child (@$parts)
|
||||
{
|
||||
# only consider our own autosplit parts (check should not be nec.)
|
||||
# next if !exists $child->{autosplit_basename} ||
|
||||
# $child->{autosplit_basename} ne $basename;
|
||||
|
||||
my $v = $child->{att}->{$k}; $v = '' if !defined $v;
|
||||
$not_equal ++ if $v ne $first;
|
||||
$val .= $v . '|';
|
||||
}
|
||||
# all parts equal, so do "red|red|red" => "red"
|
||||
$val = $first if $not_equal == 0;
|
||||
|
||||
$val =~ s/\|+\z/\|/; # "silver|||" => "silver|"
|
||||
$val =~ s/\|\z// if $val =~ /\|.*\|/; # "silver|" => "silver|"
|
||||
# but "red|blue|" => "red|blue"
|
||||
$attributes->{$k} = $val unless $val eq '|'; # skip '|'
|
||||
}
|
||||
$attributes->{basename} = $self->{att}->{basename} if defined $self->{att}->{basename};
|
||||
}
|
||||
|
||||
my $new = $g->_remap_attributes( $self, $attributes, $remap, 'noquote', 'encode' );
|
||||
|
||||
# For nodes, we do not output their group attribute, since they simple appear
|
||||
# at the right place in the txt:
|
||||
delete $new->{group};
|
||||
|
||||
# for groups inside groups, insert their group attribute
|
||||
$new->{group} = $self->{group}->{name}
|
||||
if $self->isa('Graph::Easy::Group') && exists $self->{group};
|
||||
|
||||
if (defined $self->{origin})
|
||||
{
|
||||
$new->{origin} = $self->{origin}->{name};
|
||||
$new->{offset} = join(',', $self->offset());
|
||||
}
|
||||
|
||||
# shorten output for multi-celled nodes
|
||||
# for "rows: 2;" still output "rows: 2;", because it is shorter
|
||||
if (exists $new->{columns})
|
||||
{
|
||||
$new->{size} = ($new->{columns}||1) . ',' . ($new->{rows}||1);
|
||||
delete $new->{rows};
|
||||
delete $new->{columns};
|
||||
# don't output the default size
|
||||
delete $new->{size} if $new->{size} eq '1,1';
|
||||
}
|
||||
|
||||
for my $atr (sort keys %$new)
|
||||
{
|
||||
next if $atr =~ /^border/; # handled special
|
||||
|
||||
$att .= "$atr: $new->{$atr}; ";
|
||||
}
|
||||
|
||||
if (!$self->isa_cell())
|
||||
{
|
||||
my $border;
|
||||
if (!exists $self->{autosplit})
|
||||
{
|
||||
$border = $self->border_attribute();
|
||||
}
|
||||
else
|
||||
{
|
||||
$border = Graph::Easy::_border_attribute(
|
||||
$attributes->{borderstyle}||'',
|
||||
$attributes->{borderwidth}||'',
|
||||
$attributes->{bordercolor}||'');
|
||||
}
|
||||
|
||||
# XXX TODO: should do this for all attributes, not only for border
|
||||
# XXX TODO: this seems wrong anyway
|
||||
|
||||
# don't include default border
|
||||
$border = '' if ref $g && $g->attribute($class,'border') eq $border;
|
||||
$att .= "border: $border; " if $border ne '';
|
||||
}
|
||||
|
||||
# if we have a subclass, we probably need to include it
|
||||
my $c = '';
|
||||
$c = $1 if $class =~ /\.(\w+)/;
|
||||
|
||||
# but we do not need to include it if our group has a nodeclass attribute
|
||||
$c = '' if ref($self->{group}) && $self->{group}->attribute('nodeclass') eq $c;
|
||||
|
||||
# include our subclass as attribute
|
||||
$att .= "class: $c; " if $c ne '' && $c ne 'anon';
|
||||
|
||||
# generate attribute text if nec.
|
||||
$att = ' { ' . $att . '}' if $att ne '';
|
||||
|
||||
$att;
|
||||
}
|
||||
|
||||
sub _as_part_txt
|
||||
{
|
||||
# for edges, we need the name of the part of the first part, not the entire
|
||||
# autosplit text
|
||||
my $self = shift;
|
||||
|
||||
my $name = $self->{name};
|
||||
|
||||
# quote special chars in name
|
||||
$name =~ s/([\[\]\|\{\}\#])/\\$1/g;
|
||||
|
||||
'[ ' . $name . ' ]';
|
||||
}
|
||||
|
||||
sub as_pure_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
if (exists $self->{autosplit} && defined $self->{autosplit})
|
||||
{
|
||||
my $name = $self->{autosplit};
|
||||
|
||||
# quote special chars in name (but not |)
|
||||
$name =~ s/([\[\]\{\}\#])/\\$1/g;
|
||||
|
||||
return '[ '. $name .' ]'
|
||||
}
|
||||
|
||||
my $name = $self->{name};
|
||||
|
||||
# quote special chars in name
|
||||
$name =~ s/([\[\]\|\{\}\#])/\\$1/g;
|
||||
|
||||
'[ ' . $name . ' ]';
|
||||
}
|
||||
|
||||
sub as_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
if (exists $self->{autosplit})
|
||||
{
|
||||
return '' unless defined $self->{autosplit};
|
||||
my $name = $self->{autosplit};
|
||||
# quote special chars in name (but not |)
|
||||
$name =~ s/([\[\]\{\}\#])/\\$1/g;
|
||||
return '[ ' . $name . ' ]'
|
||||
}
|
||||
|
||||
my $name = $self->{name};
|
||||
|
||||
# quote special chars in name
|
||||
$name =~ s/([\[\]\|\{\}\#])/\\$1/g;
|
||||
|
||||
'[ ' . $name . ' ]' . $self->attributes_as_txt();
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Edge;
|
||||
|
||||
my $styles = {
|
||||
solid => '--',
|
||||
dotted => '..',
|
||||
double => '==',
|
||||
'double-dash' => '= ',
|
||||
dashed => '- ',
|
||||
'dot-dash' => '.-',
|
||||
'dot-dot-dash' => '..-',
|
||||
wave => '~~',
|
||||
};
|
||||
|
||||
sub _as_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# '- Name ' or ''
|
||||
my $n = $self->{att}->{label}; $n = '' unless defined $n;
|
||||
|
||||
my $left = ' '; $left = ' <' if $self->{bidirectional};
|
||||
my $right = '> '; $right = ' ' if $self->{undirected};
|
||||
|
||||
my $s = $self->style() || 'solid';
|
||||
|
||||
my $style = '--';
|
||||
|
||||
# suppress border on edges
|
||||
my $suppress = { all => { label => undef } };
|
||||
if ($s =~ /^(bold|bold-dash|broad|wide|invisible)\z/)
|
||||
{
|
||||
# output "--> { style: XXX; }"
|
||||
$style = '--';
|
||||
}
|
||||
else
|
||||
{
|
||||
# output "-->" or "..>" etc
|
||||
$suppress->{all}->{style} = undef;
|
||||
|
||||
$style = $styles->{ $s };
|
||||
if (!defined $style)
|
||||
{
|
||||
require Carp;
|
||||
Carp::confess ("Unknown edge style '$s'\n");
|
||||
}
|
||||
}
|
||||
|
||||
$n = $style . " $n " if $n ne '';
|
||||
|
||||
# make " - " into " - - "
|
||||
$style = $style . $style if $self->{undirected} && substr($style,1,1) eq ' ';
|
||||
|
||||
# ' - Name -->' or ' --> ' or ' -- '
|
||||
my $a = $self->attributes_as_txt($suppress) . ' '; $a =~ s/^\s//;
|
||||
$left . $n . $style . $right . $a;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::As_txt - Generate textual description from graph object
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
my $bonn = Graph::Easy::Node->new(
|
||||
name => 'Bonn',
|
||||
);
|
||||
my $berlin = Graph::Easy::Node->new(
|
||||
name => 'Berlin',
|
||||
);
|
||||
|
||||
$graph->add_edge ($bonn, $berlin);
|
||||
|
||||
print $graph->as_txt();
|
||||
|
||||
# prints something like:
|
||||
|
||||
# [ Bonn ] -> [ Berlin ]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<Graph::Easy::As_txt> contains just the code for converting a
|
||||
L<Graph::Easy|Graph::Easy> object to a human-readable textual description.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
Exports nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2007 by Tels L<http://bloodgate.com>
|
||||
|
||||
See the LICENSE file for information.
|
||||
|
||||
=cut
|
||||
|
||||
586
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/As_vcg.pm
Normal file
586
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/As_vcg.pm
Normal file
@@ -0,0 +1,586 @@
|
||||
#############################################################################
|
||||
# Output the graph as VCG or GDL text.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::As_vcg;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $vcg_remap = {
|
||||
node => {
|
||||
align => \&_vcg_remap_align,
|
||||
autolabel => undef,
|
||||
autolink => undef,
|
||||
autotitle => undef,
|
||||
background => undef,
|
||||
basename => undef,
|
||||
class => undef,
|
||||
colorscheme => undef,
|
||||
columns => undef,
|
||||
flow => undef,
|
||||
fontsize => undef,
|
||||
format => undef,
|
||||
group => undef,
|
||||
id => undef,
|
||||
link => undef,
|
||||
linkbase => undef,
|
||||
offset => undef,
|
||||
origin => undef,
|
||||
pointstyle => undef,
|
||||
rank => 'level',
|
||||
rotate => undef,
|
||||
rows => undef,
|
||||
shape => \&_vcg_remap_shape,
|
||||
size => undef,
|
||||
textstyle => undef,
|
||||
textwrap => undef,
|
||||
title => undef,
|
||||
},
|
||||
edge => {
|
||||
color => 'color', # this entry overrides 'all'!
|
||||
align => undef,
|
||||
arrowshape => undef,
|
||||
arrowstyle => undef,
|
||||
autojoin => undef,
|
||||
autolabel => undef,
|
||||
autolink => undef,
|
||||
autosplit => undef,
|
||||
autotitle => undef,
|
||||
border => undef,
|
||||
bordercolor => undef,
|
||||
borderstyle => undef,
|
||||
borderwidth => undef,
|
||||
colorscheme => undef,
|
||||
end => undef,
|
||||
fontsize => undef,
|
||||
format => undef,
|
||||
id => undef,
|
||||
labelcolor => 'textcolor',
|
||||
link => undef,
|
||||
linkbase => undef,
|
||||
minlen => undef,
|
||||
start => undef,
|
||||
# XXX TODO: remap unknown styles
|
||||
style => 'linestyle',
|
||||
textstyle => undef,
|
||||
textwrap => undef,
|
||||
title => undef,
|
||||
},
|
||||
graph => {
|
||||
align => \&_vcg_remap_align,
|
||||
flow => \&_vcg_remap_flow,
|
||||
label => 'title',
|
||||
type => undef,
|
||||
},
|
||||
group => {
|
||||
},
|
||||
all => {
|
||||
background => undef,
|
||||
color => 'textcolor',
|
||||
comment => undef,
|
||||
fill => 'color',
|
||||
font => 'fontname',
|
||||
},
|
||||
always => {
|
||||
},
|
||||
# this routine will handle all custom "x-dot-..." attributes
|
||||
x => \&_remap_custom_vcg_attributes,
|
||||
};
|
||||
|
||||
sub _remap_custom_vcg_attributes
|
||||
{
|
||||
my ($self, $name, $value) = @_;
|
||||
|
||||
# drop anything that is not starting with "x-vcg-..."
|
||||
return (undef,undef) unless $name =~ /^x-vcg-/;
|
||||
|
||||
$name =~ s/^x-vcg-//; # "x-vcg-foo" => "foo"
|
||||
($name,$value);
|
||||
}
|
||||
|
||||
my $vcg_shapes = {
|
||||
rect => 'box',
|
||||
diamond => 'rhomb',
|
||||
triangle => 'triangle',
|
||||
invtriangle => 'triangle',
|
||||
ellipse => 'ellipse',
|
||||
circle => 'circle',
|
||||
hexagon => 'hexagon',
|
||||
trapezium => 'trapeze',
|
||||
invtrapezium => 'uptrapeze',
|
||||
invparallelogram => 'lparallelogram',
|
||||
parallelogram => 'rparallelogram',
|
||||
};
|
||||
|
||||
sub _vcg_remap_shape
|
||||
{
|
||||
my ($self, $name, $shape) = @_;
|
||||
|
||||
return ('invisible','yes') if $shape eq 'invisible';
|
||||
|
||||
('shape', $vcg_shapes->{$shape} || 'box');
|
||||
}
|
||||
|
||||
sub _vcg_remap_align
|
||||
{
|
||||
my ($self, $name, $style) = @_;
|
||||
|
||||
# center => center, left => left_justify, right => right_justify
|
||||
$style .= '_justify' unless $style eq 'center';
|
||||
|
||||
('textmode', $style);
|
||||
}
|
||||
|
||||
my $vcg_flow = {
|
||||
'south' => 'top_to_bottom',
|
||||
'north' => 'bottom_to_top',
|
||||
'down' => 'top_to_bottom',
|
||||
'up' => 'bottom_to_top',
|
||||
'east' => 'left_to_right',
|
||||
'west' => 'right_to_left',
|
||||
'right' => 'left_to_right',
|
||||
'left' => 'right_to_left',
|
||||
};
|
||||
|
||||
sub _vcg_remap_flow
|
||||
{
|
||||
my ($self, $name, $style) = @_;
|
||||
|
||||
('orientation', $vcg_flow->{$style} || 'top_to_bottom');
|
||||
}
|
||||
|
||||
sub _class_attributes_as_vcg
|
||||
{
|
||||
# convert a hash with attribute => value mappings to a string
|
||||
my ($self, $a, $class) = @_;
|
||||
|
||||
|
||||
my $att = '';
|
||||
$class = '' if $class eq 'graph';
|
||||
$class .= '.' if $class ne '';
|
||||
|
||||
# create the attributes as text:
|
||||
for my $atr (sort keys %$a)
|
||||
{
|
||||
my $v = $a->{$atr};
|
||||
$v =~ s/"/\\"/g; # '2"' => '2\"'
|
||||
$v = '"' . $v . '"' unless $v =~ /^[0-9]+\z/; # 1, "1a"
|
||||
$att .= " $class$atr: $v\n";
|
||||
}
|
||||
$att =~ s/,\s$//; # remove last ","
|
||||
|
||||
$att = "\n$att" unless $att eq '';
|
||||
$att;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _generate_vcg_edge
|
||||
{
|
||||
# Given an edge, generate the VCG code for it
|
||||
my ($self, $e, $indent) = @_;
|
||||
|
||||
# skip links from/to groups, these will be done later
|
||||
return '' if
|
||||
$e->{from}->isa('Graph::Easy::Group') ||
|
||||
$e->{to}->isa('Graph::Easy::Group');
|
||||
|
||||
my $edge_att = $e->attributes_as_vcg();
|
||||
|
||||
$e->{_p} = undef; # mark as processed
|
||||
" edge:$edge_att\n"; # return edge text
|
||||
}
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
sub _as_vcg
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
# convert the graph to a textual representation
|
||||
# does not need a layout() beforehand!
|
||||
|
||||
# gather all edge classes to build the classname attribute from them:
|
||||
$self->{_vcg_edge_classes} = {};
|
||||
for my $e (ord_values ( $self->{edges} ))
|
||||
{
|
||||
my $class = $e->sub_class();
|
||||
$self->{_vcg_edge_classes}->{$class} = undef if defined $class && $class ne '';
|
||||
}
|
||||
# sort gathered class names and map them to integers
|
||||
my $class_names = '';
|
||||
if (keys %{$self->{_vcg_edge_classes}} > 0)
|
||||
{
|
||||
my $i = 1;
|
||||
$class_names = "\n";
|
||||
for my $ec (sort keys %{$self->{_vcg_edge_classes}})
|
||||
{
|
||||
$self->{_vcg_edge_classes}->{$ec} = $i; # remember mapping
|
||||
$class_names .= " classname $i: \"$ec\"\n";
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
# generate the class attributes first
|
||||
my $label = $self->label();
|
||||
my $t = ''; $t = "\n title: \"$label\"" if $label ne '';
|
||||
|
||||
my $txt = "graph: {$t\n\n" .
|
||||
" // Generated by Graph::Easy $Graph::Easy::VERSION" .
|
||||
" at " . scalar localtime() . "\n" .
|
||||
$class_names;
|
||||
|
||||
my $groups = $self->groups();
|
||||
|
||||
# to keep track of invisible helper nodes
|
||||
$self->{_vcg_invis} = {};
|
||||
# name for invisible helper nodes
|
||||
$self->{_vcg_invis_id} = 'joint0';
|
||||
|
||||
my $atts = $self->{att};
|
||||
# insert the class attributes
|
||||
for my $class (qw/edge graph node/)
|
||||
{
|
||||
next if $class =~ /\./; # skip subclasses
|
||||
|
||||
my $out = $self->_remap_attributes( $class, $atts->{$class}, $vcg_remap, 'noquote');
|
||||
$txt .= $self->_class_attributes_as_vcg($out, $class);
|
||||
}
|
||||
|
||||
$txt .= "\n" if $txt ne ''; # insert newline
|
||||
|
||||
###########################################################################
|
||||
# output groups as subgraphs
|
||||
|
||||
# insert the edges into the proper group
|
||||
$self->_edges_into_groups() if $groups > 0;
|
||||
|
||||
# output the groups (aka subclusters)
|
||||
my $indent = ' ';
|
||||
for my $group (sort { $a->{name} cmp $b->{name} } values %{$self->{groups}})
|
||||
{
|
||||
# quote special chars in group name
|
||||
my $name = $group->{name}; $name =~ s/([\[\]\(\)\{\}\#"])/\\$1/g;
|
||||
|
||||
# # output group attributes first
|
||||
# $txt .= " subgraph \"cluster$group->{id}\" {\n${indent}label=\"$name\";\n";
|
||||
|
||||
# Make a copy of the attributes, including our class attributes:
|
||||
my $copy = {};
|
||||
my $attribs = $group->get_attributes();
|
||||
|
||||
for my $a (keys %$attribs)
|
||||
{
|
||||
$copy->{$a} = $attribs->{$a};
|
||||
}
|
||||
# # set some defaults
|
||||
# $copy->{'borderstyle'} = 'solid' unless defined $copy->{'borderstyle'};
|
||||
|
||||
my $out = {};
|
||||
# my $out = $self->_remap_attributes( $group->class(), $copy, $vcg_remap, 'noquote');
|
||||
|
||||
# Set some defaults:
|
||||
$out->{fillcolor} = '#a0d0ff' unless defined $out->{fillcolor};
|
||||
# $out->{labeljust} = 'l' unless defined $out->{labeljust};
|
||||
|
||||
my $att = '';
|
||||
# we need to output style first ("filled" and "color" need come later)
|
||||
for my $atr (reverse sort keys %$out)
|
||||
{
|
||||
my $v = $out->{$atr};
|
||||
$v = '"' . $v . '"';
|
||||
$att .= " $atr: $v\n";
|
||||
}
|
||||
$txt .= $att . "\n" if $att ne '';
|
||||
|
||||
# # output nodes (w/ or w/o attributes) in that group
|
||||
# for my $n ($group->sorted_nodes())
|
||||
# {
|
||||
# my $att = $n->attributes_as_vcg();
|
||||
# $n->{_p} = undef; # mark as processed
|
||||
# $txt .= $indent . $n->as_graphviz_txt() . $att . "\n";
|
||||
# }
|
||||
|
||||
# # output node connections in this group
|
||||
# for my $e (ord_values ( $group->{edges} ))
|
||||
# {
|
||||
# next if exists $e->{_p};
|
||||
# $txt .= $self->_generate_edge($e, $indent);
|
||||
# }
|
||||
|
||||
$txt .= " }\n";
|
||||
}
|
||||
|
||||
my $root = $self->attribute('root');
|
||||
$root = '' unless defined $root;
|
||||
|
||||
my $count = 0;
|
||||
# output nodes with attributes first, sorted by their name
|
||||
for my $n (sort { $a->{name} cmp $b->{name} } values %{$self->{nodes}})
|
||||
{
|
||||
next if exists $n->{_p};
|
||||
my $att = $n->attributes_as_vcg($root);
|
||||
if ($att ne '')
|
||||
{
|
||||
$n->{_p} = undef; # mark as processed
|
||||
$count++;
|
||||
$txt .= " node:" . $att . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$txt .= "\n" if $count > 0; # insert a newline
|
||||
|
||||
my @nodes = $self->sorted_nodes();
|
||||
|
||||
foreach my $n (@nodes)
|
||||
{
|
||||
my @out = $n->successors();
|
||||
my $first = $n->as_vcg_txt();
|
||||
if ((@out == 0) && ( (scalar $n->predecessors() || 0) == 0))
|
||||
{
|
||||
# single node without any connections (unless already output)
|
||||
$txt .= " node: { title: " . $first . " }\n" unless exists $n->{_p};
|
||||
}
|
||||
# for all outgoing connections
|
||||
foreach my $other (reverse @out)
|
||||
{
|
||||
# in case there is more than one edge going from N to O
|
||||
my @edges = $n->edges_to($other);
|
||||
foreach my $e (@edges)
|
||||
{
|
||||
next if exists $e->{_p};
|
||||
$txt .= $self->_generate_vcg_edge($e, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# insert now edges between groups (clusters/subgraphs)
|
||||
|
||||
# foreach my $e (ord_values ( $self->{edges} ))
|
||||
# {
|
||||
# $txt .= $self->_generate_group_edge($e, ' ')
|
||||
# if $e->{from}->isa('Graph::Easy::Group') ||
|
||||
# $e->{to}->isa('Graph::Easy::Group');
|
||||
# }
|
||||
|
||||
# clean up
|
||||
for my $n ( ord_values ( $self->{nodes} ), ord_values ( $self->{edges} ))
|
||||
{
|
||||
delete $n->{_p};
|
||||
}
|
||||
delete $self->{_vcg_invis}; # invisible helper nodes for joints
|
||||
delete $self->{_vcg_invis_id}; # invisible helper node name
|
||||
delete $self->{_vcg_edge_classes};
|
||||
|
||||
$txt . "\n}\n"; # close the graph
|
||||
}
|
||||
|
||||
package Graph::Easy::Node;
|
||||
|
||||
sub attributes_as_vcg
|
||||
{
|
||||
# return the attributes of this node as text description
|
||||
my ($self, $root) = @_;
|
||||
$root = '' unless defined $root;
|
||||
|
||||
my $att = '';
|
||||
my $class = $self->class();
|
||||
|
||||
return '' unless ref $self->{graph};
|
||||
|
||||
my $g = $self->{graph};
|
||||
|
||||
# get all attributes, excluding the class attributes
|
||||
my $a = $self->raw_attributes();
|
||||
|
||||
# add the attributes that are listed under "always":
|
||||
my $attr = $self->{att};
|
||||
my $base_class = $class; $base_class =~ s/\..*//;
|
||||
my $list = $vcg_remap->{always}->{$class} || $vcg_remap->{always}->{$base_class};
|
||||
|
||||
for my $name (@$list)
|
||||
{
|
||||
# for speed, try to look it up directly
|
||||
|
||||
# look if we have a code ref, if yes, simple set the value to undef
|
||||
# and let the coderef handle it later:
|
||||
if ( ref($vcg_remap->{$base_class}->{$name}) ||
|
||||
ref($vcg_remap->{all}->{$name}) )
|
||||
{
|
||||
$a->{$name} = $attr->{$name};
|
||||
}
|
||||
else
|
||||
{
|
||||
$a->{$name} = $attr->{$name};
|
||||
$a->{$name} = $self->attribute($name) unless defined $a->{$name} && $a->{$name} ne 'inherit';
|
||||
}
|
||||
}
|
||||
|
||||
$a = $g->_remap_attributes( $self, $a, $vcg_remap, 'noquote');
|
||||
|
||||
if ($self->isa('Graph::Easy::Edge'))
|
||||
{
|
||||
$a->{sourcename} = $self->{from}->{name};
|
||||
$a->{targetname} = $self->{to}->{name};
|
||||
my $class = $self->sub_class();
|
||||
$a->{class} = $self->{graph}->{_vcg_edge_classes}->{ $class } if defined $class && $class ne '';
|
||||
}
|
||||
else
|
||||
{
|
||||
# title: "Bonn"
|
||||
$a->{title} = $self->{name};
|
||||
}
|
||||
|
||||
# do not needlessly output labels:
|
||||
delete $a->{label} if !$self->isa('Graph::Easy::Edge') && # not an edge
|
||||
exists $a->{label} && $a->{label} eq $self->{name};
|
||||
|
||||
# bidirectional and undirected edges
|
||||
if ($self->{bidirectional})
|
||||
{
|
||||
delete $a->{dir};
|
||||
my ($n,$s) = Graph::Easy::_graphviz_remap_arrow_style(
|
||||
$self,'', $self->attribute('arrowstyle'));
|
||||
$a->{arrowhead} = $s;
|
||||
$a->{arrowtail} = $s;
|
||||
}
|
||||
if ($self->{undirected})
|
||||
{
|
||||
delete $a->{dir};
|
||||
$a->{arrowhead} = 'none';
|
||||
$a->{arrowtail} = 'none';
|
||||
}
|
||||
|
||||
# borderstyle: double:
|
||||
if (!$self->isa('Graph::Easy::Edge'))
|
||||
{
|
||||
my $style = $self->attribute('borderstyle');
|
||||
$a->{peripheries} = 2 if $style =~ /^double/;
|
||||
}
|
||||
|
||||
# For nodes with shape plaintext, set the fillcolor to the background of
|
||||
# the graph/group
|
||||
my $shape = $a->{shape} || 'rect';
|
||||
if ($class =~ /node/ && $shape eq 'plaintext')
|
||||
{
|
||||
my $p = $self->parent();
|
||||
$a->{fillcolor} = $p->attribute('fill');
|
||||
$a->{fillcolor} = 'white' if $a->{fillcolor} eq 'inherit';
|
||||
}
|
||||
|
||||
$shape = $self->attribute('shape') unless $self->isa_cell();
|
||||
|
||||
# for point-shaped nodes, include the point as label and set width/height
|
||||
if ($shape eq 'point')
|
||||
{
|
||||
require Graph::Easy::As_ascii; # for _u8 and point-style
|
||||
|
||||
my $style = $self->_point_style( $self->attribute('pointstyle') );
|
||||
|
||||
$a->{label} = $style;
|
||||
# for point-shaped invisible nodes, set height/width = 0
|
||||
$a->{width} = 0, $a->{height} = 0 if $style eq '';
|
||||
}
|
||||
if ($shape eq 'invisible')
|
||||
{
|
||||
$a->{label} = ' ';
|
||||
}
|
||||
|
||||
$a->{rank} = '0' if $root ne '' && $root eq $self->{name};
|
||||
|
||||
# create the attributes as text:
|
||||
for my $atr (sort keys %$a)
|
||||
{
|
||||
my $v = $a->{$atr};
|
||||
$v =~ s/"/\\"/g; # '2"' => '2\"'
|
||||
$v = '"' . $v . '"' unless $v =~ /^[0-9]+\z/; # 1, "1a"
|
||||
$att .= "$atr: $v ";
|
||||
}
|
||||
$att =~ s/,\s$//; # remove last ","
|
||||
|
||||
# generate attribute text if nec.
|
||||
$att = ' { ' . $att . '}' if $att ne '';
|
||||
|
||||
$att;
|
||||
}
|
||||
|
||||
sub as_vcg_txt
|
||||
{
|
||||
# return the node itself (w/o attributes) as VCG representation
|
||||
my $self = shift;
|
||||
|
||||
my $name = $self->{name};
|
||||
|
||||
# escape special chars in name (including doublequote!)
|
||||
$name =~ s/([\[\]\(\)\{\}"])/\\$1/g;
|
||||
|
||||
# quote:
|
||||
'"' . $name . '"';
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::As_vcg - Generate VCG/GDL text from Graph::Easy object
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
my $bonn = Graph::Easy::Node->new(
|
||||
name => 'Bonn',
|
||||
);
|
||||
my $berlin = Graph::Easy::Node->new(
|
||||
name => 'Berlin',
|
||||
);
|
||||
|
||||
$graph->add_edge ($bonn, $berlin);
|
||||
|
||||
print $graph->as_vcg();
|
||||
|
||||
|
||||
This prints something like this:
|
||||
|
||||
graph: {
|
||||
node: { title: "Bonn" }
|
||||
node: { title: "Berlin" }
|
||||
edge: { sourcename: "Bonn" targetname: "Berlin" }
|
||||
}
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<Graph::Easy::As_vcg> contains just the code for converting a
|
||||
L<Graph::Easy|Graph::Easy> object to either a VCG
|
||||
or GDL textual description.
|
||||
|
||||
Note that the generated format is compatible to C<GDL> aka I<Graph
|
||||
Description Language>.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
Exports nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>, L<http://rw4.cs.uni-sb.de/~sander/html/gsvcg1.html>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004-2008 by Tels L<http://bloodgate.com>
|
||||
|
||||
See the LICENSE file for information.
|
||||
|
||||
=cut
|
||||
4182
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Attributes.pm
Normal file
4182
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Attributes.pm
Normal file
File diff suppressed because it is too large
Load Diff
486
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Base.pm
Normal file
486
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Base.pm
Normal file
@@ -0,0 +1,486 @@
|
||||
#############################################################################
|
||||
# A baseclass for Graph::Easy objects like nodes, edges etc.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Base;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
#############################################################################
|
||||
|
||||
{
|
||||
# protected vars
|
||||
my $id = 0;
|
||||
sub _new_id { $id++; }
|
||||
sub _reset_id { $id = 0; }
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub new
|
||||
{
|
||||
# Create a new object. This is a generic routine that is inherited
|
||||
# by many other things like Edge, Cell etc.
|
||||
my $self = bless { id => _new_id() }, shift;
|
||||
|
||||
my $args = $_[0];
|
||||
$args = { name => $_[0] } if ref($args) ne 'HASH' && @_ == 1;
|
||||
$args = { @_ } if ref($args) ne 'HASH' && @_ > 1;
|
||||
|
||||
$self->_init($args);
|
||||
}
|
||||
|
||||
sub _init
|
||||
{
|
||||
# Generic init routine, to be overriden in subclasses.
|
||||
my ($self,$args) = @_;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub self
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub no_fatal_errors
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{fatal_errors} = ($_[1] ? 1 : 0) if @_ > 0;
|
||||
|
||||
~ ($self->{fatal_errors} || 0);
|
||||
}
|
||||
|
||||
sub fatal_errors
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{fatal_errors} = ($_[1] ? 0 : 1) if @_ > 0;
|
||||
|
||||
$self->{fatal_errors} || 0;
|
||||
}
|
||||
|
||||
sub error
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# If we switched to a temp. Graphviz parser, then set the error on the
|
||||
# original parser object, too:
|
||||
$self->{_old_self}->error(@_) if ref($self->{_old_self});
|
||||
|
||||
# if called on a member on a graph, call error() on the graph itself:
|
||||
return $self->{graph}->error(@_) if ref($self->{graph});
|
||||
|
||||
if (defined $_[0])
|
||||
{
|
||||
$self->{error} = $_[0];
|
||||
if ($self->{_catch_errors})
|
||||
{
|
||||
push @{$self->{_errors}}, $self->{error};
|
||||
}
|
||||
else
|
||||
{
|
||||
$self->_croak($self->{error}, 2)
|
||||
if ($self->{fatal_errors}) && $self->{error} ne '';
|
||||
}
|
||||
}
|
||||
$self->{error} || '';
|
||||
}
|
||||
|
||||
sub error_as_html
|
||||
{
|
||||
# return error() properly escaped
|
||||
my $self = shift;
|
||||
|
||||
my $msg = $self->{error};
|
||||
|
||||
$msg =~ s/&/&/g;
|
||||
$msg =~ s/</</g;
|
||||
$msg =~ s/>/>/g;
|
||||
$msg =~ s/"/"/g;
|
||||
|
||||
$msg;
|
||||
}
|
||||
|
||||
sub catch_messages
|
||||
{
|
||||
# Catch all warnings (and errors if no_fatal_errors() was used)
|
||||
# these can later be retrieved with warnings() and errors():
|
||||
my $self = shift;
|
||||
|
||||
if (@_ > 0)
|
||||
{
|
||||
if ($_[0])
|
||||
{
|
||||
$self->{_catch_warnings} = 1;
|
||||
$self->{_catch_errors} = 1;
|
||||
$self->{_warnings} = [];
|
||||
$self->{_errors} = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
$self->{_catch_warnings} = 0;
|
||||
$self->{_catch_errors} = 0;
|
||||
}
|
||||
}
|
||||
$self;
|
||||
}
|
||||
|
||||
sub catch_warnings
|
||||
{
|
||||
# Catch all warnings
|
||||
# these can later be retrieved with warnings():
|
||||
my $self = shift;
|
||||
|
||||
if (@_ > 0)
|
||||
{
|
||||
if ($_[0])
|
||||
{
|
||||
$self->{_catch_warnings} = 1;
|
||||
$self->{_warnings} = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
$self->{_catch_warnings} = 0;
|
||||
}
|
||||
}
|
||||
$self->{_catch_warnings};
|
||||
}
|
||||
|
||||
sub catch_errors
|
||||
{
|
||||
# Catch all errors
|
||||
# these can later be retrieved with errors():
|
||||
my $self = shift;
|
||||
|
||||
if (@_ > 0)
|
||||
{
|
||||
if ($_[0])
|
||||
{
|
||||
$self->{_catch_errors} = 1;
|
||||
$self->{_errors} = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
$self->{_catch_errors} = 0;
|
||||
}
|
||||
}
|
||||
$self->{_catch_errors};
|
||||
}
|
||||
|
||||
sub warnings
|
||||
{
|
||||
# return all warnings that occurred after catch_messages(1)
|
||||
my $self = shift;
|
||||
|
||||
@{$self->{_warnings}};
|
||||
}
|
||||
|
||||
sub errors
|
||||
{
|
||||
# return all errors that occurred after catch_messages(1)
|
||||
my $self = shift;
|
||||
|
||||
@{$self->{_errors}};
|
||||
}
|
||||
|
||||
sub warn
|
||||
{
|
||||
my ($self, $msg) = @_;
|
||||
|
||||
if ($self->{_catch_warnings})
|
||||
{
|
||||
push @{$self->{_warnings}}, $msg;
|
||||
}
|
||||
else
|
||||
{
|
||||
require Carp;
|
||||
Carp::carp('Warning: ' . $msg);
|
||||
}
|
||||
}
|
||||
|
||||
sub _croak
|
||||
{
|
||||
my ($self, $msg, $level) = @_;
|
||||
$level = 1 unless defined $level;
|
||||
|
||||
require Carp;
|
||||
if (ref($self) && $self->{debug})
|
||||
{
|
||||
$Carp::CarpLevel = $level; # don't report Base itself
|
||||
Carp::confess($msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
Carp::croak($msg);
|
||||
}
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# class management
|
||||
|
||||
sub sub_class
|
||||
{
|
||||
# get/set the subclass
|
||||
my $self = shift;
|
||||
|
||||
if (defined $_[0])
|
||||
{
|
||||
$self->{class} =~ s/\..*//; # nix subclass
|
||||
$self->{class} .= '.' . $_[0]; # append new one
|
||||
delete $self->{cache};
|
||||
$self->{cache}->{subclass} = $_[0];
|
||||
$self->{cache}->{class} = $self->{class};
|
||||
return;
|
||||
}
|
||||
$self->{class} =~ /\.(.*)/;
|
||||
|
||||
return $1 if defined $1;
|
||||
|
||||
return $self->{cache}->{subclass} if defined $self->{cache}->{subclass};
|
||||
|
||||
# Subclass not defined, so check our base class for a possible set class
|
||||
# attribute and return this:
|
||||
|
||||
# take a shortcut
|
||||
my $g = $self->{graph};
|
||||
if (defined $g)
|
||||
{
|
||||
my $subclass = $g->{att}->{$self->{class}}->{class};
|
||||
$subclass = '' unless defined $subclass;
|
||||
$self->{cache}->{subclass} = $subclass;
|
||||
$self->{cache}->{class} = $self->{class};
|
||||
return $subclass;
|
||||
}
|
||||
|
||||
# not part of a graph?
|
||||
$self->{cache}->{subclass} = $self->attribute('class');
|
||||
}
|
||||
|
||||
sub class
|
||||
{
|
||||
# return our full class name like "node.subclass" or "node"
|
||||
my $self = shift;
|
||||
|
||||
$self->error("class() method does not take arguments") if @_ > 0;
|
||||
|
||||
$self->{class} =~ /\.(.*)/;
|
||||
|
||||
return $self->{class} if defined $1;
|
||||
|
||||
return $self->{cache}->{class} if defined $self->{cache}->{class};
|
||||
|
||||
# Subclass not defined, so check our base class for a possible set class
|
||||
# attribute and return this:
|
||||
|
||||
my $subclass;
|
||||
# take a shortcut:
|
||||
my $g = $self->{graph};
|
||||
if (defined $g)
|
||||
{
|
||||
$subclass = $g->{att}->{$self->{class}}->{class};
|
||||
$subclass = '' unless defined $subclass;
|
||||
}
|
||||
|
||||
$subclass = $self->{att}->{class} unless defined $subclass;
|
||||
$subclass = '' unless defined $subclass;
|
||||
$self->{cache}->{subclass} = $subclass;
|
||||
$subclass = '.' . $subclass if $subclass ne '';
|
||||
|
||||
$self->{cache}->{class} = $self->{class} . $subclass;
|
||||
}
|
||||
|
||||
sub main_class
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{class} =~ /^(.+?)(\.|\z)/; # extract first part
|
||||
|
||||
$1;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Base - base class for Graph::Easy objects like nodes, edges etc
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
package Graph::Easy::My::Node;
|
||||
use Graph::Easy::Base;
|
||||
@ISA = qw/Graph::Easy::Base/;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Used automatically and internally by L<Graph::Easy> - should not be used
|
||||
directly.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 new()
|
||||
|
||||
my $object = Graph::Easy::Base->new();
|
||||
|
||||
Create a new object, and call C<_init()> on it.
|
||||
|
||||
=head2 error()
|
||||
|
||||
$last_error = $object->error();
|
||||
|
||||
$object->error($error); # set new messages
|
||||
$object->error(''); # clear the error
|
||||
|
||||
Returns the last error message, or '' for no error.
|
||||
|
||||
When setting a new error message, C<< $self->_croak($error) >> will be called
|
||||
unless C<< $object->no_fatal_errors() >> is true.
|
||||
|
||||
=head2 error_as_html()
|
||||
|
||||
my $error = $object->error_as_html();
|
||||
|
||||
Returns the same error message as L<error()>, but properly escaped
|
||||
as HTML so it is safe to output to the client.
|
||||
|
||||
=head2 warn()
|
||||
|
||||
$object->warn('Warning!');
|
||||
|
||||
Warn on STDERR with the given message.
|
||||
|
||||
=head2 no_fatal_errors()
|
||||
|
||||
$object->no_fatal_errors(1);
|
||||
|
||||
Set the flag that determines whether setting an error message
|
||||
via C<error()> is fatal, e.g. results in a call to C<_croak()>.
|
||||
|
||||
A true value will make errors non-fatal. See also L<fatal_errors>.
|
||||
|
||||
=head2 fatal_errors()
|
||||
|
||||
$fatal = $object->fatal_errors();
|
||||
$object->fatal_errors(0); # turn off
|
||||
$object->fatal_errors(1); # turn on
|
||||
|
||||
Set/get the flag that determines whether setting an error message
|
||||
via C<error()> is fatal, e.g. results in a call to C<_croak()>.
|
||||
|
||||
A true value makes errors fatal.
|
||||
|
||||
=head2 catch_errors()
|
||||
|
||||
my $catch_errors = $object->catch_errors(); # query
|
||||
$object->catch_errors(1); # enable
|
||||
|
||||
$object->...(); # some error
|
||||
if ($object->error())
|
||||
{
|
||||
my @errors = $object->errors(); # retrieve
|
||||
}
|
||||
|
||||
Enable/disable catching of all error messages. When enabled,
|
||||
all previously caught error messages are thrown away, and from this
|
||||
poin on new errors are non-fatal and stored internally. You can
|
||||
retrieve these errors later with the errors() method.
|
||||
|
||||
=head2 catch_warnings()
|
||||
|
||||
my $catch_warns = $object->catch_warnings(); # query
|
||||
$object->catch_warnings(1); # enable
|
||||
|
||||
$object->...(); # some error
|
||||
if ($object->warning())
|
||||
{
|
||||
my @warnings = $object->warnings(); # retrieve
|
||||
}
|
||||
|
||||
Enable/disable catching of all warnings. When enabled, all previously
|
||||
caught warning messages are thrown away, and from this poin on new
|
||||
warnings are stored internally. You can retrieve these errors later
|
||||
with the errors() method.
|
||||
|
||||
=head2 catch_messages()
|
||||
|
||||
# catch errors and warnings
|
||||
$object->catch_messages(1);
|
||||
# stop catching errors and warnings
|
||||
$object->catch_messages(0);
|
||||
|
||||
A true parameter is equivalent to:
|
||||
|
||||
$object->catch_warnings(1);
|
||||
$object->catch_errors(1);
|
||||
|
||||
See also: L<catch_warnings()> and L<catch_errors()> as well as
|
||||
L<errors()> and L<warnings()>.
|
||||
|
||||
=head2 errors()
|
||||
|
||||
my @errors = $object->errors();
|
||||
|
||||
Return all error messages that occurred after L<catch_messages()> was
|
||||
called.
|
||||
|
||||
=head2 warnings()
|
||||
|
||||
my @warnings = $object->warnings();
|
||||
|
||||
Return all warning messages that occurred after L<catch_messages()>
|
||||
or L<catch_errors()> was called.
|
||||
|
||||
=head2 self()
|
||||
|
||||
my $self = $object->self();
|
||||
|
||||
Returns the object itself.
|
||||
|
||||
=head2 class()
|
||||
|
||||
my $class = $object->class();
|
||||
|
||||
Returns the full class name like C<node.cities>. See also C<sub_class>.
|
||||
|
||||
=head2 sub_class()
|
||||
|
||||
my $sub_class = $object->sub_class();
|
||||
|
||||
Returns the sub class name like C<cities>. See also C<class>.
|
||||
|
||||
=head2 main_class()
|
||||
|
||||
my $main_class = $object->main_class();
|
||||
|
||||
Returns the main class name like C<node>. See also C<sub_class>.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2008 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
X<tels>
|
||||
X<bloodgate>
|
||||
X<license>
|
||||
X<gpl>
|
||||
|
||||
=cut
|
||||
751
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Edge.pm
Normal file
751
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Edge.pm
Normal file
@@ -0,0 +1,751 @@
|
||||
#############################################################################
|
||||
# An edge connecting two nodes in Graph::Easy.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Edge;
|
||||
|
||||
use Graph::Easy::Node;
|
||||
@ISA = qw/Graph::Easy::Node/; # an edge is just a special node
|
||||
$VERSION = '0.76';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use constant isa_cell => 1;
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _init
|
||||
{
|
||||
# generic init, override in subclasses
|
||||
my ($self,$args) = @_;
|
||||
|
||||
$self->{class} = 'edge';
|
||||
|
||||
# leave this unitialized until we need it
|
||||
# $self->{cells} = [ ];
|
||||
|
||||
foreach my $k (sort keys %$args)
|
||||
{
|
||||
if ($k !~ /^(label|name|style)\z/)
|
||||
{
|
||||
require Carp;
|
||||
Carp::confess ("Invalid argument '$k' passed to Graph::Easy::Node->new()");
|
||||
}
|
||||
my $n = $k; $n = 'label' if $k eq 'name';
|
||||
|
||||
$self->{att}->{$n} = $args->{$k};
|
||||
}
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# accessor methods
|
||||
|
||||
sub bidirectional
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
if (@_ > 0)
|
||||
{
|
||||
my $old = $self->{bidirectional} || 0;
|
||||
$self->{bidirectional} = $_[0] ? 1 : 0;
|
||||
|
||||
# invalidate layout?
|
||||
$self->{graph}->{score} = undef if $old != $self->{bidirectional} && ref($self->{graph});
|
||||
}
|
||||
|
||||
$self->{bidirectional};
|
||||
}
|
||||
|
||||
sub undirected
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
if (@_ > 0)
|
||||
{
|
||||
my $old = $self->{undirected} || 0;
|
||||
$self->{undirected} = $_[0] ? 1 : 0;
|
||||
|
||||
# invalidate layout?
|
||||
$self->{graph}->{score} = undef if $old != $self->{undirected} && ref($self->{graph});
|
||||
}
|
||||
|
||||
$self->{undirected};
|
||||
}
|
||||
|
||||
sub has_ports
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
my $s_port = $self->{att}->{start} || $self->attribute('start');
|
||||
|
||||
return 1 if $s_port ne '';
|
||||
|
||||
my $e_port = $self->{att}->{end} || $self->attribute('end');
|
||||
|
||||
return 1 if $e_port ne '';
|
||||
|
||||
0;
|
||||
}
|
||||
|
||||
sub start_port
|
||||
{
|
||||
# return the side and portnumber if the edge has a shared source port
|
||||
# undef for none
|
||||
my $self = shift;
|
||||
|
||||
my $s = $self->{att}->{start} || $self->attribute('start');
|
||||
return undef if !defined $s || $s !~ /,/; # "south, 0" => ok, "south" => no
|
||||
|
||||
return (split /\s*,\s*/, $s) if wantarray;
|
||||
|
||||
$s =~ s/\s+//g; # remove spaces to normalize "south, 0" to "south,0"
|
||||
$s;
|
||||
}
|
||||
|
||||
sub end_port
|
||||
{
|
||||
# return the side and portnumber if the edge has a shared source port
|
||||
# undef for none
|
||||
my $self = shift;
|
||||
|
||||
my $s = $self->{att}->{end} || $self->attribute('end');
|
||||
return undef if !defined $s || $s !~ /,/; # "south, 0" => ok, "south" => no
|
||||
|
||||
return split /\s*,\s*/, $s if wantarray;
|
||||
|
||||
$s =~ s/\s+//g; # remove spaces to normalize "south, 0" to "south,0"
|
||||
$s;
|
||||
}
|
||||
|
||||
sub style
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{att}->{style} || $self->attribute('style');
|
||||
}
|
||||
|
||||
sub name
|
||||
{
|
||||
# returns actually the label
|
||||
my $self = shift;
|
||||
|
||||
$self->{att}->{label} || '';
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# cell management - used by the cell-based layouter
|
||||
|
||||
sub _cells
|
||||
{
|
||||
# return all the cells this edge currently occupies
|
||||
my $self = shift;
|
||||
|
||||
$self->{cells} = [] unless defined $self->{cells};
|
||||
|
||||
@{$self->{cells}};
|
||||
}
|
||||
|
||||
sub _clear_cells
|
||||
{
|
||||
# remove all belonging cells
|
||||
my $self = shift;
|
||||
|
||||
$self->{cells} = [];
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _unplace
|
||||
{
|
||||
# Take an edge, and remove all the cells it covers from the cells area
|
||||
my ($self, $cells) = @_;
|
||||
|
||||
print STDERR "# clearing path from $self->{from}->{name} to $self->{to}->{name}\n" if $self->{debug};
|
||||
|
||||
for my $key (@{$self->{cells}})
|
||||
{
|
||||
# XXX TODO: handle crossed edges differently (from CROSS => HOR or VER)
|
||||
# free in our cells area
|
||||
delete $cells->{$key};
|
||||
}
|
||||
|
||||
$self->clear_cells();
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _distance
|
||||
{
|
||||
# estimate the distance from SRC to DST node
|
||||
my ($self) = @_;
|
||||
|
||||
my $src = $self->{from};
|
||||
my $dst = $self->{to};
|
||||
|
||||
# one of them not yet placed?
|
||||
return 100000 unless defined $src->{x} && defined $dst->{x};
|
||||
|
||||
my $cells = $self->{graph}->{cells};
|
||||
|
||||
# get all the starting positions
|
||||
# distance = 1: slots, generate starting types, the direction is shifted
|
||||
# by 90° counter-clockwise
|
||||
|
||||
my @start = $src->_near_places($cells, 1, undef, undef, $src->_shift(-90) );
|
||||
|
||||
# potential stop positions
|
||||
my @stop = $dst->_near_places($cells, 1); # distance = 1: slots
|
||||
|
||||
my ($s_p,@ss_p) = $self->port('start');
|
||||
my ($e_p,@ee_p) = $self->port('end');
|
||||
|
||||
# the edge has a port description, limiting the start places
|
||||
@start = $src->_allowed_places( \@start, $src->_allow( $s_p, @ss_p ), 3)
|
||||
if defined $s_p;
|
||||
|
||||
# the edge has a port description, limiting the stop places
|
||||
@stop = $dst->_allowed_places( \@stop, $dst->_allow( $e_p, @ee_p ), 3)
|
||||
if defined $e_p;
|
||||
|
||||
my $stop = scalar @stop;
|
||||
|
||||
return 0 unless @stop > 0 && @start > 0; # no free slots on one node?
|
||||
|
||||
my $lowest;
|
||||
|
||||
my $i = 0;
|
||||
while ($i < scalar @start)
|
||||
{
|
||||
my $sx = $start[$i]; my $sy = $start[$i+1]; $i += 2;
|
||||
|
||||
# for each start point, calculate the distance to each stop point, then use
|
||||
# the smallest as value
|
||||
|
||||
for (my $u = 0; $u < $stop; $u += 2)
|
||||
{
|
||||
my $dist = Graph::Easy::_astar_distance($sx,$sy, $stop[$u], $stop[$u+1]);
|
||||
$lowest = $dist if !defined $lowest || $dist < $lowest;
|
||||
}
|
||||
}
|
||||
|
||||
$lowest;
|
||||
}
|
||||
|
||||
sub _add_cell
|
||||
{
|
||||
# add a cell to the list of cells this edge covers. If $after is a ref
|
||||
# to a cell, then the new cell will be inserted right after this cell.
|
||||
# if after is defined, but not a ref, the new cell will be inserted
|
||||
# at the specified position.
|
||||
my ($self, $cell, $after, $before) = @_;
|
||||
|
||||
$self->{cells} = [] unless defined $self->{cells};
|
||||
my $cells = $self->{cells};
|
||||
|
||||
# if both are defined, but belong to different edges, just ignore $before:
|
||||
$before = undef if ref($before) && $before->{edge} != $self;
|
||||
$after = undef if ref($after) && $after->{edge} != $self;
|
||||
if (!defined $after && ref($before))
|
||||
{
|
||||
$after = $before; $before = undef;
|
||||
}
|
||||
|
||||
if (defined $after)
|
||||
{
|
||||
# insert the new cell right after $after
|
||||
my $ofs = $after;
|
||||
if (ref($after) && !ref($before))
|
||||
{
|
||||
# insert after $after
|
||||
$ofs = 1;
|
||||
for my $cell (@$cells)
|
||||
{
|
||||
last if $cell == $after;
|
||||
$ofs++;
|
||||
}
|
||||
}
|
||||
elsif (ref($after) && ref($before))
|
||||
{
|
||||
# insert between after and before (or before/after for "reversed edges)
|
||||
$ofs = 0;
|
||||
my $found = 0;
|
||||
while ($ofs < scalar @$cells - 1) # 0,1,2,3 => 0 .. 2
|
||||
{
|
||||
my $c1 = $cells->[$ofs];
|
||||
my $c2 = $cells->[$ofs+1];
|
||||
$ofs++;
|
||||
$found++, last if (($c1 == $after && $c2 == $before) ||
|
||||
($c1 == $before && $c2 == $after));
|
||||
}
|
||||
if (!$found)
|
||||
{
|
||||
# XXX TODO: last effort
|
||||
|
||||
# insert after $after
|
||||
$ofs = 1;
|
||||
for my $cell (@$cells)
|
||||
{
|
||||
last if $cell == $after;
|
||||
$ofs++;
|
||||
}
|
||||
$found++;
|
||||
}
|
||||
$self->_croak("Could not find $after and $before") unless $found;
|
||||
}
|
||||
splice (@$cells, $ofs, 0, $cell);
|
||||
}
|
||||
else
|
||||
{
|
||||
# insert new cell at the end
|
||||
push @$cells, $cell;
|
||||
}
|
||||
|
||||
$cell->_update_boundaries();
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub from
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{from};
|
||||
}
|
||||
|
||||
sub to
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{to};
|
||||
}
|
||||
|
||||
sub nodes
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
($self->{from}, $self->{to});
|
||||
}
|
||||
|
||||
sub start_at
|
||||
{
|
||||
# move the edge's start point from the current node to the given node
|
||||
my ($self, $node) = @_;
|
||||
|
||||
# if not a node yet, or not part of this graph, make into one proper node
|
||||
$node = $self->{graph}->add_node($node);
|
||||
|
||||
$self->_croak("start_at() needs a node object, but got $node")
|
||||
unless ref($node) && $node->isa('Graph::Easy::Node');
|
||||
|
||||
# A => A => nothing to do
|
||||
return $node if $self->{from} == $node;
|
||||
|
||||
# delete self at A
|
||||
delete $self->{from}->{edges}->{ $self->{id} };
|
||||
|
||||
# set "from" to B
|
||||
$self->{from} = $node;
|
||||
|
||||
# add to B
|
||||
$self->{from}->{edges}->{ $self->{id} } = $self;
|
||||
|
||||
# invalidate layout
|
||||
$self->{graph}->{score} = undef if ref($self->{graph});
|
||||
|
||||
# return new start point
|
||||
$node;
|
||||
}
|
||||
|
||||
sub end_at
|
||||
{
|
||||
# move the edge's end point from the current node to the given node
|
||||
my ($self, $node) = @_;
|
||||
|
||||
# if not a node yet, or not part of this graph, make into one proper node
|
||||
$node = $self->{graph}->add_node($node);
|
||||
|
||||
$self->_croak("start_at() needs a node object, but got $node")
|
||||
unless ref($node) && $node->isa('Graph::Easy::Node');
|
||||
|
||||
# A => A => nothing to do
|
||||
return $node if $self->{to} == $node;
|
||||
|
||||
# delete self at A
|
||||
delete $self->{to}->{edges}->{ $self->{id} };
|
||||
|
||||
# set "to" to B
|
||||
$self->{to} = $node;
|
||||
|
||||
# add to node B
|
||||
$self->{to}->{edges}->{ $self->{id} } = $self;
|
||||
|
||||
# invalidate layout
|
||||
$self->{graph}->{score} = undef if ref($self->{graph});
|
||||
|
||||
# return new end point
|
||||
$node;
|
||||
}
|
||||
|
||||
sub edge_flow
|
||||
{
|
||||
# return the flow at this edge or '' if the edge itself doesn't have a flow
|
||||
my $self = shift;
|
||||
|
||||
# our flow comes from ourselves
|
||||
my $flow = $self->{att}->{flow};
|
||||
$flow = $self->raw_attribute('flow') unless defined $flow;
|
||||
|
||||
$flow;
|
||||
}
|
||||
|
||||
sub flow
|
||||
{
|
||||
# return the flow at this edge (including inheriting flow from node)
|
||||
my ($self) = @_;
|
||||
|
||||
# print STDERR "# flow from $self->{from}->{name} to $self->{to}->{name}\n";
|
||||
|
||||
# our flow comes from ourselves
|
||||
my $flow = $self->{att}->{flow};
|
||||
# or maybe our class
|
||||
$flow = $self->raw_attribute('flow') unless defined $flow;
|
||||
|
||||
# if the edge doesn't have a flow, maybe the node has a default out flow
|
||||
$flow = $self->{from}->{att}->{flow} if !defined $flow;
|
||||
|
||||
# if that didn't work out either, use the parents flows
|
||||
$flow = $self->parent()->attribute('flow') if !defined $flow;
|
||||
# or finally, the default "east":
|
||||
$flow = 90 if !defined $flow;
|
||||
|
||||
# absolute flow does not depend on the in-flow, so can return early
|
||||
return $flow if $flow =~ /^(0|90|180|270)\z/;
|
||||
|
||||
# in-flow comes from our "from" node
|
||||
my $in = $self->{from}->flow();
|
||||
|
||||
# print STDERR "# in: $self->{from}->{name} = $in\n";
|
||||
|
||||
my $out = $self->{graph}->_flow_as_direction($in,$flow);
|
||||
$out;
|
||||
}
|
||||
|
||||
sub port
|
||||
{
|
||||
my ($self, $which) = @_;
|
||||
|
||||
$self->_croak("'$which' must be one of 'start' or 'end' in port()") unless $which =~ /^(start|end)/;
|
||||
|
||||
# our flow comes from ourselves
|
||||
my $sp = $self->attribute($which);
|
||||
|
||||
return (undef,undef) unless defined $sp && $sp ne '';
|
||||
|
||||
my ($side, $port) = split /\s*,\s*/, $sp;
|
||||
|
||||
# if absolut direction, return as is
|
||||
my $s = Graph::Easy->_direction_as_side($side);
|
||||
|
||||
if (defined $s)
|
||||
{
|
||||
my @rc = ($s); push @rc, $port if defined $port;
|
||||
return @rc;
|
||||
}
|
||||
|
||||
# in_flow comes from our "from" node
|
||||
my $in = 90; $in = $self->{from}->flow() if ref($self->{from});
|
||||
|
||||
# turn left in "south" etc:
|
||||
$s = Graph::Easy->_flow_as_side($in,$side);
|
||||
|
||||
my @rc = ($s); push @rc, $port if defined $port;
|
||||
@rc;
|
||||
}
|
||||
|
||||
sub flip
|
||||
{
|
||||
# swap from and to for this edge
|
||||
my ($self) = @_;
|
||||
|
||||
($self->{from}, $self->{to}) = ($self->{to}, $self->{from});
|
||||
|
||||
# invalidate layout
|
||||
$self->{graph}->{score} = undef if ref($self->{graph});
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub as_ascii
|
||||
{
|
||||
my ($self, $x,$y) = @_;
|
||||
|
||||
# invisible nodes, or very small ones
|
||||
return '' if $self->{w} == 0 || $self->{h} == 0;
|
||||
|
||||
my $fb = $self->_framebuffer($self->{w}, $self->{h});
|
||||
|
||||
###########################################################################
|
||||
# "draw" the label into the framebuffer (e.g. the edge and the text)
|
||||
$self->_draw_label($fb, $x, $y, '');
|
||||
|
||||
join ("\n", @$fb);
|
||||
}
|
||||
|
||||
sub as_txt
|
||||
{
|
||||
require Graph::Easy::As_ascii;
|
||||
|
||||
_as_txt(@_);
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Edge - An edge (a path connecting one ore more nodes)
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $ssl = Graph::Easy::Edge->new(
|
||||
label => 'encrypted connection',
|
||||
style => 'solid',
|
||||
);
|
||||
$ssl->set_attribute('color', 'red');
|
||||
|
||||
my $src = Graph::Easy::Node->new('source');
|
||||
|
||||
my $dst = Graph::Easy::Node->new('destination');
|
||||
|
||||
$graph = Graph::Easy->new();
|
||||
|
||||
$graph->add_edge($src, $dst, $ssl);
|
||||
|
||||
print $graph->as_ascii();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Edge> represents an edge between two (or more) nodes in a
|
||||
simple graph.
|
||||
|
||||
Each edge has a direction (from source to destination, or back and forth),
|
||||
plus a style (line width and style), colors etc. It can also have a label,
|
||||
e.g. a text associated with it.
|
||||
|
||||
During the layout phase, each edge also contains a list of path-elements
|
||||
(also called cells), which make up the path from source to destination.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 error()
|
||||
|
||||
$last_error = $edge->error();
|
||||
|
||||
$cvt->error($error); # set new messages
|
||||
$cvt->error(''); # clear error
|
||||
|
||||
Returns the last error message, or '' for no error.
|
||||
|
||||
=head2 as_ascii()
|
||||
|
||||
my $ascii = $edge->as_ascii();
|
||||
|
||||
Returns the edge as a little ascii representation.
|
||||
|
||||
=head2 as_txt()
|
||||
|
||||
my $txt = $edge->as_txt();
|
||||
|
||||
Returns the edge as a little Graph::Easy textual representation.
|
||||
|
||||
=head2 label()
|
||||
|
||||
my $label = $edge->label();
|
||||
|
||||
Returns the label (also known as 'name') of the edge.
|
||||
|
||||
=head2 name()
|
||||
|
||||
my $label = $edge->name();
|
||||
|
||||
To make the interface more consistent, the C<name()> method of
|
||||
an edge can also be called, and it will returned either the edge
|
||||
label, or the empty string if the edge doesn't have a label.
|
||||
|
||||
=head2 style()
|
||||
|
||||
my $style = $edge->style();
|
||||
|
||||
Returns the style of the edge, like 'solid', 'dotted', 'double', etc.
|
||||
|
||||
=head2 nodes()
|
||||
|
||||
my @nodes = $edge->nodes();
|
||||
|
||||
Returns the source and target node that this edges connects as objects.
|
||||
|
||||
=head2 bidirectional()
|
||||
|
||||
$edge->bidirectional(1);
|
||||
if ($edge->bidirectional())
|
||||
{
|
||||
}
|
||||
|
||||
Returns true if the edge is bidirectional, aka has arrow heads on both ends.
|
||||
An optional parameter will set the bidirectional status of the edge.
|
||||
|
||||
=head2 undirected()
|
||||
|
||||
$edge->undirected(1);
|
||||
if ($edge->undirected())
|
||||
{
|
||||
}
|
||||
|
||||
Returns true if the edge is undirected, aka has now arrow at all.
|
||||
An optional parameter will set the undirected status of the edge.
|
||||
|
||||
=head2 has_ports()
|
||||
|
||||
if ($edge->has_ports())
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
Return true if the edge has restriction on the starting or ending
|
||||
port, e.g. either the C<start> or C<end> attribute is set on
|
||||
this edge.
|
||||
|
||||
=head2 start_port()
|
||||
|
||||
my $port = $edge->start_port();
|
||||
|
||||
Return undef if the edge does not have a fixed start port, otherwise
|
||||
returns the port as "side, number", for example "south, 0".
|
||||
|
||||
=head2 end_port()
|
||||
|
||||
my $port = $edge->end_port();
|
||||
|
||||
Return undef if the edge does not have a fixed end port, otherwise
|
||||
returns the port as "side, number", for example "south, 0".
|
||||
|
||||
=head2 from()
|
||||
|
||||
my $from = $edge->from();
|
||||
|
||||
Returns the node that this edge starts at. See also C<to()>.
|
||||
|
||||
=head2 to()
|
||||
|
||||
my $to = $edge->to();
|
||||
|
||||
Returns the node that this edge leads to. See also C<from()>.
|
||||
|
||||
=head2 start_at()
|
||||
|
||||
$edge->start_at($other);
|
||||
my $other = $edge->start_at('some node');
|
||||
|
||||
Set the edge's start point to the given node. If given a node name,
|
||||
will add that node to the graph first.
|
||||
|
||||
Returns the new edge start point node.
|
||||
|
||||
=head2 end_at()
|
||||
|
||||
$edge->end_at($other);
|
||||
my $other = $edge->end_at('some other node');
|
||||
|
||||
Set the edge's end point to the given node. If given a node name,
|
||||
will add that node to the graph first.
|
||||
|
||||
Returns the new edge end point node.
|
||||
|
||||
=head2 flip()
|
||||
|
||||
$edge->flip();
|
||||
|
||||
Swaps the C<start> and C<end> nodes on this edge, e.g. reverses the direction
|
||||
of the edge.
|
||||
|
||||
X<transpose>
|
||||
|
||||
=head2 flow()
|
||||
|
||||
my $flow = $edge->flow();
|
||||
|
||||
Returns the flow for this edge, honoring inheritance. An edge without
|
||||
a specific flow set will inherit the flow from the node it comes from.
|
||||
|
||||
=head2 edge_flow()
|
||||
|
||||
my $flow = $edge->edge_flow();
|
||||
|
||||
Returns the flow for this edge, or undef if it has none set on either
|
||||
the object itself or its class.
|
||||
|
||||
=head2 port()
|
||||
|
||||
my ($side, $number) = $edge->port('start');
|
||||
my ($side, $number) = $edge->port('end');
|
||||
|
||||
Return the side and port number where this edge starts or ends.
|
||||
|
||||
Returns undef for $side if the edge has no port restriction. The
|
||||
returned side will be one absolute direction of C<east>, C<west>,
|
||||
C<north> or C<south>, depending on the port restriction and
|
||||
flow at that edge.
|
||||
|
||||
=head2 get_attributes()
|
||||
|
||||
my $att = $object->get_attributes();
|
||||
|
||||
Return all effective attributes on this object (graph/node/group/edge) as
|
||||
an anonymous hash ref. This respects inheritance and default values.
|
||||
|
||||
See also L<raw_attributes()>.
|
||||
|
||||
=head2 raw_attributes()
|
||||
|
||||
my $att = $object->get_attributes();
|
||||
|
||||
Return all set attributes on this object (graph/node/group/edge) as
|
||||
an anonymous hash ref. This respects inheritance, but does not include
|
||||
default values for unset attributes.
|
||||
|
||||
See also L<get_attributes()>.
|
||||
|
||||
=head2 attribute related methods
|
||||
|
||||
You can call all the various attribute related methods like C<set_attribute()>,
|
||||
C<get_attribute()>, etc. on an edge, too. For example:
|
||||
|
||||
$edge->set_attribute('label', 'by train');
|
||||
my $attr = $edge->get_attributes();
|
||||
my $raw_attr = $edge->raw_attributes();
|
||||
|
||||
You can find more documentation in L<Graph::Easy>.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2008 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
1464
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Edge/Cell.pm
Normal file
1464
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Edge/Cell.pm
Normal file
File diff suppressed because it is too large
Load Diff
828
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Group.pm
Normal file
828
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Group.pm
Normal file
@@ -0,0 +1,828 @@
|
||||
#############################################################################
|
||||
# A group of nodes. Part of Graph::Easy.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Group;
|
||||
|
||||
use Graph::Easy::Group::Cell;
|
||||
use Graph::Easy;
|
||||
use Scalar::Util qw/weaken/;
|
||||
|
||||
@ISA = qw/Graph::Easy::Node Graph::Easy/;
|
||||
$VERSION = '0.76';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _init
|
||||
{
|
||||
# generic init, override in subclasses
|
||||
my ($self,$args) = @_;
|
||||
|
||||
$self->{name} = 'Group #'. $self->{id};
|
||||
$self->{class} = 'group';
|
||||
$self->{_cells} = {}; # the Group::Cell objects
|
||||
# $self->{cx} = 1;
|
||||
# $self->{cy} = 1;
|
||||
|
||||
foreach my $k (sort keys %$args)
|
||||
{
|
||||
if ($k !~ /^(graph|name)\z/)
|
||||
{
|
||||
require Carp;
|
||||
Carp::confess ("Invalid argument '$k' passed to Graph::Easy::Group->new()");
|
||||
}
|
||||
$self->{$k} = $args->{$k};
|
||||
}
|
||||
|
||||
$self->{nodes} = {};
|
||||
$self->{groups} = {};
|
||||
$self->{att} = {};
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# accessor methods
|
||||
|
||||
sub nodes
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
wantarray ? ( ord_values ( $self->{nodes} ) ) : scalar keys %{$self->{nodes}};
|
||||
}
|
||||
|
||||
sub edges
|
||||
{
|
||||
# edges leading from/to this group
|
||||
my $self = shift;
|
||||
|
||||
wantarray ? ( ord_values ( $self->{edges} ) ) : scalar keys %{$self->{edges}};
|
||||
}
|
||||
|
||||
sub edges_within
|
||||
{
|
||||
# edges between nodes inside this group
|
||||
my $self = shift;
|
||||
|
||||
wantarray ? ( ord_values ( $self->{edges_within} ) ) :
|
||||
scalar keys %{$self->{edges_within}};
|
||||
}
|
||||
|
||||
sub _groups_within
|
||||
{
|
||||
my ($self, $level, $max_level, $cur) = @_;
|
||||
|
||||
no warnings 'recursion';
|
||||
|
||||
push @$cur, ord_values ( $self->{groups} );
|
||||
|
||||
return if $level >= $max_level;
|
||||
|
||||
for my $g (ord_values ( $self->{groups} ))
|
||||
{
|
||||
$g->_groups_within($level+1,$max_level, $cur) if scalar keys %{$g->{groups}} > 0;
|
||||
}
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub set_attribute
|
||||
{
|
||||
my ($self, $name, $val, $class) = @_;
|
||||
|
||||
$self->SUPER::set_attribute($name, $val, $class);
|
||||
|
||||
# if defined attribute "nodeclass", put our nodes into that class
|
||||
if ($name eq 'nodeclass')
|
||||
{
|
||||
my $class = $self->{att}->{nodeclass};
|
||||
for my $node (ord_values ( $self->{nodes} ) )
|
||||
{
|
||||
$node->sub_class($class);
|
||||
}
|
||||
}
|
||||
$self;
|
||||
}
|
||||
|
||||
sub shape
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
# $self->{att}->{shape} || $self->attribute('shape');
|
||||
'';
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# node handling
|
||||
|
||||
sub add_node
|
||||
{
|
||||
# add a node to this group
|
||||
my ($self,$n) = @_;
|
||||
|
||||
if (!ref($n) || !$n->isa("Graph::Easy::Node"))
|
||||
{
|
||||
if (!ref($self->{graph}))
|
||||
{
|
||||
return $self->error("Cannot add non node-object $n to group '$self->{name}'");
|
||||
}
|
||||
$n = $self->{graph}->add_node($n);
|
||||
}
|
||||
$self->{nodes}->{ $n->{name} } = $n;
|
||||
|
||||
# if defined attribute "nodeclass", put our nodes into that class
|
||||
$n->sub_class($self->{att}->{nodeclass}) if exists $self->{att}->{nodeclass};
|
||||
|
||||
# register ourselves with the member
|
||||
$n->{group} = $self;
|
||||
|
||||
# set the proper attribute (for layout)
|
||||
$n->{att}->{group} = $self->{name};
|
||||
|
||||
# Register the nodes and the edge with our graph object
|
||||
# and weaken the references. Be careful to not needlessly
|
||||
# override and weaken again an already existing reference, this
|
||||
# is an O(N) operation in most Perl versions, and thus very slow.
|
||||
|
||||
# If the node does not belong to a graph yet or belongs to another
|
||||
# graph, add it to our own graph:
|
||||
weaken($n->{graph} = $self->{graph}) unless
|
||||
$n->{graph} && $self->{graph} && $n->{graph} == $self->{graph};
|
||||
|
||||
$n;
|
||||
}
|
||||
|
||||
sub add_member
|
||||
{
|
||||
# add a node or group to this group
|
||||
my ($self,$n) = @_;
|
||||
|
||||
if (!ref($n) || !$n->isa("Graph::Easy::Node"))
|
||||
{
|
||||
if (!ref($self->{graph}))
|
||||
{
|
||||
return $self->error("Cannot add non node-object $n to group '$self->{name}'");
|
||||
}
|
||||
$n = $self->{graph}->add_node($n);
|
||||
}
|
||||
return $self->_add_edge($n) if $n->isa("Graph::Easy::Edge");
|
||||
return $self->add_group($n) if $n->isa('Graph::Easy::Group');
|
||||
|
||||
$self->{nodes}->{ $n->{name} } = $n;
|
||||
|
||||
# if defined attribute "nodeclass", put our nodes into that class
|
||||
my $cl = $self->attribute('nodeclass');
|
||||
$n->sub_class($cl) if $cl ne '';
|
||||
|
||||
# register ourselves with the member
|
||||
$n->{group} = $self;
|
||||
|
||||
# set the proper attribute (for layout)
|
||||
$n->{att}->{group} = $self->{name};
|
||||
|
||||
# Register the nodes and the edge with our graph object
|
||||
# and weaken the references. Be careful to not needlessly
|
||||
# override and weaken again an already existing reference, this
|
||||
# is an O(N) operation in most Perl versions, and thus very slow.
|
||||
|
||||
# If the node does not belong to a graph yet or belongs to another
|
||||
# graph, add it to our own graph:
|
||||
weaken($n->{graph} = $self->{graph}) unless
|
||||
$n->{graph} && $self->{graph} && $n->{graph} == $self->{graph};
|
||||
|
||||
$n;
|
||||
}
|
||||
|
||||
sub del_member
|
||||
{
|
||||
# delete a node or group from this group
|
||||
my ($self,$n) = @_;
|
||||
|
||||
# XXX TOOD: groups vs. nodes
|
||||
my $class = 'nodes'; my $key = 'name';
|
||||
if ($n->isa('Graph::Easy::Group'))
|
||||
{
|
||||
# XXX TOOD: groups vs. nodes
|
||||
$class = 'groups'; $key = 'id';
|
||||
}
|
||||
delete $self->{$class}->{ $n->{$key} };
|
||||
delete $n->{group}; # unregister us
|
||||
|
||||
if ($n->isa('Graph::Easy::Node'))
|
||||
{
|
||||
# find all edges that mention this node and drop them from the group
|
||||
my $edges = $self->{edges_within};
|
||||
for my $e (ord_values ( $edges))
|
||||
{
|
||||
delete $edges->{ $e->{id} } if $e->{from} == $n || $e->{to} == $n;
|
||||
}
|
||||
}
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub del_node
|
||||
{
|
||||
# delete a node from this group
|
||||
my ($self,$n) = @_;
|
||||
|
||||
delete $self->{nodes}->{ $n->{name} };
|
||||
delete $n->{group}; # unregister us
|
||||
delete $n->{att}->{group}; # delete the group attribute
|
||||
|
||||
# find all edges that mention this node and drop them from the group
|
||||
my $edges = $self->{edges_within};
|
||||
for my $e (ord_values ( $edges))
|
||||
{
|
||||
delete $edges->{ $e->{id} } if $e->{from} == $n || $e->{to} == $n;
|
||||
}
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub add_nodes
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# make a copy in case of scalars
|
||||
my @arg = @_;
|
||||
foreach my $n (@arg)
|
||||
{
|
||||
if (!ref($n) && !ref($self->{graph}))
|
||||
{
|
||||
return $self->error("Cannot add non node-object $n to group '$self->{name}'");
|
||||
}
|
||||
return $self->error("Cannot add group-object $n to group '$self->{name}'")
|
||||
if $n->isa('Graph::Easy::Group');
|
||||
|
||||
$n = $self->{graph}->add_node($n) unless ref($n);
|
||||
|
||||
$self->{nodes}->{ $n->{name} } = $n;
|
||||
|
||||
# set the proper attribute (for layout)
|
||||
$n->{att}->{group} = $self->{name};
|
||||
|
||||
# XXX TODO TEST!
|
||||
# # if defined attribute "nodeclass", put our nodes into that class
|
||||
# $n->sub_class($self->{att}->{nodeclass}) if exists $self->{att}->{nodeclass};
|
||||
|
||||
# register ourselves with the member
|
||||
$n->{group} = $self;
|
||||
|
||||
# Register the nodes and the edge with our graph object
|
||||
# and weaken the references. Be careful to not needlessly
|
||||
# override and weaken again an already existing reference, this
|
||||
# is an O(N) operation in most Perl versions, and thus very slow.
|
||||
|
||||
# If the node does not belong to a graph yet or belongs to another
|
||||
# graph, add it to our own graph:
|
||||
weaken($n->{graph} = $self->{graph}) unless
|
||||
$n->{graph} && $self->{graph} && $n->{graph} == $self->{graph};
|
||||
|
||||
}
|
||||
|
||||
@arg;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _del_edge
|
||||
{
|
||||
# delete an edge from this group
|
||||
my ($self,$e) = @_;
|
||||
|
||||
delete $self->{edges_within}->{ $e->{id} };
|
||||
delete $e->{group}; # unregister us
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _add_edge
|
||||
{
|
||||
# add an edge to this group (e.g. when both from/to of this edge belong
|
||||
# to this group)
|
||||
my ($self,$e) = @_;
|
||||
|
||||
if (!ref($e) || !$e->isa("Graph::Easy::Edge"))
|
||||
{
|
||||
return $self->error("Cannot add non edge-object $e to group '$self->{name}'");
|
||||
}
|
||||
$self->{edges_within}->{ $e->{id} } = $e;
|
||||
|
||||
# if defined attribute "edgeclass", put our edges into that class
|
||||
my $edge_class = $self->attribute('edgeclass');
|
||||
$e->sub_class($edge_class) if $edge_class ne '';
|
||||
|
||||
# XXX TODO: inline
|
||||
$self->add_node($e->{from});
|
||||
$self->add_node($e->{to});
|
||||
|
||||
# register us, but don't do weaken() if the ref was already set
|
||||
weaken($e->{group} = $self) unless defined $e->{group} && $e->{group} == $self;
|
||||
|
||||
$e;
|
||||
}
|
||||
|
||||
sub add_edge
|
||||
{
|
||||
# Add an edge to the graph of this group, then register it with this group.
|
||||
my ($self,$from,$to) = @_;
|
||||
|
||||
my $g = $self->{graph};
|
||||
return $self->error("Cannot add edge to group '$self->{name}' without graph")
|
||||
unless defined $g;
|
||||
|
||||
my $edge = $g->add_edge($from,$to);
|
||||
|
||||
$self->_add_edge($edge);
|
||||
}
|
||||
|
||||
sub add_edge_once
|
||||
{
|
||||
# Add an edge to the graph of this group, then register it with this group.
|
||||
my ($self,$from,$to) = @_;
|
||||
|
||||
my $g = $self->{graph};
|
||||
return $self->error("Cannot non edge to group '$self->{name}' without graph")
|
||||
unless defined $g;
|
||||
|
||||
my $edge = $g->add_edge_once($from,$to);
|
||||
# edge already exists => so fetch it
|
||||
$edge = $g->edge($from,$to) unless defined $edge;
|
||||
|
||||
$self->_add_edge($edge);
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub add_group
|
||||
{
|
||||
# add a group to us
|
||||
my ($self,$group) = @_;
|
||||
|
||||
# group with that name already exists?
|
||||
my $name = $group;
|
||||
$group = $self->{groups}->{ $group } unless ref $group;
|
||||
|
||||
# group with that name doesn't exist, so create new one
|
||||
$group = $self->{graph}->add_group($name) unless ref $group;
|
||||
|
||||
# index under the group name for easier lookup
|
||||
$self->{groups}->{ $group->{name} } = $group;
|
||||
|
||||
# make attribute->('group') work
|
||||
$group->{att}->{group} = $self->{name};
|
||||
|
||||
# register group with the graph and ourself
|
||||
$group->{graph} = $self->{graph};
|
||||
$group->{group} = $self;
|
||||
{
|
||||
no warnings; # don't warn on already weak references
|
||||
weaken($group->{graph});
|
||||
weaken($group->{group});
|
||||
}
|
||||
$self->{graph}->{score} = undef; # invalidate last layout
|
||||
|
||||
$group;
|
||||
}
|
||||
|
||||
# cell management - used by the layouter
|
||||
|
||||
sub _cells
|
||||
{
|
||||
# return all the cells this group currently occupies
|
||||
my $self = shift;
|
||||
|
||||
$self->{_cells};
|
||||
}
|
||||
|
||||
sub _clear_cells
|
||||
{
|
||||
# remove all belonging cells
|
||||
my $self = shift;
|
||||
|
||||
$self->{_cells} = {};
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _add_cell
|
||||
{
|
||||
# add a cell to the list of cells this group covers
|
||||
my ($self,$cell) = @_;
|
||||
|
||||
$cell->_update_boundaries();
|
||||
$self->{_cells}->{"$cell->{x},$cell->{y}"} = $cell;
|
||||
$cell;
|
||||
}
|
||||
|
||||
sub _del_cell
|
||||
{
|
||||
# delete a cell from the list of cells this group covers
|
||||
my ($self,$cell) = @_;
|
||||
|
||||
delete $self->{_cells}->{"$cell->{x},$cell->{y}"};
|
||||
delete $cell->{group};
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _find_label_cell
|
||||
{
|
||||
# go through all cells of this group and find one where to attach the label
|
||||
my $self = shift;
|
||||
|
||||
my $g = $self->{graph};
|
||||
|
||||
my $align = $self->attribute('align');
|
||||
my $loc = $self->attribute('labelpos');
|
||||
|
||||
# depending on whether the label should be on top or bottom:
|
||||
my $match = qr/^\s*gt\s*\z/;
|
||||
$match = qr/^\s*gb\s*\z/ if $loc eq 'bottom';
|
||||
|
||||
my $lc; # the label cell
|
||||
|
||||
for my $c (ord_values ( $self->{_cells} ))
|
||||
{
|
||||
# find a cell where to put the label
|
||||
next unless $c->{cell_class} =~ $match;
|
||||
|
||||
if (defined $lc)
|
||||
{
|
||||
if ($align eq 'left')
|
||||
{
|
||||
# find top-most, left-most cell
|
||||
next if $lc->{x} < $c->{x} || $lc->{y} < $c->{y};
|
||||
}
|
||||
elsif ($align eq 'center')
|
||||
{
|
||||
# just find any top-most cell
|
||||
next if $lc->{y} < $c->{y};
|
||||
}
|
||||
elsif ($align eq 'right')
|
||||
{
|
||||
# find top-most, right-most cell
|
||||
next if $lc->{x} > $c->{x} || $lc->{y} < $c->{y};
|
||||
}
|
||||
}
|
||||
$lc = $c;
|
||||
}
|
||||
|
||||
# find the cell mostly near the center in the found top-row
|
||||
if (ref($lc) && $align eq 'center')
|
||||
{
|
||||
my ($left, $right);
|
||||
# find left/right most coordinates
|
||||
for my $c (ord_values ( $self->{_cells} ))
|
||||
{
|
||||
next if $c->{y} != $lc->{y};
|
||||
$left = $c->{x} if !defined $left || $left > $c->{x};
|
||||
$right = $c->{x} if !defined $right || $right < $c->{x};
|
||||
}
|
||||
my $center = int(($right - $left) / 2 + $left);
|
||||
my $min_dist;
|
||||
# find the cell mostly near the center in the found top-row
|
||||
for my $c (ord_values ( $self->{_cells} ))
|
||||
{
|
||||
next if $c->{y} != $lc->{y};
|
||||
# squared to get rid of sign
|
||||
my $dist = ($center - $c->{x}); $dist *= $dist;
|
||||
next if defined $min_dist && $dist > $min_dist;
|
||||
$min_dist = $dist; $lc = $c;
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR "# Setting label for group '$self->{name}' at $lc->{x},$lc->{y}\n"
|
||||
if $self->{debug};
|
||||
|
||||
$lc->_set_label() if ref($lc);
|
||||
}
|
||||
|
||||
sub layout
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->_croak('Cannot call layout() on a Graph::Easy::Group directly.');
|
||||
}
|
||||
|
||||
sub _layout
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
###########################################################################
|
||||
# set local {debug} for groups
|
||||
local $self->{debug} = $self->{graph}->{debug};
|
||||
|
||||
$self->SUPER::_layout();
|
||||
}
|
||||
|
||||
sub _set_cell_types
|
||||
{
|
||||
my ($self, $cells) = @_;
|
||||
|
||||
# Set the right cell class for all of our cells:
|
||||
for my $cell (ord_values ( $self->{_cells} ))
|
||||
{
|
||||
$cell->_set_type($cells);
|
||||
}
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Group - A group of nodes (aka subgraph) in Graph::Easy
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $bonn = Graph::Easy::Node->new('Bonn');
|
||||
|
||||
$bonn->set_attribute('border', 'solid 1px black');
|
||||
|
||||
my $berlin = Graph::Easy::Node->new( name => 'Berlin' );
|
||||
|
||||
my $cities = Graph::Easy::Group->new(
|
||||
name => 'Cities',
|
||||
);
|
||||
$cities->set_attribute('border', 'dashed 1px blue');
|
||||
|
||||
$cities->add_nodes ($bonn);
|
||||
# $bonn will be ONCE in the group
|
||||
$cities->add_nodes ($bonn, $berlin);
|
||||
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Group> represents a group of nodes in an C<Graph::Easy>
|
||||
object. These nodes are grouped together on output.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 new()
|
||||
|
||||
my $group = Graph::Easy::Group->new( $options );
|
||||
|
||||
Create a new, empty group. C<$options> are the possible options, see
|
||||
L<Graph::Easy::Node> for a list.
|
||||
|
||||
=head2 error()
|
||||
|
||||
$last_error = $group->error();
|
||||
|
||||
$group->error($error); # set new messages
|
||||
$group->error(''); # clear error
|
||||
|
||||
Returns the last error message, or '' for no error.
|
||||
|
||||
=head2 as_ascii()
|
||||
|
||||
my $ascii = $group->as_ascii();
|
||||
|
||||
Return the group as a little box drawn in ASCII art as a string.
|
||||
|
||||
=head2 name()
|
||||
|
||||
my $name = $group->name();
|
||||
|
||||
Return the name of the group.
|
||||
|
||||
=head2 id()
|
||||
|
||||
my $id = $group->id();
|
||||
|
||||
Returns the group's unique ID number.
|
||||
|
||||
=head2 set_attribute()
|
||||
|
||||
$group->set_attribute('border-style', 'none');
|
||||
|
||||
Sets the specified attribute of this (and only this!) group to the
|
||||
specified value.
|
||||
|
||||
=head2 add_member()
|
||||
|
||||
$group->add_member($node);
|
||||
$group->add_member($group);
|
||||
|
||||
Add the specified object to this group and returns this member. If the
|
||||
passed argument is a scalar, will treat it as a node name.
|
||||
|
||||
Note that each object can only be a member of one group at a time.
|
||||
|
||||
=head2 add_node()
|
||||
|
||||
$group->add_node($node);
|
||||
|
||||
Add the specified node to this group and returns this node.
|
||||
|
||||
Note that each object can only be a member of one group at a time.
|
||||
|
||||
=head2 add_edge(), add_edge_once()
|
||||
|
||||
$group->add_edge($edge); # Graph::Easy::Edge
|
||||
$group->add_edge($from, $to); # Graph::Easy::Node or
|
||||
# Graph::Easy::Group
|
||||
$group->add_edge('From', 'To'); # Scalars
|
||||
|
||||
If passed an Graph::Easy::Edge object, moves the nodes involved in
|
||||
this edge to the group.
|
||||
|
||||
if passed two nodes, adds these nodes to the graph (unless they already
|
||||
exist) and adds an edge between these two nodes. See L<add_edge_once()>
|
||||
to avoid creating multiple edges.
|
||||
|
||||
This method works only on groups that are part of a graph.
|
||||
|
||||
Note that each object can only be a member of one group at a time,
|
||||
and edges are automatically a member of a group if and only if both
|
||||
the target and the destination node are a member of the same group.
|
||||
|
||||
=head2 add_group()
|
||||
|
||||
my $inner = $group->add_group('Group name');
|
||||
my $nested = $group->add_group($group);
|
||||
|
||||
Add a group as subgroup to this group and returns this group.
|
||||
|
||||
=head2 del_member()
|
||||
|
||||
$group->del_member($node);
|
||||
$group->del_member($group);
|
||||
|
||||
Delete the specified object from this group.
|
||||
|
||||
=head2 del_node()
|
||||
|
||||
$group->del_node($node);
|
||||
|
||||
Delete the specified node from this group.
|
||||
|
||||
=head2 del_edge()
|
||||
|
||||
$group->del_edge($edge);
|
||||
|
||||
Delete the specified edge from this group.
|
||||
|
||||
=head2 add_nodes()
|
||||
|
||||
$group->add_nodes($node, $node2, ... );
|
||||
|
||||
Add all the specified nodes to this group and returns them as a list.
|
||||
|
||||
=head2 nodes()
|
||||
|
||||
my @nodes = $group->nodes();
|
||||
|
||||
Returns a list of all node objects that belong to this group.
|
||||
|
||||
=head2 edges()
|
||||
|
||||
my @edges = $group->edges();
|
||||
|
||||
Returns a list of all edge objects that lead to or from this group.
|
||||
|
||||
Note: This does B<not> return edges between nodes that are inside the group,
|
||||
for this see L<edges_within()>.
|
||||
|
||||
=head2 edges_within()
|
||||
|
||||
my @edges_within = $group->edges_within();
|
||||
|
||||
Returns a list of all edge objects that are I<inside> this group, in arbitrary
|
||||
order. Edges are automatically considered I<inside> a group if their starting
|
||||
and ending node both are in the same group.
|
||||
|
||||
Note: This does B<not> return edges between this group and other groups,
|
||||
nor edges between this group and nodes outside this group, for this see
|
||||
L<edges()>.
|
||||
|
||||
=head2 groups()
|
||||
|
||||
my @groups = $group->groups();
|
||||
|
||||
Returns the contained groups of this group as L<Graph::Easy::Group> objects,
|
||||
in arbitrary order.
|
||||
|
||||
=head2 groups_within()
|
||||
|
||||
# equivalent to $group->groups():
|
||||
my @groups = $group->groups_within(); # all
|
||||
my @toplevel_groups = $group->groups_within(0); # level 0 only
|
||||
|
||||
Return the groups that are inside this group, up to the specified level,
|
||||
in arbitrary order.
|
||||
|
||||
The default level is -1, indicating no bounds and thus all contained
|
||||
groups are returned.
|
||||
|
||||
A level of 0 means only the direct children, and hence only the toplevel
|
||||
groups will be returned. A level 1 means the toplevel groups and their
|
||||
toplevel children, and so on.
|
||||
|
||||
=head2 as_txt()
|
||||
|
||||
my $txt = $group->as_txt();
|
||||
|
||||
Returns the group as Graph::Easy textual description.
|
||||
|
||||
=head2 _find_label_cell()
|
||||
|
||||
$group->_find_label_cell();
|
||||
|
||||
Called by the layouter once for each group. Goes through all cells of this
|
||||
group and finds one where to attach the label to. Internal usage only.
|
||||
|
||||
=head2 get_attributes()
|
||||
|
||||
my $att = $object->get_attributes();
|
||||
|
||||
Return all effective attributes on this object (graph/node/group/edge) as
|
||||
an anonymous hash ref. This respects inheritance and default values.
|
||||
|
||||
See also L<raw_attributes()>.
|
||||
|
||||
=head2 raw_attributes()
|
||||
|
||||
my $att = $object->get_attributes();
|
||||
|
||||
Return all set attributes on this object (graph/node/group/edge) as
|
||||
an anonymous hash ref. This respects inheritance, but does not include
|
||||
default values for unset attributes.
|
||||
|
||||
See also L<get_attributes()>.
|
||||
|
||||
=head2 attribute related methods
|
||||
|
||||
You can call all the various attribute related methods like C<set_attribute()>,
|
||||
C<get_attribute()>, etc. on a group, too. For example:
|
||||
|
||||
$group->set_attribute('label', 'by train');
|
||||
my $attr = $group->get_attributes();
|
||||
|
||||
You can find more documentation in L<Graph::Easy>.
|
||||
|
||||
=head2 layout()
|
||||
|
||||
This routine should not be called on groups, it only works on the graph
|
||||
itself.
|
||||
|
||||
=head2 shape()
|
||||
|
||||
my $shape = $group->shape();
|
||||
|
||||
Returns the shape of the group as string.
|
||||
|
||||
=head2 has_as_successor()
|
||||
|
||||
if ($group->has_as_successor($other))
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
Returns true if C<$other> (a node or group) is a successor of this group, e.g.
|
||||
if there is an edge leading from this group to C<$other>.
|
||||
|
||||
=head2 has_as_predecessor()
|
||||
|
||||
if ($group->has_as_predecessor($other))
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
Returns true if the group has C<$other> (a group or node) as predecessor, that
|
||||
is if there is an edge leading from C<$other> to this group.
|
||||
|
||||
=head2 root_node()
|
||||
|
||||
my $root = $group->root_node();
|
||||
|
||||
Return the root node as L<Graph::Easy::Node> object, if it was
|
||||
set with the 'root' attribute.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>, L<Graph::Easy::Node>, L<Graph::Easy::Manual>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2008 by Tels L<http://bloodgate.com>
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
124
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Group/Anon.pm
Normal file
124
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Group/Anon.pm
Normal file
@@ -0,0 +1,124 @@
|
||||
#############################################################################
|
||||
# (c) by Tels 2004. Part of Graph::Easy. An anonymous group.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Group::Anon;
|
||||
|
||||
use Graph::Easy::Group;
|
||||
use warnings;
|
||||
|
||||
@ISA = qw/Graph::Easy::Group/;
|
||||
$VERSION = '0.76';
|
||||
|
||||
use strict;
|
||||
|
||||
sub _init
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->SUPER::_init(@_);
|
||||
|
||||
$self->{name} = 'Group #' . $self->{id};
|
||||
$self->{class} = 'group.anon';
|
||||
|
||||
$self->{att}->{label} = '';
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _correct_size
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{w} = 3;
|
||||
$self->{h} = 3;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub attributes_as_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->SUPER::attributes_as_txt( {
|
||||
node => {
|
||||
label => undef,
|
||||
shape => undef,
|
||||
class => undef,
|
||||
} } );
|
||||
}
|
||||
|
||||
sub as_pure_txt
|
||||
{
|
||||
'( )';
|
||||
}
|
||||
|
||||
sub _as_part_txt
|
||||
{
|
||||
'( )';
|
||||
}
|
||||
|
||||
sub as_graphviz_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
my $name = $self->{name};
|
||||
|
||||
# quote special chars in name
|
||||
$name =~ s/([\[\]\(\)\{\}\#])/\\$1/g;
|
||||
|
||||
'"' . $name . '"';
|
||||
}
|
||||
|
||||
sub text_styles_as_css
|
||||
{
|
||||
'';
|
||||
}
|
||||
|
||||
sub is_anon
|
||||
{
|
||||
# is an anon group
|
||||
1;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Group::Anon - An anonymous group of nodes in Graph::Easy
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy::Group::Anon;
|
||||
|
||||
my $anon = Graph::Easy::Group::Anon->new();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Group::Anon> represents an anonymous group of nodes,
|
||||
e.g. a group without a name.
|
||||
|
||||
The syntax in the Graph::Easy textual description language looks like this:
|
||||
|
||||
( [ Bonn ] -> [ Berlin ] )
|
||||
|
||||
This module is loaded and used automatically by Graph::Easy, so there is
|
||||
no need to use it manually.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy::Group>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2006 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
401
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Group/Cell.pm
Normal file
401
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Group/Cell.pm
Normal file
@@ -0,0 +1,401 @@
|
||||
#############################################################################
|
||||
# A cell of a group during layout. Part of Graph::Easy.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Group::Cell;
|
||||
|
||||
use Graph::Easy::Node;
|
||||
|
||||
@ISA = qw/Graph::Easy::Node/;
|
||||
$VERSION = '0.76';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN
|
||||
{
|
||||
*get_attribute = \&attribute;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
# The different types for a group-cell:
|
||||
use constant {
|
||||
GROUP_INNER => 0, # completely sourounded by group cells
|
||||
GROUP_RIGHT => 1, # right border only
|
||||
GROUP_LEFT => 2, # left border only
|
||||
GROUP_TOP => 3, # top border only
|
||||
GROUP_BOTTOM => 4, # bottom border only
|
||||
GROUP_ALL => 5, # completely sourounded by non-group cells
|
||||
|
||||
GROUP_BOTTOM_RIGHT => 6, # bottom and right border
|
||||
GROUP_BOTTOM_LEFT => 7, # bottom and left border
|
||||
GROUP_TOP_RIGHT => 8, # top and right border
|
||||
GROUP_TOP_LEFT => 9, # top and left order
|
||||
|
||||
GROUP_MAX => 5, # max number
|
||||
};
|
||||
|
||||
my $border_styles =
|
||||
{
|
||||
# type top, bottom, left, right, class
|
||||
GROUP_INNER() => [ 0, 0, 0, 0, ['gi'] ],
|
||||
GROUP_RIGHT() => [ 0, 0, 0, 1, ['gr'] ],
|
||||
GROUP_LEFT() => [ 0, 0, 1, 0, ['gl'] ],
|
||||
GROUP_TOP() => [ 1, 0, 0, 0, ['gt'] ],
|
||||
GROUP_BOTTOM() => [ 0, 1, 0, 0, ['gb'] ],
|
||||
GROUP_ALL() => [ 0, 0, 0, 0, ['ga'] ],
|
||||
GROUP_BOTTOM_RIGHT() => [ 0, 1, 0, 1, ['gb','gr'] ],
|
||||
GROUP_BOTTOM_LEFT() => [ 0, 1, 1, 0, ['gb','gl'] ],
|
||||
GROUP_TOP_RIGHT() => [ 1, 0, 0, 1, ['gt','gr'] ],
|
||||
GROUP_TOP_LEFT() => [ 1, 0, 1, 0, ['gt','gl'] ],
|
||||
};
|
||||
|
||||
my $border_name = [ 'top', 'bottom', 'left', 'right' ];
|
||||
|
||||
sub _css
|
||||
{
|
||||
my ($c, $id, $group, $border) = @_;
|
||||
|
||||
my $css = '';
|
||||
|
||||
for my $type (0 .. 5)
|
||||
{
|
||||
my $b = $border_styles->{$type};
|
||||
|
||||
# If border eq 'none', this would needlessly repeat the "border: none"
|
||||
# from the general group class.
|
||||
next if $border eq 'none';
|
||||
|
||||
my $cl = '.' . $b->[4]->[0]; # $cl .= "-$group" unless $group eq '';
|
||||
|
||||
$css .= "table.graph$id $cl {";
|
||||
if ($type == GROUP_INNER)
|
||||
{
|
||||
$css .= " border: none;"; # shorter CSS
|
||||
}
|
||||
elsif ($type == GROUP_ALL)
|
||||
{
|
||||
$css .= " border-style: $border;"; # shorter CSS
|
||||
}
|
||||
else
|
||||
{
|
||||
for (my $i = 0; $i < 4; $i++)
|
||||
{
|
||||
$css .= ' border-' . $border_name->[$i] . "-style: $border;" if $b->[$i];
|
||||
}
|
||||
}
|
||||
$css .= "}\n";
|
||||
}
|
||||
|
||||
$css;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _init
|
||||
{
|
||||
# generic init, override in subclasses
|
||||
my ($self,$args) = @_;
|
||||
|
||||
$self->{class} = 'group';
|
||||
$self->{cell_class} = ' gi';
|
||||
$self->{name} = '';
|
||||
|
||||
$self->{'x'} = 0;
|
||||
$self->{'y'} = 0;
|
||||
|
||||
# XXX TODO check arguments
|
||||
foreach my $k (sort keys %$args)
|
||||
{
|
||||
$self->{$k} = $args->{$k};
|
||||
}
|
||||
|
||||
if (defined $self->{group})
|
||||
{
|
||||
# register ourselves at this group
|
||||
$self->{group}->_add_cell ($self);
|
||||
# XXX CHECK also implement sub_class()
|
||||
$self->{class} = $self->{group}->{class};
|
||||
$self->{class} = 'group' unless defined $self->{class};
|
||||
}
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _set_type
|
||||
{
|
||||
# set the proper type of this cell based on the sourrounding cells
|
||||
my ($self, $cells) = @_;
|
||||
|
||||
# +------+--------+-------+
|
||||
# | LT TOP RU |
|
||||
# + + + +
|
||||
# | LEFT INNER Right |
|
||||
# + + + +
|
||||
# | LB BOTTOM RB |
|
||||
# +------+--------+-------+
|
||||
|
||||
my @coord = (
|
||||
[ 0, -1, ' gt' ],
|
||||
[ +1, 0, ' gr' ],
|
||||
[ 0, +1, ' gb' ],
|
||||
[ -1, 0, ' gl' ],
|
||||
);
|
||||
|
||||
my ($sx,$sy) = ($self->{x},$self->{y});
|
||||
|
||||
my $class = '';
|
||||
my $gr = $self->{group};
|
||||
foreach my $co (@coord)
|
||||
{
|
||||
my ($x,$y,$c) = @$co; $x += $sx; $y += $sy;
|
||||
my $cell = $cells->{"$x,$y"};
|
||||
|
||||
# belongs to the same group?
|
||||
my $go = 0; $go = $cell->group() if UNIVERSAL::can($cell, 'group');
|
||||
|
||||
$class .= $c unless defined $go && $gr == $go;
|
||||
}
|
||||
|
||||
$class = ' ga' if $class eq ' gt gr gb gl';
|
||||
|
||||
$self->{cell_class} = $class;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _set_label
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{has_label} = 1;
|
||||
|
||||
$self->{name} = $self->{group}->label();
|
||||
}
|
||||
|
||||
sub shape
|
||||
{
|
||||
'rect';
|
||||
}
|
||||
|
||||
sub attribute
|
||||
{
|
||||
my ($self, $name) = @_;
|
||||
|
||||
# print STDERR "called attribute($name)\n";
|
||||
# return $self->{group}->attribute($name);
|
||||
|
||||
my $group = $self->{group};
|
||||
|
||||
return $group->{att}->{$name} if exists $group->{att}->{$name};
|
||||
|
||||
$group->{cache} = {} unless exists $group->{cache};
|
||||
$group->{cache}->{att} = {} unless exists $group->{cache}->{att};
|
||||
|
||||
my $cache = $group->{cache}->{att};
|
||||
return $cache->{$name} if exists $cache->{$name};
|
||||
|
||||
$cache->{$name} = $group->attribute($name);
|
||||
}
|
||||
|
||||
use constant isa_cell => 1;
|
||||
|
||||
#############################################################################
|
||||
# conversion to ASCII or HTML
|
||||
|
||||
sub as_ascii
|
||||
{
|
||||
my ($self, $x,$y) = @_;
|
||||
|
||||
my $fb = $self->_framebuffer($self->{w}, $self->{h});
|
||||
|
||||
my $border_style = $self->attribute('borderstyle');
|
||||
my $EM = 14;
|
||||
# use $self here and not $self->{group} to engage attribute cache:
|
||||
my $border_width = Graph::Easy::_border_width_in_pixels($self,$EM);
|
||||
|
||||
# convert overly broad borders to the correct style
|
||||
$border_style = 'bold' if $border_width > 2;
|
||||
$border_style = 'broad' if $border_width > $EM * 0.2 && $border_width < $EM * 0.75;
|
||||
$border_style = 'wide' if $border_width >= $EM * 0.75;
|
||||
|
||||
if ($border_style ne 'none')
|
||||
{
|
||||
|
||||
#########################################################################
|
||||
# draw our border into the framebuffer
|
||||
|
||||
my $c = $self->{cell_class};
|
||||
|
||||
my $b_top = $border_style;
|
||||
my $b_left = $border_style;
|
||||
my $b_right = $border_style;
|
||||
my $b_bottom = $border_style;
|
||||
if ($c !~ 'ga')
|
||||
{
|
||||
$b_top = 'none' unless $c =~ /gt/;
|
||||
$b_left = 'none' unless $c =~ /gl/;
|
||||
$b_right = 'none' unless $c =~ /gr/;
|
||||
$b_bottom = 'none' unless $c =~ /gb/;
|
||||
}
|
||||
|
||||
$self->_draw_border($fb, $b_right, $b_bottom, $b_left, $b_top, $x, $y);
|
||||
}
|
||||
|
||||
if ($self->{has_label})
|
||||
{
|
||||
# include our label
|
||||
|
||||
my $align = $self->attribute('align');
|
||||
# the default label cell as a top border, but no left/right border
|
||||
my $ys = 0.5;
|
||||
$ys = 0 if $border_style eq 'none';
|
||||
my $h = $self->{h} - 1; $h ++ if $border_style eq 'none';
|
||||
|
||||
$self->_printfb_aligned ($fb, 0, $ys, $self->{w}, $h,
|
||||
$self->_aligned_label($align), 'middle');
|
||||
}
|
||||
|
||||
join ("\n", @$fb);
|
||||
}
|
||||
|
||||
sub class
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{class} . $self->{cell_class};
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
# for rendering this cell as ASCII/Boxart, we need to correct our width based
|
||||
# on whether we have a border or not. But this is only known after parsing is
|
||||
# complete.
|
||||
|
||||
sub _correct_size
|
||||
{
|
||||
my ($self,$format) = @_;
|
||||
|
||||
if (!defined $self->{w})
|
||||
{
|
||||
my $border = $self->attribute('borderstyle');
|
||||
$self->{w} = 0;
|
||||
$self->{h} = 0;
|
||||
# label needs space
|
||||
$self->{h} = 1 if $self->{has_label};
|
||||
if ($border ne 'none')
|
||||
{
|
||||
# class "gt", "gb", "gr" or "gr" will be compressed away
|
||||
# (e.g. only edge cells will be existent)
|
||||
if ($self->{has_label} || ($self->{cell_class} =~ /g[rltb] /))
|
||||
{
|
||||
$self->{w} = 2;
|
||||
$self->{h} = 2;
|
||||
}
|
||||
elsif ($self->{cell_class} =~ /^ g[rl]\z/)
|
||||
{
|
||||
$self->{w} = 2;
|
||||
}
|
||||
elsif ($self->{cell_class} =~ /^ g[bt]\z/)
|
||||
{
|
||||
$self->{h} = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($self->{has_label})
|
||||
{
|
||||
my ($w,$h) = $self->dimensions();
|
||||
$self->{h} += $h;
|
||||
$self->{w} += $w;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Group::Cell - A cell in a group
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $ssl = Graph::Easy::Edge->new( );
|
||||
|
||||
$ssl->set_attributes(
|
||||
label => 'encrypted connection',
|
||||
style => '-->',
|
||||
color => 'red',
|
||||
);
|
||||
|
||||
$graph = Graph::Easy->new();
|
||||
|
||||
$graph->add_edge('source', 'destination', $ssl);
|
||||
|
||||
print $graph->as_ascii();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Group::Cell> represents a cell of a group.
|
||||
|
||||
Group cells can have a background and, if they are on the outside, a border.
|
||||
|
||||
There should be no need to use this package directly.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 error()
|
||||
|
||||
$last_error = $group->error();
|
||||
|
||||
$group->error($error); # set new messages
|
||||
$group->error(''); # clear error
|
||||
|
||||
Returns the last error message, or '' for no error.
|
||||
|
||||
=head2 as_ascii()
|
||||
|
||||
my $ascii = $cell->as_ascii();
|
||||
|
||||
Returns the cell as a little ascii representation.
|
||||
|
||||
=head2 as_html()
|
||||
|
||||
my $html = $cell->as_html($tag,$id);
|
||||
|
||||
Returns the cell as HTML code.
|
||||
|
||||
=head2 label()
|
||||
|
||||
my $label = $cell->label();
|
||||
|
||||
Returns the name (also known as 'label') of the cell.
|
||||
|
||||
=head2 class()
|
||||
|
||||
my $class = $cell->class();
|
||||
|
||||
Returns the classname(s) of this cell, like:
|
||||
|
||||
group_cities gr gb
|
||||
|
||||
for a cell with a bottom (gb) and right (gr) border in the class C<cities>.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
None.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2007 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
1071
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Layout.pm
Normal file
1071
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Layout.pm
Normal file
File diff suppressed because it is too large
Load Diff
570
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Layout/Chain.pm
Normal file
570
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Layout/Chain.pm
Normal file
@@ -0,0 +1,570 @@
|
||||
#############################################################################
|
||||
# One chain of nodes in a Graph::Easy - used internally for layouts.
|
||||
#
|
||||
# (c) by Tels 2004-2006. Part of Graph::Easy
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Layout::Chain;
|
||||
|
||||
use Graph::Easy::Base;
|
||||
$VERSION = '0.76';
|
||||
@ISA = qw/Graph::Easy::Base/;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
use constant {
|
||||
_ACTION_NODE => 0, # place node somewhere
|
||||
_ACTION_TRACE => 1, # trace path from src to dest
|
||||
_ACTION_CHAIN => 2, # place node in chain (with parent)
|
||||
_ACTION_EDGES => 3, # trace all edges (shortes connect. first)
|
||||
};
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _init
|
||||
{
|
||||
# Generic init routine, to be overriden in subclasses.
|
||||
my ($self,$args) = @_;
|
||||
|
||||
foreach my $k (sort keys %$args)
|
||||
{
|
||||
if ($k !~ /^(start|graph)\z/)
|
||||
{
|
||||
require Carp;
|
||||
Carp::confess ("Invalid argument '$k' passed to __PACKAGE__->new()");
|
||||
}
|
||||
$self->{$k} = $args->{$k};
|
||||
}
|
||||
|
||||
$self->{end} = $self->{start};
|
||||
|
||||
# store chain at node (to lookup node => chain info)
|
||||
$self->{start}->{_chain} = $self;
|
||||
$self->{start}->{_next} = undef;
|
||||
|
||||
$self->{len} = 1;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub start
|
||||
{
|
||||
# return first node in the chain
|
||||
my $self = shift;
|
||||
|
||||
$self->{start};
|
||||
}
|
||||
|
||||
sub end
|
||||
{
|
||||
# return last node in the chain
|
||||
my $self = shift;
|
||||
|
||||
$self->{end};
|
||||
}
|
||||
|
||||
sub add_node
|
||||
{
|
||||
# add a node at the end of the chain
|
||||
my ($self, $node) = @_;
|
||||
|
||||
# store at end
|
||||
$self->{end}->{_next} = $node;
|
||||
$self->{end} = $node;
|
||||
|
||||
# store chain at node (to lookup node => chain info)
|
||||
$node->{_chain} = $self;
|
||||
$node->{_next} = undef;
|
||||
|
||||
$self->{len} ++;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub length
|
||||
{
|
||||
# Return the length of the chain in nodes. Takes optional
|
||||
# node from where to calculate length.
|
||||
my ($self, $node) = @_;
|
||||
|
||||
return $self->{len} unless defined $node;
|
||||
|
||||
my $len = 0;
|
||||
while (defined $node)
|
||||
{
|
||||
$len++; $node = $node->{_next};
|
||||
}
|
||||
|
||||
$len;
|
||||
}
|
||||
|
||||
sub nodes
|
||||
{
|
||||
# return all the nodes in the chain as a list, in order.
|
||||
my $self = shift;
|
||||
|
||||
my @nodes = ();
|
||||
my $n = $self->{start};
|
||||
while (defined $n)
|
||||
{
|
||||
push @nodes, $n;
|
||||
$n = $n->{_next};
|
||||
}
|
||||
|
||||
@nodes;
|
||||
}
|
||||
|
||||
sub layout
|
||||
{
|
||||
# Return an action stack containing the nec. actions to
|
||||
# lay out the nodes in the chain, plus any connections between
|
||||
# them.
|
||||
my ($self, $edge) = @_;
|
||||
|
||||
# prevent doing it twice
|
||||
return [] if $self->{_done}; $self->{_done} = 1;
|
||||
|
||||
my @TODO = ();
|
||||
|
||||
my $g = $self->{graph};
|
||||
|
||||
# first, layout all the nodes in the chain:
|
||||
|
||||
# start with first node
|
||||
my $pre = $self->{start}; my $n = $pre->{_next};
|
||||
if (exists $pre->{_todo})
|
||||
{
|
||||
# edges with a flow attribute must be handled differently
|
||||
# XXX TODO: the test for attribute('flow') might be wrong (raw_attribute()?)
|
||||
if ($edge && ($edge->{to} == $pre) && ($edge->attribute('flow') || $edge->has_ports()))
|
||||
{
|
||||
push @TODO, $g->_action( _ACTION_CHAIN, $pre, 0, $edge->{from}, $edge);
|
||||
}
|
||||
else
|
||||
{
|
||||
push @TODO, $g->_action( _ACTION_NODE, $pre, 0, $edge );
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR "# Stack after first:\n" if $g->{debug};
|
||||
$g->_dump_stack(@TODO) if $g->{debug};
|
||||
|
||||
while (defined $n)
|
||||
{
|
||||
if (exists $n->{_todo})
|
||||
{
|
||||
# CHAIN means if $n isn't placed yet, it will be done with
|
||||
# $pre as parent:
|
||||
|
||||
# in case there are multiple edges to the target node, use the first
|
||||
# one to determine the flow:
|
||||
my @edges = $g->edge($pre,$n);
|
||||
|
||||
push @TODO, $g->_action( _ACTION_CHAIN, $n, 0, $pre, $edges[0] );
|
||||
}
|
||||
$pre = $n;
|
||||
$n = $n->{_next};
|
||||
}
|
||||
|
||||
print STDERR "# Stack after chaining:\n" if $g->{debug};
|
||||
$g->_dump_stack(@TODO) if $g->{debug};
|
||||
|
||||
# link from each node to the next
|
||||
$pre = $self->{start}; $n = $pre->{_next};
|
||||
while (defined $n)
|
||||
{
|
||||
# first do edges going from P to N
|
||||
#for my $e (sort { $a->{to}->{name} cmp $b->{to}->{name} } values %{$pre->{edges}})
|
||||
for my $e (ord_values ( $pre->{edges}))
|
||||
{
|
||||
# skip selfloops and backward links, these will be done later
|
||||
next if $e->{to} != $n;
|
||||
|
||||
next unless exists $e->{_todo};
|
||||
|
||||
# skip links from/to groups
|
||||
next if $e->{to}->isa('Graph::Easy::Group') ||
|
||||
$e->{from}->isa('Graph::Easy::Group');
|
||||
|
||||
# # skip edges with a flow
|
||||
# next if exists $e->{att}->{start} || exist $e->{att}->{end};
|
||||
|
||||
push @TODO, [ _ACTION_TRACE, $e ];
|
||||
delete $e->{_todo};
|
||||
}
|
||||
|
||||
} continue { $pre = $n; $n = $n->{_next}; }
|
||||
|
||||
print STDERR "# Stack after chain-linking:\n" if $g->{debug};
|
||||
$g->_dump_stack(@TODO) if $g->{debug};
|
||||
|
||||
# Do all other links inside the chain (backwards, going forward more than
|
||||
# one node etc)
|
||||
|
||||
$n = $self->{start};
|
||||
while (defined $n)
|
||||
{
|
||||
my @edges;
|
||||
|
||||
my @count;
|
||||
|
||||
print STDERR "# inter-chain link from $n->{name}\n" if $g->{debug};
|
||||
|
||||
# gather all edges starting at $n, but do the ones with a flow first
|
||||
# for my $e (sort { $a->{to}->{name} cmp $b->{to}->{name} } values %{$n->{edges}})
|
||||
for my $e (ord_values ( $n->{edges}))
|
||||
{
|
||||
# skip selfloops, these will be done later
|
||||
next if $e->{to} == $n;
|
||||
|
||||
next if !ref($e->{to}->{_chain});
|
||||
next if !ref($e->{from}->{_chain});
|
||||
|
||||
next if $e->has_ports();
|
||||
|
||||
# skip links from/to groups
|
||||
next if $e->{to}->isa('Graph::Easy::Group') ||
|
||||
$e->{from}->isa('Graph::Easy::Group');
|
||||
|
||||
print STDERR "# inter-chain link from $n->{name} to $e->{to}->{name}\n" if $g->{debug};
|
||||
|
||||
# leaving the chain?
|
||||
next if $e->{to}->{_chain} != $self;
|
||||
|
||||
# print STDERR "# trying for $n->{name}:\t $e->{from}->{name} to $e->{to}->{name}\n";
|
||||
next unless exists $e->{_todo};
|
||||
|
||||
# calculate for this edge, how far it goes
|
||||
my $count = 0;
|
||||
my $curr = $n;
|
||||
while (defined $curr && $curr != $e->{to})
|
||||
{
|
||||
$curr = $curr->{_next}; $count ++;
|
||||
}
|
||||
if (!defined $curr)
|
||||
{
|
||||
# edge goes backward
|
||||
|
||||
# start at $to
|
||||
$curr = $e->{to};
|
||||
$count = 0;
|
||||
while (defined $curr && $curr != $e->{from})
|
||||
{
|
||||
$curr = $curr->{_next}; $count ++;
|
||||
}
|
||||
$count = 100000 if !defined $curr; # should not happen
|
||||
}
|
||||
push @edges, [ $count, $e ];
|
||||
push @count, [ $count, $e->{from}->{name}, $e->{to}->{name} ];
|
||||
}
|
||||
|
||||
# use Data::Dumper; print STDERR "count\n", Dumper(@count);
|
||||
|
||||
# do edges, shortest first
|
||||
for my $e (sort { $a->[0] <=> $b->[0] } @edges)
|
||||
{
|
||||
push @TODO, [ _ACTION_TRACE, $e->[1] ];
|
||||
delete $e->[1]->{_todo};
|
||||
}
|
||||
|
||||
$n = $n->{_next};
|
||||
}
|
||||
|
||||
# also do all selfloops on $n
|
||||
$n = $self->{start};
|
||||
while (defined $n)
|
||||
{
|
||||
# for my $e (sort { $a->{to}->{name} cmp $b->{to}->{name} } values %{$n->{edges}})
|
||||
for my $e (ord_values $n->{edges})
|
||||
{
|
||||
next unless exists $e->{_todo};
|
||||
|
||||
# print STDERR "# $e->{from}->{name} to $e->{to}->{name} on $n->{name}\n";
|
||||
# print STDERR "# ne $e->{to} $n $e->{id}\n"
|
||||
# if $e->{from} != $n || $e->{to} != $n; # no selfloop?
|
||||
|
||||
next if $e->{from} != $n || $e->{to} != $n; # no selfloop?
|
||||
|
||||
push @TODO, [ _ACTION_TRACE, $e ];
|
||||
delete $e->{_todo};
|
||||
}
|
||||
$n = $n->{_next};
|
||||
}
|
||||
|
||||
print STDERR "# Stack after self-loops:\n" if $g->{debug};
|
||||
$g->_dump_stack(@TODO) if $g->{debug};
|
||||
|
||||
# XXX TODO
|
||||
# now we should do any links that start or end at this chain, recursively
|
||||
|
||||
$n = $self->{start};
|
||||
while (defined $n)
|
||||
{
|
||||
|
||||
# all chains that start at this node
|
||||
for my $e (sort { $a->{to}->{name} cmp $b->{to}->{name} } values %{$n->{edges}})
|
||||
{
|
||||
my $to = $e->{to};
|
||||
|
||||
# skip links to groups
|
||||
next if $to->isa('Graph::Easy::Group');
|
||||
|
||||
# print STDERR "# chain-tracking to: $to->{name} $to->{_chain}\n";
|
||||
|
||||
next unless exists $to->{_chain} && ref($to->{_chain}) =~ /Chain/;
|
||||
my $chain = $to->{_chain};
|
||||
next if $chain->{_done};
|
||||
|
||||
# print STDERR "# chain-tracking to: $to->{name}\n";
|
||||
|
||||
# pass the edge along, in case it has a flow
|
||||
# my @pass = ();
|
||||
# push @pass, $e if $chain->{_first} && $e->{to} == $chain->{_first};
|
||||
push @TODO, @{ $chain->layout($e) } unless $chain->{_done};
|
||||
|
||||
# link the edges to $to
|
||||
next unless exists $e->{_todo}; # was already done above?
|
||||
|
||||
# next if $e->has_ports();
|
||||
|
||||
push @TODO, [ _ACTION_TRACE, $e ];
|
||||
delete $e->{_todo};
|
||||
}
|
||||
$n = $n->{_next};
|
||||
}
|
||||
|
||||
\@TODO;
|
||||
}
|
||||
|
||||
sub dump
|
||||
{
|
||||
# dump the chain to STDERR
|
||||
my ($self, $indent) = @_;
|
||||
|
||||
$indent = '' unless defined $indent;
|
||||
|
||||
print STDERR "#$indent chain id $self->{id} (len $self->{len}):\n";
|
||||
print STDERR "#$indent is empty\n" and return if $self->{len} == 0;
|
||||
|
||||
my $n = $self->{start};
|
||||
while (defined $n)
|
||||
{
|
||||
print STDERR "#$indent $n->{name} (chain id: $n->{_chain}->{id})\n";
|
||||
$n = $n->{_next};
|
||||
}
|
||||
$self;
|
||||
}
|
||||
|
||||
sub merge
|
||||
{
|
||||
# take another chain, and merge it into ourselves. If $where is defined,
|
||||
# absorb only the nodes from $where onwards (instead of all of them).
|
||||
my ($self, $other, $where) = @_;
|
||||
|
||||
my $g = $self->{graph};
|
||||
|
||||
print STDERR "# panik: ", join(" \n",caller()),"\n" if !defined $other;
|
||||
|
||||
print STDERR
|
||||
"# Merging chain $other->{id} (len $other->{len}) into $self->{id} (len $self->{len})\n"
|
||||
if $g->{debug};
|
||||
|
||||
print STDERR
|
||||
"# Merging from $where->{name} onwards\n"
|
||||
if $g->{debug} && ref($where);
|
||||
|
||||
# cannot merge myself into myself (without allocating infinitely memory)
|
||||
return if $self == $other;
|
||||
|
||||
# start at start as default
|
||||
$where = undef unless ref($where) && exists $where->{_chain} && $where->{_chain} == $other;
|
||||
|
||||
$where = $other->{start} unless defined $where;
|
||||
|
||||
# make all nodes from chain #1 belong to it (to detect loops)
|
||||
my $n = $self->{start};
|
||||
while (defined $n)
|
||||
{
|
||||
$n->{_chain} = $self;
|
||||
$n = $n->{_next};
|
||||
}
|
||||
|
||||
print STDERR "# changed nodes\n" if $g->{debug};
|
||||
$self->dump() if $g->{debug};
|
||||
|
||||
# terminate at $where
|
||||
$self->{end}->{_next} = $where;
|
||||
$self->{end} = $other->{end};
|
||||
|
||||
# start at joiner
|
||||
$n = $where;
|
||||
while (ref($n))
|
||||
{
|
||||
$n->{_chain} = $self;
|
||||
my $pre = $n;
|
||||
$n = $n->{_next};
|
||||
|
||||
# sleep(1);
|
||||
# print "# at $n->{name} $n->{_chain}\n" if ref($n);
|
||||
if (ref($n) && defined $n->{_chain} && $n->{_chain} == $self) # already points into ourself?
|
||||
{
|
||||
# sleep(1);
|
||||
# print "# pre $pre->{name} $pre->{_chain}\n";
|
||||
$pre->{_next} = undef; # terminate
|
||||
$self->{end} = $pre;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
# could speed this up
|
||||
$self->{len} = 0; $n = $self->{start};
|
||||
while (defined $n)
|
||||
{
|
||||
$self->{len}++; $n = $n->{_next};
|
||||
}
|
||||
|
||||
# print "done merging, dumping result:\n";
|
||||
# $self->dump(); sleep(10);
|
||||
|
||||
if (defined $other->{start} && $where == $other->{start})
|
||||
{
|
||||
# we absorbed the other chain completely, so drop it
|
||||
$other->{end} = undef;
|
||||
$other->{start} = undef;
|
||||
$other->{len} = 0;
|
||||
# caller is responsible for cleaning it up
|
||||
}
|
||||
|
||||
print STDERR "# after merging\n" if $g->{debug};
|
||||
$self->dump() if $g->{debug};
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Layout::Chain - Chain of nodes for layouter
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
# used internally, do not use directly
|
||||
|
||||
use Graph::Easy;
|
||||
use Graph::Easy::Layout::Chain;
|
||||
|
||||
my $graph = Graph::Easy->new( );
|
||||
my ($node, $node2) = $graph->add_edge( 'A', 'B' );
|
||||
|
||||
my $chain = Graph::Easy::Layout::Chain->new(
|
||||
start => $node,
|
||||
graph => $graph, );
|
||||
|
||||
$chain->add_node( $node2 );
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Layout::Chain> object represents a chain of nodes
|
||||
for the layouter.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 new()
|
||||
|
||||
my $chain = Graph::Easy::Layout::Chain->new( start => $node );
|
||||
|
||||
Create a new chain and set its starting node to C<$node>.
|
||||
|
||||
=head2 length()
|
||||
|
||||
my $len = $chain->length();
|
||||
|
||||
Return the length of the chain, in nodes.
|
||||
|
||||
my $len = $chain->length( $node );
|
||||
|
||||
Given an optional C<$node> as argument, returns the length
|
||||
from that node onwards. For the chain with the three nodes
|
||||
A, B and C would return 3, 2, and 1 for A, B and C, respectively.
|
||||
|
||||
Returns 0 if the passed node is not part of this chain.
|
||||
|
||||
=head2 nodes()
|
||||
|
||||
my @nodes = $chain->nodes();
|
||||
|
||||
Return all the node objects in the chain as list, in order.
|
||||
|
||||
=head2 add_node()
|
||||
|
||||
$chain->add_node( $node );
|
||||
|
||||
Add C<$node> to the end of the chain.
|
||||
|
||||
=head2 start()
|
||||
|
||||
my $node = $chain->start();
|
||||
|
||||
Return first node in the chain.
|
||||
|
||||
=head2 end()
|
||||
|
||||
my $node = $chain->end();
|
||||
|
||||
Return last node in the chain.
|
||||
|
||||
=head2 layout()
|
||||
|
||||
my $todo = $chain->layout();
|
||||
|
||||
Return an action stack as array ref, containing the nec. actions to
|
||||
layout the chain (nodes, plus interlinks in the chain).
|
||||
|
||||
Will recursively traverse all chains linked to this chain.
|
||||
|
||||
=head2 merge()
|
||||
|
||||
my $chain->merge ( $other_chain );
|
||||
my $chain->merge ( $other_chain, $where );
|
||||
|
||||
Merge the other chain into ourselves, adding its nodes at our end.
|
||||
The other chain is emptied and must be deleted by the caller.
|
||||
|
||||
If C<$where> is defined and a member of C<$other_chain>, absorb only the
|
||||
nodes from C<$where> onwards, instead of all of them.
|
||||
|
||||
=head2 error()
|
||||
|
||||
$last_error = $node->error();
|
||||
|
||||
$node->error($error); # set new messages
|
||||
$node->error(''); # clear error
|
||||
|
||||
Returns the last error message, or '' for no error.
|
||||
|
||||
=head2 dump()
|
||||
|
||||
$chain->dump();
|
||||
|
||||
Dump the chain to STDERR, to aid debugging.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>, L<Graph::Easy::Layout>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2006 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
251
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Layout/Force.pm
Normal file
251
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Layout/Force.pm
Normal file
@@ -0,0 +1,251 @@
|
||||
#############################################################################
|
||||
# Force-based layouter for Graph::Easy.
|
||||
#
|
||||
# (c) by Tels 2004-2007.
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Layout::Force;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
sub _layout_force
|
||||
{
|
||||
# Calculate for each node the force on it, then move them accordingly.
|
||||
# When things have settled, stop.
|
||||
my ($self) = @_;
|
||||
|
||||
# For each node, calculate the force acting on it, separated into two
|
||||
# components along the X and Y axis:
|
||||
|
||||
# XXX TODO: replace with all contained nodes + groups
|
||||
my @nodes = $self->nodes();
|
||||
|
||||
return if @nodes == 0;
|
||||
|
||||
my $root = $self->root_node();
|
||||
|
||||
if (!defined $root)
|
||||
{
|
||||
# find a suitable root node
|
||||
$root = $nodes[0];
|
||||
}
|
||||
|
||||
# this node never moves
|
||||
$root->{_pinned} = undef;
|
||||
$root->{x} = 0;
|
||||
$root->{y} = 0;
|
||||
|
||||
# get the "gravity" force
|
||||
my $gx = 0; my $gy = 0;
|
||||
|
||||
my $flow = $self->flow();
|
||||
if ($flow == 0)
|
||||
{
|
||||
$gx = 1;
|
||||
}
|
||||
elsif ($flow == 90)
|
||||
{
|
||||
$gy = -1;
|
||||
}
|
||||
elsif ($flow == 270)
|
||||
{
|
||||
$gy = 1;
|
||||
}
|
||||
else # ($flow == 180)
|
||||
{
|
||||
$gx = -1;
|
||||
}
|
||||
|
||||
my @particles;
|
||||
# set initial positions
|
||||
for my $n (@nodes)
|
||||
{
|
||||
# the net force on this node is the gravity
|
||||
$n->{_x_force} = $gx;
|
||||
$n->{_y_force} = $gy;
|
||||
if ($root == $n || defined $n->{origin})
|
||||
{
|
||||
# nodes that are relative to another are "pinned"
|
||||
$n->{_pinned} = undef;
|
||||
}
|
||||
else
|
||||
{
|
||||
$n->{x} = rand(100);
|
||||
$n->{y} = rand(100);
|
||||
push @particles, $n;
|
||||
}
|
||||
}
|
||||
|
||||
my $energy = 1;
|
||||
while ($energy > 0.1)
|
||||
{
|
||||
$energy = 0;
|
||||
for my $n (@particles)
|
||||
{
|
||||
# reset forces on this node
|
||||
$n->{_x_force} = 0;
|
||||
$n->{_y_force} = 0;
|
||||
|
||||
# Add forces of all other nodes. We need to include pinned nodes here,
|
||||
# too, since a moving node might get near a pinned one and get repelled.
|
||||
for my $n2 (@nodes)
|
||||
{
|
||||
next if $n2 == $n; # don't repel yourself
|
||||
|
||||
my $dx = ($n->{x} - $n2->{x});
|
||||
my $dy = ($n->{y} - $n2->{y});
|
||||
|
||||
my $r = $dx * $dx + $dy * $dy;
|
||||
|
||||
$r = 0.01 if $r < 0.01; # too small?
|
||||
if ($r < 4)
|
||||
{
|
||||
# not too big
|
||||
$n->{_x_force} += 1 / $dx * $dx;
|
||||
$n->{_y_force} += 1 / $dy * $dy;
|
||||
|
||||
my $dx2 = 1 / $dx * $dx;
|
||||
my $dy2 = 1 / $dy * $dy;
|
||||
|
||||
print STDERR "# Force between $n->{name} and $n2->{name}: fx $dx2, fy $dy2\n";
|
||||
}
|
||||
}
|
||||
|
||||
# for all edges connected at this node
|
||||
for my $e (ord_values ( $n->{edges} ))
|
||||
{
|
||||
# exclude self-loops
|
||||
next if $e->{from} == $n && $e->{to} == $n;
|
||||
|
||||
# get the other end-point of this edge
|
||||
my $n2 = $e->{from}; $n2 = $e->{to} if $n2 == $n;
|
||||
|
||||
# XXX TODO
|
||||
# we should "connect" the edges to the appropriate port so that
|
||||
# they excert an off-center force
|
||||
|
||||
my $dx = -($n->{x} - $n2->{x}) / 2;
|
||||
my $dy = -($n->{y} - $n2->{y}) / 2;
|
||||
|
||||
print STDERR "# Spring force between $n->{name} and $n2->{name}: fx $dx, fy $dy\n";
|
||||
$n->{_x_force} += $dx;
|
||||
$n->{_y_force} += $dy;
|
||||
}
|
||||
|
||||
print STDERR "# $n->{name}: Summed force: fx $n->{_x_force}, fy $n->{_y_force}\n";
|
||||
|
||||
# for grid-like layouts, add a small force drawing this node to the gridpoint
|
||||
# 0.7 => 1 - 0.7 => 0.3
|
||||
# 1.2 => 1 - 1.2 => -0.2
|
||||
|
||||
my $dx = int($n->{x} + 0.5) - $n->{x};
|
||||
$n->{_x_force} += $dx;
|
||||
my $dy = int($n->{y} + 0.5) - $n->{y};
|
||||
$n->{_y_force} += $dy;
|
||||
|
||||
print STDERR "# $n->{name}: Final force: fx $n->{_x_force}, fy $n->{_y_force}\n";
|
||||
|
||||
$energy += $n->{_x_force} * $n->{_x_force} + $n->{_x_force} * $n->{_y_force};
|
||||
|
||||
print STDERR "# Net energy: $energy\n";
|
||||
}
|
||||
|
||||
# after having calculated all forces, move the nodes
|
||||
for my $n (@particles)
|
||||
{
|
||||
my $dx = $n->{_x_force};
|
||||
$dx = 5 if $dx > 5; # limit it
|
||||
$n->{x} += $dx;
|
||||
|
||||
my $dy = $n->{_y_force};
|
||||
$dy = 5 if $dy > 5; # limit it
|
||||
$n->{y} += $dy;
|
||||
|
||||
print STDERR "# $n->{name}: Position $n->{x}, $n->{y}\n";
|
||||
}
|
||||
|
||||
sleep(1); print STDERR "\n";
|
||||
}
|
||||
|
||||
for my $n (@nodes)
|
||||
{
|
||||
delete $n->{_x_force};
|
||||
delete $n->{_y_force};
|
||||
}
|
||||
$self;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Layout::Force - Force-based layouter for Graph::Easy
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
$graph->add_edge ('Bonn', 'Berlin');
|
||||
$graph->add_edge ('Bonn', 'Ulm');
|
||||
$graph->add_edge ('Ulm', 'Berlin');
|
||||
|
||||
$graph->layout( type => 'force' );
|
||||
|
||||
print $graph->as_ascii( );
|
||||
|
||||
# prints:
|
||||
|
||||
# +------------------------+
|
||||
# | v
|
||||
# +------+ +-----+ +--------+
|
||||
# | Bonn | --> | Ulm | --> | Berlin |
|
||||
# +------+ +-----+ +--------+
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<Graph::Easy::Layout::Force> contains routines that calculate a
|
||||
force-based layout for a graph.
|
||||
|
||||
Nodes repell each other, while edges connecting them draw them together.
|
||||
|
||||
The layouter calculates the forces on each node, then moves them around
|
||||
according to these forces until things have settled down.
|
||||
|
||||
Used automatically by Graph::Easy.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
Exports nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
This module injects the following methods into Graph::Easy:
|
||||
|
||||
=head2 _layout_force()
|
||||
|
||||
Calculates the node position with a force-based method.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2007 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for information.
|
||||
|
||||
=cut
|
||||
348
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Layout/Grid.pm
Normal file
348
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Layout/Grid.pm
Normal file
@@ -0,0 +1,348 @@
|
||||
#############################################################################
|
||||
# Grid-management and layout preparation.
|
||||
#
|
||||
# (c) by Tels 2004-2006.
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Layout::Grid;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
sub _balance_sizes
|
||||
{
|
||||
# Given a list of column/row sizes and a minimum size that their sum must
|
||||
# be, will grow individual sizes until the constraint (sum) is met.
|
||||
my ($self, $sizes, $need) = @_;
|
||||
|
||||
# XXX TODO: we can abort the loop and distribute the remaining nec. size
|
||||
# once all elements in $sizes are equal.
|
||||
|
||||
return if $need < 1;
|
||||
|
||||
# if there is only one element, return it immediately
|
||||
if (@$sizes == 1)
|
||||
{
|
||||
$sizes->[0] = $need if $sizes->[0] < $need;
|
||||
return;
|
||||
}
|
||||
|
||||
# endless loop until constraint is met
|
||||
while (1)
|
||||
{
|
||||
|
||||
# find the smallest size, and also compute their sum
|
||||
my $sum = 0; my $i = 0;
|
||||
my $sm = $need + 1; # start with an arbitrary size
|
||||
my $sm_i = 0; # if none is != 0, then use the first
|
||||
for my $s (@$sizes)
|
||||
{
|
||||
$sum += $s;
|
||||
next if $s == 0;
|
||||
if ($s < $sm)
|
||||
{
|
||||
$sm = $s; $sm_i = $i;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
# their sum is already equal or bigger than what we need?
|
||||
last if $sum >= $need;
|
||||
|
||||
# increase the smallest size by one, then try again
|
||||
$sizes->[$sm_i]++;
|
||||
}
|
||||
|
||||
# use Data::Dumper; print STDERR "# " . Dumper($sizes),"\n";
|
||||
|
||||
undef;
|
||||
}
|
||||
|
||||
sub _prepare_layout
|
||||
{
|
||||
# this method is used by as_ascii() and as_svg() to find out the
|
||||
# sizes and placement of the different cells (edges, nodes etc).
|
||||
my ($self,$format) = @_;
|
||||
|
||||
# Find out for each row and column how big they are:
|
||||
# +--------+-----+------+
|
||||
# | Berlin | --> | Bonn |
|
||||
# +--------+-----+------+
|
||||
# results in:
|
||||
# w, h, x, y
|
||||
# 0,0 => 10, 3, 0, 0
|
||||
# 1,0 => 7, 3, 10, 0
|
||||
# 2,0 => 8, 3, 16, 0
|
||||
|
||||
# Technically, we also need to "compress" away non-existent columns/rows.
|
||||
# We achieve that by simply rendering them with size 0, so they become
|
||||
# practically invisible.
|
||||
|
||||
my $cells = $self->{cells};
|
||||
my $rows = {};
|
||||
my $cols = {};
|
||||
|
||||
# the last column/row (highest X,Y pair)
|
||||
my $mx = -1000000; my $my = -1000000;
|
||||
|
||||
# We need to do this twice, once for single-cell objects, and again for
|
||||
# objects covering multiple cells. The single-cell objects can be solved
|
||||
# first:
|
||||
|
||||
# find all x and y occurrences to sort them by row/columns
|
||||
for my $cell (ord_values $cells)
|
||||
{
|
||||
my ($x,$y) = ($cell->{x}, $cell->{y});
|
||||
|
||||
{
|
||||
no strict 'refs';
|
||||
|
||||
my $method = '_correct_size_' . $format;
|
||||
$method = '_correct_size' unless $cell->can($method);
|
||||
$cell->$method();
|
||||
}
|
||||
|
||||
my $w = $cell->{w} || 0;
|
||||
my $h = $cell->{h} || 0;
|
||||
|
||||
# Set the minimum cell size only for single-celled objects:
|
||||
if ( (($cell->{cx}||1) + ($cell->{cy}||1)) == 2)
|
||||
{
|
||||
# record maximum size for that col/row
|
||||
$rows->{$y} = $h if $h >= ($rows->{$y} || 0);
|
||||
$cols->{$x} = $w if $w >= ($cols->{$x} || 0);
|
||||
}
|
||||
|
||||
# Find highest X,Y pair. Always use x,y, and not x+cx,y+cy, because
|
||||
# a multi-celled object "sticking" out will not count unless there
|
||||
# is another object in the same row/column.
|
||||
$mx = $x if $x > $mx;
|
||||
$my = $y if $y > $my;
|
||||
}
|
||||
|
||||
# insert a dummy row/column with size=0 as last
|
||||
$rows->{$my+1} = 0;
|
||||
$cols->{$mx+1} = 0;
|
||||
|
||||
# do the last step again, but for multi-celled objects
|
||||
for my $cell (ord_values $cells)
|
||||
{
|
||||
my ($x,$y) = ($cell->{x}, $cell->{y});
|
||||
|
||||
my $w = $cell->{w} || 0;
|
||||
my $h = $cell->{h} || 0;
|
||||
|
||||
# Set the minimum cell size only for multi-celled objects:
|
||||
if ( (($cell->{cx} || 1) + ($cell->{cy}||1)) > 2)
|
||||
{
|
||||
$cell->{cx} ||= 1;
|
||||
$cell->{cy} ||= 1;
|
||||
|
||||
# do this twice, for X and Y:
|
||||
|
||||
# print STDERR "\n# ", $cell->{name} || $cell->{id}, " cx=$cell->{cx} cy=$cell->{cy} $cell->{w},$cell->{h}:\n";
|
||||
|
||||
# create an array with the current sizes for the affacted rows/columns
|
||||
my @sizes;
|
||||
|
||||
# print STDERR "# $cell->{cx} $cell->{cy} at cx:\n";
|
||||
|
||||
# XXX TODO: no need to do this for empty/zero cols
|
||||
for (my $i = 0; $i < $cell->{cx}; $i++)
|
||||
{
|
||||
push @sizes, $cols->{$i+$x} || 0;
|
||||
}
|
||||
$self->_balance_sizes(\@sizes, $cell->{w});
|
||||
# store the result back
|
||||
for (my $i = 0; $i < $cell->{cx}; $i++)
|
||||
{
|
||||
# print STDERR "# store back $sizes[$i] to col ", $i+$x,"\n";
|
||||
$cols->{$i+$x} = $sizes[$i];
|
||||
}
|
||||
|
||||
@sizes = ();
|
||||
|
||||
# print STDERR "# $cell->{cx} $cell->{cy} at cy:\n";
|
||||
|
||||
# XXX TODO: no need to do this for empty/zero cols
|
||||
for (my $i = 0; $i < $cell->{cy}; $i++)
|
||||
{
|
||||
push @sizes, $rows->{$i+$y} || 0;
|
||||
}
|
||||
$self->_balance_sizes(\@sizes, $cell->{h});
|
||||
# store the result back
|
||||
for (my $i = 0; $i < $cell->{cy}; $i++)
|
||||
{
|
||||
# print STDERR "# store back $sizes[$i] to row ", $i+$y,"\n";
|
||||
$rows->{$i+$y} = $sizes[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR "# Calculating absolute positions for rows/columns\n" if $self->{debug};
|
||||
|
||||
# Now run through all rows/columns and get their absolute pos by taking all
|
||||
# previous ones into account.
|
||||
my $pos = 0;
|
||||
for my $y (sort { $a <=> $b } keys %$rows)
|
||||
{
|
||||
my $s = $rows->{$y};
|
||||
$rows->{$y} = $pos; # first is 0, second is $rows[1] etc
|
||||
$pos += $s;
|
||||
}
|
||||
$pos = 0;
|
||||
for my $x (sort { $a <=> $b } keys %$cols)
|
||||
{
|
||||
my $s = $cols->{$x};
|
||||
$cols->{$x} = $pos;
|
||||
$pos += $s;
|
||||
}
|
||||
|
||||
# find out max. dimensions for framebuffer
|
||||
print STDERR "# Finding max. dimensions for framebuffer\n" if $self->{debug};
|
||||
my $max_y = 0; my $max_x = 0;
|
||||
|
||||
for my $v (ord_values $cells)
|
||||
{
|
||||
# Skip multi-celled nodes for later.
|
||||
next if ($v->{cx}||1) + ($v->{cy}||1) != 2;
|
||||
|
||||
# X and Y are col/row, so translate them to real pos
|
||||
my $x = $cols->{ $v->{x} };
|
||||
my $y = $rows->{ $v->{y} };
|
||||
|
||||
# Also set correct the width/height of each cell to be the maximum
|
||||
# width/height of that row/column and store the previous size in 'minw'
|
||||
# and 'minh', respectively.
|
||||
|
||||
$v->{minw} = $v->{w};
|
||||
$v->{minh} = $v->{h};
|
||||
|
||||
# find next col/row
|
||||
my $nx = $v->{x} + 1;
|
||||
my $next_col = $cols->{ $nx };
|
||||
my $ny = $v->{y} + 1;
|
||||
my $next_row = $rows->{ $ny };
|
||||
|
||||
$next_col = $cols->{ ++$nx } while (!defined $next_col);
|
||||
$next_row = $rows->{ ++$ny } while (!defined $next_row);
|
||||
|
||||
$v->{w} = $next_col - $x;
|
||||
$v->{h} = $next_row - $y;
|
||||
|
||||
my $m = $y + $v->{h} - 1;
|
||||
$max_y = $m if $m > $max_y;
|
||||
$m = $x + $v->{w} - 1;
|
||||
$max_x = $m if $m > $max_x;
|
||||
}
|
||||
|
||||
# repeat the previous step, now for multi-celled objects
|
||||
foreach my $v (ord_values ( $self->{cells} ))
|
||||
{
|
||||
next unless defined $v->{x} && (($v->{cx}||1) + ($v->{cy}||1) > 2);
|
||||
|
||||
# X and Y are col/row, so translate them to real pos
|
||||
my $x = $cols->{ $v->{x} };
|
||||
my $y = $rows->{ $v->{y} };
|
||||
|
||||
$v->{minw} = $v->{w};
|
||||
$v->{minh} = $v->{h};
|
||||
|
||||
# find next col/row
|
||||
my $nx = $v->{x} + ($v->{cx} || 1);
|
||||
my $next_col = $cols->{ $nx };
|
||||
my $ny = $v->{y} + ($v->{cy} || 1);
|
||||
my $next_row = $rows->{ $ny };
|
||||
|
||||
$next_col = $cols->{ ++$nx } while (!defined $next_col);
|
||||
$next_row = $rows->{ ++$ny } while (!defined $next_row);
|
||||
|
||||
$v->{w} = $next_col - $x;
|
||||
$v->{h} = $next_row - $y;
|
||||
|
||||
my $m = $y + $v->{h} - 1;
|
||||
$max_y = $m if $m > $max_y;
|
||||
$m = $x + $v->{w} - 1;
|
||||
$max_x = $m if $m > $max_x;
|
||||
}
|
||||
|
||||
# return what we found out:
|
||||
($rows,$cols,$max_x,$max_y);
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Layout::Grid - Grid management and size calculation
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
my $bonn = Graph::Easy::Node->new(
|
||||
name => 'Bonn',
|
||||
);
|
||||
my $berlin = Graph::Easy::Node->new(
|
||||
name => 'Berlin',
|
||||
);
|
||||
|
||||
$graph->add_edge ($bonn, $berlin);
|
||||
|
||||
$graph->layout();
|
||||
|
||||
print $graph->as_ascii( );
|
||||
|
||||
# prints:
|
||||
|
||||
# +------+ +--------+
|
||||
# | Bonn | --> | Berlin |
|
||||
# +------+ +--------+
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<Graph::Easy::Layout::Grid> contains routines that calculate cell sizes
|
||||
on the grid, which is necessary for ASCII, boxart and SVG output.
|
||||
|
||||
Used automatically by Graph::Easy.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
Exports nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
This module injects the following methods into Graph::Easy:
|
||||
|
||||
=head2 _prepare_layout()
|
||||
|
||||
my ($rows,$cols,$max_x,$max_y, \@V) = $graph->_prepare_layout();
|
||||
|
||||
Returns two hashes (C<$rows> and C<$cols>), containing the columns and rows
|
||||
of the layout with their nec. sizes (in chars) plus the maximum
|
||||
framebuffer size nec. for this layout. Also returns reference of
|
||||
a list of all cells to be rendered.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2006 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for information.
|
||||
|
||||
=cut
|
||||
916
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Layout/Path.pm
Normal file
916
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Layout/Path.pm
Normal file
@@ -0,0 +1,916 @@
|
||||
#############################################################################
|
||||
# Path and cell management for Graph::Easy.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Layout::Path;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Node;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Graph::Easy::Edge::Cell qw/
|
||||
EDGE_END_E EDGE_END_N EDGE_END_S EDGE_END_W
|
||||
/;
|
||||
|
||||
sub _shuffle_dir
|
||||
{
|
||||
# take a list with four entries and shuffle them around according to $dir
|
||||
my ($self, $e, $dir) = @_;
|
||||
|
||||
# $dir: 0 => north, 90 => east, 180 => south, 270 => west
|
||||
|
||||
$dir = 90 unless defined $dir; # default is east
|
||||
|
||||
return [ @$e ] if $dir == 90; # default is no shuffling
|
||||
|
||||
my @shuffle = (0,1,2,3); # the default
|
||||
@shuffle = (1,2,0,3) if $dir == 180; # south
|
||||
@shuffle = (2,3,1,0) if $dir == 270; # west
|
||||
@shuffle = (3,0,2,1) if $dir == 0; # north
|
||||
|
||||
[
|
||||
$e->[ $shuffle[0] ],
|
||||
$e->[ $shuffle[1] ],
|
||||
$e->[ $shuffle[2] ],
|
||||
$e->[ $shuffle[3] ],
|
||||
];
|
||||
}
|
||||
|
||||
sub _shift
|
||||
{
|
||||
# get a flow shifted by X° to $dir
|
||||
my ($self, $turn) = @_;
|
||||
|
||||
my $dir = $self->flow();
|
||||
|
||||
$dir += $turn;
|
||||
$dir += 360 if $dir < 0;
|
||||
$dir -= 360 if $dir > 360;
|
||||
$dir;
|
||||
}
|
||||
|
||||
sub _near_places
|
||||
{
|
||||
# Take a node and return a list of possible placements around it and
|
||||
# prune out already occupied cells. $d is the distance from the node
|
||||
# border and defaults to two (for placements). Set it to one for
|
||||
# adjacent cells.
|
||||
|
||||
# If defined, $type contains four flags for each direction. If undef,
|
||||
# two entries (x,y) will be returned for each pos, instead of (x,y,type).
|
||||
|
||||
# If $loose is true, no checking whether the returned fields are free
|
||||
# is done.
|
||||
|
||||
my ($n, $cells, $d, $type, $loose, $dir) = @_;
|
||||
|
||||
my $cx = $n->{cx} || 1;
|
||||
my $cy = $n->{cy} || 1;
|
||||
|
||||
$d = 2 unless defined $d; # default is distance = 2
|
||||
|
||||
my $flags = $type;
|
||||
|
||||
if (ref($flags) ne 'ARRAY')
|
||||
{
|
||||
$flags = [
|
||||
EDGE_END_W,
|
||||
EDGE_END_N,
|
||||
EDGE_END_E,
|
||||
EDGE_END_S,
|
||||
];
|
||||
}
|
||||
$dir = $n->flow() unless defined $dir;
|
||||
|
||||
my $index = $n->_shuffle_dir( [ 0,3,6,9], $dir);
|
||||
|
||||
my @places = ();
|
||||
|
||||
# single-celled node
|
||||
if ($cx + $cy == 2)
|
||||
{
|
||||
my @tries = (
|
||||
$n->{x} + $d, $n->{y}, $flags->[0], # right
|
||||
$n->{x}, $n->{y} + $d, $flags->[1], # down
|
||||
$n->{x} - $d, $n->{y}, $flags->[2], # left
|
||||
$n->{x}, $n->{y} - $d, $flags->[3], # up
|
||||
);
|
||||
|
||||
for my $i (0..3)
|
||||
{
|
||||
my $idx = $index->[$i];
|
||||
my ($x,$y,$t) = ($tries[$idx], $tries[$idx+1], $tries[$idx+2]);
|
||||
|
||||
# print STDERR "# Considering place $x, $y \n";
|
||||
|
||||
# This quick check does not take node clusters or multi-celled nodes
|
||||
# into account. These are handled in $node->_do_place() later.
|
||||
next if !$loose && exists $cells->{"$x,$y"};
|
||||
push @places, $x, $y;
|
||||
push @places, $t if defined $type;
|
||||
}
|
||||
return @places;
|
||||
}
|
||||
|
||||
# Handle a multi-celled node. For a 3x2 node:
|
||||
# A B C
|
||||
# J [00][10][20] D
|
||||
# I [10][11][21] E
|
||||
# H G F
|
||||
# we have 10 (3 * 2 + 2 * 2) places to consider
|
||||
|
||||
my $nx = $n->{x};
|
||||
my $ny = $n->{y};
|
||||
my ($px,$py);
|
||||
|
||||
my $idx = 0;
|
||||
my @results = ( [], [], [], [] );
|
||||
|
||||
$cy--; $cx--;
|
||||
my $t = $flags->[$idx++];
|
||||
# right
|
||||
$px = $nx + $cx + $d;
|
||||
for my $y (0 .. $cy)
|
||||
{
|
||||
$py = $y + $ny;
|
||||
next if exists $cells->{"$px,$py"} && !$loose;
|
||||
push @{$results[0]}, $px, $py;
|
||||
push @{$results[0]}, $t if defined $type;
|
||||
}
|
||||
|
||||
# below
|
||||
$py = $ny + $cy + $d;
|
||||
$t = $flags->[$idx++];
|
||||
for my $x (0 .. $cx)
|
||||
{
|
||||
$px = $x + $nx;
|
||||
next if exists $cells->{"$px,$py"} && !$loose;
|
||||
push @{$results[1]}, $px, $py;
|
||||
push @{$results[1]}, $t if defined $type;
|
||||
}
|
||||
|
||||
# left
|
||||
$px = $nx - $d;
|
||||
$t = $flags->[$idx++];
|
||||
for my $y (0 .. $cy)
|
||||
{
|
||||
$py = $y + $ny;
|
||||
next if exists $cells->{"$px,$py"} && !$loose;
|
||||
push @{$results[2]}, $px, $py;
|
||||
push @{$results[2]}, $t if defined $type;
|
||||
}
|
||||
|
||||
# top
|
||||
$py = $ny - $d;
|
||||
$t = $flags->[$idx];
|
||||
for my $x (0 .. $cx)
|
||||
{
|
||||
$px = $x + $nx;
|
||||
next if exists $cells->{"$px,$py"} && !$loose;
|
||||
push @{$results[3]}, $px, $py;
|
||||
push @{$results[3]}, $t if defined $type;
|
||||
}
|
||||
|
||||
# accumulate the results in the requested, shuffled order
|
||||
for my $i (0..3)
|
||||
{
|
||||
my $idx = $index->[$i] / 3;
|
||||
push @places, @{$results[$idx]};
|
||||
}
|
||||
|
||||
@places;
|
||||
}
|
||||
|
||||
sub _allowed_places
|
||||
{
|
||||
# given a list of potential positions, and a list of allowed positions,
|
||||
# return the valid ones (e.g. that are in both lists)
|
||||
my ($self, $places, $allowed, $step) = @_;
|
||||
|
||||
print STDERR
|
||||
"# calculating allowed places for $self->{name} from " . @$places .
|
||||
" positions and " . scalar @$allowed . " allowed ones:\n"
|
||||
if $self->{graph}->{debug};
|
||||
|
||||
$step ||= 2; # default: "x,y"
|
||||
|
||||
my @good;
|
||||
my $i = 0;
|
||||
while ($i < @$places)
|
||||
{
|
||||
my ($x,$y) = ($places->[$i], $places->[$i+1]);
|
||||
my $allow = 0;
|
||||
my $j = 0;
|
||||
while ($j < @$allowed)
|
||||
{
|
||||
my ($m,$n) = ($allowed->[$j], $allowed->[$j+1]);
|
||||
$allow++ and last if ($m == $x && $n == $y);
|
||||
} continue { $j += 2; }
|
||||
next unless $allow;
|
||||
push @good, $places->[$i + $_ -1] for (1..$step);
|
||||
} continue { $i += $step; }
|
||||
|
||||
print STDERR "# left with " . ((scalar @good) / $step) . " position(s)\n" if $self->{graph}->{debug};
|
||||
@good;
|
||||
}
|
||||
|
||||
sub _allow
|
||||
{
|
||||
# return a list of places, depending on the start/end attribute:
|
||||
# "south" - any place south
|
||||
# "south,0" - first place south
|
||||
# "south,-1" - last place south
|
||||
# XXX TODO:
|
||||
# "south,0..2" - first three places south
|
||||
# "south,0,1,-1" - first, second and last place south
|
||||
|
||||
my ($self, $dir, @pos) = @_;
|
||||
|
||||
# for relative direction, get the absolute flow from the node
|
||||
if ($dir =~ /^(front|forward|back|left|right)\z/)
|
||||
{
|
||||
# get the flow at the node
|
||||
$dir = $self->flow();
|
||||
}
|
||||
|
||||
my $place = {
|
||||
'south' => [ 0,0, 0,1, 'cx', 1,0 ],
|
||||
'north' => [ 0,-1, 0,0, 'cx', 1,0 ],
|
||||
'east' => [ 0,0, 1,0, 'cy', 0,1 ],
|
||||
'west' => [ -1,0, 0,0, 'cy', 0,1 ] ,
|
||||
180 => [ 0,0, 0,1, 'cx', 1,0 ],
|
||||
0 => [ 0,-1, 0,0, 'cx', 1,0 ],
|
||||
90 => [ 0,0, 1,0, 'cy', 0,1 ],
|
||||
270 => [ -1,0, 0,0, 'cy', 0,1 ] ,
|
||||
};
|
||||
|
||||
my $p = $place->{$dir};
|
||||
|
||||
return [] unless defined $p;
|
||||
|
||||
# start pos
|
||||
my $x = $p->[0] + $self->{x} + $p->[2] * $self->{cx};
|
||||
my $y = $p->[1] + $self->{y} + $p->[3] * $self->{cy};
|
||||
|
||||
my @allowed;
|
||||
push @pos, '' if @pos == 0;
|
||||
|
||||
my $c = $p->[4];
|
||||
if (@pos == 1 && $pos[0] eq '')
|
||||
{
|
||||
# allow all of them
|
||||
for (1 .. $self->{$c})
|
||||
{
|
||||
push @allowed, $x, $y;
|
||||
$x += $p->[5];
|
||||
$y += $p->[6];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
# allow only the given position
|
||||
my $ps = $pos[0];
|
||||
# limit to 0..$self->{cx}-1
|
||||
$ps = $self->{$c} + $ps if $ps < 0;
|
||||
$ps = 0 if $ps < 0;
|
||||
$ps = $self->{$c} - 1 if $ps >= $self->{$c};
|
||||
$x += $p->[5] * $ps;
|
||||
$y += $p->[6] * $ps;
|
||||
push @allowed, $x, $y;
|
||||
}
|
||||
|
||||
\@allowed;
|
||||
}
|
||||
|
||||
package Graph::Easy;
|
||||
use strict;
|
||||
use Graph::Easy::Node::Cell;
|
||||
|
||||
use Graph::Easy::Edge::Cell qw/
|
||||
EDGE_HOR EDGE_VER EDGE_CROSS
|
||||
EDGE_TYPE_MASK
|
||||
EDGE_HOLE
|
||||
/;
|
||||
|
||||
sub _clear_tries
|
||||
{
|
||||
# Take a list of potential positions for a node, and then remove the
|
||||
# ones that are immediately near any other node.
|
||||
# Returns a list of "good" positions. Afterwards $node->{x} is undef.
|
||||
my ($self, $node, $cells, $tries) = @_;
|
||||
|
||||
my $src = 0; my @new;
|
||||
|
||||
print STDERR "# clearing ", scalar @$tries / 2, " tries for $node->{name}\n" if $self->{debug};
|
||||
|
||||
my $node_grandpa = $node->find_grandparent();
|
||||
|
||||
while ($src < scalar @$tries)
|
||||
{
|
||||
# check the current position
|
||||
|
||||
# temporary place node here
|
||||
my $x = $tries->[$src];
|
||||
my $y = $tries->[$src+1];
|
||||
|
||||
# print STDERR "# checking $x,$y\n" if $self->{debug};
|
||||
|
||||
$node->{x} = $x;
|
||||
$node->{y} = $y;
|
||||
|
||||
my @near = $node->_near_places($cells, 1, undef, 1);
|
||||
|
||||
# push also the four corner cells to avoid placing nodes corner-to-corner
|
||||
push @near, $x-1, $y-1, # upperleft corner
|
||||
$x-1, $y+($node->{cy}||1), # lowerleft corner
|
||||
$x+($node->{cx}||1), $y+($node->{cy}||1), # lowerright corner
|
||||
$x+($node->{cx}||1), $y-1; # upperright corner
|
||||
|
||||
# check all near places to be free from nodes (except our children)
|
||||
my $j = 0; my $g = 0;
|
||||
while ($j < @near)
|
||||
{
|
||||
my $xy = $near[$j]. ',' . $near[$j+1];
|
||||
|
||||
# print STDERR "# checking near-place: $xy: " . ref($cells->{$xy}) . "\n" if $self->{debug};
|
||||
|
||||
my $cell = $cells->{$xy};
|
||||
|
||||
# skip, unless we are a children of node, or the cell is our children
|
||||
next unless ref($cell) && $cell->isa('Graph::Easy::Node');
|
||||
|
||||
my $grandpa = $cell->find_grandparent();
|
||||
|
||||
# this cell is our children
|
||||
# this cell is our grandpa
|
||||
# has the same grandpa as node
|
||||
next if $grandpa == $node || $cell == $node_grandpa || $grandpa == $node_grandpa;
|
||||
|
||||
$g++; last;
|
||||
|
||||
} continue { $j += 2; }
|
||||
|
||||
if ($g == 0)
|
||||
{
|
||||
push @new, $tries->[$src], $tries->[$src+1];
|
||||
}
|
||||
$src += 2;
|
||||
}
|
||||
|
||||
$node->{x} = undef;
|
||||
|
||||
@new;
|
||||
}
|
||||
|
||||
my $flow_shift = {
|
||||
270 => [ 0, -1 ],
|
||||
90 => [ 0, 1 ],
|
||||
0 => [ 1, 0 ],
|
||||
180 => [ -1, 0 ],
|
||||
};
|
||||
|
||||
sub _placed_shared
|
||||
{
|
||||
# check whether one of the nodes from the list of shared was already placed
|
||||
my ($self) = shift;
|
||||
|
||||
my $placed;
|
||||
for my $n (@_)
|
||||
{
|
||||
$placed = [$n->{x}, $n->{y}] and last if defined $n->{x};
|
||||
}
|
||||
$placed;
|
||||
}
|
||||
|
||||
use Graph::Easy::Util qw(first_kv);
|
||||
|
||||
sub _find_node_place
|
||||
{
|
||||
# Try to place a node (or node cluster). Return score (usually 0).
|
||||
my ($self, $node, $try, $parent, $edge) = @_;
|
||||
|
||||
$try ||= 0;
|
||||
|
||||
print STDERR "# Finding place for $node->{name}, try #$try\n" if $self->{debug};
|
||||
print STDERR "# Parent node is '$parent->{name}'\n" if $self->{debug} && ref $parent;
|
||||
|
||||
print STDERR "# called from ". join (" ", caller) . "\n" if $self->{debug};
|
||||
|
||||
# If the node has a user-set rank, see if we already placed another node in that
|
||||
# row/column
|
||||
if ($node->{rank} >= 0)
|
||||
{
|
||||
my $r = abs($node->{rank});
|
||||
# print STDERR "# User-set rank for $node->{name} (rank $r)\n";
|
||||
my $c = $self->{_rank_coord};
|
||||
# use Data::Dumper; print STDERR "# rank_pos: \n", Dumper($self->{_rank_pos});
|
||||
if (exists $self->{_rank_pos}->{ $r })
|
||||
{
|
||||
my $co = { x => 0, y => 0 };
|
||||
$co->{$c} = $self->{_rank_pos}->{ $r };
|
||||
while (1 < 3)
|
||||
{
|
||||
# print STDERR "# trying to force placement of '$node->{name}' at $co->{x} $co->{y}\n";
|
||||
return 0 if $node->_do_place($co->{x},$co->{y},$self);
|
||||
$co->{$c} += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $cells = $self->{cells};
|
||||
|
||||
# local $self->{debug} = 1;
|
||||
|
||||
my $min_dist = 2;
|
||||
# minlen = 0 => min_dist = 2,
|
||||
# minlen = 1 => min_dist = 2,
|
||||
# minlen = 2 => min_dist = 3, etc
|
||||
$min_dist = $edge->attribute('minlen') + 1 if ref($edge);
|
||||
|
||||
# if the node has outgoing edges (which might be shared)
|
||||
if (!ref($edge))
|
||||
{
|
||||
(undef,$edge) = first_kv($node->{edges}) if keys %{$node->{edges}} > 0;
|
||||
}
|
||||
|
||||
my $dir = undef; $dir = $edge->flow() if ref($edge);
|
||||
|
||||
my @tries;
|
||||
# if (ref($parent) && defined $parent->{x})
|
||||
if (keys %{$node->{edges}} > 0)
|
||||
{
|
||||
my $src_node = $parent; $src_node = $edge->{from} if ref($edge) && !ref($parent);
|
||||
print STDERR "# from $src_node->{name} to $node->{name}: edge $edge dir $dir\n" if $self->{debug};
|
||||
|
||||
# if there are more than one edge to this node, and they share a start point,
|
||||
# move the node at least 3 cells away to create space for the joints
|
||||
|
||||
my ($s_p, @ss_p);
|
||||
($s_p, @ss_p) = $edge->port('start') if ref($edge);
|
||||
|
||||
my ($from,$to);
|
||||
if (ref($edge))
|
||||
{
|
||||
$from = $edge->{from}; $to = $edge->{to};
|
||||
}
|
||||
|
||||
my @shared_nodes;
|
||||
@shared_nodes = $from->nodes_sharing_start($s_p,@ss_p) if defined $s_p && @ss_p > 0;
|
||||
|
||||
print STDERR "# Edge from '$src_node->{name}' shares an edge start with ", scalar @shared_nodes, " other nodes\n"
|
||||
if $self->{debug};
|
||||
|
||||
if (@shared_nodes > 1)
|
||||
{
|
||||
$min_dist = 3 if $min_dist < 3; # make space
|
||||
$min_dist++ if $edge->label() ne ''; # make more space for the label
|
||||
|
||||
# if we are the first shared node to be placed
|
||||
my $placed = $self->_placed_shared(@shared_nodes);
|
||||
|
||||
if (defined $placed)
|
||||
{
|
||||
# we are not the first, so skip the placement below
|
||||
# instead place on the same column/row as already placed node(s)
|
||||
my ($bx, $by) = @$placed;
|
||||
|
||||
my $flow = $node->flow();
|
||||
|
||||
print STDERR "# One of the shared nodes was already placed at ($bx,$by) with flow $flow\n"
|
||||
if $self->{debug};
|
||||
|
||||
my $ofs = 2; # start with a distance of 2
|
||||
my ($mx, $my) = @{ ($flow_shift->{$flow} || [ 0, 1 ]) };
|
||||
|
||||
while (1)
|
||||
{
|
||||
my $x = $bx + $mx * $ofs; my $y = $by + $my * $ofs;
|
||||
|
||||
print STDERR "# Trying to place $node->{name} at ($x,$y)\n"
|
||||
if $self->{debug};
|
||||
|
||||
next if $self->_clear_tries($node, $cells, [ $x,$y ]) == 0;
|
||||
last if $node->_do_place($x,$y,$self);
|
||||
}
|
||||
continue {
|
||||
$ofs += 2;
|
||||
}
|
||||
return 0; # found place already
|
||||
} # end we-are-the-first-to-be-placed
|
||||
}
|
||||
|
||||
# shared end point?
|
||||
($s_p, @ss_p) = $edge->port('end') if ref($edge);
|
||||
|
||||
@shared_nodes = $to->nodes_sharing_end($s_p,@ss_p) if defined $s_p && @ss_p > 0;
|
||||
|
||||
print STDERR "# Edge from '$src_node->{name}' shares an edge end with ", scalar @shared_nodes, " other nodes\n"
|
||||
if $self->{debug};
|
||||
|
||||
if (@shared_nodes > 1)
|
||||
{
|
||||
$min_dist = 3 if $min_dist < 3;
|
||||
$min_dist++ if $edge->label() ne ''; # make more space for the label
|
||||
|
||||
# if the node to be placed is not in the list to be placed, it is the end-point
|
||||
|
||||
# see if we are the first shared node to be placed
|
||||
my $placed = $self->_placed_shared(@shared_nodes);
|
||||
|
||||
# print STDERR "# "; for (@shared_nodes) { print $_->{name}, " "; } print "\n";
|
||||
|
||||
if ((grep( $_ == $node, @shared_nodes)) && defined $placed)
|
||||
{
|
||||
# we are not the first, so skip the placement below
|
||||
# instead place on the same column/row as already placed node(s)
|
||||
my ($bx, $by) = @$placed;
|
||||
|
||||
my $flow = $node->flow();
|
||||
|
||||
print STDERR "# One of the shared nodes was already placed at ($bx,$by) with flow $flow\n"
|
||||
if $self->{debug};
|
||||
|
||||
my $ofs = 2; # start with a distance of 2
|
||||
my ($mx, $my) = @{ ($flow_shift->{$flow} || [ 0, 1 ]) };
|
||||
|
||||
while (1)
|
||||
{
|
||||
my $x = $bx + $mx * $ofs; my $y = $by + $my * $ofs;
|
||||
|
||||
print STDERR "# Trying to place $node->{name} at ($x,$y)\n"
|
||||
if $self->{debug};
|
||||
|
||||
next if $self->_clear_tries($node, $cells, [ $x,$y ]) == 0;
|
||||
last if $node->_do_place($x,$y,$self);
|
||||
}
|
||||
continue {
|
||||
$ofs += 2;
|
||||
}
|
||||
return 0; # found place already
|
||||
} # end we-are-the-first-to-be-placed
|
||||
}
|
||||
}
|
||||
|
||||
if (ref($parent) && defined $parent->{x})
|
||||
{
|
||||
@tries = $parent->_near_places($cells, $min_dist, undef, 0, $dir);
|
||||
|
||||
print STDERR
|
||||
"# Trying chained placement of $node->{name} with min distance $min_dist from parent $parent->{name}\n"
|
||||
if $self->{debug};
|
||||
|
||||
# weed out positions that are unsuitable
|
||||
@tries = $self->_clear_tries($node, $cells, \@tries);
|
||||
|
||||
splice (@tries,0,$try) if $try > 0; # remove the first N tries
|
||||
print STDERR "# Left with " . scalar @tries . " tries for node $node->{name}\n" if $self->{debug};
|
||||
|
||||
while (@tries > 0)
|
||||
{
|
||||
my $x = shift @tries;
|
||||
my $y = shift @tries;
|
||||
|
||||
print STDERR "# Trying to place $node->{name} at $x,$y\n" if $self->{debug};
|
||||
return 0 if $node->_do_place($x,$y,$self);
|
||||
} # for all trial positions
|
||||
}
|
||||
|
||||
print STDERR "# Trying to place $node->{name} at 0,0\n" if $try == 0 && $self->{debug};
|
||||
# Try to place node at upper left corner (the very first node to be
|
||||
# placed will usually end up there).
|
||||
return 0 if $try == 0 && $node->_do_place(0,0,$self);
|
||||
|
||||
# try to place node near the predecessor(s)
|
||||
my @pre_all = $node->predecessors();
|
||||
|
||||
print STDERR "# Predecessors of $node->{name} " . scalar @pre_all . "\n" if $self->{debug};
|
||||
|
||||
# find all already placed predecessors
|
||||
my @pre;
|
||||
for my $p (@pre_all)
|
||||
{
|
||||
push @pre, $p if defined $p->{x};
|
||||
print STDERR "# Placed predecessors of $node->{name}: $p->{name} at $p->{x},$p->{y}\n" if $self->{debug} && defined $p->{x};
|
||||
}
|
||||
|
||||
# sort predecessors on their rank (to try first the higher ranking ones on placement)
|
||||
@pre = sort { $b->{rank} <=> $a->{rank} } @pre;
|
||||
|
||||
print STDERR "# Number of placed predecessors of $node->{name}: " . scalar @pre . "\n" if $self->{debug};
|
||||
|
||||
if (@pre <= 2 && @pre > 0)
|
||||
{
|
||||
|
||||
if (@pre == 1)
|
||||
{
|
||||
# only one placed predecessor, so place $node near it
|
||||
print STDERR "# placing $node->{name} near predecessor\n" if $self->{debug};
|
||||
@tries = ( $pre[0]->_near_places($cells, $min_dist), $pre[0]->_near_places($cells,$min_dist+2) );
|
||||
}
|
||||
else
|
||||
{
|
||||
# two placed predecessors, so place at crossing point of both of them
|
||||
# compute difference between the two nodes
|
||||
|
||||
my $dx = ($pre[0]->{x} - $pre[1]->{x});
|
||||
my $dy = ($pre[0]->{y} - $pre[1]->{y});
|
||||
|
||||
# are both nodes NOT on a straight line?
|
||||
if ($dx != 0 && $dy != 0)
|
||||
{
|
||||
# ok, so try to place at the crossing point
|
||||
@tries = (
|
||||
$pre[0]->{x}, $pre[1]->{y},
|
||||
$pre[0]->{y}, $pre[1]->{x},
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
# two nodes on a line, try to place node in the middle
|
||||
if ($dx == 0)
|
||||
{
|
||||
@tries = ( $pre[1]->{x}, $pre[1]->{y} + int($dy / 2) );
|
||||
}
|
||||
else
|
||||
{
|
||||
@tries = ( $pre[1]->{x} + int($dx / 2), $pre[1]->{y} );
|
||||
}
|
||||
}
|
||||
# XXX TODO BUG: shouldn't we also try this if we have more than 2
|
||||
# placed predecessors?
|
||||
|
||||
# In addition, we can also try to place the node around the
|
||||
# different nodes:
|
||||
foreach my $n (@pre)
|
||||
{
|
||||
push @tries, $n->_near_places($cells, $min_dist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my @suc_all = $node->successors();
|
||||
|
||||
# find all already placed successors
|
||||
my @suc;
|
||||
for my $s (@suc_all)
|
||||
{
|
||||
push @suc, $s if defined $s->{x};
|
||||
}
|
||||
print STDERR "# Number of placed successors of $node->{name}: " . scalar @suc . "\n" if $self->{debug};
|
||||
foreach my $s (@suc)
|
||||
{
|
||||
# for each successors (especially if there is only one), try to place near
|
||||
push @tries, $s->_near_places($cells, $min_dist);
|
||||
push @tries, $s->_near_places($cells, $min_dist + 2);
|
||||
}
|
||||
|
||||
# weed out positions that are unsuitable
|
||||
@tries = $self->_clear_tries($node, $cells, \@tries);
|
||||
|
||||
print STDERR "# Left with " . scalar @tries . " for node $node->{name}\n" if $self->{debug};
|
||||
|
||||
splice (@tries,0,$try) if $try > 0; # remove the first N tries
|
||||
|
||||
while (@tries > 0)
|
||||
{
|
||||
my $x = shift @tries;
|
||||
my $y = shift @tries;
|
||||
|
||||
print STDERR "# Trying to place $node->{name} at $x,$y\n" if $self->{debug};
|
||||
return 0 if $node->_do_place($x,$y,$self);
|
||||
|
||||
} # for all trial positions
|
||||
|
||||
##############################################################################
|
||||
# all simple possibilities exhausted, try a generic approach
|
||||
|
||||
print STDERR "# No more simple possibilities for node $node->{name}\n" if $self->{debug};
|
||||
|
||||
# XXX TODO:
|
||||
# find out which sides of the node predecessor node(s) still have free
|
||||
# ports/slots. With increasing distances, try to place the node around these.
|
||||
|
||||
# If no predecessors/incoming edges, try to place in column 0, otherwise
|
||||
# considered the node's rank, too
|
||||
|
||||
my $col = 0; $col = $node->{rank} * 2 if @pre > 0;
|
||||
|
||||
$col = $pre[0]->{x} if @pre > 0;
|
||||
|
||||
# find the first free row
|
||||
my $y = 0;
|
||||
$y +=2 while (exists $cells->{"$col,$y"});
|
||||
$y += 1 if exists $cells->{"$col," . ($y-1)}; # leave one cell spacing
|
||||
|
||||
# now try to place node (or node cluster)
|
||||
while (1)
|
||||
{
|
||||
next if $self->_clear_tries($node, $cells, [ $col,$y ]) == 0;
|
||||
last if $node->_do_place($col,$y,$self);
|
||||
}
|
||||
continue {
|
||||
$y += 2;
|
||||
}
|
||||
|
||||
$node->{x} = $col;
|
||||
|
||||
0; # success, score 0
|
||||
}
|
||||
|
||||
sub _trace_path
|
||||
{
|
||||
# find a free way from $src to $dst (both need to be placed beforehand)
|
||||
my ($self, $src, $dst, $edge) = @_;
|
||||
|
||||
print STDERR "# Finding path from '$src->{name}' to '$dst->{name}'\n" if $self->{debug};
|
||||
print STDERR "# src: $src->{x}, $src->{y} dst: $dst->{x}, $dst->{y}\n" if $self->{debug};
|
||||
|
||||
my $coords = $self->_find_path ($src, $dst, $edge);
|
||||
|
||||
# found no path?
|
||||
if (!defined $coords)
|
||||
{
|
||||
print STDERR "# Unable to find path from $src->{name} ($src->{x},$src->{y}) to $dst->{name} ($dst->{x},$dst->{y})\n" if $self->{debug};
|
||||
return undef;
|
||||
}
|
||||
|
||||
# path is empty, happens for sharing edges with only a joint
|
||||
return 1 if scalar @$coords == 0;
|
||||
|
||||
# Create all cells from the returned list and score path (lower score: better)
|
||||
my $i = 0;
|
||||
my $score = 0;
|
||||
while ($i < scalar @$coords)
|
||||
{
|
||||
my $type = $coords->[$i+2];
|
||||
$self->_create_cell($edge,$coords->[$i],$coords->[$i+1],$type);
|
||||
$score ++; # each element: one point
|
||||
$type &= EDGE_TYPE_MASK; # mask flags
|
||||
# edge bend or cross: one point extra
|
||||
$score ++ if $type != EDGE_HOR && $type != EDGE_VER;
|
||||
$score += 3 if $type == EDGE_CROSS; # crossings are doubleplusungood
|
||||
$i += 3;
|
||||
}
|
||||
|
||||
$score;
|
||||
}
|
||||
|
||||
sub _create_cell
|
||||
{
|
||||
my ($self,$edge,$x,$y,$type) = @_;
|
||||
|
||||
my $cells = $self->{cells}; my $xy = "$x,$y";
|
||||
|
||||
if (ref($cells->{$xy}) && $cells->{$xy}->isa('Graph::Easy::Edge'))
|
||||
{
|
||||
$cells->{$xy}->_make_cross($edge,$type & EDGE_FLAG_MASK);
|
||||
# insert a EDGE_HOLE into the cells of the edge (but not into the list of
|
||||
# to-be-rendered cells). This cell will be removed by the optimizer later on.
|
||||
Graph::Easy::Edge::Cell->new( type => EDGE_HOLE, edge => $edge, x => $x, y => $y );
|
||||
return;
|
||||
}
|
||||
|
||||
my $path = Graph::Easy::Edge::Cell->new( type => $type, edge => $edge, x => $x, y => $y );
|
||||
$cells->{$xy} = $path; # store in cells
|
||||
}
|
||||
|
||||
sub _path_is_clear
|
||||
{
|
||||
# For all points (x,y pairs) in the path, check that the cell is still free
|
||||
# $path points to a list of [ x,y,type, x,y,type, ...]
|
||||
my ($self,$path) = @_;
|
||||
|
||||
my $cells = $self->{cells};
|
||||
my $i = 0;
|
||||
while ($i < scalar @$path)
|
||||
{
|
||||
my $x = $path->[$i];
|
||||
my $y = $path->[$i+1];
|
||||
# my $t = $path->[$i+2];
|
||||
$i += 3;
|
||||
|
||||
return 0 if exists $cells->{"$x,$y"}; # obstacle hit
|
||||
}
|
||||
1; # path is clear
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Layout::Path - Path management for Manhattan-style grids
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
my $bonn = Graph::Easy::Node->new(
|
||||
name => 'Bonn',
|
||||
);
|
||||
my $berlin = Graph::Easy::Node->new(
|
||||
name => 'Berlin',
|
||||
);
|
||||
|
||||
$graph->add_edge ($bonn, $berlin);
|
||||
|
||||
$graph->layout();
|
||||
|
||||
print $graph->as_ascii( );
|
||||
|
||||
# prints:
|
||||
|
||||
# +------+ +--------+
|
||||
# | Bonn | --> | Berlin |
|
||||
# +------+ +--------+
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<Graph::Easy::Layout::Scout> contains just the actual path-managing code for
|
||||
L<Graph::Easy|Graph::Easy>, e.g. to create/destroy/maintain paths, node
|
||||
placement etc.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
Exports nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 METHODS into Graph::Easy
|
||||
|
||||
This module injects the following methods into C<Graph::Easy>:
|
||||
|
||||
=head2 _path_is_clear()
|
||||
|
||||
$graph->_path_is_clear($path);
|
||||
|
||||
For all points (x,y pairs) in the path, check that the cell is still free.
|
||||
C<$path> points to a list x,y,type pairs as in C<< [ [x,y,type], [x,y,type], ...] >>.
|
||||
|
||||
=head2 _create_cell()
|
||||
|
||||
my $cell = $graph->($edge,$x,$y,$type);
|
||||
|
||||
Create a cell at C<$x,$y> coordinates with type C<$type> for the specified
|
||||
edge.
|
||||
|
||||
=head2 _path_is_clear()
|
||||
|
||||
$graph->_path_is_clear();
|
||||
|
||||
For all points (x,y pairs) in the path, check that the cell is still free.
|
||||
C<$path> points to a list of C<[ x,y,type, x,y,type, ...]>.
|
||||
|
||||
Returns true when the path is clear, false otherwise.
|
||||
|
||||
=head2 _trace_path()
|
||||
|
||||
my $path = my $graph->_trace_path($src,$dst,$edge);
|
||||
|
||||
Find a free way from source node/group to destination node/group for the
|
||||
specified edge. Both source and destination need to be placed beforehand.
|
||||
|
||||
=head1 METHODS in Graph::Easy::Node
|
||||
|
||||
This module injects the following methods into C<Graph::Easy::Node>:
|
||||
|
||||
=head2 _near_places()
|
||||
|
||||
my $node->_near_places();
|
||||
|
||||
Take a node and return a list of possible placements around it and
|
||||
prune out already occupied cells. $d is the distance from the node
|
||||
border and defaults to two (for placements). Set it to one for
|
||||
adjacent cells.
|
||||
|
||||
=head2 _shuffle_dir()
|
||||
|
||||
my $dirs = $node->_shuffle_dir( [ 0,1,2,3 ], $dir);
|
||||
|
||||
Take a ref to an array with four entries and shuffle them around according to
|
||||
C<$dir>.
|
||||
|
||||
=head2 _shift()
|
||||
|
||||
my $dir = $node->_shift($degrees);
|
||||
|
||||
Return a the C<flow()> direction shifted by X degrees to C<$dir>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2007 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for information.
|
||||
|
||||
=cut
|
||||
649
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Layout/Repair.pm
Normal file
649
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Layout/Repair.pm
Normal file
@@ -0,0 +1,649 @@
|
||||
#############################################################################
|
||||
# Layout directed graphs on a flat plane. Part of Graph::Easy.
|
||||
#
|
||||
# Code to repair spliced layouts (after group cells have been inserted).
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Layout::Repair;
|
||||
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
# for layouts with groups:
|
||||
|
||||
package Graph::Easy;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Graph::Easy::Util qw(ord_values);
|
||||
|
||||
sub _edges_into_groups
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Put all edges between two nodes with the same group in the group as well
|
||||
for my $edge (ord_values $self->{edges})
|
||||
{
|
||||
my $gf = $edge->{from}->group();
|
||||
my $gt = $edge->{to}->group();
|
||||
|
||||
$gf->_add_edge($edge) if defined $gf && defined $gt && $gf == $gt;
|
||||
}
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _repair_nodes
|
||||
{
|
||||
# Splicing the rows/columns to add filler cells will have torn holes into
|
||||
# multi-edges nodes, so we insert additional filler cells.
|
||||
my ($self) = @_;
|
||||
my $cells = $self->{cells};
|
||||
|
||||
# Make multi-celled nodes occupy the proper double space due to splicing
|
||||
# in group cell has doubled the layout in each direction:
|
||||
for my $n ($self->nodes())
|
||||
{
|
||||
# 1 => 1, 2 => 3, 3 => 5, 4 => 7 etc
|
||||
$n->{cx} = $n->{cx} * 2 - 1;
|
||||
$n->{cy} = $n->{cy} * 2 - 1;
|
||||
}
|
||||
|
||||
# We might get away with not inserting filler cells if we just mark the
|
||||
# cells as used (e.g. use only one global filler cell) since filler cells
|
||||
# aren't actually rendered, anyway.
|
||||
|
||||
for my $cell (ord_values $cells)
|
||||
{
|
||||
next unless $cell->isa('Graph::Easy::Node::Cell');
|
||||
|
||||
# we have "[ empty ] [ filler ]" (unless cell is on the same column as node)
|
||||
if ($cell->{x} > $cell->{node}->{x})
|
||||
{
|
||||
my $x = $cell->{x} - 1; my $y = $cell->{y};
|
||||
|
||||
# print STDERR "# inserting filler at $x,$y for $cell->{node}->{name}\n";
|
||||
$cells->{"$x,$y"} =
|
||||
Graph::Easy::Node::Cell->new(node => $cell->{node}, x => $x, y => $y );
|
||||
}
|
||||
|
||||
# we have " [ empty ] "
|
||||
# " [ filler ] " (unless cell is on the same row as node)
|
||||
if ($cell->{y} > $cell->{node}->{y})
|
||||
{
|
||||
my $x = $cell->{x}; my $y = $cell->{y} - 1;
|
||||
|
||||
# print STDERR "# inserting filler at $x,$y for $cell->{node}->{name}\n";
|
||||
$cells->{"$x,$y"} =
|
||||
Graph::Easy::Node::Cell->new(node => $cell->{node}, x => $x, y => $y );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub _repair_cell
|
||||
{
|
||||
my ($self, $type, $edge, $x, $y, $after, $before) = @_;
|
||||
|
||||
# already repaired?
|
||||
return if exists $self->{cells}->{"$x,$y"};
|
||||
|
||||
# print STDERR "# Insert edge cell at $x,$y (type $type) for edge $edge->{from}->{name} --> $edge->{to}->{name}\n";
|
||||
|
||||
$self->{cells}->{"$x,$y"} =
|
||||
Graph::Easy::Edge::Cell->new(
|
||||
type => $type,
|
||||
edge => $edge, x => $x, y => $y, before => $before, after => $after );
|
||||
|
||||
}
|
||||
|
||||
sub _splice_edges
|
||||
{
|
||||
# Splicing the rows/columns to add filler cells might have torn holes into
|
||||
# edges, so we splice these together again.
|
||||
my ($self) = @_;
|
||||
|
||||
my $cells = $self->{cells};
|
||||
|
||||
print STDERR "# Reparing spliced layout\n" if $self->{debug};
|
||||
|
||||
# Edge end/start points inside groups are not handled here, but in
|
||||
# _repair_group_edge()
|
||||
|
||||
# go over the old layout, because the new cells were inserted into odd
|
||||
# rows/columns and we do not care for these:
|
||||
for my $cell (sort { $a->{x} <=> $b->{x} || $a->{y} <=> $b->{y} } values %$cells)
|
||||
{
|
||||
next unless $cell->isa('Graph::Easy::Edge::Cell');
|
||||
|
||||
my $edge = $cell->{edge};
|
||||
|
||||
#########################################################################
|
||||
# check for "[ JOINT ] [ empty ] [ edge ]"
|
||||
|
||||
my $x = $cell->{x} + 2; my $y = $cell->{y};
|
||||
|
||||
my $type = $cell->{type} & EDGE_TYPE_MASK;
|
||||
|
||||
# left is a joint and right exists
|
||||
if ( ($type == EDGE_S_E_W || $type == EDGE_N_E_W || $type == EDGE_E_N_S)
|
||||
&& exists $cells->{"$x,$y"})
|
||||
{
|
||||
my $right = $cells->{"$x,$y"};
|
||||
|
||||
# print STDERR "# at $x,$y\n";
|
||||
|
||||
# |-> [ empty ] [ node ]
|
||||
if ($right->isa('Graph::Easy::Edge::Cell'))
|
||||
{
|
||||
# when the left one is a joint, the right one must be an edge
|
||||
$self->error("Found non-edge piece ($right->{type} $right) right to a joint ($type)")
|
||||
unless $right->isa('Graph::Easy::Edge::Cell');
|
||||
|
||||
# print STDERR "splicing in HOR piece to the right of joint at $x, $y ($edge $right $right->{edge})\n";
|
||||
|
||||
# insert the new piece before the first part of the edge after the joint
|
||||
$self->_repair_cell(EDGE_HOR(), $right->{edge},$cell->{x}+1,$y,0)
|
||||
if $edge != $right->{edge};
|
||||
}
|
||||
}
|
||||
|
||||
#########################################################################
|
||||
# check for "[ edge ] [ empty ] [ joint ]"
|
||||
|
||||
$x = $cell->{x} - 2; $y = $cell->{y};
|
||||
|
||||
# right is a joint and left exists
|
||||
if ( ($type == EDGE_S_E_W || $type == EDGE_N_E_W || $type == EDGE_W_N_S)
|
||||
&& exists $cells->{"$x,$y"})
|
||||
{
|
||||
my $left = $cells->{"$x,$y"};
|
||||
|
||||
# [ node ] [ empty ] [ <-| ]
|
||||
if (!$left->isa('Graph::Easy::Node'))
|
||||
{
|
||||
# when the left one is a joint, the right one must be an edge
|
||||
$self->error('Found non-edge piece right to a joint')
|
||||
unless $left->isa('Graph::Easy::Edge::Cell');
|
||||
|
||||
# insert the new piece before the joint
|
||||
$self->_repair_cell(EDGE_HOR(), $edge, $cell->{x}+1,$y,0) # $left,$cell)
|
||||
if $edge != $left->{edge};
|
||||
}
|
||||
}
|
||||
|
||||
#########################################################################
|
||||
# check for " [ joint ]
|
||||
# [ empty ]
|
||||
# [ edge ]"
|
||||
|
||||
$x = $cell->{x}; $y = $cell->{y} + 2;
|
||||
|
||||
# top is a joint and down exists
|
||||
if ( ($type == EDGE_S_E_W || $type == EDGE_E_N_S || $type == EDGE_W_N_S)
|
||||
&& exists $cells->{"$x,$y"})
|
||||
{
|
||||
my $bottom = $cells->{"$x,$y"};
|
||||
|
||||
# when top is a joint, the bottom one must be an edge
|
||||
$self->error('Found non-edge piece below a joint')
|
||||
unless $bottom->isa('Graph::Easy::Edge::Cell');
|
||||
|
||||
# print STDERR "splicing in VER piece below joint at $x, $y\n";
|
||||
|
||||
# XXX TODO
|
||||
# insert the new piece after the joint
|
||||
$self->_repair_cell(EDGE_VER(), $bottom->{edge},$x,$cell->{y}+1,0)
|
||||
if $edge != $bottom->{edge};
|
||||
}
|
||||
|
||||
#########################################################################
|
||||
# check for "[ --- ] [ empty ] [ ---> ]"
|
||||
|
||||
$x = $cell->{x} + 2; $y = $cell->{y};
|
||||
|
||||
if (exists $cells->{"$x,$y"})
|
||||
{
|
||||
my $right = $cells->{"$x,$y"};
|
||||
|
||||
$self->_repair_cell(EDGE_HOR(), $edge, $cell->{x}+1,$y,$cell,$right)
|
||||
if $right->isa('Graph::Easy::Edge::Cell') &&
|
||||
defined $right->{edge} && defined $right->{type} &&
|
||||
# check that both cells belong to the same edge
|
||||
( $edge == $right->{edge} ||
|
||||
# or the right part is a cross
|
||||
$right->{type} == EDGE_CROSS ||
|
||||
# or the left part is a cross
|
||||
$cell->{type} == EDGE_CROSS );
|
||||
}
|
||||
|
||||
#########################################################################
|
||||
# check for [ | ]
|
||||
# [ empty ]
|
||||
# [ | ]
|
||||
$x = $cell->{x}; $y = $cell->{y}+2;
|
||||
|
||||
if (exists $cells->{"$x,$y"})
|
||||
{
|
||||
my $below = $cells->{"$x,$y"};
|
||||
|
||||
$self->_repair_cell(EDGE_VER(),$edge,$x,$cell->{y}+1,$cell,$below)
|
||||
if $below->isa('Graph::Easy::Edge::Cell') &&
|
||||
# check that both cells belong to the same edge
|
||||
( $edge == $below->{edge} ||
|
||||
# or the lower part is a cross
|
||||
$below->{type} == EDGE_CROSS ||
|
||||
# or the upper part is a cross
|
||||
$cell->{type} == EDGE_CROSS );
|
||||
}
|
||||
|
||||
} # end for all cells
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _new_edge_cell
|
||||
{
|
||||
# create a new edge cell to be spliced into the layout for repairs
|
||||
my ($self, $cells, $group, $edge, $x, $y, $after, $type) = @_;
|
||||
|
||||
$type += EDGE_SHORT_CELL() if defined $group;
|
||||
|
||||
my $e_cell = Graph::Easy::Edge::Cell->new(
|
||||
type => $type, edge => $edge, x => $x, y => $y, after => $after);
|
||||
$group->_del_cell($e_cell) if defined $group;
|
||||
$cells->{"$x,$y"} = $e_cell;
|
||||
}
|
||||
|
||||
sub _check_edge_cell
|
||||
{
|
||||
# check a start/end edge cell and if nec. repair it
|
||||
my ($self, $cell, $x, $y, $flag, $type, $match, $check, $where) = @_;
|
||||
|
||||
my $edge = $cell->{edge};
|
||||
if (grep { exists $_->{cell_class} && $_->{cell_class} =~ $match } ord_values ($check))
|
||||
{
|
||||
$cell->{type} &= ~ $flag; # delete the flag
|
||||
|
||||
$self->_new_edge_cell(
|
||||
$self->{cells}, $edge->{group}, $edge, $x, $y, $where, $type + $flag);
|
||||
}
|
||||
}
|
||||
|
||||
sub _repair_group_edge
|
||||
{
|
||||
# repair an edges inside a group
|
||||
my ($self, $cell, $rows, $cols, $group) = @_;
|
||||
|
||||
my $cells = $self->{cells};
|
||||
my ($x,$y,$doit);
|
||||
|
||||
my $type = $cell->{type};
|
||||
|
||||
#########################################################################
|
||||
# check for " [ empty ] [ |---> ]"
|
||||
$x = $cell->{x} - 1; $y = $cell->{y};
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_START_W, EDGE_HOR, qr/g[rl]/, $cols->{$x}, 0)
|
||||
if (($type & EDGE_START_MASK) == EDGE_START_W);
|
||||
|
||||
#########################################################################
|
||||
# check for " [ <--- ] [ empty ]"
|
||||
$x = $cell->{x} + 1;
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_START_E, EDGE_HOR, qr/g[rl]/, $cols->{$x}, 0)
|
||||
if (($type & EDGE_START_MASK) == EDGE_START_E);
|
||||
|
||||
#########################################################################
|
||||
# check for " [ --> ] [ empty ]"
|
||||
$x = $cell->{x} + 1;
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_END_E, EDGE_HOR, qr/g[rl]/, $cols->{$x}, -1)
|
||||
if (($type & EDGE_END_MASK) == EDGE_END_E);
|
||||
|
||||
# $self->_check_edge_cell($cell, $x, $y, EDGE_END_E, EDGE_E_N_S, qr/g[rl]/, $cols->{$x}, -1)
|
||||
# if (($type & EDGE_END_MASK) == EDGE_END_E);
|
||||
|
||||
#########################################################################
|
||||
# check for " [ empty ] [ <-- ]"
|
||||
$x = $cell->{x} - 1;
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_END_W, EDGE_HOR, qr/g[rl]/, $cols->{$x}, -1)
|
||||
if (($type & EDGE_END_MASK) == EDGE_END_W);
|
||||
|
||||
#########################################################################
|
||||
#########################################################################
|
||||
# vertical cases
|
||||
|
||||
#########################################################################
|
||||
# check for [empty]
|
||||
# [ | ]
|
||||
$x = $cell->{x}; $y = $cell->{y} - 1;
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_START_N, EDGE_VER, qr/g[tb]/, $rows->{$y}, 0)
|
||||
if (($type & EDGE_START_MASK) == EDGE_START_N);
|
||||
|
||||
#########################################################################
|
||||
# check for [ |]
|
||||
# [ empty ]
|
||||
$y = $cell->{y} + 1;
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_START_S, EDGE_VER, qr/g[tb]/, $rows->{$y}, 0)
|
||||
if (($type & EDGE_START_MASK) == EDGE_START_S);
|
||||
|
||||
#########################################################################
|
||||
# check for [ v ]
|
||||
# [empty]
|
||||
$y = $cell->{y} + 1;
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_END_S, EDGE_VER, qr/g[tb]/, $rows->{$y}, -1)
|
||||
if (($type & EDGE_END_MASK) == EDGE_END_S);
|
||||
|
||||
#########################################################################
|
||||
# check for [ empty ]
|
||||
# [ ^ ]
|
||||
$y = $cell->{y} - 1;
|
||||
|
||||
$self->_check_edge_cell($cell, $x, $y, EDGE_END_N, EDGE_VER, qr/g[tb]/, $rows->{$y}, -1)
|
||||
if (($type & EDGE_END_MASK) == EDGE_END_N);
|
||||
}
|
||||
|
||||
sub _repair_edge
|
||||
{
|
||||
# repair an edge outside a group
|
||||
my ($self, $cell, $rows, $cols) = @_;
|
||||
|
||||
my $cells = $self->{cells};
|
||||
|
||||
#########################################################################
|
||||
# check for [ |\n|\nv ]
|
||||
# [empty] ... [non-empty]
|
||||
# [node]
|
||||
|
||||
my $x = $cell->{x}; my $y = $cell->{y} + 1;
|
||||
|
||||
my $below = $cells->{"$x,$y"}; # must be empty
|
||||
|
||||
if (!ref($below) && (($cell->{type} & EDGE_END_MASK) == EDGE_END_S))
|
||||
{
|
||||
if (grep { exists $_->{cell_class} && $_->{cell_class} =~ /g[tb]/ } ord_values $rows->{$y})
|
||||
{
|
||||
# delete the start flag
|
||||
$cell->{type} &= ~ EDGE_END_S;
|
||||
|
||||
$self->_new_edge_cell($cells, undef, $cell->{edge}, $x, $y, -1,
|
||||
EDGE_VER() + EDGE_END_S() );
|
||||
}
|
||||
}
|
||||
# XXX TODO: do the other ends (END_N, END_W, END_E), too
|
||||
|
||||
}
|
||||
|
||||
sub _repair_edges
|
||||
{
|
||||
# fix edge end/start cells to be closer to the node cell they point at
|
||||
my ($self, $rows, $cols) = @_;
|
||||
|
||||
my $cells = $self->{cells};
|
||||
|
||||
# go over all existing cells
|
||||
for my $cell (sort { $a->{x} <=> $b->{x} || $a->{y} <=> $b->{y} } values %$cells)
|
||||
{
|
||||
next unless $cell->isa('Graph::Easy::Edge::Cell');
|
||||
|
||||
# skip odd positions
|
||||
next unless ($cell->{x} & 1) == 0 && ($cell->{y} & 1) == 0;
|
||||
|
||||
my $group = $cell->group();
|
||||
|
||||
$self->_repair_edge($cell,$rows,$cols) unless $group;
|
||||
$self->_repair_group_edge($cell,$rows,$cols,$group) if $group;
|
||||
|
||||
} # end for all cells
|
||||
}
|
||||
|
||||
sub _fill_group_cells
|
||||
{
|
||||
# after doing a layout(), we need to add the group to each cell based on
|
||||
# what group the nearest node is in.
|
||||
my ($self, $cells_layout) = @_;
|
||||
|
||||
print STDERR "\n# Padding with fill cells, have ",
|
||||
scalar $self->groups(), " groups.\n" if $self->{debug};
|
||||
|
||||
# take a shortcut if we do not have groups
|
||||
return $self if $self->groups == 0;
|
||||
|
||||
$self->{padding_cells} = 1; # set to true
|
||||
|
||||
# We need to insert "filler" cells around each node/edge/cell:
|
||||
|
||||
# To "insert" the filler cells, we simple multiply each X and Y by 2, this
|
||||
# is O(N) where N is the number of actually existing cells. Otherwise we
|
||||
# would have to create the full table-layout, and then insert rows/columns.
|
||||
my $cells = {};
|
||||
for my $key (sort keys %$cells_layout)
|
||||
{
|
||||
my ($x,$y) = split /,/, $key;
|
||||
my $cell = $cells_layout->{$key};
|
||||
|
||||
$x *= 2;
|
||||
$y *= 2;
|
||||
$cell->{x} = $x;
|
||||
$cell->{y} = $y;
|
||||
|
||||
$cells->{"$x,$y"} = $cell;
|
||||
}
|
||||
|
||||
$self->{cells} = $cells; # override with new cell layout
|
||||
|
||||
$self->_splice_edges(); # repair edges
|
||||
$self->_repair_nodes(); # repair multi-celled nodes
|
||||
|
||||
my $c = 'Graph::Easy::Group::Cell';
|
||||
for my $cell (ord_values $self->{cells})
|
||||
{
|
||||
# DO NOT MODIFY $cell IN THE LOOP BODY!
|
||||
|
||||
my ($x,$y) = ($cell->{x},$cell->{y});
|
||||
|
||||
# find the primary node for node cells, for group check
|
||||
my $group = $cell->group();
|
||||
|
||||
# not part of group, so no group-cells nec.
|
||||
next unless $group;
|
||||
|
||||
# now insert up to 8 filler cells around this cell
|
||||
my $ofs = [ -1, 0,
|
||||
0, -1,
|
||||
+1, 0,
|
||||
+1, 0,
|
||||
0, +1,
|
||||
0, +1,
|
||||
-1, 0,
|
||||
-1, 0, ];
|
||||
while (@$ofs > 0)
|
||||
{
|
||||
$x += shift @$ofs;
|
||||
$y += shift @$ofs;
|
||||
|
||||
$cells->{"$x,$y"} = $c->new ( graph => $self, group => $group, x => $x, y => $y )
|
||||
unless exists $cells->{"$x,$y"};
|
||||
}
|
||||
}
|
||||
|
||||
# Nodes positioned two cols/rows apart (f.i. y == 0 and y == 2) will be
|
||||
# three cells apart (y == 0 and y == 4) after the splicing, the step above
|
||||
# will not be able to close that hole - it will create fillers at y == 1 and
|
||||
# y == 3. So we close these holes now with an extra step.
|
||||
for my $cell (ord_values ( $self->{cells} ))
|
||||
{
|
||||
# only for filler cells
|
||||
next unless $cell->isa('Graph::Easy::Group::Cell');
|
||||
|
||||
my ($sx,$sy) = ($cell->{x},$cell->{y});
|
||||
my $group = $cell->{group};
|
||||
|
||||
my $x = $sx; my $y2 = $sy + 2; my $y = $sy + 1;
|
||||
# look for:
|
||||
# [ group ]
|
||||
# [ empty ]
|
||||
# [ group ]
|
||||
if (exists $cells->{"$x,$y2"} && !exists $cells->{"$x,$y"})
|
||||
{
|
||||
my $down = $cells->{"$x,$y2"};
|
||||
if ($down->isa('Graph::Easy::Group::Cell') && $down->{group} == $group)
|
||||
{
|
||||
$cells->{"$x,$y"} = $c->new ( graph => $self, group => $group, x => $x, y => $y );
|
||||
}
|
||||
}
|
||||
$x = $sx+1; my $x2 = $sx + 2; $y = $sy;
|
||||
# look for:
|
||||
# [ group ] [ empty ] [ group ]
|
||||
if (exists $cells->{"$x2,$y"} && !exists $cells->{"$x,$y"})
|
||||
{
|
||||
my $right = $cells->{"$x2,$y"};
|
||||
if ($right->isa('Graph::Easy::Group::Cell') && $right->{group} == $group)
|
||||
{
|
||||
$cells->{"$x,$y"} = $c->new ( graph => $self, group => $group, x => $x, y => $y );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# XXX TODO
|
||||
# we should "grow" the group area to close holes
|
||||
|
||||
for my $group (ord_values ( $self->{groups} ))
|
||||
{
|
||||
$group->_set_cell_types($cells);
|
||||
}
|
||||
|
||||
# create a mapping for each row/column so that we can repair edge starts/ends
|
||||
my $rows = {};
|
||||
my $cols = {};
|
||||
for my $cell (ord_values ($cells))
|
||||
{
|
||||
$rows->{$cell->{y}}->{$cell->{x}} = $cell;
|
||||
$cols->{$cell->{x}}->{$cell->{y}} = $cell;
|
||||
}
|
||||
$self->_repair_edges($rows,$cols); # insert short edge cells on group
|
||||
# border rows/columns
|
||||
|
||||
# for all groups, set the cell carrying the label (top-left-most cell)
|
||||
for my $group (ord_values ( $self->{groups} ))
|
||||
{
|
||||
$group->_find_label_cell();
|
||||
}
|
||||
|
||||
# DEBUG:
|
||||
# for my $cell (ord_values $cells)
|
||||
# {
|
||||
# $cell->_correct_size();
|
||||
# }
|
||||
#
|
||||
# my $y = 0;
|
||||
# for my $cell (sort { $a->{y} <=> $b->{y} || $a->{x} <=> $b->{x} } values %$cells)
|
||||
# {
|
||||
# print STDERR "\n" if $y != $cell->{y};
|
||||
# print STDERR "$cell->{x},$cell->{y}, $cell->{w},$cell->{h}, ", $cell->{group}->{name} || 'none', "\t";
|
||||
# $y = $cell->{y};
|
||||
# }
|
||||
# print STDERR "\n";
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Layout::Repair - Repair spliced layout with group cells
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
my $bonn = Graph::Easy::Node->new(
|
||||
name => 'Bonn',
|
||||
);
|
||||
my $berlin = Graph::Easy::Node->new(
|
||||
name => 'Berlin',
|
||||
);
|
||||
|
||||
$graph->add_edge ($bonn, $berlin);
|
||||
|
||||
$graph->layout();
|
||||
|
||||
print $graph->as_ascii( );
|
||||
|
||||
# prints:
|
||||
|
||||
# +------+ +--------+
|
||||
# | Bonn | --> | Berlin |
|
||||
# +------+ +--------+
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<Graph::Easy::Layout::Repair> contains code that can splice in
|
||||
group cells into a layout, as well as repair the layout after that step.
|
||||
|
||||
It is part of L<Graph::Easy|Graph::Easy> and used automatically.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
C<Graph::Easy::Layout> injects the following methods into the C<Graph::Easy>
|
||||
namespace:
|
||||
|
||||
=head2 _edges_into_groups()
|
||||
|
||||
Put the edges into the appropriate group and class.
|
||||
|
||||
=head2 _assign_ranks()
|
||||
|
||||
$graph->_assign_ranks();
|
||||
|
||||
=head2 _repair_nodes()
|
||||
|
||||
Splicing the rows/columns to add filler cells will have torn holes into
|
||||
multi-edges nodes, so we insert additional filler cells to repair this.
|
||||
|
||||
=head2 _splice_edges()
|
||||
|
||||
Splicing the rows/columns to add filler cells might have torn holes into
|
||||
multi-celled edges, so we splice these together again.
|
||||
|
||||
=head2 _repair_edges()
|
||||
|
||||
Splicing the rows/columns to add filler cells might have put "holes"
|
||||
between an edge start/end and the node cell it points to. This
|
||||
routine fixes this problem by extending the edge by one cell if
|
||||
necessary.
|
||||
|
||||
=head2 _fill_group_cells()
|
||||
|
||||
After doing a C<layout()>, we need to add the group to each cell based on
|
||||
what group the nearest node is in.
|
||||
|
||||
This routine will also find the label cell for each group, and repair
|
||||
edge/node damage done by the splicing.
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
Exports nothing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2007 by Tels L<http://bloodgate.com>
|
||||
|
||||
See the LICENSE file for information.
|
||||
|
||||
=cut
|
||||
1717
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Layout/Scout.pm
Normal file
1717
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Layout/Scout.pm
Normal file
File diff suppressed because it is too large
Load Diff
2865
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Node.pm
Normal file
2865
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Node.pm
Normal file
File diff suppressed because it is too large
Load Diff
116
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Node/Anon.pm
Normal file
116
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Node/Anon.pm
Normal file
@@ -0,0 +1,116 @@
|
||||
#############################################################################
|
||||
# (c) by Tels 2004. Part of Graph::Easy. An anonymous (invisible) node.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Node::Anon;
|
||||
|
||||
use Graph::Easy::Node;
|
||||
|
||||
@ISA = qw/Graph::Easy::Node/;
|
||||
$VERSION = '0.76';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub _init
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->SUPER::_init(@_);
|
||||
|
||||
$self->{name} = '#' . $self->{id};
|
||||
$self->{class} = 'node.anon';
|
||||
|
||||
$self->{att}->{label} = ' ';
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _correct_size
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{w} = 3;
|
||||
$self->{h} = 3;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub attributes_as_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->SUPER::attributes_as_txt( {
|
||||
node => {
|
||||
label => undef,
|
||||
shape => undef,
|
||||
class => undef,
|
||||
} } );
|
||||
}
|
||||
|
||||
sub as_pure_txt
|
||||
{
|
||||
'[ ]';
|
||||
}
|
||||
|
||||
sub _as_part_txt
|
||||
{
|
||||
'[ ]';
|
||||
}
|
||||
|
||||
sub as_txt
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
'[ ]' . $self->attributes_as_txt();
|
||||
}
|
||||
|
||||
sub text_styles_as_css
|
||||
{
|
||||
'';
|
||||
}
|
||||
|
||||
sub is_anon
|
||||
{
|
||||
# is an anon node
|
||||
1;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Node::Anon - An anonymous, invisible node in Graph::Easy
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy::Node::Anon;
|
||||
|
||||
my $anon = Graph::Easy::Node::Anon->new();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Node::Anon> represents an anonymous, invisible node.
|
||||
These can be used to let edges start and end "nowhere".
|
||||
|
||||
The syntax in the Graph::Easy textual description language looks like this:
|
||||
|
||||
[ ] -> [ Bonn ] -> [ ]
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy::Node>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2006 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
140
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Node/Cell.pm
Normal file
140
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Node/Cell.pm
Normal file
@@ -0,0 +1,140 @@
|
||||
#############################################################################
|
||||
# (c) by Tels 2004 - 2005. An empty filler cell. Part of Graph::Easy.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Node::Cell;
|
||||
|
||||
use Graph::Easy::Node;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw(@ISA $VERSION);
|
||||
|
||||
@ISA = qw/Graph::Easy::Node/;
|
||||
$VERSION = '0.76';
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _init
|
||||
{
|
||||
# generic init, override in subclasses
|
||||
my ($self,$args) = @_;
|
||||
|
||||
$self->{class} = '';
|
||||
$self->{name} = '';
|
||||
|
||||
$self->{'x'} = 0;
|
||||
$self->{'y'} = 0;
|
||||
|
||||
# default: belongs to no node
|
||||
$self->{node} = undef;
|
||||
|
||||
foreach my $k (sort keys %$args)
|
||||
{
|
||||
if ($k !~ /^(node|graph|x|y)\z/)
|
||||
{
|
||||
require Carp;
|
||||
Carp::confess ("Invalid argument '$k' passed to Graph::Easy::Node::Cell->new()");
|
||||
}
|
||||
$self->{$k} = $args->{$k};
|
||||
}
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _correct_size
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{w} = 0;
|
||||
$self->{h} = 0;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub node
|
||||
{
|
||||
# return the node this cell belongs to
|
||||
my $self = shift;
|
||||
|
||||
$self->{node};
|
||||
}
|
||||
|
||||
sub as_ascii
|
||||
{
|
||||
'';
|
||||
}
|
||||
|
||||
sub as_html
|
||||
{
|
||||
'';
|
||||
}
|
||||
|
||||
sub group
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{node}->group();
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Node::Cell - An empty filler cell
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Graph::Easy;
|
||||
use Graph::Easy::Edge;
|
||||
|
||||
my $graph = Graph::Easy->new();
|
||||
|
||||
my $node = $graph->add_node('A');
|
||||
|
||||
my $path = Graph::Easy::Node::Cell->new(
|
||||
graph => $graph, node => $node,
|
||||
);
|
||||
|
||||
...
|
||||
|
||||
print $graph->as_ascii();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Node::Cell> is used to reserve a cell in the grid for nodes
|
||||
that occupy more than one cell.
|
||||
|
||||
You should not need to use this class directly.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 error()
|
||||
|
||||
$last_error = $cell->error();
|
||||
|
||||
$cvt->error($error); # set new messages
|
||||
$cvt->error(''); # clear error
|
||||
|
||||
Returns the last error message, or '' for no error.
|
||||
|
||||
=head2 node()
|
||||
|
||||
my $node = $cell->node();
|
||||
|
||||
Returns the node this filler cell belongs to.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2005 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
69
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Node/Empty.pm
Normal file
69
perl/lib/Graph-Easy-0.76/lib/Graph/Easy/Node/Empty.pm
Normal file
@@ -0,0 +1,69 @@
|
||||
#############################################################################
|
||||
# An empty, borderless cell. Part of Graph::Easy.
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
package Graph::Easy::Node::Empty;
|
||||
|
||||
use Graph::Easy::Node;
|
||||
|
||||
@ISA = qw/Graph::Easy::Node/;
|
||||
$VERSION = '0.76';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
#############################################################################
|
||||
|
||||
sub _init
|
||||
{
|
||||
# generic init, override in subclasses
|
||||
my ($self,$args) = @_;
|
||||
|
||||
$self->SUPER::_init($args);
|
||||
|
||||
$self->{class} = 'node.empty';
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
sub _correct_size
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->{w} = 3;
|
||||
$self->{h} = 3;
|
||||
|
||||
$self;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Graph::Easy::Node::Empty - An empty, borderless cell in a node cluster
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $cell = Graph::Easy::Node::Empty->new();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A C<Graph::Easy::Node::Empty> represents a borderless, empty cell in
|
||||
a node cluster. It is mainly used to have an object to render collapsed
|
||||
borders in ASCII output.
|
||||
|
||||
You should not need to use this class directly.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Graph::Easy::Node>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2004 - 2007 by Tels L<http://bloodgate.com>.
|
||||
|
||||
See the LICENSE file for more details.
|
||||
|
||||
=cut
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user