pax_global_header00006660000000000000000000000064116440331100014503gustar00rootroot0000000000000052 comment=f9dc14473367c9d522323223654a405a03181c12 ruby-svg-graph-1.0.5/000077500000000000000000000000001164403311000143635ustar00rootroot00000000000000ruby-svg-graph-1.0.5/.gitignore000066400000000000000000000001171164403311000163520ustar00rootroot00000000000000*.html install.rb test.rb *.gem images/ Gemfile.lock pkg/ pkg/* *~ *.swp *.swo ruby-svg-graph-1.0.5/GPL.txt000066400000000000000000000431311164403311000155500ustar00rootroot00000000000000 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 How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. ruby-svg-graph-1.0.5/Gemfile000066400000000000000000000003011164403311000156500ustar00rootroot00000000000000source "http://rubygems.org" # Add dependencies to develop your gem here. # Include everything needed to run rake, tests, features, etc. group :development do gem "jeweler", "~> 1.6.4" end ruby-svg-graph-1.0.5/History.txt000066400000000000000000000017721164403311000165740ustar00rootroot00000000000000=== 1.0.5 / 2011-09-03 * enabled all svg_graph tests[liehann] === 1.0.4 / 2011-09-03 * Data point shape is configurable based on rule matches on description. It defaults to usual circle with radius 2.5. [pgbossi] * New shapes can be specified through lambdas that accept parameters x,y (coordinates) and the dataset number.[pgbossi] * Max x and y values & allow Time objects in TimeSeries data. [pgbossi] * Popup hovering area is now customizable (it defaults to radius=10). [pgbossi] * Some DataPoint shapes can be marked as overlays. [pgbossi] === 1.0.3 / 2011-08-04 * Removed unused variables which raises warnings on ruby head * Bug fix: Test raises error on different order of xml attribute * Merge pull request #2 from liehann/master * Max values and improved Time data handling. * Merge pull request #1 from thinkingbox/master * Adding scattered plot support with :show_lines =>false Rounding values to integers in popups can be disabled with :round_popups => false Optional description gets shown in popup ruby-svg-graph-1.0.5/LICENSE.txt000066400000000000000000000047521164403311000162160ustar00rootroot00000000000000SVG::Graph is copyrighted free software by Sean Russell . You can redistribute it and/or modify it under either the terms of the GPL (see GPL.txt file), or the conditions below: 1. You may make and give away verbatim copies of the source form of the software without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may modify your copy of the software in any way, provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or by allowing the author to include your modifications in the software. b) use the modified software only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided. d) make other distribution arrangements with the author. 3. You may distribute the software in object code or executable form, provided that you do at least ONE of the following: a) distribute the executables and library files of the software, together with instructions (in the manual page or equivalent) on where to get the original distribution. b) accompany the distribution with the machine-readable source of the software. c) give non-standard executables non-standard names, with instructions on where to get the original software distribution. d) make other distribution arrangements with the author. 4. You may modify and include the part of the software into any other software (possibly commercial). But some files in the distribution are not written by the author, so that they are not under this terms. All files of this sort are located under the contrib/ directory. See each file for the copying condition. 5. The scripts and library files supplied as input to or produced as output from the software do not automatically fall under the copyright of the software, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this software. 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ruby-svg-graph-1.0.5/Manifest.txt000066400000000000000000000017631164403311000167010ustar00rootroot00000000000000GPL.txt History.txt LICENSE.txt Manifest.txt README.markdown README.txt Rakefile build.xml dist.xml.in images/bar.png images/bar.svg images/bar.svgz images/barhorizontal.png images/barhorizontal.svg images/barhorizontal.svgz images/line.png images/line.svg images/line.svgz images/pie.png images/pie.svg images/pie.svgz images/plot.png images/plot.svg images/plot.svgz images/schedule.png images/schedule.svg images/timeseries.png images/timeseries.svg images/timeseries.svgz index.xml install.rb lib/SVG/Graph/Bar.rb lib/SVG/Graph/BarBase.rb lib/SVG/Graph/BarHorizontal.rb lib/SVG/Graph/DataPoint.rb lib/SVG/Graph/Graph.rb lib/SVG/Graph/Line.rb lib/SVG/Graph/Pie.rb lib/SVG/Graph/Plot.rb lib/SVG/Graph/Schedule.rb lib/SVG/Graph/TimeSeries.rb lib/svggraph.rb screenshots.xml style/common.xsl style/release_html.xsl style/release_txt.xsl svg-graph.gemspec test/data.txt test/plot.rb test/schedule.rb test/single.rb test/test.rb test/test_data_point.rb test/test_plot.rb test/test_svg_graph.rb test/timeseries.rb ruby-svg-graph-1.0.5/README.markdown000066400000000000000000000013161164403311000170650ustar00rootroot00000000000000SVG::Graph19 ============ Description ----------- This is a minor revision of the [SVG::Graph library](http://www.germane-software.com/software/SVG/SVG::Graph/) by Sean Russell with few minor touch-ups to make it run on Ruby 1.9.x and to have it [gem-installable](http://gemcutter.org/gems/svg-graph19). Warning ------- I'm not sure that all the parts of the original SVG library work as expected under 1.9.x too. Please notify me (via github messages or on the Issues section) if you find any bug. Usage ----- Yet to be written. Look at the original [SVG::Graph web page](http://www.germane-software.com/software/SVG/SVG::Graph/) for the moment (I'm not introducing new methods nor changing APIs, for the moment). ruby-svg-graph-1.0.5/README.txt000066400000000000000000000074541164403311000160730ustar00rootroot00000000000000= SVG::Graph http://www.germane-software.com/software/SVG/SVG::Graph/ == AUTHOR Sean E. Russell Copyright 2004 Sean E. Russell This software is available under the Ruby license[LICENSE.txt] == DEVELOPERS * Claudio Bustos . You can redistribute it and/or modify it under either the terms of the GPL (see GPL.txt file), or the conditions below: 1. You may make and give away verbatim copies of the source form of the software without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may modify your copy of the software in any way, provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or by allowing the author to include your modifications in the software. b) use the modified software only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided. d) make other distribution arrangements with the author. 3. You may distribute the software in object code or executable form, provided that you do at least ONE of the following: a) distribute the executables and library files of the software, together with instructions (in the manual page or equivalent) on where to get the original distribution. b) accompany the distribution with the machine-readable source of the software. c) give non-standard executables non-standard names, with instructions on where to get the original software distribution. d) make other distribution arrangements with the author. 4. You may modify and include the part of the software into any other software (possibly commercial). But some files in the distribution are not written by the author, so that they are not under this terms. They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some files under the ./missing directory. See each file for the copying condition. 5. The scripts and library files supplied as input to or produced as output from the software do not automatically fall under the copyright of the software, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this software. 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ruby-svg-graph-1.0.5/Rakefile000066400000000000000000000011161164403311000160270ustar00rootroot00000000000000# -*- ruby -*- # -*- coding: utf-8 -*- $:.unshift(File.dirname(__FILE__)+"/lib/") require 'rubygems' require 'hoe' Hoe.plugin :git $:.unshift("./lib") require 'svggraph' Hoe.spec 'svg-graph' do self.version=SVG::Graph::VERSION self.developer('Sean Russell', 'ser_AT_germane-software.com') self.developer('Claudio Bustos', 'clbustos_AT_gmail.com') self.developer("Liehann Loots","liehhanl_AT_gmail.com") self.developer("Piergiuliano Bossi","pgbossi_AT_gmail.com") self.rubyforge_name = 'ruby-statsample' # if different than 'svg_graph' self.remote_rdoc_dir = 'svg-graph' end ruby-svg-graph-1.0.5/VERSION000066400000000000000000000000061164403311000154270ustar00rootroot000000000000001.0.5 ruby-svg-graph-1.0.5/build.xml000066400000000000000000000111701164403311000162040ustar00rootroot00000000000000 Cleaning up... ruby-svg-graph-1.0.5/dist.xml.in000066400000000000000000000031721164403311000164600ustar00rootroot00000000000000 SVG Ruby Libraries

SVG:::Graph is a pure Ruby library for generating charts, which are a type of graph where the values of one axis are not scalar. SVG::Graph has a verry similar API to the Perl library SVG::TT::Graph, and the resulting charts also look the same.

This software is distribute under the Ruby license.

ruby-svg-graph-1.0.5/images/000077500000000000000000000000001164403311000156305ustar00rootroot00000000000000ruby-svg-graph-1.0.5/images/bar.png000066400000000000000000000204341164403311000171050ustar00rootroot00000000000000PNG  IHDR5 cHRMz&u0`:pQ<gAMA|QsRGBbKGD pHYs+ IDATx T7w`X(e"A Pj(ADjl.qET| .D₈ԸјJD\Qp !hQp#A•}>~= 8=S5=9ӿ~ST@.(vPfΜK/e8t%={6(m7k6 yc„ [nIv[ѣGi :th駟__`Suoĉiڴi$iŋ 7ܐt衇&Md?{t7f|VXalkh-^xawu״{V۷o=}tᇧ6ml;S7ߜveP4⩧J]vY5~fwΜ9?yz;~dA[o-]NHW^ye*}ɓ'}'-X 'm۶R7wϦv-?>zY뮻9z Q ׮]j{w6{t@(D͚5K-ZBS]k&u9S!m Ի\@ZtiҥKZdIZvm2h&[.4[{I&([.k.?שSsei!m 44F(9|4`5+mz M>r'xbzg4*FL2%u֭&Ͷ(ӪUҹ{4~7WH fƍBR<^rm6s߸}KzkV@ge%zmj~Zpaя~-Zd䁨)[fm/㪫wPt1XP6piȑo =F?ۧ{G?য়~n믧:w;wNÆ +Gʚ>}zv,g9r=w15:_xrŗ:{!.{mڔ8Nrqy>}lXVwjZ.ٲe˴zr? ΛW.泲0 M6[lN0ZVΚ5+ホ:37[.{ゥk׮{W0aB袋ҠAW_}uݻw8;̙dv9Վ6mZz^|?Aiԁs=7 z3fH,}>j'ϟ&Mƌ,Y-w.|q[`A:6k9N81=䓩m۶J KhuK.׼~SSܹsS~y2Q7dȐ+d5|Un ƍSZuyquM"U_gFzQnIm֟Dl]?oʔ)[ژ9-]4X:6~`+;vlք{C=t^:&O]~ZBl4Li(5i$aҜ9s*Å }ueڵԣ%K?yx>$ES 'Pi{n0aB1cF8pԣ> u)=CiĈiĉx>'j ^y啬Yf.]d6QhON_~yׯ_>;=:ѣ6l- @;c=?J[|y/FG?+V}2eJ/j}'U.v :4|iȑ;6u]iiZ]]7wlh[yWn٘K0a @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@aM-Z&M|a*nK/M/_N>Խ{~z%'*֭[g@`ff̘F Zn&)T,VZ)?kvWf?w;d90rȴqF Pmw}w4h3k׮jƍi5Ν#馛k֬Qby(MӧO/ի~[ҤIRӪUA~XlY+))c@,jbӘ1cRqqq֜زe> _S4-guC"(D `"СC#S4|1֢l` D*? bzJp@6oǎHQ~4'NňέMx'xb5k`9sfڵ`6lذt=[Q .L]tQzGN;TwW{]ۗ#,Z(-^A/woqqZq٭Xd>/%KҮ˗u{csW(7r=w1ooa9i/צӧOE%|0mv~?(u֞rJo5o6ujj:o^Aln/B{/miӦ$ 쒣A6m> 8߭Ze UOfL PP(LM@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@Wllŝ]\6nܘ֮]k(?FI&Es͚5vHC(͛7/}ѩu (?`o߾y橨^x! (?fΡ`t~ڎP~%\*6NW@ϟfoꐲ3ϡe;~ne˖+ @!CsΡ lWRRb'(?P#.\0GISL@uUܪs( 2^wu3Ho=%~p%o㳟/ A  lGik߾}СC-ZT -7~ioG,o|#8"e-Yv]<7<1(emIoGiVmUSZa/s8-q'E颋+~ѵ/1M>}u,*옻bxK/-禥Ķ.};6\SSy bk/dؖZ(lދ`!gUĶiSn\׭_(յ,6<  rMPP(<@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@bP\6m6nܘ֭[g[1n!1bDj׮]Zre*))-Pl֭|>|xvm)M@9lM<Թstuץ&M-ZgqlF;nc@~N_f͚z*?r)/m5!ۓN:) 0 ;nXv""e˖e6q-tBS{0wL L CF|ܖ5'-4ͲC5dȉawNk֬n;S0eժUiÆ y-4c3իWgq uj^z'fO>y-z[h|}ᇳ/.6C]Q 9N̝;7kl ԯ\ `/rzWq, %5@9y7ra*jl ԯ86cG48fcJ*p̙Yg}9Իwo{ W"|7WHۂ{|饗ҕW^&M=o]cHćNm,>7xck3PgMݻw/}ܭ[4c { P`8jG@nu<ͅвerGrԨ @Gj(dJ (-2>G*;,͚5+"^x!|$ lKdbŊGe;<{ TYӽD˖-K'xbٳP0Do ZLA\ er(~N۷/}.>w-7|tx+L~xs=\ٵ;vMO5pA%zmtAON/j?lטq]wݕ!C=uUWW%Yj_xn믿O~`U_c8VZV\ͯ׶m[;<\UHT<3ى|ԩNt){M9jԨz+(,?^aP[WUni…k׮͚O>$pc9&{j?xyi8_KOtM7n- ylM!.!ꫯF|Hyهn{ةݏo9Qջ_\1vGNgqF2v-G D?䣹)j|b$jiѢE|[s_|q^=b믯v]u~Q+L1ee9΅Z8ʫ6}}0~nN _ƶW?,}JVjZWſ0v//:Gށc>D{Ć\3p>}*-0hqGPZzW~WM{>ƇrHTأ>Mȵy]4?Ɨ},5|> W+X]Y@\?ѸX4 wҥFA !jc R,N9]vYVcӆH_w5e0ZN{>Y1#Wc>o]:X 7o^9b4ab0H4W Qc!% E^t4V#٭[#Dz]]]}?;g [Wʼy<W[y%[ @X+2_IENDB`ruby-svg-graph-1.0.5/images/bar.svg000066400000000000000000000225511164403311000171220ustar00rootroot00000000000000 Internet TV Newspaper Magazine Radio 0 1 2 3 2 2 1 1 2 2 1 1 0 0 0 0 3 3 3 3 0 0 1 1 Question 7 Female Male ruby-svg-graph-1.0.5/images/bar.svgz000066400000000000000000000022231164403311000173060ustar00rootroot00000000000000 {@Xis6_qfvL|4;um՝v1!cTIؒO>5Lɕl[Z6/~ hh%](9)(B; ⋌"˞g'r- m(Ț;'ҳ+`ݕ/:-xMPD/aLaj;.2҂,vSmSdK1*糀nzpg9Fb$ڈLye!Ʋ6"Z,3Nmƍ?;M+*?Fs}{ߋ?MN xӦMٳgk$ι ԋǝws΍?>JKK[7ވ+ưtZ<>f\ @][3ΈݻoX-b]w^xaӥt2eJ'ϳ(++,|G?K.$~d稼ܚzM.W]uU;t7SϥQ~eƉ'mڴu>{bL@s /R?)Ÿzym_L0! wߦ9묳ôؖ_m?|r!iV6wy'^y8bҤIq9dkG:*8 zFڵkc ,K7pC6 .)><*={#t6k׮//Yx{w}ws=w_?^cxڰaC&-86oժU}qꩧn _~9[Us{^` ֭;bر]k^azqA… ˴YhQ6W[)_Eߓ|qWdΝ;G}ibU:Ynm$@ ܯ6lX9}m6?dHj8qbvv{}k׮,[jU5K_j޹ ԋ/͛4jU3gK3fEZ@n˻i!C^U)p[SF<I=g}MUM$E5m(sI 9N2m\$}}5i&OC 8@p 7FҖ,i" eizl˔/8[{g۬˷Uu%[FsS r4FW5~y^{&mǒ?+V6i2iWҢ~X dC&.u  /0 qi  Р%ϴq:nR(diIrJ      ИttQTo'tBM6E_:LC_t@>!LG>a    Gà@z9`Q0bP`,5kׯ NjaDžKJJsyYb=I_e]׭[8#c̘1n<@!h޼yK1x/nz WӼdѢEߍ7fW>vZhR枠Agn~` 6l1hJ/:Az }_6W^^۷6Pro߾}WUO;쾴 ^æQuѣwމ?0\EV߿gG>|MooժU&y06%e ^r@zO_~ٕҧz*wߥKx'ꫯÇArKzIH{͋N:Wj)YFޥ^9s\6mG}>l<_dwUWm4nquYzJeԯQKz}ЌJnvpF-~zyRW)1"l:z Z4ײe;nj״R.(Əw\ ?{ i߾;l{5 44(A74?oƌZdI6/.]4ڶm[@wP /:A׷|0ӟ4?ZlZqW?7tS;40-ܘ0aBr)Ykb]((utou۵kdȑ~>O~ӭe]l+mwH>ˆ;wcǎ͞,mڵ9ƴ.Μ93[ztDIu 6m6~+;vv[?[K/ Boǡ䖓@tL۽|'[/ M_}l]btnuHׅWWN8蠃7W^y)uJO}L,X >jz͛7)WnrK{7oVO?v`Yn]vpwSСCM68nᆸc͚5qW/ 8ꨣՒ~8:_)]M?8:묭K/'|2̙mǷj it[JI֭Bӭ[7E C7=SNͶ؛6mZt%R;cbv\믿G}t_e̟?L5^ .0V\￿m` nA_~J]tQ?>;M~~;yjz[ou}Ѹ馛t'@-?Cvn:jwKK,iҥK7{<1cFYj<3&Ms=7X^^(vr!zI ; m9>Vfgl fJJnQF/Ln.=%`BGےFF)$N!l>fG!@'q+Uݣr?Oƅ(c}Q=q0;6cist?NIg5OUoUoļ)1;gϞ7뮻f_7b ^r)Ѳe&q}f/_<:6!F\l BH`IIb{63P^^٨ߚ5k AuAWtcvKq>i4yg]1 x}   BQ4yg]1 x}V,cԨ ЄH|= X/:A 6E>aNO[cUGRzf@@"+z@hPW0    PN= F 46nܨ@6mDf͢{q뭷ƑG)G,// 6ٳ㭷ފ?qQG ) ah|m^e/裏 lUXwߍ_=>ܹs\xq) ah0o4~EϞ=`=#GƊ+b!v-vjveߪN|(b,//??|M 9E= FЍ8E= `1qxuu4ZhmEM7*L};h)f͚Eoc9FTT/2>X`@) ah|m.W~={ljVsӑpVVV&qS+Mk׮ꫯ*T}-Cʕ+cȐ!1|p.OdI?N{n̜93?Gp֭cqE`*7`x嗳Ӣ9&k֬˗`֯_}L#~x=zlv?S}tSLQ9 R:E*04tu&Mڴ u-ؤSyueW!~)իW+V}@ o&p1a„;wn+;4hPtոdԨQbŊl(65kV6;tP({=4F ӧOW9 S[Bݪ… 7:3d0C!2y= `1qr$׻wXxq<*S; -a CP) qGS@( _u|1jHU% FtjPߌB6E>a    Gà@z9`Q0c},   B|= X/:A 6E>.e5=تUm>aÆXn]lܸQzqjcԨQq @!-\2ƍ˖-I&E۶mUP3⡇ѣGGϞ=U d…qg}/weeeѲe#4U6E>.nD5&K/48}m~϶Te];nݺe+nj]JNhbp{n{JKKyK/Ń>[o5 5 ^7ԻhѢh֬Yv98}^1vSɓc„ 1wիWwy1hР7ޔ.K/_n9W1h*l>}\ jLai+Vd1ެY?CO^oXvm` E0&LÇV_>6lXB>7~_O?vZv_z>XL{VFMݻwߡ'?/w@1= `Z/ŋO?F&MR9}\0ѣG 0 غuk(hѢx饗bҥd*t[T>{ny:W^ɶK^P=፠߉'Ç(X6E>.XRRO>$<ڵk*à 1V5jԨc֬Y1p@ȩv1>XdI6/N!`/&LVw}KDPcn[*//W9 GàA0R{쑝ҭoEEEQ@ ˗/>Λ7/xG]v}\rIGrKio  7_~b\lVgo>[ aE?x^::|饗#aT60{oL6-lc'@!4.S%b@"S૯]N8bٙS= GàA#sN\uUqaߞ7z3fE0B ԏN:Ő!Cbԩ}]v_ 11"fΜZڴ̻ロB9;vn-m` =Xѷ?߬YX~}6@ i'|m۶ο$,Ycǎ`:Jn=i枠A/W;]v% ݺu#<2[@n`N2%]MvӨ߰ab*àsK .4gݻ@N8XyGҲeݻw,^8x ȩG.ڰ08 (zq1qp˭^tKE0 [8/V9%47nܸ+T`ҡCTX`zj |= X/:A 6E>a    GàAY]>ZVo_tZB[{/͠Wr-VEIDAT_F@@@(D:uRrWr?ԁG  ?\ȵ>@='Рϟڇ~  %'wNZ$I lҬYhٲe)9$ZjJ̞=;,Yc=66lؠ@P4עE3gN;6FW\qEv? BsyWI ) ,X`,A РBm$ǪU>h ><@vXre4o<Νzf}Ǎ>luEE@!Jw}:th<ܿ7EP@ȻNI ? fFil-BryNY}i0I~UYF@ ѶF@@a"@ȻN I ݶN@@@B$򮺓@ ol-BryNz P$ӣ^qLUhvB[W ZՓ}m|mTzڱ An tOY!x6BoСG)8A4sײeKș>7|3N8(_oqOۿzH)(ݺuSrͼ)1;-Z/r|駱dɒܹsG(^! xG{W?>;8(>fϞs΍z*=83C*P09䓳ʕ+_FrH 0 ڶm9n: {o1"*HAIAC tASg .SA@     4qN!l>fG)l7? ,UUQQ'<2WҷދSN9%(ݶ BI twȑF1Tt C袏 x}, Bkժf_[EKJJܳT@"~k֬Q,$袏;@+ P   B!4yg]1 x}   BQI 6E#7NŮ9FM@Gà@z9`Q0c},ouzZdƺ}@|䞍@hPWr?T@@@RN\;ܳ Tr#P@@@@;'Pl-Bry$@@@ЬYhٲe)9P@ȻN)))VZ+gώ%Kc 6(M@I ]u'Q-ZĜ9sbر1bĈ+A,NFD I%\`KPČǪU>h ><(@k׮+WFcܹ<;(N5IS^^tyY: }7/-(--.7.{ IJA؈Pؤ@@,rU{/~gs֭[}@ PZUU/-/ht `q_f-C^DPj u1~袋T$򮺓@RK&T`.,_\(Hw՝ [60]t)S@!@ = @R M: ,y6~x wމ… zl=Z)(N!; -iاO裏]vݟ/^\E El{0y0aB̝;7zw^ 4=!BUwG;v&c1k֬/~C'|r.z1mNWUrsQvB[olP@v&?g~Æ ڲ*uס [naÆxg{*S5.)+|زeݻw2'P9᳀mB1xCRrcvm``y}8V/i`KF 0n***ώ/XrV߸q+P9bI>S9 Rn\3q}Ld*8AE 2\2 ÇW9%֭[㢋.R9bɚ5kb*GA4yg]1;Vs.]Ĕ)ST C: |q@>fE=ݪJd794hPtA Czy ѣ4[oō7>lucݻ01f̘2dHbKzmذ!M_|#eW?WzP'뮸[Зn;SuɕI&W ^Z _]1ȭ4ztRhW^={n: i9WK,#F=ܣN.y_UY^^`ѢEqO?NةS/~a_S{62VTTDVTySW_}uL `ĉ@ P?ВIENDB`ruby-svg-graph-1.0.5/images/barhorizontal.svg000066400000000000000000000242061164403311000212330ustar00rootroot00000000000000 0 1 2 3 Internet TV Newspaper Magazine Radio 2 2 1 1 2 2 1 1 0 0 0 0 3 3 3 3 0 0 1 1 Question 7 Female Male ruby-svg-graph-1.0.5/images/barhorizontal.svgz000066400000000000000000000022331164403311000214210ustar00rootroot00000000000000 {@ՙs8ئ3:S i4k:כ/jm&\ѿVŽ_$!uL0bYZ-z?a2I[#5S{k:1Q<8taeQ.~6?b0x\۵4 2Mb\(30yJY ECІ7&q\Me$C]`A:ik&9&8uS8 ŠE-\ 2!@J߭*ÌQBG $FcFXd4[Y^9oNnw~{/e1|)KiCKwaEuյBӘ)p@Yz3] UL9^Hi0UViZ/j͕T 2pp)$˃R g n)6W3shǐ"j:a[;#+ue,f xmքCW@Q7 rSӟBI >X ]̌ir5HDa&(Tzi"t@=z~,qp@,dj>ӸY01[M5{SdũF!;6uGBn=<Պ`R>YFذ]-cLL}Am܊KT޽ԯ | Y*y Ƿb&d0]䊉$pJ&6S`U׭|,Jjfc>Cm^)Jjh'(@U F޽IWD|ܩ5ׄVZz^E 0Ұh(m7(B*y%Bo3n[m>;h.h&UDq݃>ҩ^Ɍ sR!#|]ŖGv"W=% brGdAXu$ H Id G8Zk"ILB JbUϷ+AI,1t]cyfVc<)U~K*X/vN-IE(FԂ~h/(M[r"n"lJֿlfV ݈"ْ.KT>hW>rkmN*Uenje?>Y[lruby-svg-graph-1.0.5/images/line.png000066400000000000000000000637601164403311000173010ustar00rootroot00000000000000PNG  IHDR5 cHRMz&u0`:pQ<gAMA|QsRGBbKGD pHYs+ IDATxxTegz@H!@ !JBDiVwWwߢJ""=@h I% $d7l 23}]f2s{gl@DDDDC1 I% "Gھ};cϞ=[oO>.2 >4ZPm"b$"_O?ĉt3z*{ZY\~etdpG"!`"rt|Gرc!7iii폩 c&)S0uTLDTc8p nZCbNtw}.Ν;g_R%|>xݾ.˨nU^rJ{y7ݨQ#j]؇7mڄ={ϯ>|8Ə~;8Clذ]/N4Zݷo>޽9s=N0c  KWı_j*DDD߿?o "8phҤ ͛#Gڇ?GNCD W~^.##C?]81?ޕLnݺ'aEڴi>^)((ƍ?aԨQ7O~/bܹ DD D䶬Vk#Nѣ-[~oO ͛ø߶mm7Iaam+.. (NP(n=zm}'5+=v-T*?N[~MZfHAD D8{`E7qL*Ntb?A8{L^qߝݘ1cnN,Nׄb\8cѢEUbjqbpj^| y8D&"1b%dӷP&l)S?cfE VֺukI;(!~DWQt+͛7[LD!`""U^^^ ХK@"b$"" STضm[̛7b>@"b$""|&PTTy-V|v&L'""LOCbXHDn+;}4sαDDtGx?^BkoCȳ`eaaaNDDDwub&)̂sÿ/?&";a,yWboGNU/Ģ8d2\Fo""eG9LWDU3g@sNDFFro#";Ej=O!S_-sYR ` `u׿'Ӿx'?W=!((rWWpȵSRRбcGwŋ:ZoA+iLG+؃޶m /ܼ_t';˗cʔ)pqoLj.?~C +8d_DDtO''nEMEc7iR1X]!pFbĈP8p|Iƛ]ѣGСCߝm f&"*Z&q*.ayE~~crDW'OFRR-'Te$DDT[JMhaO\2]v t2$Kf͚zgYwEqwƯ-j͹o@724DiH~Sg--R@$YSNN4 ջ8_ǪUܸq|Ls r""߄T;H=4e tY{a6,汀T< HDD&Jo <.lA=gY$"@"" Y: o_ߨ9j  PZZ1$|gwuBRi* .d$50D%!hxz@!ƍS HTÔ,9[FYYӻ_,<0[m={2sХ];DDtW W)VQ:#Gz HHDDзcuEfʰa$b$""teo-Jl{ljtQGw Æ D DD2'A'ވ>}Lؿ_={x:'.|d" 9U+P.J V/ C0}:~v}~"""ZµTl`YP #G"=]^ie1ɭT*dffQFɁ\.RV ¾}n OƀPRRbpBB;DD4'á* V; hPmHN6wXHr+.\@N0{lӧO,`71Ti;n|5hPfΝ3ɽDDDɓ}tqb믿UV_mN:><5̜Nu-EEYPX,Z.  ՚ck.f&p K25 ۆʒ7 9O7|N t-?pァF b$""gzxb$$95rt0v~F8!޽<Ehd<:][8u0O9(u/߈S9%  9E8cOưo;%1GHDD Ny-UA~1VL3ȡ.UAh#z{mDŽUԸzU CHDD1y-AYѿc`"@""r͚UȉHpk^ok;q,ҥjdg㎈j\53:8n^Tg7FLc mW0]wz\NcFY#jscyЩf HHDD5Ƭ3-qX9R?W HHDD5Cچ>>ZjVtd: E8*V!']^D DD@L&]>S!+s -C%X19G*"Vs`} bg$@nj\l}[R4hP. 1DDtD'-oKOn$oYqnj }94PN`XS`}XJdcDjan7 1U;pY$:zp0o/vZZnI.N9+0!ׁC$@""*;4V#br26h+|8#xx B DDDUVQ(\Q /cz2)a0usu-+\ ӳ H DDDUqq{-WP/K_Qphr]DQqH DDD>m ԏJesuֹ7,cDg;VÍJ DDDd"qv,/2llj.`ZHDDtW\FS suOh ۄ/Tq ѝ\ǂ:Cځ&WM4wc6`r5t:701R%(hUZ {1u*&@""[^3Ux?ס3?Q]oenqNx91 .S |n; x*=_?#fְ H DDDYgBP=V~GCn1cHH0@v'E6q.ؠGoT(-6'@""p|$\߻J -CV%X]@b$""&#3g[~NyY0A. 1J5M"=P->%LU.`ppHDD4w)Ǹ{ȍIDH 3`xvⱀHDD7q>4(WЦu{2Ab^%I DD亚YD\N:P1B1cx, 17 ^%E&f1P +]͸pA. 1kg;BRlr ; x^Bqc^@v\RЁ(BT1A;?2ıǏq51 E'M`1*ץe$|2>0Zm ^,(18y=u‡UfUp6Я G(p4?j\D`rf!M(.FLc\5<;q,Euq(u!CXJt~ ղ6_B GtBPڃ з' H DDT4)Pсhy ͏\b$"Zbf@g1A  @ ڧa֜}ZBЇŸR-AûxYO?mE*t2T+W|$ r!x^Nhh,>]@b$""'+zhzwb1@w ?_3 9W?W+6`1C#{/Kt۷`2yM DDD/")'޼F5t&h׳ H#""h 3{z2BaJ%A$@ˌTo"Q #`j,M4mZ,,yNܰazصk?=_ݻ ڃ\Ef1X52 , lW_쁜#m0vX=~/n 9VnڑImeEEY`6kX@(>ϟHddd1xRn 9^#ߡ+GN$lYQc{^g@aΜ9?P~*R$r}Ÿ/ V+|2P(lHK_8f`ܸq}̭DW W &E!rOdHBy`-WnD`p`}?3gO?ˡV˿PYwa } IDAT~훏edzm{s? r. Ũb)hranX#4ȀQnFfs(0Hq{gpU|xgKNN_W$"wŶ|~@?{-,WrQڷ;Ql |dBaUT,S 2ءO˰3(OГ@CΝ;oq,.vo}q`k5v,{Da .Fb!jPnn5: 6Ϋc}v$$$⃾];|P~/wHVSy|F<բE, \=UVPg[ZFB{ź[Vk3Ix7Sn ѣFͬYܧ6!r̩ku/Q*5#bF+V. 9æ9}X+W⡇|!mnT.)ЫW/M6oq|W)7 ct**;UqꆠT\gU&Nd3SwLܟ8m8bq / nݺ7C7X-Ӧgқ?%C0D5Ȭ3A۴9gTuYÞ?êaKӟppx$G40o„ B|f5MG }5> ZΝ͘>8bׯ*7;OVӻ]o2aؐmK09w X]_6 Hk"F2z4l,QM|ҙF|b8P~84-].]̘1Ci<ՈjUI6(i/AETS.|U!n/U AЇ>zw cL5t: M $M#Sqrpb5)űNPԕ5\lݺ˖Xdb$iDEy,*5"|*Tΐ) ~YY_mLрJ'@,sA3q"D(SP'!(S{_vTeXXB I9) V__Vb1%hB8QnLb_¨Qz. 1_xO?ݿ&'6Z,巍AT,[t$@01/ f6l`1C:5b1Y8`Yȑz. 1 S_gB W)X 'o5|U ڰm&@&c>= v4UK(L~1C0#@*߬Q5\~kbԂ'C߫ YY2 H $QZъG8t OLPG\2)c>]@b$!Mc1(FuBԢ܎wÖ߳ I~p(l<# 9p.XZk .8#]@b$ɲiɡ5bZ EC&JIu >j¹sr8~b$ҏ Պ]b݃$. :l ط_~ɉm:8~Bfd.Jg#0vd1\@^Tg949{A ذAt~ Ia 2 6_$ԁ, (Ѣy[jmxi#> ITYh(|At~Jd" Br:oO= I~0?0X JWux!h{g1\H^LW9Ŏ]4]@b$*k%͛Ct)ATIƔ8 ÅhPآq hʕ*\*cщsPDAMԎgܹΡ`Ef| N $M(X^ *g֙޽Y t=3bv 7ߨ. 1D}q$*w=8 CX W A7n}2tbr^@b$bJ/ENX !0m_0sz=He| ff!ȳLh~ ,=9Kna;FC_'8؊h NHRKJ,'ʝ;Y Xge 4, `;5dx 1D)kkƌa-sWKq9uѨv8},^c$ԫdYYP䑚ew"QRߚo?^. 1D.|!kA'{?V;b1܀QsCZaa )cI=z@qOQ.قSznDL a`A 8e]@b$i0 'Q_Y,현3}UL kתXxb$i2  UJ ,y+{c-2.p#b&'S^opX@"@$V Јyqq FwCNy.]L(,~B $Q. Gxh(ҍpCy:; yƀ>bItA=y2A&NCۥ%ᆊZ6TNy=M8uJ4%O $Mz*t:$+kS|69Ag <1tœi,IV%(_wv-v81QO $Q͞. ISEh^|>ImY 7V P;B!bر<I]xh> ɹ:̋ÿzTlr= ;v($@.TϚ. Il&8BHR8pPfీH%%-[Bd Aa֙c,GtDS0}r'@(ðaPO^ Pdu}Y )|Qx#]8' Oةg  I'=  "]" !A9S_A2th<#ILII@s'AVS݋ӿHѵν{_w fbIW3 tv( C{S_7,%?2IW/ȲLKc1V ,]o N Az̜yr DPP kAnE|0G_c]Y 혈-+%/fHAy G6-MUX߾}28Gcx Dda@'Nd1}ܥ8Ӭ; r;&!$u_72҂2ZF`$&Pyz:An%?.  o!X rI\F|Y R,+2 .  5hK|<_|bK:;i.< \bxXO׮>HH$Qá32 }1 x'Х]C4]@@"*k%P͟bKsNN,bBheqd*30I~Hg'W5mNAy ܾ\TN H$MƁ꯾Lg1!Z\~]NLOSav$4,T͝bP;lF>bЯ26h+.:X)ӌ DR+&1jZe8УP(X NI.gG2`$v$];@,ըPBP嵋E..=zo DcjHƏ0_ApY8yW\bXt cH,QQ֩Պ,ՈKsRzz#n\$&kWʸaI 5;[GTmj C0m De~i8Ŏ,=5/F'}ϟ&5.Uٽ KH$E #GB3f kǍo:HϪRzdgt/ FvʂrN[•ɟG! RnjXT2I?d-辘u&]^ M[^d|b׸Vjn@"~c>bPt:ЗŠ&#e֩o_Q H$M67Šj3} » ZEVY+,12I͛!OOg1tzf,=01 /re7cL5zv$Ȧ8p 4cDzTesww$a1聕horujР ݻ1>d$(T HU` [G5''K , t +g2I3Š*in՜1ڹޥCCV.  to X !YbP1ևa=~Х7)'hd$PX㡞:Š{ʛ'y6Šu=2冁[,EX]@@"ҏ==rr# Xq1{#den{=L᱀ D%v.m/qY qbL ]jKcƍ*n$@"i_%6tSHhOU%RBW]@@"2c>bxcu톼Og./2uN&'% XJl@" 0 |c^@(|t>Z7 A<- 㱀 D&jfY Rv3%Tkr00rKpHzlZ-B=q"!Wuhqu7=y՞Qwx7ׯ.`f̞.  DٻWCbx)둮mÁ,Kqٳc5zv$ ނ뭢YuEF`]ƍйS H$Q;~2 љߣg7j]Nl2nbvuψ3d$I5 Dg6w84BX u7j=).kW/TsyTT*.`6k <u[oƒ}Q>2\J^&%בӆ{vY j5C$={ܵ+kF(߸Na[o 0@Yh(,P-X.Eh~~ l}8Ke{\YYˮct?\oQrѣG1p@B@.@$;hׁ/ػS&7Ĝj?l294, ChS.Æ1k֭]v1'H1[ظq#Nx7=f_( CIPZ /m j( A.'''lru" âEk R D6?P3v}Ȑэ&vd!䵋EȖ.= , _O#% DRRҮ:5kX ^~YԊ Ծ0pn"ٰvH?,T[qX\Vn.? , l H}4^=n#ߦM%/(׭N a?,FA%ו S\~=w7"'G#G4_*')a}*Mb8br_!TOƝ]ٛ/KIY rYMZB˃. ō. sc86']B;c,*;!W)X riyG\cps9xi̝;O<|}/^y+)(V0bW.~ BAi+=䷋C. c&7fU7;ٻW=v7MSo/{󟽑y *%oE67-AQ[IzD,ֻzː z2J!~Bݿ_g9 dXk?.^:شZ{T1meYhp?rmc=s凁7l.|Ȑcr57jw `ʔ9*۷oVTԺ5 `86m*իVW;kÊXNPbرc[e*5#Y'/^X϶wԩ6uJfs9bŋ!dh ~@۶%x"nhld=g /xAu&LAǍz Ɩa3;Anͨw8?eQ wrFqcky+F5S<~pZ0>8z?Z?Gt4TCЪU)23ar/WB IDATtR-W 11t/ڇuu&D_ۈbkG}Q2o_\GֆٳۇCCxiC90VE'0,};YyeA<Ⱦ67>̄h )9O>sÓOkhr?>ZeS n4w ɛ {ct# LH.ghذ>I 4M.FOW^~D r5U //+zsB%* V??-BN~#偨: ;S7`9Hz 嗋ß |AYgoPO. &ُtd!H2r;u001Rl+%׃4'% ֭\D(_m;"pw H!,ƩS L!P?_~2A$/A@~q`$r]rq;v(1sCW/ȳܱ\@$%9I]B06֊af%-RI͖ Ð!P XB~L-> )h<Ԅ kEXL+~=8rgkY. m,IN#ν+, *_R/Tcj@V SOA=f 7z-,IVnTg3FJS|wK3ڻBGNve4-~%'f1Gf=Z!ܼ]@'r ɲԸޡ+lb1KӦxvg4'&B5k7[Wsg]`Qxx ^xAw‰ۥ !+(vZ۰$y-"=s bH~""J0bo酋[Npu^xqO G#(m=H䞢,4h/Kk֏ܹ-d8" ͳ[fi :YgB=bǸ.R!Y @"d@ ,(I} zAـ >mb(Bߴ%_r t:i6mPڰ!s;-ȎBɉI00 4 l@%![2!4]@ÿQ翇<A'mGa`@"i:Ԁo^XXP7pְ _A#ib}Qs;`$g#( o-(O?e|YYXmb`$QPXh>.Uj2nRj*E[<4^;`$Vӟp~`CݫPlY{5r BˍM流G.™3r̜v7"#nFȻg!巊O M Dcëaf%fvXTfS+<^QpV DңZۅغUU&DpHn _nŅy7 qLB0I7ZVW{0㐟< z~X8KT!ClYB0IoQ9sHIqhSa<?ƼWڤ,h Y>gX @"D]?WcNۭ[!?w'@z,QKm:"g3I]Fxb#Mq@yFpLAaBWB4S DYRw5]͛Yh^|^"X _еlcb0I_DD yƀ O hNLj,n*ʝCQD`U1 !<CX0|L_0t( BVPX7BEAH3y-0B^-ֹ,4x'O%B#ųDw:'Cb0yx3LQP!P\N=w.dz=7=gu/At>ZBM, gIN6S'h0~w䱀y=Ftf!~ENtWs:@"OԯMW`z 3yz<x4 Qs0: l2҅.P,-, Zwpʌ`لAEe\FVa@v"^, &K޴I4_ôR-]&iE~y} $9%ܡڴv<6\/؂>_vLk &Dip,x -,rEnB wm&\/Quū'4`WuN۵Cqp04}FшãWgƂ"c g`HT*`rr6͹+D8 \Nʒï?AT㦠@C2LܗFcU=`i)>֒US8*\ ?sATC$y Iȑ>yo%&ֽ.?:_{=II9سG h.@g۷W?#O~a\yjڗ?~) &D$ITnUc'TAtSpHWZZޥ7'fHD1vl.֭`*}?S^P8U02F#po2J[⋹XH[UNݬ^^ʼwq9 @.%V%L$p<̞ΕJ>xgϺeۤo JJTGi  +BRRa.IZ-A[n.mg'Du&x @"0 |i~[gSnn<ŭ#g)xsچ+'Q嶹~MsO÷GD62NA*ӽ >Z^Uorʔ0+VFcn/ͻpȖ-;ϫřлcꐙb ;}wծoP_"[݃waHD1`11=Z$00h0l3~5!2[Gdcsy @"A ТE^|Q@}hXBB7|PGx\lL3{cp2Wĉz:{(U Kq-'W8";IǫQM@/bLsXU k4f^GnjDѱ+&ó`0$zYrs {Wܿ mP#hDv"8 jB`*9x>1穙͛Tl7ąYZ8 j>|jo8A-*Bsϫ-;#a`&DT$0))pa']>y;D[se rmC<Tϯ&R*n,/[Ԑxx\.G2LȞhZZ'RLK-Nwo\n=9 hF ٓTG0OLb~ /-D۬hԻW"'ٮu^6IbԨ\85+W4Ҥ'T6>1 3L޼傐m`J;oU5ۅN䤲tD @"J%pz--&.U@uJ <~c5:]98L>cst˗hd C@7}#s~QsY"`0$lȑyXXmfW׮ĕDNjg Shh:$'}s !sd6٧3e a)b0Q} +R*ァǮ]u0f.xdd8904j ;ٸDNS`S4>>3fqH6{ AU@Wp6EdtGu @"rH3F/둒RM_+ h4?oWߎlP"WIã A8 $ԩoR,ڵޫ?Y xi$ra8 ! |a#ƍEwO`{a9wgk] . 9 c8]0kK@<7~]ޚXD.*y xr`HDbذ W ڍF5".lv g`HD "Œ+^HI)W 'D`5P a9 \IǎxL?W /ᑓSe~2D \Zٸ`HD{wR 0/pYCϙSe@_'&jU1LȕH%pРLGz^IU@NFA+-JA/\f-wsȠ /h4 93vlBR_/֭Q UIX,I1zh5 GdA%riIj 1` 3f [z0HLYoD7 Cɹ(,,{M~oiӦ)GM ( R۴{('ȁT 9G-@x'Jr=kv` ?U@Zt7nēO>]F$Qw2Dnش9#дO:to}'S&D W #EaqIrQ1Dϰ>A.O &D"Y1uhFZj-^M@#͘#"ȑ#e}d{jIF0w}1o/<-q&40 ,(֒c J=zݼ< \rpΝꫯ<Ì4JQ9O£ !{wek(:pP ;**,µǟ@S(_QQٳSL+R,9WyܛGb-e<O?s}Շv?Kf3?WJef_AAAʏ<>}:#MD(V5}CqL.m "7۲-|SC?c]Wm*'n&r^/lG\FB/|a-Vox!rC/*By&M[{W1uT&Էo_|7իo[gV۹m?ו^ׯ;ogpQVˍwߍY{`Vf8뮻+yV._1fnݚ&2["7a8~ cC/g7ԏ!rW2Z(' gxLva52 pe3#MDDD$2 sὀL@Mu"Pebbbb jժLo.\(7_˖-_[nmb޼yx^۾};য়~rhu5F.]wQN K[! ooo##IDATϥy#u;,_/RkK.ŰaØ9w߭ܶ1<< IMMoQ.3L0qĦMmX:{FXXRɕߧ(?)) Gb)nrؐ!Cd:?O$e!Ɇ O>mW>*׳gO3g}NN2zJ֍>@kF*W="TV j?Rٺ@C*ϗA,^ǏW֛gyF9@WAyDkҤ ^ 5*He`֭J_c6[ff̘~_~-֮][V쪖UkZɓ',*MTt2gE*m*ԡC|sٖmgI5on`N+ )OϘĉ._F-{9?n8@4᫪ͥWIᥲX& `MhB9y{;òyHFאỈjod_R[h.]TnYv(Cm>@k\oa HF NS:ҋOXکKF~#?lyUIϛoYj۵Ae e@B˹]\Ksz!/WVb@?ʿ5)RA$cW^-d'b -%"~I "DCN^\\mCG+J T" 9/%CRǿǒ$JQv R+z 1L'Iv޽+}Um%z^Zٱ˿2\ʰXHd$QiҪ`em.o*w!]V\98@0@JN٤I#tC)=6*[VEKEvrSLyTrAbUe{G=Ad板T$}''u/BHNRONvuD7 ޮ䱜1b0iw…ʹ{NUsfBG˔TjsنeXN 3fҿTv́\QPPv"{zr/`"uDʉS@&C. 9ς&"L}Ƛ'2WHx {ݣ=IENDB`ruby-svg-graph-1.0.5/images/line.svg000066400000000000000000000233631164403311000173070ustar00rootroot00000000000000 Internet TV Newspaper Magazine Radio 0 1 2 3 1 1 1 1 0 0 3 3 1 1 2 2 2 2 0 0 3 3 0 0 Question 7 Female Male ruby-svg-graph-1.0.5/images/line.svgz000066400000000000000000000022501164403311000174710ustar00rootroot00000000000000 {@Ymo6_qMjWK_S4/+ 8]x6 +6ErE&wؒe7vhD玼z? &Q[ K{|zsis}urjg384?@a4G1q7WcYyu~\]Xu˕7(Զ@Jj@ oCݶEav^7ըNq[k[4PNF,Z*L&$Fn(LקNJ H  7隒9a9!onwz{/N&!|@!Qwp-p.B<ę?Rq8vL> 9 H'i, &j,6.&!mk==՚9g*>DYkk"?S3jTg9}Z/[QX7CUI#!A3d̸A[J5"oiZ5!+ ţlP*3BCro|D Npǂ MPTH}C =rI}ECHv.9MX/;Rh@.&aDv)4hr8R5 IBbXA]w6g ;Ze`s@ڼJi3&˚V"**4sHX 9wpK`= b(I`mK\E gRӍ1"e"022fs UE>>a<uIn.BLeeBɹ_|-֯EUm [Sen4.UKkV5bq߃ẌBFKsdwyΑזdDjM3,#nuFb.Pۢeh{q,[dj㌸|fg`Fflmw)[f`]NřwE]&BZy`~O`qGs,D<̖nu[:%j1}c>Wū)O* JJZʒ=ZEӋɵizzM$ruby-svg-graph-1.0.5/images/pie.png000066400000000000000000001017551164403311000171240ustar00rootroot00000000000000PNG  IHDR5 cHRMz&u0`:pQ<gAMA|QsRGBbKGD pHYs+ IDATx|eg5r~;XϳzYPϳSOzgAT "͂AB )$!u?ϻfvvfwlgݾ >o3%I# QX =}Cʕ+c9.5j!_D5f$ eL2yC#G [oJKK++77*++ |@- M.˗'L)))ė+z'c:CUUUXGɓf3? fx{sϥÇg(--̙l fP#ۧONgus=oװX,!ϧoE~ oS͚5K[n6o?8pj7vXgѩJ-~\r >}q&`h ,[~p\!|ZW~kݻ7Ag >fܸqK15𵤟]?AgϦCƍi̘1n9]>s۷/tefxWhذatM. WXX{?W\\OTIU9%i#F˿7rcC _|QWm4=ztX0lŋf\%˖- {,o nϻ'i7|@8iΝVKII=zD| ;h.>| hտYTN3یÂ$ t \?>ռ}-~ݜڱcD O4^tEwߵߑI.+$umM< ⦛nӘԄWWW'2$|7=siQ.ݰaCmV5g$rXUO4oP@8p-X&N(V<^.RhԨQ_~)BK.O%%%ĿHTcƴsb!@rrr / V8 ~?@G/<~~'} `yy9+T__/*z?ktSJJ yn9Nꪫ?  {w^|Eq}ʔ)!CDn-\!nٲe?0OTy^xA7{9JOO{̙#:u*;V\~5\Cwuxmz_T!k.{lZ-]v{\|SUxtɫ<yyDf3I|ėw'*ΥMTX8W>B}wlE*R{w9CEuNU7{222DupN3 7nFիn4h8^vAGu߆ '|N9`?@moVx55Laю"A}?;;t2I'կVG~k׮`ܶm͝;jkk38#tyTbE6--t{׊x駋6m+>cxΝ;cC۸j9𑴸 mI n]xH4$C pуOpͦFk'wfs=#4rȰtL9ho.s>]Å p81[w^q9pD4o< @ xze kE]$JK?IhZ34iL^H&oczA2MNdmN|P eBrYnE8  q3𫯾**nͭ<- uQ>}T{Zxy睢a?; SJ>}碏߄ ć)7VHyD,wgәg)h/xD7}1~:P%BCrFBHf<ksNEO< N~.w{-kH6΄> FC'^]@nJ/O@^%_ClEgZځ4;iHjRPL/(C:A!EMD M :B#S^Fr(:,$Y ^cU=*nX R>7ki=%5fƌ4~N4J+t.QþJ7&v K'g' dZ2Qͼ^21-#WEO!I@ZaюۈJXSQ2j6i/8xMUUd}9? / 2tZ'vGUq{ޖ@ΰjdzZi6l ww[_ ZaP¡  VϯCC.OyvAX/'>cIehеYرr%n| TT hac#s PE݊@soŸ\I~oꪅz1"Ջ* @,d^jkêj  @t2Mm-Rj>B~ …zm~=y^/ZE KT!Ѷ۶j^)zj((R=4ԁ@̪d{MQ %B`! 4W|C VՓvʓ\OY̟~Jw!Ha I,}mDk_wubyZԨ%OփS5B[Qh ?٦N 3u?Augǎ4a„wGKGu=mz"0Qh療߸^"vRAޙ52w.) TV vMr ]p_/РArJq[VVݰa 2֮]+|lѢE?1vիcKooTTTZ__~fF`Ki4wnFF#BrSVxG;8"gw0ZG`Z&@d^"o)Z5 + qp:_W}hʔ)l2ꪫmwmڴfϞMGYf#8".H(//Tz饗[o 7Ҹq>^wn+jInn1 oHZ΍R{݃_'қQyʥof908|&A^Z?Of) 6Jut@6)SVs; 4 ׯqU裏[TUUEӧO iFgu+{j6mY,k.:Ce{2|ߓrxa!B&ފ׈jک7k2N#70ZHV|z6,͛q_MC ʿ3W/_N'x" #Fո}׿k7|3YdL4F^Q?^wj NhAٳg,\ڷ.:<3ǿ@XODf Yr%4zn&…!M(ᮁ[o%œlر4o޼ 6>:sun~u'tRs09C|A6,r>ru1~8Ecƌi6CGW3K]_Sp >>)U>G.^|q/cX% 4SeI$,+/UBD1}6Xwxas3 Wy:/ɓOWX!F}rxmZH(+c`-[m 6Ho0`h{_>uTǏ+g>^wj{w~aqkۗ"?@Ax-}5L)@>u>9-tk۟~xil#᪟2r۰!p_E$_Z"QCCC1ՑGqw_VXSC~y),2VSs#"W8p0#C>6666N"SOKyy4Z)P<0@z|-_~W]EnSwp=zsw~aܱ_HY)@^xᅚӔȸ2(7v:}:={ Rvq j@j!nQO kq?[fXO'vv_{ ߾_2u 8*>PfHp;G~ҷ*q.EKfOA1# ?<뾖DlXk~^s|I駟D3{ܹS,%Om/F\7Ou\,pR_9q4UjBRԴgQAy`x/>}WHkR[ދwѽ3+U𠑏>HUV۹Zȯp b34CbBOC ( nq0 PdffB7|p[!H<}O xgcЇṽAnډP]ߞDvq`VS; |r< xI^m O%K]yz?.[x7bɮH"*'0駟2 K&-pO`F lZZ͞MZMMM UQIlk 2c,OLPSATI\U@r/ 7]AOO~!B┾ݗ@e2q%E| -[<\?LGyhWxGfa "}{ND@H7#0KH&$QTQ2ꡡs}NHB {7%s9a<ΤI**%7\.ۑebp5|,3h q+jDOw-֝Z* t,Sy9e?TT|Se^~]\@sͿj{}xt0ɶ=PZrXwϾe˸+<褥WcUDp5R^=8u5}^C+؜ r">hv Љ!ۣGXؓZs_[oNL1SkBɓŴ.EEE4{l1K$<+_(DЀgLM#Iyd|iL+R$L !WX]ʃBA!Sq?OP֮_5)myLKe QGD4"Kxt0v{i8ݻK%u/T;WnD&ݷHvXB]< k4/[l =T`^DE˂?sMKr2\e#y rDxI 0 vhDJ\̶T!Pl RtZ vQҗت!!"Oc $0s#|'CeL($t><T#zO߃  q'ln"vZD<ҐO&9 Yxg=yuvO--{:?aG4MU@JrsҜ=Sl/D&`j *1z(Ku0i&?D_@N?H| Ses0B B*LT ۡLêʃٙv'eٖ,? Z0B Bb+fCKwVo)H&쀡U I{YAڴifMHT* B`~м_|j]nϕr'|r Bru}~JY*)#h8xAoS b?ի"okmk9˗/'DTtfo ;ևM ~,} U@Cp(W)4rx@GAr-??_+[~=;,X@yl'L@\s ][?.\إ tDS >!5L-?76D ?OTS1j!oʔ)4zuymܸƍ[o믿^r׺馛DH ;Lmy>6O mfVS5qDHT^2l kT:aݮj|.%t>m'{իW7L=WoԨQ󱢢B1~{nցvsAdziD0 U@3͜)QUO/^,NÆ Cha;Lb2SfO}9ꦡ4$OaS}C-327ԩSEٳgꫯ=#|ɓ' 7 ^_Kپk.PQr"7&"NfV8&;ݓJ^Ÿ~xilT\*.~w<-A|t1 @ x9 ݻJ$K_uF"zJ(--,Ƀ?b uF ~We0cH۹mIȊ Lٯh|FC yf`Bna }_ۢzVQyOȀe{xTs5eCpU9 >:M'ajM!?eZ`,?k5Kkp t,Ѱۡf)םGLy7yP0iolm/C@XbЇ-i.R@\8>&E0A >(,  BTA`sH]C+,Ql4X;7:)cnJRh/¸V%Pl4& `H3gT !}yȹ@_)ۺ594&U@#255Q)Σ >V,v0QT yL0Zr*kCUt>@\* Y~r&qњ@s#9 ӻR-պM xdF=s&4 7V{Z <8(p`LVpY)X^". ߄]MsUAZPhшT#u۴)K@hw &|NDꃂV?@v~U@˙9ST_„ *5ev?MT~ҜP?cj@W5vH`Ea u_@ٟ2]@2.$GEw往&af`@7 lgll[f!儲,/GcP04 YQ92Xkw@hVa$|beER(f޲%h "B?vH\̱-$J>:+Ã`p9 V4"X?JAytG * mD6D ܀ V%Pbi&?]@'_[!$*!ɂ~/ L ) v8)ߌꥥT_`@dPȵ!Y?TU@\ʦ`i1.U@ZNgVQ?@@HtoTl$_@)&W_[#354P?ɮv!?|u(`i#xi P0ŋVSNh Fd' R)9c]O嗚k#2 $D2lL* n**nl4K_,+"B"TBUhc.и vm./wc(v\e, |'ed`pX,MMQĴ0vM0Wi6)!VFb#׬b^!Q,z^*. ABU@\08"X9AtQDD. L Aw5ZZ]tdFfjjo y"!B"ᑿ~/D&Gz+3`0t)KG ?h{ eY,SAFOaz !BHyqHU@rDy.T ./E0ڒpiPD[CTZDٶ>CsTϼmeoݪ9"X=W &FDP A,l*RW nTO/_ R=/B  t[!+uo ;XZ@oИ 6M(7U@r(of&`eh6vYLf<`W*6 *{rW@0 OUNLXJ #˲u+VW5FF Av@}Afs;FUw @0~5K\T`[y'4.%[G((GADִB TuB<4ԁ!@EekV5-  Bg:E C:qiNT *u2~`$[: [J5Qgѩ>l4J{ "0F˾ykZ>wH>q3EMmuG)Ϳ9la-/<xixFm`E2 BG5~e"5Eib`0͛Т1A2 @(sa;@z6QXߐ2 f?H\Fc.+2f_ *lt+ @_@VT/|$rnvNgl\/!} 2y 6dSԀ`/@P2,]CT]Dyl3C}ۺ z*&*  tL F:xj#vTT&oЧSk:F??`;,J1BƖe(H:RZ}*(cH@|Unl4mL)O0i6 О7`!%)(B)&ߊaH_n8D?20""B{ڿ`P&_-u;PW ˼4%٨Ij:H# +C/3 dY\U@H_N #- '?!޸qбVs$ƣ `R^/ZCCv X/ӳXTDd\A(vj Gb;*\"N}0>C;E@|6hN/|  ӿ@]JVSf6aCŸި~?CU^F(06݅* ]E?@uTy{ ! SХzWS:,i o iAvIIXS?e! Bx*St-~/Z Ac *  KlU@EAFz:{q"BGi܈m]K=lurџ2]@sCBe%"V#FDxq"Bpl[0Iwf ٺ7t0m  X'S S/RT?Fxz6iVp;+ÃЉR#._5m ZMDy4)%\zPZ3$e_P 6 >D(F_Aq$”Yy&Z"LzXPA,&C],s  &c<dž#cMDd Yw̋~^04$\L(,Z#x U@Nbݻ7xY/1L ܌m-U@ejo }갧u " nU73T:lNgz_uD1 $臐 S^'XLDWea.Lΐ^UO-Ҁ@X꥽Ɖ +7G wΐ [_cD67 u 14 B0 $8 BB# U@sgXS~QS?=@wi Z[=t0{yyXey80ZS@Ъ0'T:",)MB*TR \Edqӑi8tX,+kk Ыi?B@US$+ oxef #6jG %5<jJT֛ K#RhԄ?I#>@@)Ke!eTVy.T:&,Eϸ,_H*DM\RRRA~|o7y4-Y'JO;@꾯^TAuH`@@HB~/X6y!O|M!(4H-їvY]^TˠrO.6vTF $ǔ7Y)$)EWG@h{ )yWRmyP}r dy)>zWڗJlQYJ*S_9!j6"Q6c 9}J޺~8  D6U`^D5M9}Rh~mܙMn4wPSc }0'j~R*lHZV&ʶSoHx 14SiwїJ=TH 쫫,?!"BW%:$3WnD!!Pnjs\iH3 ym3d ).~5CG@#B i "WrUat`ZV.h#WP x܀.zNl}r3"@R*+|>D3-AZ*YFSRhN[0)a0&``Y Sar|,wa&UٰTY9d0ydo,Oy)A3bhlTJGJԐUAıvͷ[iK4Iy#8`S9׵  dVSOJ<)drh _ShҮc\1Mz֬Ny?!BK T2Kl:V za*+|:Q4Yum '_?}P/a1оv6ԝZyp(E4ٱ:8`zho POqU@8rs"tBU@yUZM,)VQ?a0 @t>-iVW-G݅M!mfPK!!ޅH,T?lrh@#WsS|4*Cj;O<[o@;߻|:F M ̋uuHTAxj>R{TmCe枢yԛGMTސIMhPY][@@U@bel\oT:(XUniVT_ x^H xT&^Jo L w??y&ZEeZo*M*x<“KelO' 1*݆ f6ʵBࢬ}4݋=NeR+m#h]y=Rķ﷟l6l3ֈ6݋^@@h\Z(QR.J ~!1 1ۇ;*~J W:B @h7>򉩛`c9E30 @r,'Ae0_h\_u @(~>0MT-a2#g$~|.ʲ?A! b? @ʴAZ(a0R9 R鷐_;@FF#B1 +&OV @B|_cwҧ){bă5J*˺M+@.l,  K6@W c>@xКo? @~) ~"bBB R’mFr?@ SAMff.i_@M `_Anl,p\  zc4 MFFSLC?@h,4V?@@~l <?@hm+]zzcHS[&B" uaā^?@偊 4{tz8h)Z@,55~ՃC,r?@'QГz$} FI+ӭ)Gc \wipH2+Fċ.0,[~rr-v!BKY D.V+tIJ H,"1` 4aO=Lnt`SKГ!"dB`@ |~~K~ЬgϽOy]ʧU lv#9 $\kާ%_ JrD0C?@Hv{tß))_C@e G~Z'9vE?@Hj}G lr "Bk1 @\?4'd֫W}Z>|F~ہqlU. ~fj݀w9R30! H+ $=T! 1ɨw:+4ZoCH.0$=tSHZ)l2ccđz>@ dfS(RӚPZX`;đz>@eOyB?@H6} ~zAP`>@hq J#6@| }m V+H=Lˡ ~ (AXuSZZl##?)cZ g~J0x.6`@nקsTOޙR燡&@e4 *?`ЧU)@h# q-4@ԡ!Qu-'„F )Zq#,6@eYZzO#4kFĎe?}6@!O|B2۷:j B; WV_QvZd ;l6M琳oPqDڳg=;7-jtҼyh ܞvhߤIh#}jHG >nt:骫~ǿ "T^F?@HZӽ?~0p;wߥfϞMÆ gy&qSL{w Bׯ1cƈ x'Ļロ֮]KMMMQ {V^;0@4Ss+<۷o Ûĉ/_ћB܎{g}V؝qe &38?hԩ"|{G\s r`Ux3a)j A?@H$ާU?AƃOڵk۶h"q}ܹtG47C;C8->}:5J\|84`ȸꫯM}+;W.2q/ ͜930bWÇ7MdlYN9qq[g]'I'y?7r.uQ>}GryѬYDsnBy+V_OoV}&ψ*7#oV} oz4n7YQ%@08o N'c]`誎>Azz"kӿt=q3m41"33SzHTxt._A#<% /;̥Н>JL_H?hf# ]M^FiRH\DUPn>gt)iD2SGz>lt"QCC{)^F}5'V^~]kh ZͿs0.1S/<(fx%)MΟÛ|^&L;(c$_`@^͹YZKj'I_#r\Rs J?vߓѾޔhk͉\@-`e ky>{]>WB!* Cǧ~O>Q&fE|¼G͢䵀|DN]H*mU@>X0BkGfhCGS [@6 /_r?@4CWƓFB  tdBÙi)E+ȷCSX{",T (6y7~rS@e|ydEXѠ%ӾR@,ݦ_9xo22ZCY TF tJ2GcAyHASʥO727Ѐ~Q P޳'*`L>0`W&{FD@0l 6{&s;!PyP~>W1U?.WCC?%_)^qF(W>AX}4tvE* X@0P; maٜ5"l,0x4K!B beL &-!U?u+4acaoGQ<( {f`_P D˖@\D}B?@0]>NDjVWu  h5y>@ϓz98~`WY"U1 t0mJ[5Gb?:JO EZ u]1@֞X (rD?@l'T97Fb?@"O9by.h>o,{EH0SA?@L<_^o B ʓߺ?OƆh,ێ`STA?@,gQ_2?Iy6@+52VPृ*"S44 B ړ|gbͷ(pʴ{b&d~ /1>OFu~?8K$5$Hֻ8I?P`SԓJ~!߶h+CԀS7X!y6R>cK3> S7mu#L:Ǥϔ([HQe͚Þڇגo^E)@R_ QL^! R#%Kl,2ee_lŚmK鳁mþ&EMPžl-g $q`=bJAo )ѓZg@c#?o"{͜)qG]btn0Ad2x<3fyOcƤ/@1G$5$]1:6!p=q$Pdw[k]ı{xڎ}otM=Wȑ#x67O>ro۶ ֭#܍'X*:Rه绯bwUW9?ӧ_7 { K,qg>gyƕ?!ri0?yRhz @k#+,ʓҥy<z@a1ر?pq)|ӟvk?Qor{:s#Ȕ[o/[ⷿƍ8~8ۇ~կ~BE,ED#)ZaiC%_Br\û?#u]c\KE/={@3a|wI5פNqI:tȍ-^===$H>'@ss#ւ6%/I)hj IDAT%sIBlB|ya_u\)FSj]f'^yxvDQ7Zw5Ktݻwϟ !&<=89_1CDΝB~ý"oxC6q7-[brxWgyB MP^퍉ab;!'KyJdr^P\9!_1Wԧ;r_ ,poO8Q"j;;!}N;z;!uv_/oB d闱G$:)EFgCb@ҧy/{%/&񓈺~=3oرQΤੳyEYgq_z/Zd\ޗ#B <@=FMv-Z.&BE(@RgXB$ҭ{HXյMQA.0!~l~PY'O/7U$P 9w8 eM BYPS(OlVq]`ͦMgx1 uy_UP?UI@ !% 3=J!R⡓%'eP <}:ۋ=;~L?[Q ,-Hg# EBpP+utp5y终dzF15E<@ےoHH AQ/,Z'4D>u]b@WO|"EH]m#{ce_Lȡ_B$$Pvt˟A<@-[V`q̓VEkmؗCH&,}6l@n66!rf ;[bNKlM~9K(dB51"6{ΜE'|`^hxSz >_Uá_B$@}(4`gH35A˗s`$Sg!@BK Uj'QXo>cٖz/I@ZT ec&h=3l:BݵtY.zO/I@ϡ`^pFdocc=\(xUev:>/ *zgwZCɻPH^"uIܫN:HBq)'%,^yj)V"wϯг-@0rɸ(6 Z=@yՁ(o" ,~mBϔ?B$/>SJ edO>3II˟(LLHLÿmN @RUUHj5P(P9ksϟJJL4'O jv76uu֖ìYE6X  xvBg@S1PIՊImWHm|<@qܫQ?]WDJpm5D/g;QkHTD$&ڀgOf㑚@gZxزϬ0gr/HjRvpIld㑪'KX8M&_]r_B$*sC߇lM\/T'#cg1 cO,u}^^=STe)fU u, _ lDRUL rcJ<`]@n~GVZH;5gV #bd0H@8^Q\†#UEƤ4X /,֍[ng?ey-F#@RwWa\x>|l1TO(PySO"!&{k=h髎R3R/?B$(nV~člDR5y2(O>y_8Sz>kZVG(XśIGk lLR<@Um~\ye˟y'| !,aq[X/_ˆ$(1-4C7 rjɟE#@BSel{_.Co~3K0+]B8c;hiċILA"&JC%Po1-$LRodbW1ItJ=yo,-[g%ɟ)O/FMa_B$d $Яt(2 П& -V޽`Ӧ~Sll}>ö*JCj$PWr5p~ݛ߀襘Q)"كlTrQX'zHILý"H$HZCMhEl+ vLu-8A\dpiTZyMq}C^S!oZ]ʟċi%P MlŢeh=e8tz6*("r;4+g+gu$U-}j-CG|n1 w9C% X,.zk+? ouSm{(H@p@uH6X`~-BK17?qAcض.0GY<[ߵxw g?|?rEP?U "RC>߈=h]-B=T FXybw۶Xŝz|?/ R,j',KH/|y@~%p.?T_9SL)"p8Z Zy$T[tcÎ*}`^3| &P @ T''S(xn"^Yb)TPWttdP/y 7B29+Y+zJjg.#@BD9Ha}? 4L*~ӳ5(#Ӿg ~{  P 0 ԅPaau^9_rT<@I8{v[v~oﴞ5k2ߏP " Ou '/o[04<@yՎ)j:u7ߎY@s9$獞(}|ED`&oӦ>Ys̹?+W'' r%L~?07,W$ ᮒcД(e˄HUvܹyGdq뚽刟=5'ٽ&TZ? !5.:kԨمF̌>xMF'ac Ks`"/`ÆSʹLTeݼ5a=B(԰*}2 dZ+$^ ̎f4"H$b(Ce+.pG:1wn{6Yg+b[- _ !5*j^&Fʘ"~rsjtP}P~fwbZC}ARzhM@<@߷aC-;,R+~6 2C}!@BPsMB(# #B~ ͸$ ?{J˵mxW\YĎW+5&HE3*2#E0]h{#w ^GSXP :WJI.v`SSk k+G 9{ v3IS!@BH ' eP/#OLqЄcףXSo !?NyD<@1Ļ~}?V:AO JZkxW}]%HyKbT JFj`FKbdž9]%-*Rsp,qL8իti;)W'~왊7Otŏ !&5S#K$PSe.Coz¡[pIhA~(5Up"y3gb0-Ɯ9QW>J.lh_9ü2V/L#HA}r>S@5Kk558Vx.t񃩕K]}w.]Ú5gpH&)4.hrzm}^hz^S$G)@:[]21><,\3s[p ӣo U|dj(@+b5=pΜ"::X2Hfu6u#~V(m@h@?9kA<9}G*갰5>QD5 @?KG

|MKjJtkYARl{m.,LA>]ҼDPӣ&Isp%W Z:;T] CkmkM!EP?@,$g e\(~P O\0MIWQĶ$-,A$ -6!^8z\2d &~M9zO^0*~wx&s]C?(~P Oz#:[lZC6Bs}4CrBQtZ7^eCoz2~(9vtWf&tT]lQ?6[NO;#HH]-O4k=%BPp)ч1$½htB?زr9 c(w90҂|1b]zPi_|UƟM 6{pB$DPΨ'|=i_!hA4$mq&nkHź9x}!MsD/6%Ow }#3G ^>CKD ZRh=%|^Ś)}P !>"h{҇HmM ..rHN/p^9\sCLr:>DCm/5#P tdqa0|3!\[T/!S$]y)Ԧ8H}F#HEP;!rLb'NA[ޠ2-=ʟI2nÀ#E]×X$D+93zv1T컨]C3FC[tn҅&dJ=Uo5i>X§Wjzk gZB$"Dd~^2(EP}B=b 9vK0nQd!Q0r \IK+F)4=s1W PϬo%LߧNPgx")Rn[B(ԽT5u(X>.{rnmCchpLXɟY]IDATs׶|ZЙy D>B() =OPU y?'#K,RDm> Ml2VP_#z~7OOY(G !@B $sz EPkꅅm5\M\.(>]uMt˫2$exd3B$L 'gӚ^2JI#h*iDlT'"z7$%~ze~^3o "!HdsL|º .g |epOdmӋ4ۊ.޷EL>B( A}-:ho[d"֣^H`1YtXU-Дh]߫b@B*PLC.^MkM^"xҧV>BS{mbTyWIBMmG#HbTۆ4NE)yKd}D&=L+l]kۂ.fa !@BHˠ)J z)L0ܘ?{ ۆDd_s~kz ~*=B` 0 "~9}N I. Z$q6zO5,!Hc4 +Շx'*F??)4 kn>_BB7\KIļDO8 3t5IdH\%!P !e&ZArm25{BAfNT>WA=j!$V?%IENDB`ruby-svg-graph-1.0.5/images/pie.svg000066400000000000000000000204611164403311000171310ustar00rootroot00000000000000 Question 7 Internet [3] TV [3] Newspaper [0] Magazine [6] Radio [1] Internet 23% Internet 23% TV 23% TV 23% Magazine 46% Magazine 46% Radio 8% Radio 8% ruby-svg-graph-1.0.5/images/pie.svgz000066400000000000000000000024301164403311000173170ustar00rootroot00000000000000 {@YmO8ίfvl')-Eh%㠷;n?m#BKR o7ގN{QƉ=~gqt}N0i{n]u/nήm`g5Y4Ox1FXAT !Q_4yc}kU;u @@r#tMl 5 iTD@bl4ޙ&歹T\'`өV1 LG$Z;I :u?LRC{G!L_|Tdx5:a iiz*HwѠG8/GBCDR " _y-=9D{jL!Ǖ02U<@^v:xGk8OOTg .yͮV߉ d > 6/2qIWX3M8bĵDêApu\p cxq0Ӏh4qWlrng8Q+jĄkQaǗNgI}͜~1Us-V[ T2Τ'80||1ȣg/[FCaf!].Λ. /0ܗb\*WI\GU,qB}WExO*inجlR漟mɁniŤO.2q1S&~\ +y/qљ=|p] sJqK2)$fuœ޻t 3~gci#VTT/ü(-RCzapz{ğNw H _Ãgruby-svg-graph-1.0.5/images/plot.png000066400000000000000000001020471164403311000173200ustar00rootroot00000000000000PNG  IHDR5 cHRMz&u0`:pQ<gAMA|QsRGBbKGD pHYs+ IDATx ǿsݵ$B*E-PҊ!r`u hKB(V)*AS*r?ߘ5;;;s{g>kcv<A] RbJ*=%%:uDb  #l6=ڵ6nX쵇~z Rm~~>bsɄ P4X-ɢD7h4b @tؑrrr׽tp8 wiرEeb;&O0va0b@ niڴ)kԽ{wQN:ꫯR͋mՋzr0ErlW` @@   BHɓK31=裰{` (0!y`ffv8{`@@bك=pB{`@FFCjVESK~I;|0lْx:qiZYp-{ysmQrr2ՋvA۷o'%% B[`=XΝ;Oŋ:uꈟ:# gUW_+WkV#4BsQժUid(L6iD,nڴ7oNcƌ3g?9(l=bSiO.~֨Q=*~kvyVv^@T` =k$TT~2?-.\H ,! x$t:%^矩Zj%5Á:v(>}F-vϟ_so,}]Rnĉ5%9ځ䱝@)=J7]4𔥻D;Zgz /ݻ^#H?Ŝm6[z=a-_^ a =KBrUݻpX=e~Tbb(U.ٳg 񗓓<aÆtl|s mz]~e%U={`\2͛7מ!?VZ 5G}$Zzo%8$;>ϛtڹsglpm~ץ8O?Q{tr6Rv6BDg,tPzרm۶.іݐ!C())I$fgHz&r4PaWle^Ea =yG'OI&vE]6t7ӥK,<];SO(wİ>oϞ=4k,=zx{$69v F?GSV:Պ@T\Y4:x'{of/E% ޼mQ}裏Dkկh;z'D(nѢs}ѹs炲I-5zЮ]Zr фwNDAO'[i<1ܧgϞEy,m^z v#F5kֈ Pcٲe4lذUJwg7ر(''֮ͧ TԲe2=\9{`QF1\ɁZ"{qTx7mjw= E(3th"8E0D-ry` # ,-XPg fؼY'/ `~V @P+[$B9]=۷ӇãG'ЩSt ( dnV$rwrrqf[{~{'ulA,"o3{Ųpz\c{`=le$ K#)IfI!8ǎ::p@Cӧ H9VpPki #]H~ c{`=g`P\Ƚ'd^$?>o0?Ԉ8M+QjMEo_wI?]O̙aDݻ[P\gȤYi=@_wds{hE  3gо}hʹ}{{졤$2ʹ{n >ABԐ!Qp68sFM˖pFv3ƌ0$33y:uTN,&gQ]O.<] /W03H]ZiHX-JkO̓@+Q{ȑ4tPڲeKX?Ab0pΝS+tSIų|zBfDqi.\d^aaapӦMq(!!X-Nqs!Ug)dn BБ7߄lߞOkӡC!9C=ťPѰah۶m".P+C/ {`p2o o9 ?[n-Z{`qLF~{2ȢEzL@`رf5L{hw_h 3c$gwYuҥ9 FQvvx= b/`%?UPYr&gΨEJM5J.!0n8,r~<ѣԮ*U{G{a` 8`pKw`Ν:Qț:\\8|+ (I8ydxH0 *:(]Λg-qt7a[qqsN?> D}A.s @T*cV O"\r,>8Ա ^&BE>X*V89B(-=Ҭ%w1> },GJF,B.sb^Ăᤢ|[;i4+5lhUsb|`=@e`8[)K c'8.ۿt(* @wa/Hʕ`m5trADw/0T ˾`KLyGf3Y,'(et{`Q=0 n)R <7nSꎈ7.]ޣ%`\a V+:gbpp~Z4n4DΰW8WI h4uFIII뙁ӧOSz^p N3i/ꈗ6HNGAcG@p!]* o. s/_&OL7N n]?;8YW??=\R)-Qq:dXڳvZڵ+=zzX9 8deh̘x mQJ"=c6vnq ž@%%=-D( bߧ~Zd3 `|Q/NٸQ'jʅY gq7u֯a,W2J[dD妛o8'ʕ̙3M]t)s}w! " 98I=J* {`Ox5k `D̤ZjѩSJĉ4qD%|FA, [Š :>6eժUԾ}{K\e0m۶$XX-]* ۹SGÇ3(q ApmXer $b`REAM3(wV𔂩^xW$q_kW$c -0{lQEIG"B ,J9S9V?=5j8958=^Ƚ=1fdd+zͻF,@o887@V6Ԧ+GرT,˶"W[o23Wdxo ;ގ_ $'[eiYo;mls%ءAnۆ|^Cb5@On/_,ɴ`AaHYΙ3',߇dN'. %mϫUMjGp-AiSlmJIIr  [ t{3䁒{/8;^%`j؆woHjKDZ,T vysթ%` `Ğ!grledTl9-\1 R?%Aa{pxs8X ޽e*Z;3'۫Wh=J='8vҊ?v @3Ȏ}X(Zcև nxh^X?.QuFHH IDAT.! CA/ΜQckW+v *@ZZ:n}c 4eڽ{x j,X rSRfdBW !+ b@/6ڰ!x/`g2ШQc'Ğ?XqkR>}'0;[H@p  ?sQ~ h$S SdKMnMywy0Jx9J߿x8R0VWISjt{rr2PRR%$$GR1D"!!ZD|vmOh2E$ A./* >znS0Ž;l@( '.1JA$V>qѳ>үihnVOIWKXpMS a>w5 (>P^9 PZ0s[bb"',]jPg@%8 Iї={ЬYhUYCshfޝ . 󂽀\;S?R6T܂$=i4taaW!@Jre+Ν;gi.uA>G-[:&j]'S.\8cމ)uh-hg7(i80oeq![?>mٲ%E'HΑHVM2p| {# V$E">SB.2GŠ!un+ܦͭVȬYb `Ox2L .^NA+~rGRbET<;|O|`">PFp"jh]@w5de oe(݃H.qDׯAXaďN>\|YwvMEo#ENM79U+;i!ͼyTз/ڵ4ff1/hٓp&ӦE& s[p =+jjڴ׏.a,2}-'ɢDoa1,x|!c-@@onӨQ\joߞrK\> --MP"U8U[shjWt_^y1sػ/Yh5Kd6#,` MxODz4n\#4\[ T( @ϨE\t[$qb,,LBXMS?=gkl=Al ApH;vڵ*z2ň4b Bg$&NH6m*zƍp=#ߚFuqKeG%.NuwmݮJ=p ^"ۙIq<NYL[znJq;&G;֯KǏw.[iG>f:t8*e;Q-pi?r{d М9'ʻo(ZYyDxR:h[d<6 &ʕԴ]s D"w_b760ȣV޿4+1_p3֮][B)5m8-ËR2߂>N\ Pnq"=8%#+ܘW3fb|BAÊ"F̗ @ ,#%EO=ө1Od_rxhW@~." mؐ/D߫vmBeb nA)U ;/QL(rE HGr?6QDmթ[c2E,aoDp ">0#srD Ia.a{*ƙ3j:rDCY'O1>۾q>>1߀tڵD3fpzǴn`x 2ij^95W/Vuk3+ 8jԠ%K7ɰx1%u",k~( t0bD"͜i*Q{Ђk.t` v xVVfp@unV{= dn n";(a@!YBX,s~( ../:ۺucaC;p;2aDtΝ;~TS'v*b "sgv0_\ <f] ng9m&MvU`2D$'l6^h졤'-87д7 &Pݤ:^A֭A'k.s~(ml c'L`Ν~c؞*U=ή]Z!{̊$(h3O @/"wP*XJd"}}R/'q!rTeoyQ?>#˿tBԱcd"|}_yz<sfe9/̣Fb1lgig 'O.d_<( r ,olx\aRS Ԟf͒iS:JUdI![6S F\EOó&7, EyB%!e{DmƷƱcERv%378ÞշѨQ7 ,ݻ ܲ%EH¡LH(|H/ܲEXWpa!v0hƍ8NKO[6|8<ySn2/z P† " "ObБb-%%9 Z|[,] 1 _ofڵ(f+ D#K߾ԻLXDˆq4rS /%NxL=,nWwBOIGh0:E4y2ѷ*xG)dmo"ΰ=P$A_$E-vhٰܺ!C#5 D|҄7֡r_uk2͜xYs!` XٵK+w3\m99Z1@NP"8/@|#b|[t$8 zAX`x Y\-Kf}x'hbҶ֮ 0rC 20kW 6j{x ;?;z:= f|RkE+1b49ŏ@pDki$SXiNk0>.}q?@~p~n^HA4r$M;uJԆCg=!CK W N3ɺut42e} w,!4h,)Sxok-D/(w 1J+ '~Ò /'<q^E%v''NVw?+Vp԰C"bd:U&OrZa1?5E$Q݉ Nv^~@vw'5yՋC1[}947: @;'+HMҒ̟=̉ r.ӵ1hpg%y<-Ѓi\2deBdžA#`âl:Mj=r]p۷#1/| |J>QZDng !jF9rj4Q3Ö8bY,%D$×v\_>Jnre&dZp?HiydJ~n8_ܵѱ!ďVE$^^5y< lVHoy75t!# G'=_Aq&%Q/S'B)0!I O#K&pf֭l/%JK+=p %`PVX8p]9W+nl"q섇E Ԩ=,#DPgRH/8~ {ؑOekqE|m@cf&\QxZK7a%If(=rL e|X⾿cSި^?PsHu"9 W?ʁΝTXM3ztNF"H|e4Ǭ 4?s]rvԅcgг>K۷o m*A9'OiԨ{xycfL: c8lߟ9233VZtEs/Ҕ)ShΜ94|Zr$ &8a (f/gVpX]UOt;Q#AԽ{dժUԾ}{M={6Tua>eK֭QVXWT*elnI\S=J9&Tt|bzH @f4-vpg{zs=@~/D>SjULUV;=gϒqž brݤf͒tY Ta)ОH&~cA*2>lӂHUk׮+7E8Qt1YV ea"2+{{x P Z*×xB_?k۾>Ap@FFƍD:po{i]^?7v'Tjӆ=zo@KH$Z-_^wt+r<ΟW'O^v 4TNQc*nݺرcm۶9gϝ;(˗=t”B='`rYqႊz(*U!ptB6htu`0f]7Q*Z/`'G䦧K/D.]_U>L"H@sFypfȲ]ہȆ˿%+^$0d筷~:'[;< /$аaf &MtVKa;],^l EU~< .X ¨ϟ R0(-wՊďXt(_\PY9J' ?p>C{Nl _GrqTN+Td,m-^:Q&iΜBI"pp.r_Ot6@1E1[ !-[q7hPA2k4QjJƞowЉ5t:]Zpá<>m/R,NbO԰ e.b B)a8 !wф hkzIQGI 8v7o4x\;=rb{Ȣ3 _X$EIIUT,e IDAT D^|E5'@J 'Iy8p6uTZ$E` xA|۳GXJ&{$hPp!/&Օ+E7N8A͛7N:֭[Z{A&~h< 7=TF"gg7NȑolX%ߓI@@4pԨA޽0oOw=Wn]: Š1/bJ |paQ ׬q=߇L:D&MbP6>l1E͛I}x _r%͜9{v){ıիtbʚojw"\gR=U҅~h @5n,oc]68A{"n N%i9gaQӦMi˖-yavdr-@;%ďٳ?oxoRᐃ4{zNv_ A`NqbQ5-M'P׋r|3>|"(/M3 !R'=RorNٸQ/wӦvL@'pR"JNa/d3~۷oOiҤIhDp;~$&&O MΉ YY3ƌ dO`\lfwd=t1vmvJ^q-ϗz1hxG'ҲPYIu5y߷ko $kC9o֬тHR#FPOs0xΦ<. U8E|.t}p/@lxբ2DldM.uݱ8t(wv'|pR'5I\$ "'?NO>^^@2n\edٳ ¬,I"Dɭ3D>Dy;.W@<-l= |#1{rrBYV+ϟ=Rmφ :ٗ~A!hPq]@0&ѷr'{)%`2xH`oЀ<"9{8Xx>vx r;p)=sԢ%C7H 豇{17$̙DoED: C['m &7n>kW=~ܯg֭J=ZܔX%[j*9WmRO=HFYdYP|!h)%0(f [gsQ^Egg Lse!M b"Hv;@2sÍ(˾f̐qH24i;+m[w~?2ď#A'~#xr@o>bnQ֥K@ $R[wP6uO=.qu&Y㤖 4l]['ݫ~՞m@V^=A֩]X>{Q…to8 <\ZưhQyړkR~ڵa2W;n]y@$YZ*\ ݻEXpMڻW"yhvxnf5”K׬ee8~i1uiZNF4Ʉw~; O'I^GdukC /PS̔)FJc,\Bot`v;cngUno[MիsAoϠG(jCwI]}rr\eu>v 4TNg5\ }.;]ZE=u|i[ izAJ߇5#Yb 6];v}ܕ+D=5\BHOM!X٣}ETwD.F)fnGRUL̞Aׯs5sDpyf͚`_A{jud>]~}9"fg()ɉ '&!c2_J:~E.[f ?G˿wu$< $ykE0밢 ;ㅧw)06x?ճso-pӨrs78gOKL)رB rFwq쨉ӧ O^%% CdD!h -ZEL{<;*7>,6rreƛUՕ+ٿ_,'*DG#ܻI~|+O rqppǏ?kՅ"9:e'8p I"@qO/;}m&,)/Ƈ'ˬ5W{o\ 4h`K"/ڥ͒oĠa2JUKáA]r%WibTSDٿ{Þg.BJ"HV¥_8ߜU!$[۶3 $Җv~,f*/xq rI-Ѷ9q@B&,K}|G8ዺI>X\?  ?XT{97v3h{үY#`ғOrP$a@!(E RSK=esVd^AΞՈd@8p%ڵ`rq;ju(^J9%@–X"֥K!3a;񃽤G.ş#5>rH\sgkȅr>AW,+﹇- !@ppm@.&,ZL Aď#!q/΢[‰  h~Vȑ(z십U+Jod c\ /#O')dKM xY=\,)OʕupB!osvڻ70e^sH|3@`PQH xXPhqD|P ~B٘J'}D*6IDenǡpa!&?X}] pYeD!h"{6رjd0D/Ux?f(=\R;'SC;^n1.b$Gz-U (W`n!5[ l= h%3 B-H +V^{PBBB !ȉcVď`=G|o4Ƨ];KiʕpBڵ+%''p~Vޠ@ Q#Q*Wi޼yp8(==rrr8y5$޽MƎU {=c}y\sP/# @ӤI7NŠٓwޡƓ1gNa+ihN"ds]m9l= @53?/u4͝K( %{וC*U諯6m/7e:"}vr~wNc۾zx@dC @NqBаEj#A.Ǻ`$ǓPpi\b/FpaZEoEh#??$& {mw0Z~AL`(;{Eg [BgϦԲL@-Rp2,^\oh#=>_,6X >}^yE'ʾDm˿2*?o^8i2dɓ'iԨQ1c|\@ 2`Z8L|bقJ \6[N7a XL` ?n[&H X$G:d*zMŠ=pG/ԸnNծ=p(s:rD# ?8GĊL4dS!_ {9R<e#K-[ o@% D:~\#Ÿq&n IDAToP5~ٰAG7(?_E.LSUQl{gUy=I&%PeWXRF_"Hk""JhEV֕URD \]Qc#JH&|oBB&s۷ҜI2$&(] %Zj 2 s)q]L}sƘ?%yNL ]pD$8YXXh;jKeҥYQ~N6wl ?L 5lhɀ<~ƾOv]2>#m7S'4gYh|q97d:i G `cǎ%Kg͓w@';v.Wfsz@8MKkܹ]#V2yrtΘ˿]U FLDdS'LPRv ZPwg<{ߑ0hpڭʱv=== q@3'O,G5O3}:g@ ?N& ϝ뗋. Ia2$ /XxU_Vt/'dIȽT[; 1ipϞ=eذafয়~j@H@O#6/`>wcu:䒿I8/OB:/]Ut^^kB`[ 5|VHh=M\g)qn~4Jһڶ-dc[uvs(Fm|r㓤`H>۷{`|f0p 7U`+h Lhq?% g0N>%uUUw/~G{S hi~a|z$.Z z$\MZe> qrs BP%L\1V Ϭ;גLo<;$~,`PA `l(׮lSNr Qc~Ac5VBj6ZM6{kH1۟pU2wMHOKqK<`R.m/ 7;gZ-#_&'KeRAZZYw>:J-'MW c7"={"+V2D!`-_}U2]{sZh? Lisf5Ў_믯1֮A콂V@ 9xL0A֬Y#+`!# =-hW`\?;* R0@Ksgիvbv(*:/޽Ң_t`3bI_'fdQ'h- L.]dR^^.۶m .2&5M X>r1)- n =kz妛/7WG`z,ODn&aѣ b>(%-άr"Z &t|I8Bb$80_o^T |11d.43+`!ZT*nŬp"oVZ%K_C^W c@\_|!<`9BݺD eD.?EPRYv6W "ҧ߬Y6[C , Of$-YNbhqA׆+6c =Z%++ٿo/ =Vѣ!`+ =PW-On Z$WcWoǏK_?z'D5<KN!),l ؚ7/~097/XA0 7L)Ζ Z3o5ŢL}@-v/o=",]*yz5K hWۗ&H&0Z)z:w_ҵkWIII*˖IʓO6lsw5#+!& z<9R @ٽ6:+zv/7jT\= =ҥT@ѹs(b3gJ޽g&OpZ.Mk_ F L` @C8cb2tPٵkW/޽qM?0G;vp"0uLn,^̘1Cڴi#`ebl(r/X mC\'NݷrI˫3 `Eh bhqP($˗/QFUY :P>r1)=֢zrsݺUmӆo豚A0DO᪯M7B6Mq2~-X ]w@ӣ{>0_ f4Y"0`f͊gC41.x7m2VWSa` `yf *6cǐl"WS oMDڵ1j_U<;wr"0cϞ=2m49xO8R$=n @>Yt>=q F lC¿7XQOwo|1k;vYFRSS1AM|Kn…>).n0pСyMѣ?z %@͟~eNJz>>aペi߾rUlV:5UvE `pCb9܀b vNL_j YY=d8okENbJC5dj>h{4wx_˽׿.`Ϫvl۾}>n}O>]\}2_~e9-^~qY#p9PNg݆v~u@pZ+Vя?rW bF^~9cҽ{l­)Kwf9KG?n;l#j<wX8HU߬,.<8p.+NNbRђzt/ڵIRX莹 FzCL+x޿7X]4T0i4!{dMˤP.4 8Gv hcƔKQՋ5mk+ORMkIVUfƣ @+-0@&~ICy7xհHСZ[/m bi7>%3g*]Lo~\LHBp.N EђzCɦn_,hrɠA?'oɋ/PpKt0Pccezh & zbBFFX&M* Sb'77I,E Y/'<{фc;1!`-C&0hBȎ^ٺy+iT?t:e'q+Wn5$ط/]U/zcFPyT&O5kaA3uy߼y޿?&997W*n GKZ<0 Νò|yJru>e+&$|E_HX>@ 4=ߒSjBv5Zy$c$,;۬,]ڼUP7$e㏹cIХKHF.oPz6n;NGeZHz5ScDԣ>Ĕ2iԟۼ+Pl3n4( J[7| ';|ʄ Rkݪveƍ2g8q^_{ :F]wICI}衺'jή:gʛ`f& pFSR-OùvINNM6… 2e$%ѣn+chVr W?}gӦMIZ?HxcP\-詝 2gO OԔA˥@|>sOQ 9tgLVĉZS8Gcnz gnn@`R'dferyS„Z.f<2~xڵ׆}_R{v9NIII i2ГzBݺ}@`tyOIro-/6u\֤c5`M.rm۶tgpa !bhBܹ~5WZEE|l25!Zm`NKҜI2E\ŧgɒ%'G. ` cZC+;{Z$.K"mرz^پ=$07 W ,3zm۞tCv~:Lz2S_(.ԏ~{6˜ 6pݫWV@3Zimv?$]+o|k;Ya)z+3?`d?rW #Gzd DExonM-;i8…OQZdh"HҺu{uR֬Y#ݻwロҾ}Xn_ӧKĐU~+c=[x㍪Ȝ9R87$`[䃸2|p~ @3\t޿-}ؐyKHi:|XxZ3ѽ& `{'>E zK;ȥ5qKHqvI|/ >AOu|>oL` `L/eZ |Ŋ@=U<\$vkvpKDe~(n@`RA zЃ$ @H }wN@H0]&={8@h(E zЃ]+@`RA zЃ$Ac gdHwa!'BKG $ 1(.=q{^n@`RA zЃ$`^b q!ݻ9@H|> sDAWB(ZЃ)zdcI-AzГPP{BkWb %Tf $4 u 9@8E zЃ8IY$L*hAzЃԖp땔IJJfoh*79t2ox<2k,ȐǏK8YV$x_~һwo7on@p=o'~9R^hAzЃ}ʗ_~)rc{ ZЃǡz߹\.n$hPsRVR"ǏKǎk$`%/ɍ7ȉI `EE̜9So.@qQ=AJ9SO=e4OWxZ{=4iyo`у=豉w /JII E1ey衇Lŋ˒%Kk`.7!a^-Z$#Ζ+Ve]f^k2nZ+Fj=#0ovNǎsC]z07@lة-ޡ&?-So^"N~4 oIƏoM=)z )_Ѻ^L駟7,Æ |g.'N {P&; vc.=N A Z`@ r RV(vPة! !`@p6 j۵{E^$)(іX'"Ν"F|ESgܙ @H 0 2 4 6 8 10 12 14 16 18 0 2 4 6 8 10 12 14 16 18 5 5 11 11 13 13 11 11 9 9 2 2 9 9 7 7 0 0 18 18 12 12 17 17 15 15 4 4 2 2 6 6 12 12 6 6 14 14 Plot Time Ice Cream Cones Dataset 1 Dataset 2 ruby-svg-graph-1.0.5/images/plot.svgz000066400000000000000000000036411164403311000175250ustar00rootroot00000000000000,}@plot.svg\mo6_@5ERNI@al QTRx~腔ōb) zܑ#E}FnO$ ?N\|\yrdoTϦg_X?~Vq|=z ¥<&^;J~ 2#8TT"G;/'X,Kfg%p:n"!"H+]~yHNJ^p ˅V6Ww:v̩>*!l<+PcM9W>sbG:߾BpNuRoع^RTe[YDVx‰=\?O Y #I`:Jvd(xDZ7~`dL&h #Db_=o@EPDM+tX&5hu^ ZhX~ɵMIOH$yrﻏLZm3o+`{`|}wpnڪDlv;w[`NSx#HUpVwrg +w>C-?ETL큅}z1x&?m5(rBw^f+JHNǡahjDWfjEJ_޸s xNRvЦ84 ƩښShjԶStS%To)CN 2s S5<.9,210N1jO*CR!yKRu6}vHUhZ)itྍLHmx:R27<@TMϟE*.wi&T WhڇdizƞWtNbNbaJl V':Uy1{EgΟҩ=щTH Wt>"AA[f ~:z^~`5dO|bHyFe{yb GQF H ꀷPé<*LA + Z&l塱 Sz VHy'L u`C5*: $&b¸\Q 43Q٢@ԓ*zuy1Zw/.Op9`ܐ&Svl_C! PrzĂYV!3cT*yBg>WfE evל*kJ9 ׈Ԁ :SFVPuKit}!!웰+ LIU+fV4քL*( =!d䴀E1ج uAFX6fE@t Rw &)j/u A2vZ\Օs-;e[\y*e5ߪFõi䞭5 kdG+:Δ KQ9 afn*z%!fa=EYLVBcd= !i TDKa 7`&LD^S3}w[`!7a0BoY/f=au~&ϖ#/ q=$aθszOU n~ϖ3^Hl6Q.zMwAڍkU8T ~Z.(rڽrUc'wBnT+6c\P!7aU\TٌKCn b"~9bX ziD!TŲǨ܄_8gc&Ym~-  YF!'OEd|v{Nf}8P2۽5;/8,*>S.:* bިlDEr|̵)"0t2s؛Ůi;Qa͛4`7`mX!}uY;M RSz4]oDNZC"'P+"rsS힞z4SU=Sx'[K =QPruby-svg-graph-1.0.5/images/schedule.png000066400000000000000000000350361164403311000201410ustar00rootroot00000000000000PNG  IHDR5 cHRMz&u0`:pQ<gAMA|QsRGBbKGD pHYs+ IDATx pTe'!LDdP6:Sq.eAa"C"r'\% ,"Ȁd(SrUS2,Jn?tΥ/I>離 ҝt~}8{yߨ7 \#h@@en۷;ʠAٳ5 =~ R͛'[f͚I=dϞ=!oEƑYCNl֭2p@9rHXw9s{U ݻeʕҿ^T :T;VgX7oشiicbbMC"0 :s t?|5k۶|'ҲeKӧcOg}֌<^~=ƳUVraڵ+,,sŋ/TϗlvEz-sXܛΚ5Kea:u8qkSWL @ (z>{'/l޼/>|GTu>wΝM6nsC  Ȁ]?,\PLb<ߤ}Ii_{53vIx 9ri0>jSS=EEE2~xپ}i K05#OM41M+b6lܷe3bUm"Pɶmd߾}nݺ /HffiG?EvA3͟td۲e訞>ϸqj<{<#|u =XZZj s!\<Q󨨨wyGy:淿x#ԆF??_Ezx5V#>;͛Uyy}<=}E^`jHyyڵ31r1Xu6Qmh=to߾9=̹sN3ZS{Az :}=x|`j@!ӹmtP@sѹ\Kԑts^w-W\;SAm"kz.UD",m`i(= c3*ѽ`͞=[?^߫W!:_z*GyF|ߧ[jނih/4;W/X:Zpu[0iыF{=A럣F2Jx̙3Lͨ6]=:L)]d9ϓ.o^C^XRUoӺ^W@ 0<ˍƚCk֬iF__ѣAנc:Zoo|rs΢6\:ߴiGBB@m ]28Tzh\kґd6cU'N4?}:ǠN3uk+zػ5@~F8t^< n1?,@> "vM!4 z < X7YS>S-ҬNxvY۱nH՝qWIXnѢEb^ٱnf'}i[fmǺɚZ?p@@"dv,v풎;JEEb-0ɚR ܹse…f>@FIdM LmPGU:ٳgu2aiժŅ|ҞW^^.կd޼yn:34:~xpb+cǎTٽ{7i8R3YSwCb55u[,)) ~@fбb55u[KTT] |G}$Æ 3WV@׿*d9~[\\ 4Y:`֬YҼyϟ??lE.ZH222l.^R3YSw +&>v]w2k;MV֚up.\&@dM4LdmU4.9u*})ajhCa„L@dMCk3cX7YS֕x&kjvjݍVo]&Gq@뜀W4Rr i@LӱcGٺu+iza\j&kn[fmǺɚZw6\%55u+&P3YSw81 LЇ[j%f.={ץLΝ;'mڴI&I|||{ 6k^j2d,_\zmI8`m^z[C,Zv2a.55u7<`X7YSS'C͚53t/={ _~RXXH;] hٱnf( `>}$??_6n(>ٳl Ciwe3e|?|0i8ԋ@&LP R3YSwCb55uCdM.4 `0a.55u7<`Gv٩u7jUcRTTD%55u7+&P3YS@t_,Sҕ@꒘($ l*C&̥f`j&kToL~@dMݖj`o:P9"R^^.K׮]II `~~ZJ/^,IIImAmϟ/3f̐dK^^t4In#۷oӧ0a.55u7 +&P3YS .Hii)oLF@dMݖj׮]+iiir!)++32h ɡLIIx9zϟ7,YE Nk^.iƍ5V܍0ɚ;B>I&>_!MBX%7Jj&kn@dMݖju+WT}}1ԩS)Sȴi̕/^,3gID>\Zl)sΕWZ\@XSt}~o߾b ٸqiqw0ɚr xfbj&k h@9LKdM LMu55nVLfn@4"i}CVAh‡Ca„LMLdMHHLMLdM@0ɚɚiiɚɚɚip14jRDU^&}r#5ApB]q t#|K4 ]R3Y[ǩSɚ]dc)nWg}ijnf@@p\uii|} ^4%%%>_GEEܧ_]R3Y[GBB5S7YS}X=uӦMi<LdM4VkSSS/4/**';D .7|SΟ?/eŊ2Nn7o.&L"r"=tI ,S3Y[+&P7YS}keddȂ d߾}2l0@&kjnt@dM4"rCr xm>)F@KW4iYWGu_]***,S3Y[+&P7YS}i=*tYjKjjɚ+&P7YS3 @mرceݺu+ WD&͕gϞ֭[˫+f3f9_J͛gF̙3yECJ`=pݎ ɉٍ>5z_8hJj&k`&kj@XRRyȉUqqq>_z pPXv회=Zƍ'}qD:icdḾv;Ȏ55u;5k!͛իwLNAdm@dMnYTfh%T;v[8a0arbj&k`&kjvϟe.\ ri׮ UV4MܨX1ɚi}Edpĉfٷɓ'˼y{l8 iiirg}6O<_jW_}8\f9vZye֬Y 68&X&'f]}m>5-[4|zLs? u5576VLnf@ 4-bc5nh:X1ɚݸCl,4%Y1ɚi}˰ `@7,L M}@n&[WD.,[i`hd9{ׯKYY;wNڴi#&MxGL M}eW? "˗/޽{+++0zSN)fVLnf@_pf̟:KϞ=/":Z'5}|ٸqdgg<+g[zX?m._lVXlpRL0Ǐ`\F;3`Ǻɚvk&kvjvci`p#GtTHL M}?tP >UVɵk'?FpѢEap\ɺ;O%r<]weX3YSwjl#Ӛq .//ҭ[7sHNN/1`&MҐP3Y[M50_۶m+cǎuIll 0@^1V(e˖|gyW ,uFyE: S3Y[>@M}  M u:kVLnf@@WHUcfj8W^+&䄉i`qfjnf,8)fV]nf@_9~E Gh@XXX(999b YtL>>`:X1ɚݸ\/ .Gy4}s1eq@&kjn,@dM4h%//Ot"ߕvUonB8p႔>[kJZZ:tH}gAA 4ȜD)))/G5J99 ds(:X1ɚݸ\ͩhJ" RvB09QQQu>*EEE2NjիIXbbdff:X4Ҏ رnfnfIXRRΚɚɚ*"ccc.j>@@crbj&k&kjnnj&k&kj&kV^ɧpō3@@L'SNQ3u555uSiXvijnfnj h@jv42!!Nxi@@+P3YS7YS3u;#k&gϞr!:oܸQ}͕ٳg@ЇF'˖-y\@d„ u> =0 L -[̅ QQQj_B4?LdMdML###L a4iD's8ܺu7L ߚ BƎ+i@L?pYu"輼<ڵ+i@#11,_455u55S3vD!z_XYɚɚ L#wy X94 ]v`^{5ILL4 !ҿO8LiTffL<455u55S3f"蛾WiFΝ;G瀍婛ik޽/??_G`!qFo+4\xQZjeٳI0LX ɚɚYMqqq>_Zzyk77@{`fy&kj&kf7y: #d~^̭!-Z$h >#+WS&@'6SL1Kt0|?>n40:tl4hXAmt88$>>`%j&k&kjngdD7? 3S7YS3YS75;d-` BJ LMLȚPrrrdŊtRse>Lg󍅙婛|Nj* \RRDGGKee9rDϟ/3f̐d:@ y0==]K.3OrϮ]H ^pAJKKI- ڵk%--M:$eee>,@5+ܔb;z:^d9 VfnfvJ4yp=1 QQQu>*EEE CsiIDATП^zInn. +P3YS7YS3u;%k&:KLLLOYvk&k&kj&km)C?5+P3YS7YS3u;#k'NM⃙婛ik`@50 1 ``\ ]'Yvk&k&kj&k#yøؑNMLMOo?5Axd~Ciq'TOݎ5۵nfn'{#84 []^Y۱fMLƬY 4 uH;MLML4l,Pɚɚii@@7Dн^sV{l׺ɚu^sY;Nj׉gɚnd"IFF:22JnjѢEqehh@:u- sٍuFӧɚ7oLdM@І"X1ɚX7 `<MdΛ7Jjn h@hCNAdmLKdMn0Xɚ7oLdmU1qqq>_Jll}%%%tNi7wQQQ4|hX:0ɚX7 `:55;o(ɚ` qQ@;p:5u0a.u55n,q؛v%x¼&kjfmX3YS7Y1@p0t7"sHSNRXX+[FF,X@'Æ sLNAdmLKdMn۟ءC9q\xQN<)C5~;w1:55;o(ɚ˶mdӦMr%ɩ*8&&FK.VWWFvL Mƺ!>\{?)f&kjv[ݖkҺukb'44z?LFw-.\ 9s挴kNƍ'ZrDNAdmLKdMn۟N8Qe2o@ˈ9rr)..6}A ]VСCRVVf? dРA+0%%E&L G5.]Tf͚%v%K?+&P755$LdMnr &o6gyȟVbuS3Y捒i@X(^'5zw͚5f?ܲekNnj.yE:5u0a.u55n"H|| 8t0##C}Q=z4o:55;o(ڪ"~°aLC08Tzhxر`0au j&k`\&kjvc4Lyf&k@֋G %##V/ެ6;M  #adLMLdMHHLMLdM@쒚ɚɚiiɚɚɚi@0qՋի*Ν;l Žu555u55SwMYYYpB6v:ȚAdMֈ$4[K wyG^*/UEEEڮKi۶|'Ҳe˪>s),,<ٳg{/%%%һwo YuuYڳ6mT~aɑGy$Ƭk_]{#G/뙵ӧeǎR^^..]k׮{C%:ޱ Çe&\5h \׮]n>#{I_|QF-}~͋9j(qĉ2ydy7%117{yz^WYYi?~#gMoCݮ}:;.N zƬȁg׵]{?~\f͚%ӟ\ь+k㔔̔~?S&Nh7l.ٮ=Ia?l2yL`zӿ/]4ЮYx 3ݻ7}~mbbbUVqFYr7@YWz 6`_h:tCցu#Н~Xl7ߘkɻZi=޽[n6f]̙3<:FyKu0;۶6z^l׍umdȐ!f4LGtKnl.nVCz];jO%z^Wk}1Yh9G?x|gڷoрS+b~9>N(xq/Yļaa˗/KZZ߼=ǹs!wd l~dZzTBB9|}_ۼ۲ }ƤPmyꩧ\Vf]cǎvs&쳭.#wqPNڧj:T˖x{w1B222̛OlN3jժ3זw0C/: ʺ>u0Y~zئMBݘ̙3套^9Bݶu?_4 ˺nb s&]>c> 0>h߫WUWgU@=Mvݺus(k}\n7K=G2Pށ?[o%mڴqΤYg#гQGW0p}[nZ!w?_וnڄ{ݩ={V:uT߯e޼yeӦM3YI,^8`ށWG_7RhHݮ~d]?i-e֬Y#cƌc>aY?CJV.Iq{ށrqvs Zs>ϥJ4ibF=y>L~r];~ i˖- n:+3 83ɺ]z:uJswm?۷ow5kOn`_umnuK/x "+iѢ+w\][yaz~钔dM74e׼5۵5~iat!dMdN1V-LOO8y39[#͛76YõY[z^zY5ȚA4h" h`K1DP3A."kz&mʺi۶Ye˖U?S,:Ѓ><^%╖ڬC;@ϩ{=.dnޮC;xӦM6>#CjQ~$:uvl.ճm뚻%//,'FuiٱcU7t ͭrvO#G/&0oYHfff*ĉMW2k,ӟD8\].^0K. 9~ztY{aj\zu/YI~wmbbbJ7n+W~&Pko׆}:u\ڮmo9z^쯿sl2[LZzݻnI ]{ZYa1f͚IϞ=eΝu>Ǯ]\?PG%..,PW}rsfR=@_EEU}#w}&Pk/ʔ)SdC(Ҍ3|F]n_.@6gk2Č騟iZ?=}D_ׯӤ4ACO/ޟ\1ͱcdѢE<)tMןi߾}օu"?Ooz(˒ڬɤ=QS+b;z~`v\sh-ZȑiSR]}`׭Yvh7;ۑ#G믿Ν;=ӠZGz)F`ak17D T7l=U}Z'9P:#w6wiFWZeE;`2-@1bddd&wŧ@ʻ>۵C~`ҋn6m>ofΜ)/R?_:ukrkf7keŊ=O!۵_ s꺰4 cW=5O^TpzKڴiS+< XOssdR[~!mfn\]ʻ>۵6:򢇁ه|w]~udC@\j:m[zv o@z壎|艧z^+W̙s={V:usháoёiӦO@YxI]Yy*2o_޽{k4o>s)F\tz'1p֬eRWz>y_[lq'@˻۵N1w\۷~Ewn_.ue̶}wLZGB㫯-ZT;ِ/㨍Ny>}$%%Y>k='HJ7vM k`鉬~)8Yl޼ٮɚbp. @Wp3pꍝ64{\@Vۨ.g"ș3"\Uv"?:iȱc"#G 8 YTlۖ|#'?uDtED23EEG>}Dt]h]yڴfg]3Od|r@"c~7D}WW蛻F]^G=1cDmqqȽ~uӦdGiݚ, sL,R\Ll@F"~+2`ȇ~ws7؃ h@@4 h@G94kIENDB`ruby-svg-graph-1.0.5/images/schedule.svg000066400000000000000000000204201164403311000201430ustar00rootroot00000000000000 05/19 05/26 06/02 06/09 06/16 06/23 06/30 07/07 07/14 07/21 07/28 08/04 08/11 History 107 Algebra 011 Psychology 101 Acting 105 Billy's Schedule Time ruby-svg-graph-1.0.5/images/timeseries.png000066400000000000000000001154001164403311000205100ustar00rootroot00000000000000PNG  IHDR5 cHRMz&u0`:pQ<gAMA|QsRGBbKGD pHYs+ IDATx uǿgfΜ+vז.ղ(6RD\B,!ץJV.IPBJX?",-E-iV*wzNs̜33g.5/s>=}7؋B!BB!$B!t !BYRzFr]wI}egB@B1sv裏Ν;G?я믿q !)`B9nm6iٲOT鵾b}$M@135k֔Ǐ2i$ɑ_2}2B-1! !蜍7Jv޷~y衇dÆ r*GSNr%nb6&wyG?Ȝ9s?c 7or@Æ ^iӦxS !T Z`s۱cbz2vmңGBBB8ڼ8uTJ!LmDFmDi@B!BB!$B!t\FmDF΄FmDF$B!t !BC[,!iiinmV,!BY>H:w,v]RRRJo?ptMr˱c$;;fmDF!v0ׯ_?ټylڴIzꥢܙڈ6h#ڈ;'N[-55U+\q믫=aB!?L"s2|>|\r%ϫ۝N'aB!3:hy7o1cȴiJoO(*JFmDJl2 u_vm9rJ֩S,,,΄FmDF` @ME_s.]*̝;WfϞns\ !B}*}r֯e˖JqF!BƁ'Oʾ}dڵJϗYn}7+wjժ#@E`F!BN:U~ɉ'קO2eo^g1b=z4 ~QQ֔FmDF #[y$ĥ}mڴQ>N>-\sm媫RP8{6EK%Kr|JTh#ڈ6 BG2gʒGyD6nXrr,3l9w[ݨr%IB!_{[6m*^zi[^cǬcGt?B!];V<'|׬YC8a„2)Tj޽HY ~7`d>څqv @.}:u7oQD&C!8.fay[*~Xt7n@Y|^?|پ=9S,h#ڈ6bA5B=A\h8,^X%:6nX<(͛7WܹSt:u(W۽Ϸ T_;[ڈ6h#l`zN:UBVsJW_U̘1#󇒳իWK_WM?Vit1]uVzd'?Q49~4i$kBm#yr]wɡCO>#] TϽp~38qbcٲ|i&{ysB!޽[nZ6hp zjjjد =b8o~GÇ*+b!s٥Ku(, ^*3e\7!B zxT$/ 6TΚVZV7tPy[o-_7*3-ov˸q4(36Fm0\p[}::#v_m)?jJ#;wN!M:@5#}r2}z:w&6hMw9Hh\GQ<9s*Ggqb6@?܎ox+BHrbI/;qby%EEi#ڈ6u/i_o\::"tFrk'w&mDFHINƍe˖N5z}d[\5jܹ2p`ܙ+jbNvɄ cQe0=/ 17KsÒE A2BL ϔ'-J 7Re z89rDիW&rvI_\}ղϛ1cs=*W^yeӢux>&>jL;\ر,\0Gs Rρw7<'g}wYfE-ɺFrHvȃfp+#cǬT00?^O^f#dŊk.0`@?~\4iX}=.x=z>Fq[n̝;WjsFΝ;KϞ=e„ C o˖-~_}^z#GlKҾ}:3BQQڈ6iȣJ^|=בaؽ{w{nA]OM É'TԮ{dr2GvuE]/((s wwQ]v _7g}ꩧdj*I0Ds$LNjvpzEܙpKSHۿ )_j6Aq`  FÆ ּy*=JV88|#C ,v>X.]kY}{BUHZv-(mݲrF!nח iQW]?_T^ܹsj\"?oW9?\BuT}V8D;\b"{;enw'!3g ӰUu0Ntd'013gΔ:HKk͛.Мaٹsg确tۻw-X\{Weرޓ[T#s}aF7j C!gEQFy?_Jq'}11KƍsxU !ܹfbUܭK q8$$@̉&(!+XV̗NWsFmD27;>muD@.H&O΄;\ڈ4زFq:ė*M R$b) #AUX^x!_T@=CQQڈ69p&͚%n~NٻצD##BѮ[zPеH4w&mDs%7L %+Vع@M/ "фbPWnQSH/_n7!`\A̞=֘J'BH@!|s:u;]`݈>ؿ\{j py{z4!tK/=d[oWNV pJ2n\aZABI޽e֭cl_CtE\"'OKJǎ޿o>:u̙3Gf̘!۷ם0G' cEEi#ڈ6?@Fի\G\G ss/^X뮻N{=_=zTJoꫯVy۷o7n ]ok54k4¸8p*9qD?.FRCnj#'N-[X={փfpg.mD閽{r͞;iCfJ( 7x`hW222d2vزN"O?!Dw3éUx9s Bҽ;v(STTT.R H3תUK> xT,Q2dtIE)Fxó7o #gcE=P ]ziQ T&$|5|i?U-[Vѻ馛Cҷo߀AxӦMe&E曦s·vv_·>4hР4玐 <gg𯫈P'V-O4y`#=؅3pvqH[$=޿τz}:'vSNmdD# PG6w47iӦɂ  !$d X0lC2b('ڷ//r{KEs@8.Krsszm6Ft0x!:FOi\r ҪM>ܡ>\ .%???lM@FQrh^k| g<5B F9US3hq-f)`p[eb3N859];zAȥIZ&ahۏx8BL# gқ7ʉVUq؉BH@9_ Vf:$"o/{!bbڈ6J^E["ƌ\HРAQ"΄6F@rq[#tI pHϞ. jiƏwH&6W04!tI`"܋/X(wɓ iBHؠ YYq{nݜh9! M=rgB"NXFv0g:$͚嫯,U(mD%y'U.#;Ap\G@™4ޫJΤrN<)իWQ KؒXx6B** dRfct#nk Cşh[,r1Å/|@9بpK qQ`u yy)1H1Ǔ. +**ʙ3gh(b8Nm%d/|F* B@ڑ1qDٺuk_|EyGiӦ41[. Cs9!tIԮ]$ϧTsyyWK.rp6LD4";HB<\2pKffz՚X#c[B@AзHB>y}.X@~ߖ6h O` :tp,RBq t:t iYD -@J*ۘȑ%֬CQ`IR0?ʧp8t0!z/cǬr1&H<8"R^;uhܱ$8>_;7בloMnI?6D"! إS\GֈKS.]*;vdO~'/RAh)]8{6m*{21lh[ZC8eÆTY 5BSUW]%'N>H&L ?h֌бr{뭷 t;!zsRT@OԨQRܚ?!tKXliFrrr7eƍ?'bD1=6O>YuB.v!pfϞ-5k֔!gz3#)7j=9ɗbҰa.?W^]Xt"D$ xƌjC˖-ӇFH"S,&Fkצ:`vY"\GFkۭ>hP.q[# I&Wz.洢6߀PT_Xk! ~'曋~=̌v~Β%ȑ#vi'9<\H á&7vjN-@z3.o6)~%KrU/ʌN-n.i gXUl Ϙu r5j&B:*R0r)C ?b$0IYl[8ѪʌsBHl;Aa@#ٳ)l!rCNQ@QNZ0Ti !$Ipdv a;4*h3{vj*؜pFh^!lԽKNMWQH&q[Q,?ܝ Fp X"u6nd0ב1lh=FCp4.nk L9u?KUy<w9^"))3hIUQ-[%]Zr"_֯oi^ׯd<J[!tI)at|o^Ihn-RkWZ"D IDATyr%{&Dv '?IQ,uBd$lC &ʢΘkbLVc 7 '~W"Dk7tS5oϖ+ds!:@np,nM[QA ]Zj%a#1cƔYHNN8%~m:laNdQot)@R!7 + ̝I(g>+9x"6n8yaCe*#8R.Y:ERӧ_=&$a/Ovm$QGM!R[d'O,}LSC`BJN{)*~" &! ĊEj׷ʿ咢"ٳl߾]w]Ejt!zI:|if sD_~i`B>͟*S7I+LUr06mnI_GvHA_3)ҪEu#_KK+!tIE@:( EEˣch:ҿq4i̚5KqV$&3\GJ;g ~Fc8( mBc$Bٙ,(עhYz\.6I~~*:p{;f\GyyY.WXnQ"a6aB5LcnkĬ0SJ٘P͖ ރi v-RyM{}]\vr{>+~Qj4x! CBhH]Y@m]M" ]p9!tI9 i6T)ehdmdmg-S&zO~|mbGDY H@P$83ˆ VeF cGqFI/IV^!407Rv75BTHݺErT?w&9c9#ljA߶mr]rFkתO*gm;vF5kKعHAZ)ȩQ\C]|9g9'mK+s_.9vr0Dծ-߁۬o4EBY3rH%!U%OL^+9,) vթq2!cXrMjԨDD]Rw*ߞk'HƄ bhlI.c f'2M>Sy)&M[[´j29LnJMۑDmr;ݒqxNRΝ̡C%{$2)Q_o_3Yr$u)SҧiiHBlt:HRrs岡w#=] *'vm4jSfJ:" БI?B~}}h%_wWw>kOE[e^Yu2̷ۿZjܙy|ceֿӨQK /Hfb_ʰ;][Qz2#fT)Ǭ@^ioU-e8t撿m,s ыG:uYM#&?]JVV:p[,III ٻw<#2|p_#e`iivSΎ}{_VҧLQ#6CGyv;&:uRkE5FhZIYV!mӰ AhMIo& fiX>i9IC!55U}#8#k.>}=Z~ߗ{\r̹DQǺ[m$uVoňg}>.77]Wf=Z3dggā:e9rNVWKT\6 &N筗={H۳oWF۶1g$&N([NI p AW>}0xmk>+4+թ#;w*6e%)...]3ډ5Bfrg"ycSCE* Rjx֕>}p4qgޤIlۖ5CHCM{6ĥ5_P̏jժeܹqHM0,*x`_Tv~]ZKo/y6IڬY쳆ȤI\3$ u`EEKh7DvTβ4A2NmݦR226p4בL?::juFvN^D~\~*x4-[$uj5B.Uց^f&=Q YLXT,2+mssWK/-*S@p>v,ʩҼ/Zi8c9u̽8WE"`D^!i +'R7RR>U:@@uY7=t(ujqTo)+KWS%M%i@O, k@ ! Tw8q"Yy|M6l@4HQ j͟9is," ޱs :**@#uqn-WSOpd)^]0Adbqkus"O>Y]~%D.[Lxǰa wnƍsүSl&_E , Nvݕ+5vYbUBTL=0B g~|ٟ_Ҽ' 2`@I*ߜݧcP8h©˗K '`8yC!tE-PM( `GZd++dcLaXN @xvs\@tߩv|&TFM9l=iһwIDNfsQZS 6B!iJ5=ς[hI&s\cQe`ٻwo߾}ޮwD@#jIVɅ܆\ hwL1h*-=ϋ~.}27R(9ut\j5|~l5j gO'?ʢ[y'!<`d{pS_:R;fXFƜ'֭[>f̘|&ݶ]oew6FwLJ8l][uxT'4Xvo{ cAoӼJԖ&J%TJի.8Hmecs⡅BG khdbNLQq,4nD/Sa83FIɨҼH"։K.I_}jpKnb6) $ v*dG`'xuұp,_}@x ˤkVW'YV5D Jк6Tj{RtϜہС6g!mضnѣ!)lJ@/nn)K&W4vW'sհ ۴I,_R^.yt!X\OnǛj"Z49R6#Sˎjʅ?[*2Lh6ĥ7msߖR[ݢ (O5 B&C٨*14F0Q5|7>.#4~(&Z(12B0h/4Q@ZMkFHpA5"Ј31e"^`n.ٽۼ/juT0ë*L\f*6FD0t28pR4hPfE:!\CվضlOh"z%ip@ce$q$E d}ψ`9HF8dOri6=H㽍:J?g?e% ʟ!!e&zC&%\04s^+5|?#r6?d#8gʨQ㱌Kl4ϟ/hSn7 #bv |2 67fgM&D̘&dM"Oɗ 3YlдCV(qx雚5;?57Hph>|Xu ǺSE :Ød2B G'D"Mk̙:Eu ͞eD5S=!Ge8軙]uK !a:+]m.ɺ瞘7L\(?z7BL!]6o}z䣏k+]J(\(Ѷ`"2):Ah4,f5Tg,gBz Qԯw{Veo e*U4먙}m)ǝi4d9)FcdɑVɓB /_jT)4n\GfvM(a(0Ѷ4q];nj-W_%qmQn%2)G" -'f蟑_BQz/[&%UJU#궆N[@]p!!t9 t%ryUij4 gDZ[/?[|B|ƍ8_9񅏥<{Ĕ%KJ޽6o#:>*Y]DZs1J9^]&T; B0HI݊b18@B6#{6i:qѓ;t'8w$ףݼZz#^AZ*qi89 oU]Lw^B\kX"hp-+WJƄ b_*/m|Ҽsm x>j̈́1x4)s;v=X;͚M6 ߖUZB=pl-ERN<{w d ֮U]x;wcȒN<~nr˶mߘZn_4|GB+qZ TF6T>h.~ \dF ~6YFVҿlvlf{Ch:1EP]OcUdff tQRTj 1 O>6O ?u <s'[gUUl2nÉ"_QNYNu?5n8ﻯ]f F5kJ[owkǍsȪUvCt[Cg4>+U霋LtT\V/! cDMGX.9{<A Q7K4q{GzuIG 9Zz`ߤѥȨԵ*=lTPnLhc ŋs͒ݦM ᪂,0o- Sp z pHowg%}TC~Z>3M#f~8tȪ߉nݺ55iw"Ň 8 Rs4Alu? ҸZ4D$3JIRcծɎhY<Ք#2Dc9}Z$L4Tx77P2R5?L3sfQ#v ?觵mɺkS#xF Rh}jr:4n^f\,y ~t{.6?= O8QIcB n"pJX|~Wg*\M =X5k8+ tQTc :tȖ7oA_8G,ŲvڼHmiEҥ%ǥnQ]9TnϾBMh8܌ IKKSvwK5d̙ҢE IHs;wX,wjCV|֡Ɍ[#49sJ:XO1iCI3/]ZլIיb&&եǡxgpnB8]"!=qEᱎj‰*u/eJ9m9u{ u2:t`S IDAT˖-ҧO۷[Q5h#!mfQ<4x<)>) FVU-"`_K|̟Uk9Fw@Co0g}lҢhH1C0\|ӮPC1hn v8evKիґsvcƬ dJImٗ_~)*?G L*֭8N @t Yٳg:1n,c7.0"#`U5tV3d'AѤU+V&<9b3PcGrf#S1y}h +"=<`J7u7!C3ɼ*|7ЩO ,u|JK.WAN^M)3vGx8D:D8!t#](uل#H4ȑ#mD`C3Txq%čA-p-sPNln;ыGec]/f<(+(**)=˕=OZ>h!u#k`bDiƌhwQ0Q.sP>yRwS&BF:svQN&E 31=(Fф12PopUb%D Fτ(`8AƉwc=A\6nÁ__r:vYF|Mԩ)qQM-!gVhmkp~֯/^$TۣKcrzv'΁:tCjrQW_Lp@AC~@J<(֠npщǞ=]1Lx-[lEVkג OJgWg>UVkFΝK9cˑ#Gu8k֋@P!aQ: 솰|Aa:QuK冥=ג#3fXQbPXm[.˭ lؐ_%`dg=tӲöC~(=>c!;|uK4}De7TagV6桇 ej:tكS>'OҥKcǎ߽{3F $f@Pr.!\f}^OE |ءpa#.YvW=3w.:.` "~L0??_]4'1uJ v59fKaLq)WTt3;[ b.pԩrUWɉ*[ SLQ<-&`v@bRB[DT#ҌD(8pY< ͽG [lj |DvOT8I>piˤ.` Nf}D~}#H:x QS6D1D;$U+) x8Ǥ=j{:|eˤM6S3fOmڴ8F5< 9HCX͏W8WXrGHG|i[7j0"M͋ױ ӻt2HTv~~?wTQj Vd#ޒW$ہ @k[s/O>4WhB*Xo6;4f "V_i=Ӹ馛d׮]y3_(`Lթz=L˰ԾTU*3rx9 o \{ݹ[7;]Q,_UYB̞MZe{Wɺ'`q}Ę_:j, ,!CB*x8C% nؠߩoAxbqU\/^) !|gШRBDŽo8zϨ z:Cj8f ;4ϴt.-_\i L])1kIqmLȘP9(~*|m @G.P:a!&D.QRTLuPZ5%9jڴL0ٓY&[!sz[wg @/k֬:h2zp ?흿zVUyGt==ZN5[]+]~<_D98oy򾚦`Oi\qǼsٶs[TeKyѠ515:2kޓW>~-G;| &ls˺3/5Kb.R}+cf))v]vcʭުFGO?] |щ=W|D M b]ZeWi t :+q E(KÇgѣM8surŘ˿Eٓ& HPUqI%^5N4T$buJAtRX?7PM-HXiV{N´5j2) OO8Q?7|##G0\<ڵؐ\1ztl{&sh :v‡V!$)@D45}e˖C7qgϞރLlΌR#añRwI+VWǘS쎊p,\F@G8'w :D歕ɅոAdףT`&dNZp=(V,ߓ35M{w!vPm,ˌF~5jHo;\Ooҫq8B~"iFNC 8fMDP 5xv X^Njf}`lqha :"O=%r̙ g$(:^]ړxpq`ia?7"ńbs#HC^y%-6ɜdgHRh ܷeyr5E_.`|˅[6ɘQNn&ǣ?Lo~SzAxSƅ< 1xM"/(2~Ice/WRUK׮Uz @F:Fy)3NExc9cOtXa=w.~i?&2Bl"uJ6˶GDaIW! ;4ҿLk?Ln+**Rǿէ8 =I@ T}HС*eч93׶WG|Hgavj۶J&4ڷw(*nݜV1d:e*x#%I䥂r1$pDռ^)'Ґ`#3)` +i1AP%ΝE~Ѣo}u(rM 8zo#8-j`1-l]H8SA.f~|X8Q~6YCQQ@tr4?bQ~)S Pj#,PâW\!c!hd`?^,`hU L<]ziHtݩ_"!ѿDp1Q޽UlωF j ZjUjL9Ϟ h_V%8 (@DphPaۡ!$M0>`G$sN@y+WBڂQDԞ-/R/zr0FjQEbӔ_4Nby¨lݒr1qFݕ+h(Ä t9zL١Ԭ)y׋}޼4+E'0xpZ*..F =U.tZEɃ:ovl%u btclΙS".iԨgq? A=Pq8k0p Xo pC1jO@6" )R 6_xAzR"^i`b":% nSEwԍMj] ͞*Η5Z "k((QؗZ.F'>=HwA!!tSeIO('0Y3k- ?'ZNi]l0=4@ N`wU8&Tm""Ք "99UBiOvB~{ $+(G& Y]*z3';2L2My:HU n4de 8hPzQ~ octrb2H4#+ ?2XĠAdO>Knv,Y[w`sҥvIQ B0@d M %(_֬P(Zڳuu2bgGRo+43ٲ&/`)SBGgU uCu'V5lmiӪ7ӶVUvWGeXG@{S$]9.HqJ(eQPte;鈥 9U~){c.I7R}{t;5}DPQI#S|!sl&uJrz,.5ʩ$a%0QpD*-wzݡ0zdֿh& 6c5p:+¾BiE#qDZ,Ѓ?& g8hJ q-LRTÀq\ '-Z6.]\4IFa(v:"3Ig~ʩUB[<,6O9/Ut!2W49D|oh!Rxqmܭ3lM@׳DRn<r1~8a=vYRjQ0D! y8^=^yy]A3:@ͥlR2|P,E͆S4ҎkSJO* ?^8Wo$RL8A3 j&`pqVq c;&ugV]&$ @oÆ*DNN̚5KM&&LÇ˞={;VH@(yҢ*ˏ@_ec\ԾK#z,NLY_嗫H@Ɏyˆ/L[(dyf]G8@0l!wy:[G>}aÆ2qD>}SF`ECW_[ RUP_OϏ=v߫!P{}冣M+v_!xQ˗7;4㮸OrPfX履6̖2gܤW>.7W/# &"/ҿ?I:~ΐdB~c\ZX,I]VZH߾ǧL}ҤSNLHJqqbuU5~qO>D}QYfMLIo6Oa)][ÆGUOݼY2F={M1hܸ̟ xյ߹6 уJ =)`Q,ף\$;A@P@"RE.*D*W6 , j<`+8c2df7g̐Lַ{4 +ڶ^[):/BTų tT'C-zw]*/q*]<~Zb4xp[XѥKӝðaj9xUɪb˅T{#Gj.bA k.Ft X60[nLw D.'ϑoZ[5dz~]u9Ui;_u1ڿ$˕;Wy5Ĺ)8j5f'Bj律:z߾}# ~=喀˸qwZI?RrhxauDD?k2RWv0b_!bӘ6f{^J ]<F @}z(!eO<k%)1mHHB W_}o6͛>"??߷zMe9ؿeM`+gyDVH m#) fVD0P8|ؘ0/aߠ8`W.\뵏]ٟIch7űW]u[8$%VYGE۴ B "EiC i g 99hfz "Y'Zͨ /SFF<ΝӡS' `p'{\X*R2#!J6౴0&mLH1kбcǐ?^@BXȉ7wSri+SQA䨮m~UZm_e)] 4u=(ƫ?NJSRӺ/҃k.̛@1Ղ}ؾ)h,4n0 (<:\_hM(`qiiӦ w0):Um|x)`wYgZQu/Q\y@)N}nb~Ecl}S#bDla.Yt*I{gu#rA0Zш%K 77p8`r? YtlތhhEӟHx[x5ĩr|d+ F;{;-ދƃBrkgW^|EGx "uyI /ྋ*hf>!\55ȵF犸Wv0U{ovyO @RTF ֖EO[O$۴[g0{QAD( 4*hTץzy1~.rf+FDgM㵃 q'CfIEL۶)' (8Ije QsSj@Df s? u ߱};_XSfpa+p!79ǖ6OGĨqNs$>'WMb$ <;OEBWt|bʕ.fKsPDJk2ؓ`6W(޵ fv2 련}^O=abTD&˿pQҟ<m2p1_ne-RMUSҦ`n\Ul5P@ @BEXWj'U1٤ JVvɪtj޽5Y)s۶\zSQPP`{7Vpvd@M* ':#FXظQ_OB(sױ#N!=/O5{' ^/s[t Sqv|Afɽ=l_,b5!Õ%ˑgSU9%w23& 7T_UBed/eܟZ:ot4jBJKݥ6F]$78 ؓ[ 3mPV#fToldžݻMa[4augmȱ`_>l6mưa(;T8'/>MC5ew֛ce4#G2."tͲ'\/*e<zˤfe4p [>Rlٽ|> #4$"mmо^',֪74&QըlS9knbmwbO4v6?Q+3ދZ%!Vmqn ohF$MB_ULd /#ˬlg #q~›J6~ƣ e ˪c$u@ɢE(3֜U`7Gd_됊qtN_9Ь )d-ڲDzKJS*d?; ơCh F ˒ щZ bE))CNV`8 .;3=*[xtCz~\?p^P!%6 kWҞ'"BYߙ3Y[gVF7yJ.^}PPN\[ X@Dz o4oDޘ`f t-yy}$a}:zkWLa*HY.ٴ )Kv0 ǤX a?8We8׏>2ukDo7Ԣ] !VT4ê?+[*Xr0QWoծڍCzʬCmWj՜ִq#,Ç#Qٿ%I.jŭ Σ&&U oG׌Xa^ hA @D*{z_W3tUh+V }0Oz_;pŞ*Tdff"==]`3)Y^(++ )ٲlxZZ{^`ys&l<̠B|TT ?\Y`{NPPF#RM j-"]xiS1‡~;7T]WK:ٓ:F0[\a_ "7EgƽQ#;#Y[VOs@FЄl0Y,RmH_/3}*b,MsyY>?ǕW^ bocxidھ=Ȭn 8#?ᰲgƈ @##8?~~" 2"nin5R)eGnkXSZG^\p .K5EL޲eK_ܽ$>;uT&҄ @Zs+9E˷/)Eܻ h`p-lX޽;v܉?/^;wɽ0_ѨhN4hرcxdjT9s@$b$Cf0֡_}zٴi="YxRL:&MBNN^z%|Vş?RUU\lɕppF;^ih'|S~gBHe 8k,o*Tڐ|㗸~g P Ā8\uHK.QKS4Ȗ01G\.V+& ## ?ǭ`p1F0*t;{8жm#w~>RdfUy$ݔKS9D9RcyD8<ٳgMյkW0UgyZw*Vl[BwzNXq(Æun~O?m'Xa29#?[0t=.A4PۗI;ػ(h)ƈx}ƍZ,M0-}Uv!ɓa ޫEL$iS&QkeP?ɓygľg߾VlfBA\M4"##=; D(3~-m&#?RbTR6gl9[I]d@V)F*$}ھ^ t;6Mş$&UH>8;ˠ X"`ر5joΣ3Ά"(Y? W)<8:|^$1mwGɍP(*;o3@4s6S"WX:@$31\r%8~8z)%e9K[Ɣ)S*?ῇB55޷]hnsL92%+-큙3Sjsr]gcWn1K/^u~#.^|=b?zՕF"s%H}>X5mł'=oǰaf{:kD8GTKDq)TB:u zg2͛7W_}:~j<*_??{3Un4~/[kd}̘4t_=,Sš1wdw ϭ][QDQژ1p5izUG yر^x}jF&1bşVU%T?]0!%K;qD@zz~'yd"8ez s. xkO{9%k"mHlG˖<05`Μ2LYY+?)ȏQ dUK>[ g5ƫ~" Eeb>تU+WŭJ,e2]Mwx?{v(]XyJG_ysD?tQ#]4q6-!'.EQ՜LEe)cic-fˌdFft?IuuHPPZ׮eGF n]gxO= WcD4{'z7UTh0?lތ?W3I!GEbڸ~63#F2nz >bv1lBTC0L=`{,(]PݦGWJI|OB(~D<ҥfچQ飔I!d&|7)wXvLFBZn>  u`e HT'?fBHF h,Yp88?B}!zzz AOw1y}jeϖx.Q=IΝS/{aGuDD_?a"R~c6!$ld)a֭j3?,IYіѵګ" Fnfe_@o[|iӢj b$Ѿ=\YYk-',mj;fBHBFZ.&ƌ;wip"ߘ)1 :w0:gf :}(;m={2Α)[Q#FbFꌠD J(IxwNS=;v}lJkpK2v_Ґ R6t(Oj`!!m7u1ñsG'{d;f F,wXf oѬ5={<ޥ ,&_ӥ=XG?BCn7l&&裶LL|Xc6@ +o'pu  vp(S眜/U3gYeoU˸br^2a@_ }4ƈy'GGGG$|xPH{9ʏ2ס!>!scٔyRR!T wbCܿt HH9d:`Aqq.fy)Zw/$B|Ć`y%z"_)+"e`02 N!u<hН?ോi ,0ţpЪaB &0ᾟ?Ț9,` ]7bȐfQyOغuAOrL۶b!ĸ1t>c`@\߅GJwΦ  LPdIaJ%m `u(*BH@Au Y'øwo |X/2 K6*V>6yy(Pz8`P3@<KW@(7^­?yN#Qu^c6!F䮲<&M AdGoOCΟ<L$*#B66j_`y @>kK"ϒ'cfL?)Xr%O֭[篺٤ J.UF"b#Qd7?c @ttzj }z{w*%2CTS'Q4(߶m2F#ƈ @+8u1L6 p8~g)HGٴ)A!$,[ wt:O5xnT˸j{בBHӉ|i g)ms͜ ׫=EBGyB(IT &`X|ɿq&J/Vգi=T1b_]I1#B(9ϒJGJ=_~i;0QaEJyi vQ_c (pA\s5<*qL4lg9@)ݸPckk21%H!Hp`Ph^BHKKSFm 5kXK!h TqHaEfƴi0fJj ÉD# @T4b$8uݿ?ΝTPk1B5ƈ1" cT=bv-LwWip8`Zcd2W c銋aڶM3<#ƈPBDE؁%KfT AN^xG]cJBHHR x.N #E Ûo,HH_B$&4AҥH<c*&K<ؾ};\.zhfp9sFΉG!$.h|HaJ{?}Z='˾b2b,^XupcdڴIs<#ƈh#C1>R{d}K/UK^{?_ɪU#k1"$/b+,DF>jo 4kX4PPfG!0[n1`\wu<2D͙QԞ>yfڼ|wΝ;ѬY3MShtg"mxmv;7šÃM!A>^<*aBSQj*nwѷ5#kq5p5jEBI8HŋU`@6fگIL_4B(2FUjesj^Ν>p ǎ#)HB<#ƈh nΚ5 SN呪kA`-ޤݜ%/ isʸo\W^[.Ff'N֭G1hRgDdB-&L@ʚ5H6,6s8ZbL !(BPf-#Fzj3.' @t)jQ%!PBk0:rzH;7eHyIXPHW)+V7빍BQ$ؓ.x1o(S+Wy2cDg11TXDg~9%u%0\nhޜyD#ƈPB];˘'}VC(BI@LaӧGB$hVr_eMB(I^(1Y gXȨA#1F$A@& )F"Q1#ƈhB!PB! @B!BH3Fc1FPr0!c1F!B!B!B!Pc1F!Lc1FcD !B$BIJ!y꫸x"ݻw!B1#CSQƈ1b#ƈ{dee{Μ9<2L#ƈ1b#B*~mto~߿GB!DP?} wYY !BHyHt:}"n#55ފg۷f}EEE0`ϋy< %BG{u놛oY']w`N{yD4%gϞ߲e:.\.̃VI߻w BoVA_WaR<8pj;g*<?}T+#M| xڵnYfb#d05˰|r8%%%5y Ke]jGߕ#Æ G$< eb%bc6elݺ\VBe @YNvrssQZZZ}&Mܹs~/q5駟bȑ/Ã9~_&9UV8r}) w< u摶2dOVBeHsD}^OgϞO%̣>K/Tݙ{クꪫ ʠ!JW_} AꮺQF#QyuIC^0_W{@d[ɜGD0ZޮzJY2$xf1G#!5Ǩ_dʕlQRR^ ꫯdG\a1Bͣ^gBHnj+Vy&K~r-w;wdPG\aPיGP0Xh|A5^pk֬Avvv{nP>֭C֭ѰaCyT) 5{yDHBbSܹsJVzߤI0w IS1c;P_jo֭[XQ<.WG$o x{N\qKIH8HI>Zv5 1سSK@z>$0qOg{/f~8B( !$ax}`HϬд) <9{hl33E. l8B.BH(|иO_L !B4Mǎ/1K > |cAIx8H!й3p={{ !$Aѹ\!B .B!PB! @B!BH!B( !B !B$B!B!B!PB! @B!6#"RrIENDB`ruby-svg-graph-1.0.5/images/timeseries.svg000066400000000000000000000747421164403311000205400ustar00rootroot00000000000000 1973 1978 1983 1988 1993 1998 2003 0 2 4 6 8 10 12 14 16 18 01/11/74 7 7 06/17/74 11 11 09/01/85 2 2 09/01/88 1 1 01/15/95 13 13 09/11/01 9 9 04/13/04 11 11 08/01/73 18 18 10/21/76 15 15 01/11/80 4 4 04/03/83 14 14 06/23/86 6 6 09/13/89 12 12 12/03/92 6 6 02/24/96 17 17 05/16/99 12 12 08/05/02 7 7 01/01/78 5 5 01/01/83 13 13 01/01/93 10 10 01/01/03 5 5 Plot Time Units Ice Cream Ice Cream Cones Sprinkles ruby-svg-graph-1.0.5/images/timeseries.svgz000066400000000000000000000062371164403311000207240ustar00rootroot00000000000000.}@timeseries.svg]o6޿u %G):AQ t],_<ة\/)9EƊ&>D(x32Kwߜ|??G_O?x3q/~87uC@*>#ڱq;E07ygGttT )Op p:?i~}:?ʞ]}jad 8v</x݇e2ɓ'6 ;:u9>'9.˒٬I/e旋%,g8KlrXj߃yr{]KTd\fmX׫n>]]L.r`i.fLVx*~ [?͒e:}wY+Y=r2ϴnNGۙˆ:=\l.BD+}1F`z:ü/Jǟ_Vfr59u /EG>^ KV O{ȓREFIg/,+D]mO׋IYr Um].8KY@X+ܤ#_we:o߂L8Kڇb<\dv :z 'i5Ϣ)!!G9EP? 'WW׻t[>ڲ:Ok0b>zy>[mx$,GX<ebaBaL{jxV-ՊH2_B *2*AFcPƔKDdj[pI C(9 -qGP=ܘR""%cLT=TlY=?ي,nU&izG3lpWA/"^Qtjo^W!D"K3bA#J#n 5D ׈q_6B=( -*o6U%yoy8m3M="%)ыC%0`F"=g/'fbI& "*$ߤ\U1rC!L_!dF2)1bZ7PHF&wrBiYy}y3./f 0eN'"ZK}],*HVŌ-{Xu8X$36lkpr0lXwN(#* 3(_(AN8B tԬR$jkYi(z,RFDZS FB_*:\c$(1TXOޔV4M1Bq=hUD0QHO8LQǬ;kyyz[AǾAvks1`F` 6✞,93i:wM/ŌmC2: "Ɓ u`.y`3Vbψ|/Č={*!@\%B TuK sGYAS)V}BBIb{DhYgq]ĸ>?ۄvb''3$^ul[3sUYLݧY%f:NQBHhSd ci`!8uTY>Yǣwb;Sej^],&U%ߦ?y1ʒ<__'x#k-~ !y4Rε: *eLv+۠jlz;z+K_"KkXc{D Jv kX1/H-)gsZnDĉ>c!*O"N+:֨ Cz>k(}a{^E_0E3_ C]^"P%~TseğH W"P6 ,oNN};Xk`z؛hlJɃ@yu㤬|" W,V >Ch@K B`m2#{܍@`E ŤuX)Z@ׇ~ r7= ݢ- m}Vhj>@~bʃaÇ*LiQcB*JBPEcDbxJK XQkU&>YR3X_Q8VҬӄ2kudw `.(*8Zުd{*{>H1}rx0šU9SKb"0`5Se)P{Lb* SV}>9|~ jXY3O*R$g{6knmV%5wV 6ܧz;{0x="hȣkt x8$ Qc= /"0*4mY]bj`vv{>/*O&Dպ"÷kBAX!\Ga[St.nZNv0L1=rPEe& wLnQz5x{TEU0E}PuUÄ92dXrJGo% V0Q\ oU0)):qeiZ>O{{aգFNkG8.cp~= /b->ծƣ֪!/ mvW?X}20hj9"F`:ň#ƂnLщ6$c;Z9?b:FO &c2<}SSt|Gu~Z <'+q 6!$JGءl6~ֶ=il]nz{?J;lz<_V qmnLѽfݑ>j V~x@BzM r+00XEk tp|u#LUS!On H'o-h7CiD9 ۠+0D ^`0EZкL˵j qޑ`%$67ZY6e7t~du}?/"_fxjˆg*X+ɹ4}ӛfzمE^

SVG::Graph @ANT_VERSION@ @ANT_DATE@ Sean Russell serATgermaneHYPHENsoftwareDOTcom What is it? SVG:::Graph is a pure Ruby library for generating charts, which are a type of graph where the values of one axis are not scalar. SVG::Graph has a verry similar API to the Perl library SVG::TT::Graph, and the resulting charts also look the same. This isn't surprising, because SVG::Graph started as a loose port of SVG::TT::Graph, although the internal code no longer resembles the Perl original at all. SVG::Graph isn't only for charts. With version 0.4.0, support for Plot charts was added, so scalar data may now be charted. What's new? You can find that out in the change log. Screenshot Gotta have one. Here's a PNG version of the above image for the SVG-challenged: The SVG version is much cooler. More screenshots are available. Why use it? If you're a Ruby programmer, you can't use SVG::TT::Graph directly. This package is for you. If you want one of the features that this package has that the Perl version doesn't, and you don't mind doing your graph code in Ruby, then this package is for you. If you want to generate SVGs that aren't CSS styled (and work with more SVG renderers), use this package. If you're a Perl wienie or you have something against Ruby, use SVG::TT::Graph. If it doesn't provide you with the features you want, hack it yourself. Getting it The source archives contain the example script(s) and an install script. Documentation must be downloaded separately. Gzipped sources Gzipped documentation Zipped sources Zipped documentation Why? The SVG::TT:Graph package is just fine. It lacked a couple of features that I wanted, and it was in Perl Perl is from Hell. The language is bad enough, but working with the package system -- CPAN, etc. -- is an exercize in masochism. I'd rather chew on glass. , so I re-implemented it. SVG::Graph is not a straight port of SVG::TT::Graph, because the Perl version uses templates, and I don't believe that this is a job for templates. Templates are great when you have lots of context containing a little bit of evaluated code, but the Graph classes are mostly code with a little SVG; therefore, it makes more sense (and is a hell of a lot more readable) to have the SVG embedded in the code rather than vice versa. However, some of the logic was directly cut and paste out of SVG::TT::Graph, as was the entire CSS code and chunks of the documentation, and the part of the API that the user sees is almost identical to the original. In version 0.2.0 I completely refactored the code to make it more modular. As a result, the core Graph class contains most of the graph drawing primatives, and the specializations (Bar, BarHorizontal, Line, Pie, etc.) only draw the plot itself. This results in many fewer lines of code, and that code is more robust. SVG::TT::Graph re-implemented most of the code for every plot type, whereas SVG::Graph reuses most of the code. I intend SVG::Graph to have more features than SVG::TT::Graph, which is the main reason for the re-implementation. One such feature is the ability to change the stacking of the data sets. For instance, SVG::Graph allows you to place the data bars from multiple data sets on the same graph side-by-side, rather than overlapping. SVG::Graph also has an option to disable CSS styling of the graphs. This has a number of consequences, both positive and negative. The SVGs generated can be larger without CSS, and they are harder to dynamically style (you can't use your own styles with CSS disabled). However, a number of SVG renderers do not support or do not fully support CSS in SVG (KSVG in Konqueror and librsvg, for instance). Adobe and Batik both support CSS, so if those are your targets, you can use CSS. For maximum compatability, disable CSS with the :no_css attriibute. The currently supported graph types are: Bar BarHorizontal Line Pie Plot (this is scalar data) TimeSeries (also scalar data, with one axis being dates) Schedule (scalar time range data, with non-scalar Y axis) Dependancies REXML 3.0+, or Ruby 1.8+. If you want to use the SVG compression feature, which generates zlib compressed SVGs, you must also have the Ruby zlib library installed. Usage There's API documentation available, either online or for download. Or, you can use rdoc to generate it yourself. The API documentation is pretty comprehensive (Leo Lapworth did a really good job on the docs, and I stole most of them), and there's an example application in the main directory. Notes SVG feature support Some SVG renderers (librsvg) do not understand CSS. This is a problem with Perl's SVG::TT::Graph, because all of the styling of the charts it generates is done with CSS. SVG::Graph is able to inline the styles, rendering graphs that are compatible with more viewers. The Pie chart uses the feGaussianBlur filter to generate the shadow, whiich is also unsupperted by some renderers (librsvg). The shadow will not look as smooth on those displays, although it will render. Sizes The bar charts PNGs tend to be smaller than the uncompressed SVGs. In the best case (for PNG), the PNG is 66% of the size of the uncompressed SVG. For line and pie charts, which are a little more visually complex, the SVGs are smaller than the PNGs. In the best case, the PNG is 130% the size of the SVG. In the worst case, the PNG is 220% the size of the uncompressed SVG. The compressed SVGs are always much smaller than the PNGs. In the best case (a vertical bar chart), the PNG is 4.4 times the size of the compressed SVG, and in the worst case (for a pie chart), it is over 12 times the size of the compressed SVG. These values were measured with inline styled SVGs, and the PNGs were reduced to 8-bit, indexed images, and were crushed with pngcrush. Bugs SVG::Graph probably contains bugs. In fact, it is probably mostly bugs, held together with some working code around them. In particular, SVG::Graph is probably really intolerant of *your* bugs, and is likely entirely unhelpful in helping you track down problems. We can only pray that this situation improves Prayer is much more effective when accompanied by sums of cash sent to the author. . There is a bug and feature request database; use this, and please create a user account if you don't have one, so that I can email you if I have questions. Alternatively, post anonymously, and check the database often. Repository The sourcecode repository is a Subversion repository, located here.
ruby-svg-graph-1.0.5/install.rb000066400000000000000000000113001164403311000163510ustar00rootroot00000000000000#! /usr/bin/env ruby ################################################################################ # # # Name: install.rb # # Author: Sean E Russell # # Version: $Id: $ # Date: *2002-174 # # Description: # # This is a generic installation script for pure ruby sources. Features # # include: # # * Clean uninstall # # * Installation into an absolute path # # * Installation into a temp path (useful for systems like Portage) # # * Noop mode, for testing # # To set for a different system, change the SRC directory to point to the # # package name / source directory for the project. # # # ################################################################################ # CHANGE THIS SRC = ['SVG' ] STRIP = '' SETVERSION = 'SVG' SRCVERSION = '@ANT_VERSION@' DATE = '@ANT_DATE@' ################################################################################ # CHANGE NOTHING BELOW THIS LINE Dir.chdir ".." if Dir.pwd =~ /bin.?$/ require 'getoptlong' require 'rbconfig' require 'ftools' require 'find' opts = GetoptLong.new( [ '--uninstall', '-u', GetoptLong::NO_ARGUMENT], [ '--destdir', '-d', GetoptLong::REQUIRED_ARGUMENT ], [ '--target', '-t', GetoptLong::REQUIRED_ARGUMENT ], [ '--concurrent', '-c', GetoptLong::NO_ARGUMENT], [ '--help', '-h', GetoptLong::NO_ARGUMENT], [ '--noop', '-n', GetoptLong::NO_ARGUMENT]) destdir = File.join(Config::CONFIG['sitedir'], "#{Config::CONFIG['MAJOR']}.#{Config::CONFIG['MINOR']}") uninstall = false prepend = nil help = false opts.each do |opt,arg| case opt when '--concurrent' CONCURRENT = true when '--destdir' prepend = arg when '--uninstall' uninstall = true when '--target' destdir = arg when '--help' help = true when '--noop' NOOP = true end end destdir = File.join prepend, destdir if prepend def transmogrify( dir ) dir = dir.sub( /^#{STRIP}\//, '' ) if defined? CONCURRENT dir.sub( /#{SETVERSION}/, "#{SETVERSION}-#{SRCVERSION}") else dir end end if help puts "Installs #{SRC.inspect}.\nUsage: #$0 [[-u] [-n] [-c] [-t |-d ]|-h]" puts " -u --uninstall\n Uninstalls the package" puts " -c --concurrent\n Install concurrently, IE, into" for d in SRC puts " #{destdir}/#{transmogrify( d )}" end puts " The default behavior is to upgrade the current installation," puts " by installing into" for d in SRC puts " #{destdir}/#{transmogrify( d )}" end puts " -t --target\n Installs the software at an absolute location, EG:" puts " #$0 -t /usr/local/lib/ruby" puts " will put the software directly underneath /usr/local/lib/ruby;" for d in SRC puts " /usr/local/lib/ruby/#{transmogrify( d )}" end puts " -d --destdir\n Installs the software at a relative location, EG:" puts " #$0 -d /tmp" puts " will put the software under tmp, using your ruby environment." for d in SRC puts " /tmp#{destdir}/#{transmogrify( d )}" end puts " -n --noop\n Don't actually do anything; just print out what it" puts " would do." exit 0 end def install destdir puts "Installing in #{destdir}" begin for src in SRC Find.find(src) { |file| dstfile = transmogrify( file ) next if file =~ /CVS|\.svn/ dst = File.join( destdir, dstfile ) if defined? NOOP puts ">> #{dst}" if file =~ /\.rb$/ else File.makedirs( File.dirname(dst) ) File.install(file, dst, 0644, true) if file =~ /\.rb$/ end } end rescue puts $! end end def uninstall destdir puts "Uninstalling in #{destdir}" begin puts "Deleting:" dirs = [] Find.find(File.join(destdir,SRC)) do |file| file.sub!( /#{SRC}/, "#{SRC}-#{SRCVERSION}") if defined? CONCURRENT if defined? NOOP puts "-- #{file}" if File.file? file else File.rm_f file,true if File.file? file end dirs << file if File.directory? file end dirs.sort { |x,y| y.length <=> x.length }.each { |d| if defined? NOOP puts "-- #{d}" else puts d Dir.delete d end } rescue end end if uninstall uninstall destdir else install destdir end ruby-svg-graph-1.0.5/lib/000077500000000000000000000000001164403311000151315ustar00rootroot00000000000000ruby-svg-graph-1.0.5/lib/SVG/000077500000000000000000000000001164403311000155705ustar00rootroot00000000000000ruby-svg-graph-1.0.5/lib/SVG/Graph/000077500000000000000000000000001164403311000166315ustar00rootroot00000000000000ruby-svg-graph-1.0.5/lib/SVG/Graph/Bar.rb000066400000000000000000000101701164403311000176610ustar00rootroot00000000000000require 'rexml/document' require 'SVG/Graph/Graph' require 'SVG/Graph/BarBase' module SVG module Graph # === Create presentation quality SVG bar graphs easily # # = Synopsis # # require 'SVG/Graph/Bar' # # fields = %w(Jan Feb Mar); # data_sales_02 = [12, 45, 21] # # graph = SVG::Graph::Bar.new( # :height => 500, # :width => 300, # :fields => fields # ) # # graph.add_data( # :data => data_sales_02, # :title => 'Sales 2002' # ) # # print "Content-type: image/svg+xml\r\n\r\n" # print graph.burn # # = Description # # This object aims to allow you to easily create high quality # SVG[http://www.w3c.org/tr/svg bar graphs. You can either use the default # style sheet or supply your own. Either way there are many options which # can be configured to give you control over how the graph is generated - # with or without a key, data elements at each point, title, subtitle etc. # # = Notes # # The default stylesheet handles upto 12 data sets, if you # use more you must create your own stylesheet and add the # additional settings for the extra data sets. You will know # if you go over 12 data sets as they will have no style and # be in black. # # = Examples # # * http://germane-software.com/repositories/public/SVG/test/test.rb # # = See also # # * SVG::Graph::Graph # * SVG::Graph::BarHorizontal # * SVG::Graph::Line # * SVG::Graph::Pie # * SVG::Graph::Plot # * SVG::Graph::TimeSeries class Bar < BarBase include REXML # See Graph::initialize and BarBase::set_defaults def set_defaults super self.top_align = self.top_font = 1 end protected def get_x_labels @config[:fields] end def get_y_labels maxvalue = max_value minvalue = min_value range = maxvalue - minvalue top_pad = range == 0 ? 10 : range / 20.0 scale_range = (maxvalue + top_pad) - minvalue scale_division = scale_divisions || (scale_range / 10.0) if scale_integers scale_division = scale_division < 1 ? 1 : scale_division.round end rv = [] maxvalue = maxvalue%scale_division == 0 ? maxvalue : maxvalue + scale_division minvalue.step( maxvalue, scale_division ) {|v| rv << v} return rv end def x_label_offset( width ) width / 2.0 end def draw_data minvalue = min_value fieldwidth = field_width unit_size = (@graph_height.to_f - font_size*2*top_font) / (get_y_labels.max - get_y_labels.min) bargap = bar_gap ? (fieldwidth < 10 ? fieldwidth / 2 : 10) : 0 bar_width = fieldwidth - bargap bar_width /= @data.length if stack == :side #x_mod = (@graph_width-bargap)/2 - (stack==:side ? bar_width/2 : 0) bottom = @graph_height field_count = 0 @config[:fields].each_index { |i| dataset_count = 0 for dataset in @data # cases (assume 0 = +ve): # value min length # +ve +ve value - min # +ve -ve value - 0 # -ve -ve value.abs - 0 value = dataset[:data][i] left = (fieldwidth * field_count) length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size # top is 0 if value is negative top = bottom - (((value < 0 ? 0 : value) - minvalue) * unit_size) left += bar_width * dataset_count if stack == :side @graph.add_element( "rect", { "x" => left.to_s, "y" => top.to_s, "width" => bar_width.to_s, "height" => length.to_s, "class" => "fill#{dataset_count+1}" }) make_datapoint_text(left + bar_width/2.0, top - 6, value.to_s) dataset_count += 1 end field_count += 1 } end end end end ruby-svg-graph-1.0.5/lib/SVG/Graph/BarBase.rb000066400000000000000000000055761164403311000204720ustar00rootroot00000000000000require 'rexml/document' require 'SVG/Graph/Graph' module SVG module Graph # = Synopsis # # A superclass for bar-style graphs. Do not attempt to instantiate # directly; use one of the subclasses instead. # # = Author # # Sean E. Russell # # Copyright 2004 Sean E. Russell # This software is available under the Ruby license[LICENSE.txt] # class BarBase < SVG::Graph::Graph # Ensures that :fields are provided in the configuration. def initialize config raise "fields was not supplied or is empty" unless config[:fields] && config[:fields].kind_of?(Array) && config[:fields].length > 0 super end # In addition to the defaults set in Graph::initialize, sets # [bar_gap] true # [stack] :overlap def set_defaults init_with( :bar_gap => true, :stack => :overlap ) end # Whether to have a gap between the bars or not, default # is true, set to false if you don't want gaps. attr_accessor :bar_gap # How to stack data sets. :overlap overlaps bars with # transparent colors, :top stacks bars on top of one another, # :side stacks the bars side-by-side. Defaults to :overlap. attr_accessor :stack protected def max_value @data.collect{|x| x[:data].max}.max end def min_value min = 0 if min_scale_value.nil? min = @data.collect{|x| x[:data].min}.min min = min > 0 ? 0 : min else min = min_scale_value end return min end def get_css return < 500, # :width => 300, # :fields => fields, # }) # # graph.add_data({ # :data => data_sales_02, # :title => 'Sales 2002', # }) # # print "Content-type: image/svg+xml\r\n\r\n" # print graph.burn # # = Description # # This object aims to allow you to easily create high quality # SVG horitonzal bar graphs. You can either use the default style sheet # or supply your own. Either way there are many options which can # be configured to give you control over how the graph is # generated - with or without a key, data elements at each point, # title, subtitle etc. # # = Examples # # * http://germane-software.com/repositories/public/SVG/test/test.rb # # = See also # # * SVG::Graph::Graph # * SVG::Graph::Bar # * SVG::Graph::Line # * SVG::Graph::Pie # * SVG::Graph::Plot # * SVG::Graph::TimeSeries # # == Author # # Sean E. Russell # # Copyright 2004 Sean E. Russell # This software is available under the Ruby license[LICENSE.txt] # class BarHorizontal < BarBase # In addition to the defaults set in BarBase::set_defaults, sets # [rotate_y_labels] true # [show_x_guidelines] true # [show_y_guidelines] false def set_defaults super init_with( :rotate_y_labels => true, :show_x_guidelines => true, :show_y_guidelines => false ) self.right_align = self.right_font = 1 end protected def get_x_labels maxvalue = max_value minvalue = min_value range = maxvalue - minvalue top_pad = range == 0 ? 10 : range / 20.0 scale_range = (maxvalue + top_pad) - minvalue scale_division = scale_divisions || (scale_range / 10.0) if scale_integers scale_division = scale_division < 1 ? 1 : scale_division.round end rv = [] maxvalue = maxvalue%scale_division == 0 ? maxvalue : maxvalue + scale_division minvalue.step( maxvalue, scale_division ) {|v| rv << v} return rv end def get_y_labels @config[:fields] end def y_label_offset( height ) height / -2.0 end def draw_data minvalue = min_value fieldheight = field_height unit_size = (@graph_width.to_f - font_size*2*right_font ) / (get_x_labels.max - get_x_labels.min ) bargap = bar_gap ? (fieldheight < 10 ? fieldheight / 2 : 10) : 0 bar_height = fieldheight - bargap bar_height /= @data.length if stack == :side y_mod = (bar_height / 2) + (font_size / 2) field_count = 1 @config[:fields].each_index { |i| dataset_count = 0 for dataset in @data value = dataset[:data][i] top = @graph_height - (fieldheight * field_count) top += (bar_height * dataset_count) if stack == :side # cases (assume 0 = +ve): # value min length left # +ve +ve value.abs - min minvalue.abs # +ve -ve value.abs - 0 minvalue.abs # -ve -ve value.abs - 0 minvalue.abs + value length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size left = (minvalue.abs + (value < 0 ? value : 0)) * unit_size @graph.add_element( "rect", { "x" => left.to_s, "y" => top.to_s, "width" => length.to_s, "height" => bar_height.to_s, "class" => "fill#{dataset_count+1}" }) make_datapoint_text( left+length+5, top+y_mod, value, "text-anchor: start; " ) dataset_count += 1 end field_count += 1 } end end end end ruby-svg-graph-1.0.5/lib/SVG/Graph/DataPoint.rb000066400000000000000000000017621164403311000210470ustar00rootroot00000000000000class DataPoint OVERLAY = "OVERLAY" unless defined?(OVERLAY) DEFAULT_SHAPE = lambda{|x,y,line| ["circle", { "cx" => x, "cy" => y, "r" => "2.5", "class" => "dataPoint#{line}" }] } unless defined? DEFAULT_SHAPE CRITERIA = [] unless defined? CRITERIA def DataPoint.configure_shape_criteria(*matchers) CRITERIA.push(*matchers) end def DataPoint.reset_shape_criteria CRITERIA.clear end def initialize(x, y, line) @x = x @y = y @line = line end def shape(description=nil) shapes = CRITERIA.select {|criteria| criteria.size == 2 }.collect {|regexp, proc| proc.call(@x, @y, @line) if description =~ regexp }.compact shapes = [DEFAULT_SHAPE.call(@x, @y, @line)] if shapes.empty? overlays = CRITERIA.select { |criteria| criteria.last == OVERLAY }.collect { |regexp, proc| proc.call(@x, @y, @line) if description =~ regexp }.compact return shapes + overlays end end ruby-svg-graph-1.0.5/lib/SVG/Graph/Graph.rb000066400000000000000000000734721164403311000202340ustar00rootroot00000000000000begin require 'zlib' @@__have_zlib = true rescue @@__have_zlib = false end require 'rexml/document' module SVG module Graph # === Base object for generating SVG Graphs # # == Synopsis # # This class is only used as a superclass of specialized charts. Do not # attempt to use this class directly, unless creating a new chart type. # # For examples of how to subclass this class, see the existing specific # subclasses, such as SVG::Graph::Pie. # # == Examples # # For examples of how to use this package, see either the test files, or # the documentation for the specific class you want to use. # # * file:test/plot.rb # * file:test/single.rb # * file:test/test.rb # * file:test/timeseries.rb # # == Description # # This package should be used as a base for creating SVG graphs. # # == Acknowledgements # # Leo Lapworth for creating the SVG::TT::Graph package which this Ruby # port is based on. # # Stephen Morgan for creating the TT template and SVG. # # == See # # * SVG::Graph::BarHorizontal # * SVG::Graph::Bar # * SVG::Graph::Line # * SVG::Graph::Pie # * SVG::Graph::Plot # * SVG::Graph::TimeSeries # # == Author # # Sean E. Russell # # Copyright 2004 Sean E. Russell # This software is available under the Ruby license[LICENSE.txt] # class Graph include REXML # Initialize the graph object with the graph settings. You won't # instantiate this class directly; see the subclass for options. # [width] 500 # [height] 300 # [show_x_guidelines] false # [show_y_guidelines] true # [show_data_values] true # [min_scale_value] 0 # [show_x_labels] true # [stagger_x_labels] false # [rotate_x_labels] false # [step_x_labels] 1 # [step_include_first_x_label] true # [show_y_labels] true # [rotate_y_labels] false # [scale_integers] false # [show_x_title] false # [x_title] 'X Field names' # [show_y_title] false # [y_title_text_direction] :bt # [y_title] 'Y Scale' # [show_graph_title] false # [graph_title] 'Graph Title' # [show_graph_subtitle] false # [graph_subtitle] 'Graph Sub Title' # [key] true, # [key_position] :right, # bottom or righ # [font_size] 12 # [title_font_size] 16 # [subtitle_font_size] 14 # [x_label_font_size] 12 # [x_title_font_size] 14 # [y_label_font_size] 12 # [y_title_font_size] 14 # [key_font_size] 10 # [no_css] false # [add_popups] false def initialize( config ) @config = config @data = nil self.top_align = self.top_font = self.right_align = self.right_font = 0 init_with({ :width => 500, :height => 300, :show_x_guidelines => false, :show_y_guidelines => true, :show_data_values => true, # :min_scale_value => 0, :show_x_labels => true, :stagger_x_labels => false, :rotate_x_labels => false, :step_x_labels => 1, :step_include_first_x_label => true, :show_y_labels => true, :rotate_y_labels => false, :stagger_y_labels => false, :scale_integers => false, :show_x_title => false, :x_title => 'X Field names', :show_y_title => false, :y_title_text_direction => :bt, :y_title => 'Y Scale', :show_graph_title => false, :graph_title => 'Graph Title', :show_graph_subtitle => false, :graph_subtitle => 'Graph Sub Title', :key => true, :key_position => :right, # bottom or right :font_size =>12, :title_font_size =>16, :subtitle_font_size =>14, :x_label_font_size =>12, :y_label_font_size =>12, :x_title_font_size =>14, :y_label_font_size =>12, :y_title_font_size =>14, :key_font_size =>10, :no_css =>false, :add_popups =>false, }) set_defaults if self.respond_to? :set_defaults init_with config end # This method allows you do add data to the graph object. # It can be called several times to add more data sets in. # # data_sales_02 = [12, 45, 21]; # # graph.add_data({ # :data => data_sales_02, # :title => 'Sales 2002' # }) def add_data conf @data = [] unless (defined? @data and !@data.nil?) if conf[:data] and conf[:data].kind_of? Array @data << conf else raise "No data provided by #{conf.inspect}" end end # This method removes all data from the object so that you can # reuse it to create a new graph but with the same config options. # # graph.clear_data def clear_data @data = [] end # This method processes the template with the data and # config which has been set and returns the resulting SVG. # # This method will croak unless at least one data set has # been added to the graph object. # # print graph.burn def burn raise "No data available" unless @data.size > 0 calculations if methods.include? 'calculations' start_svg calculate_graph_dimensions @foreground = Element.new( "g" ) draw_graph draw_titles draw_legend draw_data @graph.add_element( @foreground ) style data = "" @doc.write( data, 0 ) if @config[:compress] if @@__have_zlib inp, out = IO.pipe gz = Zlib::GzipWriter.new( out ) gz.write data gz.close data = inp.read else data << ""; end end return data end # Set the height of the graph box, this is the total height # of the SVG box created - not the graph it self which auto # scales to fix the space. attr_accessor :height # Set the width of the graph box, this is the total width # of the SVG box created - not the graph it self which auto # scales to fix the space. attr_accessor :width # Set the path to an external stylesheet, set to '' if # you want to revert back to using the defaut internal version. # # To create an external stylesheet create a graph using the # default internal version and copy the stylesheet section to # an external file and edit from there. attr_accessor :style_sheet # (Bool) Show the value of each element of data on the graph attr_accessor :show_data_values # The point at which the Y axis starts, defaults to '0', # if set to nil it will default to the minimum data value. attr_accessor :min_scale_value # Whether to show labels on the X axis or not, defaults # to true, set to false if you want to turn them off. attr_accessor :show_x_labels # This puts the X labels at alternative levels so if they # are long field names they will not overlap so easily. # Default it false, to turn on set to true. attr_accessor :stagger_x_labels # This puts the Y labels at alternative levels so if they # are long field names they will not overlap so easily. # Default it false, to turn on set to true. attr_accessor :stagger_y_labels # This turns the X axis labels by 90 degrees. # Default it false, to turn on set to true. attr_accessor :rotate_x_labels # This turns the Y axis labels by 90 degrees. # Default it false, to turn on set to true. attr_accessor :rotate_y_labels # How many "steps" to use between displayed X axis labels, # a step of one means display every label, a step of two results # in every other label being displayed (label label label), # a step of three results in every third label being displayed # (label label label) and so on. attr_accessor :step_x_labels # Whether to (when taking "steps" between X axis labels) step from # the first label (i.e. always include the first label) or step from # the X axis origin (i.e. start with a gap if step_x_labels is greater # than one). attr_accessor :step_include_first_x_label # Whether to show labels on the Y axis or not, defaults # to true, set to false if you want to turn them off. attr_accessor :show_y_labels # Ensures only whole numbers are used as the scale divisions. # Default it false, to turn on set to true. This has no effect if # scale divisions are less than 1. attr_accessor :scale_integers # This defines the gap between markers on the Y axis, # default is a 10th of the max_value, e.g. you will have # 10 markers on the Y axis. NOTE: do not set this too # low - you are limited to 999 markers, after that the # graph won't generate. attr_accessor :scale_divisions # Whether to show the title under the X axis labels, # default is false, set to true to show. attr_accessor :show_x_title # What the title under X axis should be, e.g. 'Months'. attr_accessor :x_title # Whether to show the title under the Y axis labels, # default is false, set to true to show. attr_accessor :show_y_title # Aligns writing mode for Y axis label. # Defaults to :bt (Bottom to Top). # Change to :tb (Top to Bottom) to reverse. attr_accessor :y_title_text_direction # What the title under Y axis should be, e.g. 'Sales in thousands'. attr_accessor :y_title # Whether to show a title on the graph, defaults # to false, set to true to show. attr_accessor :show_graph_title # What the title on the graph should be. attr_accessor :graph_title # Whether to show a subtitle on the graph, defaults # to false, set to true to show. attr_accessor :show_graph_subtitle # What the subtitle on the graph should be. attr_accessor :graph_subtitle # Whether to show a key, defaults to false, set to # true if you want to show it. attr_accessor :key # Where the key should be positioned, defaults to # :right, set to :bottom if you want to move it. attr_accessor :key_position # Set the font size (in points) of the data point labels attr_accessor :font_size # Set the font size of the X axis labels attr_accessor :x_label_font_size # Set the font size of the X axis title attr_accessor :x_title_font_size # Set the font size of the Y axis labels attr_accessor :y_label_font_size # Set the font size of the Y axis title attr_accessor :y_title_font_size # Set the title font size attr_accessor :title_font_size # Set the subtitle font size attr_accessor :subtitle_font_size # Set the key font size attr_accessor :key_font_size # Show guidelines for the X axis attr_accessor :show_x_guidelines # Show guidelines for the Y axis attr_accessor :show_y_guidelines # Do not use CSS if set to true. Many SVG viewers do not support CSS, but # not using CSS can result in larger SVGs as well as making it impossible to # change colors after the chart is generated. Defaults to false. attr_accessor :no_css # Add popups for the data points on some graphs attr_accessor :add_popups # Customize popup radius attr_accessor :popup_radius protected def sort( *arrys ) sort_multiple( arrys ) end # Overwrite configuration options with supplied options. Used # by subclasses. def init_with config config.each { |key, value| self.send( key.to_s+"=", value ) if self.respond_to? key } @popup_radius ||= 10 end attr_accessor :top_align, :top_font, :right_align, :right_font KEY_BOX_SIZE = 12 # Override this (and call super) to change the margin to the left # of the plot area. Results in @border_left being set. def calculate_left_margin @border_left = 7 # Check for Y labels max_y_label_height_px = @rotate_y_labels ? @y_label_font_size : get_y_labels.max{|a,b| a.to_s.length<=>b.to_s.length }.to_s.length * @y_label_font_size * 0.6 @border_left += max_y_label_height_px if @show_y_labels @border_left += max_y_label_height_px + 10 if @stagger_y_labels @border_left += y_title_font_size + 5 if @show_y_title end # Calculates the width of the widest Y label. This will be the # character height if the Y labels are rotated def max_y_label_width_px return font_size if rotate_y_labels end # Override this (and call super) to change the margin to the right # of the plot area. Results in @border_right being set. def calculate_right_margin @border_right = 7 if key and key_position == :right val = keys.max { |a,b| a.length <=> b.length } @border_right += val.length * key_font_size * 0.6 @border_right += KEY_BOX_SIZE @border_right += 10 # Some padding around the box end end # Override this (and call super) to change the margin to the top # of the plot area. Results in @border_top being set. def calculate_top_margin @border_top = 5 @border_top += title_font_size if show_graph_title @border_top += 5 @border_top += subtitle_font_size if show_graph_subtitle end # Adds pop-up point information to a graph. def add_popup( x, y, label ) txt_width = label.length * font_size * 0.6 + 10 tx = (x+txt_width > width ? x-5 : x+5) t = @foreground.add_element( "text", { "x" => tx.to_s, "y" => (y - font_size).to_s, "visibility" => "hidden", }) t.attributes["style"] = "fill: #000; "+ (x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;") t.text = label.to_s t.attributes["id"] = t.object_id.to_s @foreground.add_element( "circle", { "cx" => x.to_s, "cy" => y.to_s, "r" => "#{@popup_radius}", "style" => "opacity: 0", "onmouseover" => "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )", "onmouseout" => "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )", }) end # Override this (and call super) to change the margin to the bottom # of the plot area. Results in @border_bottom being set. def calculate_bottom_margin @border_bottom = 7 if key and key_position == :bottom @border_bottom += @data.size * (font_size + 5) @border_bottom += 10 end if show_x_labels max_x_label_height_px = (not rotate_x_labels) ? x_label_font_size : get_x_labels.max{|a,b| a.to_s.length<=>b.to_s.length }.to_s.length * x_label_font_size * 0.6 @border_bottom += max_x_label_height_px @border_bottom += max_x_label_height_px + 10 if stagger_x_labels end @border_bottom += x_title_font_size + 5 if show_x_title end # Draws the background, axis, and labels. def draw_graph @graph = @root.add_element( "g", { "transform" => "translate( #@border_left #@border_top )" }) # Background @graph.add_element( "rect", { "x" => "0", "y" => "0", "width" => @graph_width.to_s, "height" => @graph_height.to_s, "class" => "graphBackground" }) # Axis @graph.add_element( "path", { "d" => "M 0 0 v#@graph_height", "class" => "axis", "id" => "xAxis" }) @graph.add_element( "path", { "d" => "M 0 #@graph_height h#@graph_width", "class" => "axis", "id" => "yAxis" }) draw_x_labels draw_y_labels end # Where in the X area the label is drawn # Centered in the field, should be width/2. Start, 0. def x_label_offset( width ) 0 end def make_datapoint_text( x, y, value, style="" ) if show_data_values @foreground.add_element( "text", { "x" => x.to_s, "y" => y.to_s, "class" => "dataPointLabel", "style" => "#{style} stroke: #fff; stroke-width: 2;" }).text = value.to_s text = @foreground.add_element( "text", { "x" => x.to_s, "y" => y.to_s, "class" => "dataPointLabel" }) text.text = value.to_s text.attributes["style"] = style if style.length > 0 end end # Draws the X axis labels def draw_x_labels stagger = x_label_font_size + 5 if show_x_labels label_width = field_width count = 0 for label in get_x_labels if step_include_first_x_label == true then step = count % step_x_labels else step = (count + 1) % step_x_labels end if step == 0 then text = @graph.add_element( "text" ) text.attributes["class"] = "xAxisLabels" text.text = label.to_s x = count * label_width + x_label_offset( label_width ) y = @graph_height + x_label_font_size + 3 #t = 0 - (font_size / 2) if stagger_x_labels and count % 2 == 1 y += stagger @graph.add_element( "path", { "d" => "M#{x} #@graph_height v#{stagger}", "class" => "staggerGuideLine" }) end text.attributes["x"] = x.to_s text.attributes["y"] = y.to_s if rotate_x_labels text.attributes["transform"] = "rotate( 90 #{x} #{y-x_label_font_size} )"+ " translate( 0 -#{x_label_font_size/4} )" text.attributes["style"] = "text-anchor: start" else text.attributes["style"] = "text-anchor: middle" end end draw_x_guidelines( label_width, count ) if show_x_guidelines count += 1 end end end # Where in the Y area the label is drawn # Centered in the field, should be width/2. Start, 0. def y_label_offset( height ) 0 end def field_width (@graph_width.to_f - font_size*2*right_font) / (get_x_labels.length - right_align) end def field_height (@graph_height.to_f - font_size*2*top_font) / (get_y_labels.length - top_align) end # Draws the Y axis labels def draw_y_labels stagger = y_label_font_size + 5 if show_y_labels label_height = field_height count = 0 y_offset = @graph_height + y_label_offset( label_height ) y_offset += font_size/1.2 unless rotate_y_labels for label in get_y_labels y = y_offset - (label_height * count) x = rotate_y_labels ? 0 : -3 if stagger_y_labels and count % 2 == 1 x -= stagger @graph.add_element( "path", { "d" => "M#{x} #{y} h#{stagger}", "class" => "staggerGuideLine" }) end text = @graph.add_element( "text", { "x" => x.to_s, "y" => y.to_s, "class" => "yAxisLabels" }) text.text = label.to_s if rotate_y_labels text.attributes["transform"] = "translate( -#{font_size} 0 ) "+ "rotate( 90 #{x} #{y} ) " text.attributes["style"] = "text-anchor: middle" else text.attributes["y"] = (y - (y_label_font_size/2)).to_s text.attributes["style"] = "text-anchor: end" end draw_y_guidelines( label_height, count ) if show_y_guidelines count += 1 end end end # Draws the X axis guidelines def draw_x_guidelines( label_height, count ) if count != 0 @graph.add_element( "path", { "d" => "M#{label_height*count} 0 v#@graph_height", "class" => "guideLines" }) end end # Draws the Y axis guidelines def draw_y_guidelines( label_height, count ) if count != 0 @graph.add_element( "path", { "d" => "M0 #{@graph_height-(label_height*count)} h#@graph_width", "class" => "guideLines" }) end end # Draws the graph title and subtitle def draw_titles if show_graph_title @root.add_element( "text", { "x" => (width / 2).to_s, "y" => (title_font_size).to_s, "class" => "mainTitle" }).text = graph_title.to_s end if show_graph_subtitle y_subtitle = show_graph_title ? title_font_size + 10 : subtitle_font_size @root.add_element("text", { "x" => (width / 2).to_s, "y" => (y_subtitle).to_s, "class" => "subTitle" }).text = graph_subtitle.to_s end if show_x_title y = @graph_height + @border_top + x_title_font_size if show_x_labels y += x_label_font_size + 5 if stagger_x_labels y += x_label_font_size + 5 end x = width / 2 @root.add_element("text", { "x" => x.to_s, "y" => y.to_s, "class" => "xAxisTitle", }).text = x_title.to_s end if show_y_title x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3) y = height / 2 text = @root.add_element("text", { "x" => x.to_s, "y" => y.to_s, "class" => "yAxisTitle", }) text.text = y_title.to_s if y_title_text_direction == :bt text.attributes["transform"] = "rotate( -90, #{x}, #{y} )" else text.attributes["transform"] = "rotate( 90, #{x}, #{y} )" end end end def keys i = 0 return @data.collect{ |d| i+=1; d[:title] || "Serie #{i}" } end # Draws the legend on the graph def draw_legend if key group = @root.add_element( "g" ) key_count = 0 for key_name in keys y_offset = (KEY_BOX_SIZE * key_count) + (key_count * 5) group.add_element( "rect", { "x" => 0.to_s, "y" => y_offset.to_s, "width" => KEY_BOX_SIZE.to_s, "height" => KEY_BOX_SIZE.to_s, "class" => "key#{key_count+1}" }) group.add_element( "text", { "x" => (KEY_BOX_SIZE + 5).to_s, "y" => (y_offset + KEY_BOX_SIZE).to_s, "class" => "keyText" }).text = key_name.to_s key_count += 1 end case key_position when :right x_offset = @graph_width + @border_left + 10 y_offset = @border_top + 20 when :bottom x_offset = @border_left + 20 y_offset = @border_top + @graph_height + 5 if show_x_labels max_x_label_height_px = (not rotate_x_labels) ? x_label_font_size : get_x_labels.max{|a,b| a.to_s.length<=>b.to_s.length }.to_s.length * x_label_font_size * 0.6 x_label_font_size y_offset += max_x_label_height_px y_offset += max_x_label_height_px + 5 if stagger_x_labels end y_offset += x_title_font_size + 5 if show_x_title end group.attributes["transform"] = "translate(#{x_offset} #{y_offset})" end end private def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 ) if lo < hi p = partition(arrys,lo,hi) sort_multiple(arrys, lo, p-1) sort_multiple(arrys, p+1, hi) end arrys end def partition( arrys, lo, hi ) p = arrys[0][lo] l = lo z = lo+1 while z <= hi if arrys[0][z] < p l += 1 arrys.each { |arry| arry[z], arry[l] = arry[l], arry[z] } end z += 1 end arrys.each { |arry| arry[lo], arry[l] = arry[l], arry[lo] } l end def style if no_css styles = parse_css @root.elements.each("//*[@class]") { |el| cl = el.attributes["class"] style = styles[cl] style += el.attributes["style"] if el.attributes["style"] el.attributes["style"] = style } end end def parse_css css = get_style rv = {} while css =~ /^(\.(\w+)(?:\s*,\s*\.\w+)*)\s*\{/m names = $1 css = $' css =~ /([^}]+)\}/m content = $1 css = $' nms = [] while names =~ /^\s*,?\s*\.(\w+)/ nms << $1 names = $' end content = content.tr( "\n\t", " ") for name in nms current = rv[name] current = current ? current+"; "+content : content rv[name] = current.strip.squeeze(" ") end end return rv end # Override and place code to add defs here def add_defs defs end def start_svg # Base document @doc = Document.new @doc << XMLDecl.new @doc << DocType.new( %q{svg PUBLIC "-//W3C//DTD SVG 1.0//EN" } + %q{"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"} ) if style_sheet && style_sheet != '' @doc << Instruction.new( "xml-stylesheet", %Q{href="#{style_sheet}" type="text/css"} ) end @root = @doc.add_element( "svg", { "width" => width.to_s, "height" => height.to_s, "viewBox" => "0 0 #{width} #{height}", "xmlns" => "http://www.w3.org/2000/svg", "xmlns:xlink" => "http://www.w3.org/1999/xlink", "xmlns:a3" => "http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/", "a3:scriptImplementation" => "Adobe" }) @root << Comment.new( " "+"\\"*66 ) @root << Comment.new( " Created with SVG::Graph " ) @root << Comment.new( " SVG::Graph by Sean E. Russell " ) @root << Comment.new( " Losely based on SVG::TT::Graph for Perl by"+ " Leo Lapworth & Stephan Morgan " ) @root << Comment.new( " "+"/"*66 ) defs = @root.add_element( "defs" ) add_defs defs if not(style_sheet && style_sheet != '') and !no_css @root << Comment.new(" include default stylesheet if none specified ") style = defs.add_element( "style", {"type"=>"text/css"} ) style << CData.new( get_style ) end @root << Comment.new( "SVG Background" ) @root.add_element( "rect", { "width" => width.to_s, "height" => height.to_s, "x" => "0", "y" => "0", "class" => "svgBackground" }) end def calculate_graph_dimensions calculate_left_margin calculate_right_margin calculate_bottom_margin calculate_top_margin @graph_width = width - @border_left - @border_right @graph_height = height - @border_top - @border_bottom end def get_style return < 500, # :width => 300, # :fields => fields, # }) # # graph.add_data({ # :data => data_sales_02, # :title => 'Sales 2002', # }) # # graph.add_data({ # :data => data_sales_03, # :title => 'Sales 2003', # }) # # print "Content-type: image/svg+xml\r\n\r\n"; # print graph.burn(); # # = Description # # This object aims to allow you to easily create high quality # SVG line graphs. You can either use the default style sheet # or supply your own. Either way there are many options which can # be configured to give you control over how the graph is # generated - with or without a key, data elements at each point, # title, subtitle etc. # # = Examples # # http://www.germane-software/repositories/public/SVG/test/single.rb # # = Notes # # The default stylesheet handles upto 10 data sets, if you # use more you must create your own stylesheet and add the # additional settings for the extra data sets. You will know # if you go over 10 data sets as they will have no style and # be in black. # # = See also # # * SVG::Graph::Graph # * SVG::Graph::BarHorizontal # * SVG::Graph::Bar # * SVG::Graph::Pie # * SVG::Graph::Plot # * SVG::Graph::TimeSeries # # == Author # # Sean E. Russell # # Copyright 2004 Sean E. Russell # This software is available under the Ruby license[LICENSE.txt] # class Line < SVG::Graph::Graph # Show a small circle on the graph where the line # goes from one point to the next. attr_accessor :show_data_points # Accumulates each data set. (i.e. Each point increased by sum of # all previous series at same point). Default is 0, set to '1' to show. attr_accessor :stacked # Fill in the area under the plot if true attr_accessor :area_fill # The constructor takes a hash reference, fields (the names for each # field on the X axis) MUST be set, all other values are defaulted to # those shown above - with the exception of style_sheet which defaults # to using the internal style sheet. def initialize config raise "fields was not supplied or is empty" unless config[:fields] && config[:fields].kind_of?(Array) && config[:fields].length > 0 super end # In addition to the defaults set in Graph::initialize, sets # [show_data_points] true # [show_data_values] true # [stacked] false # [area_fill] false def set_defaults init_with( :show_data_points => true, :show_data_values => true, :stacked => false, :area_fill => false ) self.top_align = self.top_font = self.right_align = self.right_font = 1 end protected def max_value max = 0 if (stacked == true) then sums = Array.new(@config[:fields].length).fill(0) @data.each do |data| sums.each_index do |i| sums[i] += data[:data][i].to_f end end max = sums.max else max = @data.collect{|x| x[:data].max}.max end return max end def min_value min = 0 if (min_scale_value.nil? == false) then min = min_scale_value elsif (stacked == true) then min = @data[-1][:data].min else min = @data.collect{|x| x[:data].min}.min end return min end def get_x_labels @config[:fields] end def calculate_left_margin super label_left = @config[:fields][0].length / 2 * font_size * 0.6 @border_left = label_left if label_left > @border_left end def get_y_labels maxvalue = max_value minvalue = min_value range = maxvalue - minvalue top_pad = range == 0 ? 10 : range / 20.0 scale_range = (maxvalue + top_pad) - minvalue scale_division = scale_divisions || (scale_range / 10.0) if scale_integers scale_division = scale_division < 1 ? 1 : scale_division.round end rv = [] maxvalue = maxvalue%scale_division == 0 ? maxvalue : maxvalue + scale_division minvalue.step( maxvalue, scale_division ) {|v| rv << v} return rv end def calc_coords(field, value, width = field_width, height = field_height) coords = {:x => 0, :y => 0} coords[:x] = width * field coords[:y] = @graph_height - value * height return coords end def draw_data minvalue = min_value fieldheight = (@graph_height.to_f - font_size*2*top_font) / (get_y_labels.max - get_y_labels.min) fieldwidth = field_width line = @data.length prev_sum = Array.new(@config[:fields].length).fill(0) cum_sum = Array.new(@config[:fields].length).fill(-minvalue) for data in @data.reverse lpath = "" apath = "" if not stacked then cum_sum.fill(-minvalue) end data[:data].each_index do |i| cum_sum[i] += data[:data][i] c = calc_coords(i, cum_sum[i], fieldwidth, fieldheight) lpath << "#{c[:x]} #{c[:y]} " end if area_fill if stacked then (prev_sum.length - 1).downto 0 do |i| c = calc_coords(i, prev_sum[i], fieldwidth, fieldheight) apath << "#{c[:x]} #{c[:y]} " end c = calc_coords(0, prev_sum[0], fieldwidth, fieldheight) else apath = "V#@graph_height" c = calc_coords(0, 0, fieldwidth, fieldheight) end @graph.add_element("path", { "d" => "M#{c[:x]} #{c[:y]} L" + lpath + apath + "Z", "class" => "fill#{line}" }) end @graph.add_element("path", { "d" => "M0 #@graph_height L" + lpath, "class" => "line#{line}" }) if show_data_points || show_data_values cum_sum.each_index do |i| if show_data_points @graph.add_element( "circle", { "cx" => (fieldwidth * i).to_s, "cy" => (@graph_height - cum_sum[i] * fieldheight).to_s, "r" => "2.5", "class" => "dataPoint#{line}" }) end make_datapoint_text( fieldwidth * i, @graph_height - cum_sum[i] * fieldheight - 6, cum_sum[i] + minvalue ) end end prev_sum = cum_sum.dup line -= 1 end end def get_css return < 500, # :width => 300, # :fields => fields, # }) # # graph.add_data({ # :data => data_sales_02, # :title => 'Sales 2002', # }) # # print "Content-type: image/svg+xml\r\n\r\n" # print graph.burn(); # # == Description # # This object aims to allow you to easily create high quality # SVG pie graphs. You can either use the default style sheet # or supply your own. Either way there are many options which can # be configured to give you control over how the graph is # generated - with or without a key, display percent on pie chart, # title, subtitle etc. # # = Examples # # http://www.germane-software/repositories/public/SVG/test/single.rb # # == See also # # * SVG::Graph::Graph # * SVG::Graph::BarHorizontal # * SVG::Graph::Bar # * SVG::Graph::Line # * SVG::Graph::Plot # * SVG::Graph::TimeSeries # # == Author # # Sean E. Russell # # Copyright 2004 Sean E. Russell # This software is available under the Ruby license[LICENSE.txt] # class Pie < Graph # Defaults are those set by Graph::initialize, and # [show_shadow] true # [shadow_offset] 10 # [show_data_labels] false # [show_actual_values] false # [show_percent] true # [show_key_data_labels] true # [show_key_actual_values] true # [show_key_percent] false # [expanded] false # [expand_greatest] false # [expand_gap] 10 # [show_x_labels] false # [show_y_labels] false # [datapoint_font_size] 12 def set_defaults init_with( :show_shadow => true, :shadow_offset => 10, :show_data_labels => false, :show_actual_values => false, :show_percent => true, :show_key_data_labels => true, :show_key_actual_values => true, :show_key_percent => false, :expanded => false, :expand_greatest => false, :expand_gap => 10, :show_x_labels => false, :show_y_labels => false, :datapoint_font_size => 12 ) @data = [] end # Adds a data set to the graph. # # graph.add_data( { :data => [1,2,3,4] } ) # # Note that the :title is not necessary. If multiple # data sets are added to the graph, the pie chart will # display the +sums+ of the data. EG: # # graph.add_data( { :data => [1,2,3,4] } ) # graph.add_data( { :data => [2,3,5,9] } ) # # is the same as: # # graph.add_data( { :data => [3,5,8,13] } ) def add_data arg arg[:data].each_index {|idx| @data[idx] = 0 unless @data[idx] @data[idx] += arg[:data][idx] } end # If true, displays a drop shadow for the chart attr_accessor :show_shadow # Sets the offset of the shadow from the pie chart attr_accessor :shadow_offset # If true, display the data labels on the chart attr_accessor :show_data_labels # If true, display the actual field values in the data labels attr_accessor :show_actual_values # If true, display the percentage value of each pie wedge in the data # labels attr_accessor :show_percent # If true, display the labels in the key attr_accessor :show_key_data_labels # If true, display the actual value of the field in the key attr_accessor :show_key_actual_values # If true, display the percentage value of the wedges in the key attr_accessor :show_key_percent # If true, "explode" the pie (put space between the wedges) attr_accessor :expanded # If true, expand the largest pie wedge attr_accessor :expand_greatest # The amount of space between expanded wedges attr_accessor :expand_gap # The font size of the data point labels attr_accessor :datapoint_font_size protected def add_defs defs gradient = defs.add_element( "filter", { "id"=>"dropshadow", "width" => "1.2", "height" => "1.2", } ) gradient.add_element( "feGaussianBlur", { "stdDeviation" => "4", "result" => "blur" }) end # We don't need the graph def draw_graph end def get_y_labels [""] end def get_x_labels [""] end def keys total = 0 #max_value = 0 @data.each {|x| total += x } percent_scale = 100.0 / total count = -1 @config[:fields].collect{ |x| count += 1 v = @data[count] perc = show_key_percent ? " "+(v * percent_scale).round.to_s+"%" : "" x + " [" + v.to_s + "]" + perc } end RADIANS = Math::PI/180 def draw_data @graph = @root.add_element( "g" ) background = @graph.add_element("g") midground = @graph.add_element("g") diameter = @graph_height > @graph_width ? @graph_width : @graph_height diameter -= expand_gap if expanded or expand_greatest diameter -= datapoint_font_size if show_data_labels diameter -= 10 if show_shadow radius = diameter / 2.0 xoff = (width - diameter) / 2 yoff = (height - @border_bottom - diameter) yoff -= 10 if show_shadow @graph.attributes['transform'] = "translate( #{xoff} #{yoff} )" wedge_text_pad = 5 wedge_text_pad = 20 if show_percent and show_data_labels total = 0 max_value = 0 @data.each {|x| max_value = max_value < x ? x : max_value total += x } percent_scale = 100.0 / total prev_percent = 0 rad_mult = 3.6 * RADIANS @config[:fields].each_index { |count| value = @data[count] percent = percent_scale * value radians = prev_percent * rad_mult x_start = radius+(Math.sin(radians) * radius) y_start = radius-(Math.cos(radians) * radius) radians = (prev_percent+percent) * rad_mult x_end = radius+(Math.sin(radians) * radius) x_end -= 0.00001 if @data.length == 1 y_end = radius-(Math.cos(radians) * radius) path = "M#{radius},#{radius} L#{x_start},#{y_start} "+ "A#{radius},#{radius} "+ "0, #{percent >= 50 ? '1' : '0'},1, "+ "#{x_end} #{y_end} Z" wedge = @foreground.add_element( "path", { "d" => path, "class" => "fill#{count+1}" }) translate = nil tx = 0 ty = 0 half_percent = prev_percent + percent / 2 radians = half_percent * rad_mult if show_shadow shadow = background.add_element( "path", { "d" => path, "filter" => "url(#dropshadow)", "style" => "fill: #ccc; stroke: none;" }) clear = midground.add_element( "path", { "d" => path, "style" => "fill: #fff; stroke: none;" }) end if expanded or (expand_greatest && value == max_value) tx = (Math.sin(radians) * expand_gap) ty = -(Math.cos(radians) * expand_gap) translate = "translate( #{tx} #{ty} )" wedge.attributes["transform"] = translate clear.attributes["transform"] = translate if clear end if show_shadow shadow.attributes["transform"] = "translate( #{tx+shadow_offset} #{ty+shadow_offset} )" end if show_data_labels and value != 0 label = "" label += @config[:fields][count] if show_key_data_labels label += " ["+value.to_s+"]" if show_actual_values label += " "+percent.round.to_s+"%" if show_percent msr = Math.sin(radians) mcr = Math.cos(radians) tx = radius + (msr * radius) ty = radius - (mcr * radius) if expanded or (expand_greatest && value == max_value) tx += (msr * expand_gap) ty -= (mcr * expand_gap) end @foreground.add_element( "text", { "x" => tx.to_s, "y" => ty.to_s, "class" => "dataPointLabel", "style" => "stroke: #fff; stroke-width: 2;" }).text = label.to_s @foreground.add_element( "text", { "x" => tx.to_s, "y" => ty.to_s, "class" => "dataPointLabel", }).text = label.to_s end prev_percent += percent } end def round val, to up = 10**to.to_f (val * up).to_i / up end def get_css return < 500, # :width => 300, # :key => true, # :scale_x_integers => true, # :scale_y_integerrs => true, # }) # # graph.add_data({ # :data => projection # :title => 'Projected', # }) # # graph.add_data({ # :data => actual, # :title => 'Actual', # }) # # print graph.burn() # # = Description # # Produces a graph of scalar data. # # This object aims to allow you to easily create high quality # SVG[http://www.w3c.org/tr/svg] scalar plots. You can either use the # default style sheet or supply your own. Either way there are many options # which can be configured to give you control over how the graph is # generated - with or without a key, data elements at each point, title, # subtitle etc. # # = Examples # # http://www.germane-software/repositories/public/SVG/test/plot.rb # # = Notes # # The default stylesheet handles upto 10 data sets, if you # use more you must create your own stylesheet and add the # additional settings for the extra data sets. You will know # if you go over 10 data sets as they will have no style and # be in black. # # Unlike the other types of charts, data sets must contain x,y pairs: # # [ 1, 2 ] # A data set with 1 point: (1,2) # [ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6) # # = See also # # * SVG::Graph::Graph # * SVG::Graph::BarHorizontal # * SVG::Graph::Bar # * SVG::Graph::Line # * SVG::Graph::Pie # * SVG::Graph::TimeSeries # # == Author # # Sean E. Russell # # Copyright 2004 Sean E. Russell # This software is available under the Ruby license[LICENSE.txt] # class Plot < Graph # In addition to the defaults set by Graph::initialize, sets # [show_data_values] true # [show_data_points] true # [area_fill] false # [stacked] false def set_defaults init_with( :show_data_values => true, :show_data_points => true, :area_fill => false, :stacked => false, :show_lines => true, :round_popups => true ) self.top_align = self.right_align = self.top_font = self.right_font = 1 end # Determines the scaling for the X axis divisions. # # graph.scale_x_divisions = 2 # # would cause the graph to attempt to generate labels stepped by 2; EG: # 0,2,4,6,8... attr_accessor :scale_x_divisions # Determines the scaling for the Y axis divisions. # # graph.scale_y_divisions = 0.5 # # would cause the graph to attempt to generate labels stepped by 0.5; EG: # 0, 0.5, 1, 1.5, 2, ... attr_accessor :scale_y_divisions # Make the X axis labels integers attr_accessor :scale_x_integers # Make the Y axis labels integers attr_accessor :scale_y_integers # Fill the area under the line attr_accessor :area_fill # Show a small circle on the graph where the line # goes from one point to the next. attr_accessor :show_data_points # Set the minimum value of the X axis attr_accessor :min_x_value # Set the maximum value of the X axis attr_accessor :max_x_value # Set the minimum value of the Y axis attr_accessor :min_y_value # Set the maximum value of the Y axis attr_accessor :max_y_value # Show lines connecting data points attr_accessor :show_lines # Round value of data points in popups to integer attr_accessor :round_popups # Adds data to the plot. The data must be in X,Y pairs; EG # [ 1, 2 ] # A data set with 1 point: (1,2) # [ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6) def add_data(data) @data = [] unless @data raise "No data provided by #{conf.inspect}" unless data[:data] and data[:data].kind_of? Array raise "Data supplied must be x,y pairs! "+ "The data provided contained an odd set of "+ "data points" unless data[:data].length % 2 == 0 return if data[:data].length == 0 data[:description] ||= Array.new(data[:data].size/2) if data[:description].size != data[:data].size/2 raise "Description for popups does not have same size as provided data: #{data[:description].size} vs #{data[:data].size/2}" end x = [] y = [] data[:data].each_index {|i| (i%2 == 0 ? x : y) << data[:data][i] } sort( x, y, data[:description] ) data[:data] = [x,y] @data << data end protected def keys @data.collect{ |x| x[:title] } end def calculate_left_margin super label_left = get_x_labels[0].to_s.length / 2 * font_size * 0.6 @border_left = label_left if label_left > @border_left end def calculate_right_margin super label_right = get_x_labels[-1].to_s.length / 2 * font_size * 0.6 @border_right = label_right if label_right > @border_right end X = 0 Y = 1 def max_x_range max_value = @data.collect{|x| x[:data][X][-1] }.max max_value = max_value > max_x_value ? max_value : max_x_value if max_x_value max_value end def min_x_range min_value = @data.collect{|x| x[:data][X][0] }.min min_value = min_value < min_x_value ? min_value : min_x_value if min_x_value min_value end def x_range max_value = max_x_range min_value = min_x_range range = max_value - min_value right_pad = range == 0 ? 10 : range / 20.0 scale_range = (max_value + right_pad) - min_value scale_division = scale_x_divisions || (scale_range / 10.0) if scale_x_integers scale_division = scale_division < 1 ? 1 : scale_division.round end [min_value, max_value, scale_division] end def get_x_values min_value, max_value, scale_division = x_range rv = [] min_value.step( max_value, scale_division ) {|v| rv << v} return rv end alias :get_x_labels :get_x_values def field_width values = get_x_values max = max_x_range dx = (max - values[-1]).to_f / (values[-1] - values[-2]) (@graph_width.to_f - font_size*2*right_font) / (values.length + dx - right_align) end def max_y_range max_value = @data.collect{|x| x[:data][Y].max }.max max_value = max_value > max_y_value ? max_value : max_y_value if max_y_value max_value end def min_y_range min_value = @data.collect{|x| x[:data][Y].min }.min min_value = min_value < min_y_value ? min_value : min_y_value if min_y_value min_value end def y_range max_value = max_y_range min_value = min_y_range range = max_value - min_value top_pad = range == 0 ? 10 : range / 20.0 scale_range = (max_value + top_pad) - min_value scale_division = scale_y_divisions || (scale_range / 10.0) if scale_y_integers scale_division = scale_division < 1 ? 1 : scale_division.round end return [min_value, max_value, scale_division] end def get_y_values min_value, max_value, scale_division = y_range if max_value != min_value while (max_value - min_value) < scale_division scale_division /= 10.0 end end rv = [] min_value.step( max_value, scale_division ) {|v| rv << v} rv << rv[0] + 1 if rv.length == 1 return rv end alias :get_y_labels :get_y_values def field_height values = get_y_values max = max_y_range if values.length == 1 dx = values[-1] else dx = (max - values[-1]).to_f / (values[-1] - values[-2]) end (@graph_height.to_f - font_size*2*top_font) / (values.length + dx - top_align) end def draw_data line = 1 x_min, x_max = x_range y_min, y_max = y_range x_step = (@graph_width.to_f - font_size*2) / (x_max-x_min) y_step = (@graph_height.to_f - font_size*2) / (y_max-y_min) for data in @data x_points = data[:data][X] y_points = data[:data][Y] lpath = "L" x_start = 0 y_start = 0 x_points.each_index { |idx| x = (x_points[idx] - x_min) * x_step y = @graph_height - (y_points[idx] - y_min) * y_step x_start, y_start = x,y if idx == 0 lpath << "#{x} #{y} " } if area_fill @graph.add_element( "path", { "d" => "M#{x_start} #@graph_height #{lpath} V#@graph_height Z", "class" => "fill#{line}" }) end if show_lines @graph.add_element( "path", { "d" => "M#{x_start} #{y_start} #{lpath}", "class" => "line#{line}" }) end if show_data_points || show_data_values x_points.each_index { |idx| x = (x_points[idx] - x_min) * x_step y = @graph_height - (y_points[idx] - y_min) * y_step if show_data_points DataPoint.new(x, y, line).shape(data[:description][idx]).each{|s| @graph.add_element( *s ) } add_popup(x, y, format( x_points[idx], y_points[idx], data[:description][idx])) if add_popups end make_datapoint_text( x, y-6, y_points[idx] ) if show_data_values } end line += 1 end end def format x, y, desc info = [] info << (round_popups ? (x * 100).to_i / 100 : x) info << (round_popups ? (y * 100).to_i / 100 : y) info << desc "(#{info.compact.join(', ')})" end def get_css return < 640, # :height => 480, # :graph_title => title, # :show_graph_title => true, # :no_css => true, # :scale_x_integers => true, # :scale_y_integers => true, # :min_x_value => 0, # :min_y_value => 0, # :show_data_labels => true, # :show_x_guidelines => true, # :show_x_title => true, # :x_title => "Time", # :stagger_x_labels => true, # :stagger_y_labels => true, # :x_label_format => "%m/%d/%y", # }) # # graph.add_data({ # :data => data1, # :title => 'Data', # }) # # print graph.burn() # # = Description # # Produces a graph of temporal scalar data. # # = Examples # # http://www.germane-software/repositories/public/SVG/test/schedule.rb # # = Notes # # The default stylesheet handles upto 10 data sets, if you # use more you must create your own stylesheet and add the # additional settings for the extra data sets. You will know # if you go over 10 data sets as they will have no style and # be in black. # # Note that multiple data sets within the same chart can differ in # length, and that the data in the datasets needn't be in order; # they will be ordered by the plot along the X-axis. # # The dates must be parseable by ParseDate, but otherwise can be # any order of magnitude (seconds within the hour, or years) # # = See also # # * SVG::Graph::Graph # * SVG::Graph::BarHorizontal # * SVG::Graph::Bar # * SVG::Graph::Line # * SVG::Graph::Pie # * SVG::Graph::Plot # * SVG::Graph::TimeSeries # # == Author # # Sean E. Russell # # Copyright 2004 Sean E. Russell # This software is available under the Ruby license[LICENSE.txt] # class Schedule < Graph # In addition to the defaults set by Graph::initialize and # Plot::set_defaults, sets: # [x_label_format] '%Y-%m-%d %H:%M:%S' # [popup_format] '%Y-%m-%d %H:%M:%S' def set_defaults init_with( :x_label_format => '%Y-%m-%d %H:%M:%S', :popup_format => '%Y-%m-%d %H:%M:%S', :scale_x_divisions => false, :scale_x_integers => false, :bar_gap => true ) end # The format string use do format the X axis labels. # See Time::strformat attr_accessor :x_label_format # Use this to set the spacing between dates on the axis. The value # must be of the form # "\d+ ?(days|weeks|months|years|hours|minutes|seconds)?" # # EG: # # graph.timescale_divisions = "2 weeks" # # will cause the chart to try to divide the X axis up into segments of # two week periods. attr_accessor :timescale_divisions # The formatting used for the popups. See x_label_format attr_accessor :popup_format attr_accessor :min_x_value attr_accessor :scale_x_divisions attr_accessor :scale_x_integers attr_accessor :bar_gap # Add data to the plot. # # # A data set with 1 point: Lunch from 12:30 to 14:00 # d1 = [ "Lunch", "12:30", "14:00" ] # # A data set with 2 points: "Cats" runs from 5/11/03 to 7/15/04, and # # "Henry V" runs from 6/12/03 to 8/20/03 # d2 = [ "Cats", "5/11/03", "7/15/04", # "Henry V", "6/12/03", "8/20/03" ] # # graph.add_data( # :data => d1, # :title => 'Meetings' # ) # graph.add_data( # :data => d2, # :title => 'Plays' # ) # # Note that the data must be in time,value pairs, and that the date format # may be any date that is parseable by ParseDate. # Also note that, in this example, we're mixing scales; the data from d1 # will probably not be discernable if both data sets are plotted on the same # graph, since d1 is too granular. def add_data data @data = [] unless @data raise "No data provided by #{conf.inspect}" unless data[:data] and data[:data].kind_of? Array raise "Data supplied must be title,from,to tripples! "+ "The data provided contained an odd set of "+ "data points" unless data[:data].length % 3 == 0 return if data[:data].length == 0 y = [] x_start = [] x_end = [] data[:data].each_index {|i| im3 = i%3 if im3 == 0 y << data[:data][i] else if TIME_PARSE_AVAIL then arr = DateTime.parse(data[:data][i]) t = arr.to_time else arr = ParseDate.parsedate( data[:data][i] ) t = Time.local( *arr[0,6].compact ) end (im3 == 1 ? x_start : x_end) << t.to_i end } sort( x_start, x_end, y ) @data = [x_start, x_end, y ] end protected def min_x_value=(value) if TIME_PARSE_AVAIL then arr = Time.parse(value) t = arr.to_time else arr = ParseDate.parsedate( value ) t = Time.local( *arr[0,6].compact ) end @min_x_value = t.to_i end def format x, y Time.at( x ).strftime( popup_format ) end def get_x_labels rv = get_x_values.collect { |v| Time.at(v).strftime( x_label_format ) } end def y_label_offset( height ) height / -2.0 end def get_y_labels @data[2] end def draw_data fieldheight = field_height fieldwidth = field_width bargap = bar_gap ? (fieldheight < 10 ? fieldheight / 2 : 10) : 0 subbar_height = fieldheight - bargap field_count = 1 y_mod = (subbar_height / 2) + (font_size / 2) min,max,div = x_range scale = (@graph_width.to_f - font_size*2) / (max-min) @data[0].each_index { |i| x_start = @data[0][i] x_end = @data[1][i] y = @graph_height - (fieldheight * field_count) bar_width = (x_end-x_start) * scale bar_start = x_start * scale - (min * scale) @graph.add_element( "rect", { "x" => bar_start.to_s, "y" => y.to_s, "width" => bar_width.to_s, "height" => subbar_height.to_s, "class" => "fill#{field_count+1}" }) field_count += 1 } end def get_css return < 12 arr[5] += (arr[4] / 12).to_i arr[4] = (arr[4] % 12) end cur = Time.local(*arr).to_i end when "years" cur = min while cur < max rv << cur arr = Time.at( cur ).to_a arr[5] += amount cur = Time.local(*arr).to_i end when "weeks" step = 7 * 24 * 60 * 60 * amount when "days" step = 24 * 60 * 60 * amount when "hours" step = 60 * 60 * amount when "minutes" step = 60 * amount when "seconds" step = amount end min.step( max, step ) {|v| rv << v} if step return rv end end min.step( max, scale_division ) {|v| rv << v} return rv end end end end ruby-svg-graph-1.0.5/lib/SVG/Graph/TimeSeries.rb000066400000000000000000000173071164403311000212370ustar00rootroot00000000000000require 'SVG/Graph/Plot' TIME_PARSE_AVAIL = (RUBY_VERSION =~ /1\.9\./) ? true : false if not TIME_PARSE_AVAIL then require 'parsedate' end module SVG module Graph # === For creating SVG plots of scalar temporal data # # = Synopsis # # require 'SVG/Graph/TimeSeries' # # # Data sets are x,y pairs # projection = ["6/17/72", 11, "1/11/72", 7, "4/13/04 17:31", 11, # "9/11/01", 9, "9/1/85", 2, "9/1/88", 1, "1/15/95", 13] # actual = ["8/1/73", 18, "3/1/77", 15, "10/1/98", 4, # "5/1/02", 14, "3/1/95", 6, "8/1/91", 12, "12/1/87", 6, # "5/1/84", 17, "10/1/80", 12] # # title = "Ice Cream Cone Consumption" # # graph = SVG::Graph::TimeSeries.new( { # :width => 640, # :height => 480, # :graph_title => title, # :show_graph_title => true, # :no_css => true, # :key => true, # :scale_x_integers => true, # :scale_y_integers => true, # :min_x_value => 0, # :min_y_value => 0, # :show_data_values => true, # :show_x_guidelines => true, # :show_x_title => true, # :x_title => "Time", # :show_y_title => true, # :y_title => "Ice Cream Cones", # :y_title_text_direction => :bt, # :stagger_x_labels => true, # :x_label_format => "%m/%d/%y", # }) # # graph.add_data({ # :data => projection, # :title => 'Projected', # }) # # graph.add_data({ # :data => actual, # :title => 'Actual', # }) # # print graph.burn() # # = Description # # Produces a graph of temporal scalar data. # # = Examples # # http://www.germane-software/repositories/public/SVG/test/timeseries.rb # # = Notes # # The default stylesheet handles upto 10 data sets, if you # use more you must create your own stylesheet and add the # additional settings for the extra data sets. You will know # if you go over 10 data sets as they will have no style and # be in black. # # Unlike the other types of charts, data sets must contain x,y pairs: # # [ "12:30", 2 ] # A data set with 1 point: ("12:30",2) # [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and # # ("14:20",6) # # Note that multiple data sets within the same chart can differ in length, # and that the data in the datasets needn't be in order; they will be ordered # by the plot along the X-axis. # # The dates must be parseable by ParseDate, but otherwise can be # any order of magnitude (seconds within the hour, or years) # # = See also # # * SVG::Graph::Graph # * SVG::Graph::BarHorizontal # * SVG::Graph::Bar # * SVG::Graph::Line # * SVG::Graph::Pie # * SVG::Graph::Plot # # == Author # # Sean E. Russell # # Copyright 2004 Sean E. Russell # This software is available under the Ruby license[LICENSE.txt] # class TimeSeries < Plot # In addition to the defaults set by Graph::initialize and # Plot::set_defaults, sets: # [x_label_format] '%Y-%m-%d %H:%M:%S' # [popup_format] '%Y-%m-%d %H:%M:%S' def set_defaults super init_with( #:max_time_span => '', :x_label_format => '%Y-%m-%d %H:%M:%S', :popup_format => '%Y-%m-%d %H:%M:%S' ) end # The format string use do format the X axis labels. # See Time::strformat attr_accessor :x_label_format # Use this to set the spacing between dates on the axis. The value # must be of the form # "\d+ ?(days|weeks|months|years|hours|minutes|seconds)?" # # EG: # # graph.timescale_divisions = "2 weeks" # # will cause the chart to try to divide the X axis up into segments of # two week periods. attr_accessor :timescale_divisions # The formatting used for the popups. See x_label_format attr_accessor :popup_format # Add data to the plot. # # d1 = [ "12:30", 2 ] # A data set with 1 point: ("12:30",2) # d2 = [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and # # ("14:20",6) # graph.add_data( # :data => d1, # :title => 'One' # ) # graph.add_data( # :data => d2, # :title => 'Two' # ) # # Note that the data must be in time,value pairs. The time may be any date in # a format that is parseable by ParseDate, a Time object, or a number of seconds # after the unix epoch. def add_data data data[:data].each_index do |i| data[:data][i] = parse_time(data[:data][i]).to_i if i % 2 == 0 end super(data) end protected def min_x_value=(value) t = parse_time(value) @min_x_value = t.to_i end def max_x_value=(value) t = parse_time(value) @max_x_value = t.to_i end def format x, y, description info = [ Time.at(x).strftime(popup_format), round_popups ? (y * 100).to_i / 100 : y, description ].compact.join(', ') end def get_x_labels get_x_values.collect { |v| Time.at(v).strftime( x_label_format ) } end private # Accepts date time as a string, number of seconds since the epoch, or Time # object and returns a Time object. Raises an error if not a valid date time # representation. def parse_time(time) case time when Time return time when String if TIME_PARSE_AVAIL then arr = DateTime.parse(time) return arr.to_time else arr = ParseDate.parsedate(time) return Time.local( *arr[0,6].compact ) end when Integer return Time.at(time) else raise "Can not parse time #{time.inspect}" end end def get_x_values rv = [] min, max, scale_division = x_range if timescale_divisions timescale_divisions =~ /(\d+) ?(day|week|month|year|hour|minute|second)?/ division_units = $2 ? $2 : "day" amount = $1.to_i if amount step = nil case division_units when "month" cur = min while cur < max rv << cur arr = Time.at( cur ).to_a arr[4] += amount if arr[4] > 12 arr[5] += (arr[4] / 12).to_i arr[4] = (arr[4] % 12) end cur = Time.local(*arr).to_i end when "year" cur = min while cur < max rv << cur arr = Time.at( cur ).to_a arr[5] += amount cur = Time.local(*arr).to_i end when "week" step = 7 * 24 * 60 * 60 * amount when "day" step = 24 * 60 * 60 * amount when "hour" step = 60 * 60 * amount when "minute" step = 60 * amount when "second" step = amount end min.step( max, step ) {|v| rv << v} if step return rv end end min.step( max, scale_division ) {|v| rv << v} return rv end end end end ruby-svg-graph-1.0.5/lib/svggraph.rb000066400000000000000000000007021164403311000172760ustar00rootroot00000000000000module SVG module Graph VERSION = '1.0.5' autoload(:Bar, 'SVG/Graph/Bar') autoload(:BarBase, 'SVG/Graph/BarBase') autoload(:BarHorizontal, 'SVG/Graph/BarHorizontal') autoload(:Line, 'SVG/Graph/Line') autoload(:Pie, 'SVG/Graph/Pie') autoload(:Plot, 'SVG/Graph/Plot') autoload(:Schedule, 'SVG/Graph/Schedule') autoload(:TimeSeries, 'SVG/Graph/TimeSeries') autoload(:DataPoint, 'SVG/Graph/DataPoint') end end ruby-svg-graph-1.0.5/screenshots.xml000066400000000000000000000072731164403311000174560ustar00rootroot00000000000000
Screenshots SVGs Here are the SVG screenshots in all their glory. KSVG doesn't handle CSS properly yet, and neither does librsvg. Adobe and Batik both do. SVG::Chart::Bar SVG::Chart::BarHorizontal SVG::Chart::Line SVG::Chart::Pie SVG::Chart::Plot SVG::Chart::TimeSeries SVG::Chart::Schedule PNGs Here are the icky, bitmapped PNGs. PNG is a beautiful file format, don't get me wrong; but it is best for things that vector graphics aren't good at, such as pictures. SVG::Chart::Bar SVG::Chart::BarHorizontal SVG::Chart::Line SVG::Chart::Pie SVG::Chart::Plot SVG::Chart::TimeSeries SVG::Chart::Schedule
ruby-svg-graph-1.0.5/style/000077500000000000000000000000001164403311000155235ustar00rootroot00000000000000ruby-svg-graph-1.0.5/style/common.xsl000077500000000000000000000026331164403311000175520ustar00rootroot00000000000000 ruby-svg-graph-1.0.5/style/release_html.xsl000077500000000000000000000132151164403311000207240ustar00rootroot00000000000000 FormsTK Change Log

FormsTK Change Log

()

Since previous minor release ()

Since last major release ()

()
80
  • Executive Summary
    ruby-svg-graph-1.0.5/style/release_txt.xsl000077500000000000000000000015011164403311000205720ustar00rootroot00000000000000 FormsTK Change Log ============================================================ () ruby-svg-graph-1.0.5/svg-graph.gemspec000066400000000000000000000043441164403311000176330ustar00rootroot00000000000000# Generated by jeweler # DO NOT EDIT THIS FILE DIRECTLY # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' # -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = %q{svg-graph} s.version = "1.0.2" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = [%q{Sean Russell}, %q{Claudio Bustos}, %q{Liehann Loots}, %q{Piergiuliano Bossi}] s.date = %q{2011-08-05} s.description = %q{Gem version of SVG:::Graph. SVG:::Graph is a pure Ruby library for generating charts, which are a type of graph where the values of one axis are not scalar. SVG::Graph has a verry similar API to the Perl library SVG::TT::Graph, and the resulting charts also look the same. This isn't surprising, because SVG::Graph started as a loose port of SVG::TT::Graph, although the internal code no longer resembles the Perl original at all. } s.email = [%q{ser_AT_germane-software.com}, %q{clbustos_AT_gmail.com}, %q{liehannl_AT_gmail_DOT_com}, %q{pgbossi_AT_gmail_DOT_com}] s.extra_rdoc_files = [ "LICENSE.txt", "README.markdown", "README.txt" ] s.files = [ "GPL.txt", "History.txt", "LICENSE.txt", "README.txt", "Rakefile", "lib/SVG/Graph/Bar.rb", "lib/SVG/Graph/BarBase.rb", "lib/SVG/Graph/BarHorizontal.rb", "lib/SVG/Graph/Graph.rb", "lib/SVG/Graph/Line.rb", "lib/SVG/Graph/Pie.rb", "lib/SVG/Graph/Plot.rb", "lib/SVG/Graph/Schedule.rb", "lib/SVG/Graph/TimeSeries.rb", "lib/svggraph.rb", "test/test_svg_graph.rb" ] s.homepage = %q{http://www.germane-software.com/software/SVG/SVG::Graph/} s.licenses = [%q{GPL}] s.require_paths = [%q{lib}] s.rubyforge_project = %q{ruby-statsample} s.rubygems_version = %q{1.8.6} s.summary = %q{SVG:::Graph is a pure Ruby library for generating charts, which are a type of graph where the values of one axis are not scalar.} if s.respond_to? :specification_version then s.specification_version = 3 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q, ["~> 1.6.4"]) else s.add_dependency(%q, ["~> 1.6.4"]) end else s.add_dependency(%q, ["~> 1.6.4"]) end end ruby-svg-graph-1.0.5/test/000077500000000000000000000000001164403311000153425ustar00rootroot00000000000000ruby-svg-graph-1.0.5/test/data.txt000066400000000000000000000001051164403311000170100ustar00rootroot00000000000000Question 7 Internet,TV,Newspaper,Magazine,Radio -2 3 1 3 1 0 2 1 5 4 ruby-svg-graph-1.0.5/test/plot.rb000066400000000000000000000021441164403311000166460ustar00rootroot00000000000000$: << File.dirname(__FILE__) + '/../lib' require 'SVG/Graph/Plot' title = "Plot" #data1 = [] #(rand(10)+5).times{ # data1 << rand(20) # data1 << rand(20) #} data1 = [6.1, 11.2, 0.3, 5.4, 18.5, 7.6, 1.7, 11.8, 13.9, 9.11, 11.22, 2.33, 19.44, 0.555, 3.6666, 13.77777, 7.888888, 9.9999999] #data2 = [] #(rand(10)+5).times{ # data2 << rand(20) # data2 << rand(20) #} data2 = [0, 18, 8, 15, 9, 4, 18, 14, 10, 2, 11, 6, 14, 12, 15, 6, 4, 17, 2, 12] graph = SVG::Graph::Plot.new( { :width => 640, :height => 480, :graph_title => title, :show_graph_title => true, :no_css => true, :key => true, :scale_x_integers => true, :scale_y_integers => false, :min_x_value => 0, :min_y_value => 0, :show_data_labels => true, :show_x_guidelines => true, :show_x_title => true, :x_title => "Time", :show_y_title => true, :y_title => "Ice Cream Cones", :y_title_text_direction => :bt, :show_lines => false, :add_popups => true, :round_popups => false }) graph.add_data( :data => data1, :title => "Dataset 1" ) graph.add_data( :data => data2, :title => "Dataset 2" ) puts graph.burn ruby-svg-graph-1.0.5/test/schedule.rb000066400000000000000000000016161164403311000174670ustar00rootroot00000000000000require 'SVG/Graph/Schedule' title = "Billy's Schedule" data1 = [ "History 107", "5/19/04", "6/30/04", "Algebra 011", "6/2/04", "8/11/04", "Psychology 101", "6/28/04", "8/9/04", "Acting 105", "7/7/04", "8/16/04" ] graph = SVG::Graph::Schedule.new( { :width => 640, :height => 480, :graph_title => title, :show_graph_title => true, :no_css => true, :key => false, :scale_x_integers => true, :scale_y_integers => true, :show_data_labels => true, :show_y_guidelines => false, :show_x_guidelines => true, :show_x_title => true, :x_title => "Time", :show_y_title => false, :rotate_x_labels => true, :rotate_y_labels => false, :x_label_format => "%m/%d", :timescale_divisions => "1 weeks", :add_popups => true, :popup_format => "%m/%d/%y", :area_fill => true, :min_y_value => 0, }) graph.add_data( :data => data1, :title => "Data" ) puts graph.burn ruby-svg-graph-1.0.5/test/single.rb000066400000000000000000000013351164403311000171520ustar00rootroot00000000000000require 'SVG/Graph/BarHorizontal' require 'SVG/Graph/Bar' require 'SVG/Graph/Line' require 'SVG/Graph/Pie' File.open( "data.txt" ) { |fin| title = fin.readline fields = fin.readline.split( /,/ ) female_data = fin.readline.split( " " ).collect{|x| x.to_i} male_data = fin.readline.split( " " ).collect{|x| x.to_i} graph = SVG::Graph::Pie.new( { :width => 640, :height => 480, :fields => fields, :graph_title => title, :show_graph_title => true, :no_css => true, :expanded => true, :show_data_labels => true }) graph.add_data( { :data => female_data, :title => "Female" }) graph.add_data( { :data => male_data, :title => "Male" }) puts graph.burn } ruby-svg-graph-1.0.5/test/test.rb000066400000000000000000000025761164403311000166600ustar00rootroot00000000000000require 'SVG/Graph/BarHorizontal' require 'SVG/Graph/Bar' require 'SVG/Graph/Line' require 'SVG/Graph/Pie' def gen klass, args, title, fields, female_data, male_data args[ :width ] = 640 args[ :height ] = 480 #args[ :compress ] = true args[ :fields ] = fields args[ :graph_title ] = title args[ :show_graph_title ] = true args[ :no_css ] = true puts klass.name graph = klass.new( args ) graph.add_data( { :data => female_data, :title => "Female" }) graph.add_data( { :data => male_data, :title => "Male" }) return graph.burn end File.open( "data.txt" ) { |fin| title = fin.readline fields = fin.readline.split( /,/ ) female_data = fin.readline.split( " " ).collect{|x| x.to_i} male_data = fin.readline.split( " " ).collect{|x| x.to_i} for file, klass, args in [ [ "bar", SVG::Graph::Bar, { :scale_integers => true, :stack => :side } ], [ "barhorizontal",SVG::Graph::BarHorizontal, {:scale_integers=> true, :stack=>:side, :rotate_x_labels => true }], [ "line", SVG::Graph::Line, { :scale_integers => true, :area_fill => true, } ], [ "pie", SVG::Graph::Pie, { :expand_greatest => true, :show_data_labels => true, } ], ] File.open("#{file}.svg", "w") {|fout| fout.print( gen(klass, args, title, fields, female_data, male_data ) ) } end } ruby-svg-graph-1.0.5/test/test_data_point.rb000066400000000000000000000037551164403311000210620ustar00rootroot00000000000000$: << File.dirname(__FILE__) + '/../lib' require "test/unit" require "SVG/Graph/DataPoint" class TestDataPoint < Test::Unit::TestCase def setup DataPoint.reset_shape_criteria end def teardown DataPoint.reset_shape_criteria end def test_default_shape_is_circle_with_2_point_5_radius assert_equal([['circle', { "cx" => 100.0, "cy" => 100.0, "r" => "2.5", "class" => "dataPoint1" }]], DataPoint.new(100.0, 100.0, 1).shape) end def test_default_shape_based_on_description_without_criteria_is_circle_with_2_point_5_radius assert_equal([['circle', { "cx" => 100.0, "cy" => 100.0, "r" => "2.5", "class" => "dataPoint1" }]], DataPoint.new(100.0, 100.0, 1).shape("description")) end def test_shape_for_matching_regular_expression_is_lambda_value DataPoint.configure_shape_criteria( [/angle/, lambda{|x,y,line| ['rect', { "x" => x, "y" => y, "width" => "5", "height" => "5", "class" => "dataPoint#{line}" }] }] ) assert_equal([['rect', { "x" => 100.0, "y" => 50.0, "width" => "5", "height" => "5", "class" => "dataPoint2" }]], DataPoint.new(100.0, 50.0, 2).shape("rectangle")) end def test_multiple_criteria_generate_shapes_in_order DataPoint.configure_shape_criteria( [/3/, lambda{|x,y,line| "three" }], [/2/, lambda{|x,y,line| "two" }], [/1/, lambda{|x,y,line| "one" }] ) assert_equal(["three", "two", "one"], DataPoint.new(100.0, 50.0, 2).shape("1 3 2")) end def test_overlay_match_is_last_and_does_not_prevent_default DataPoint.configure_shape_criteria( [/3/, lambda{|x,y,line| "three" }, DataPoint::OVERLAY] ) default_circle = ['circle', { "cx" => 100.0, "cy" => 50.0, "r" => "2.5", "class" => "dataPoint2" }] assert_equal([default_circle, "three"], DataPoint.new(100.0, 50.0, 2).shape("1 3 2")) end endruby-svg-graph-1.0.5/test/test_plot.rb000066400000000000000000000151211164403311000177040ustar00rootroot00000000000000$: << File.dirname(__FILE__) + '/../lib' require "test/unit" require "svggraph" require "SVG/Graph/DataPoint" class TestSvgGraphPlot < Test::Unit::TestCase def setup DataPoint.reset_shape_criteria end def teardown DataPoint.reset_shape_criteria end def test_plot projection = [ 6, 11, 0, 5, 18, 7, 1, 11, 13, 9, 1, 2, 19, 0, 3, 13, 7, 9 ] actual = [ 0, 18, 8, 15, 9, 4, 18, 14, 10, 2, 11, 6, 14, 12, 15, 6, 4, 17, 2, 12 ] graph = SVG::Graph::Plot.new({ :height => 500, :width => 300, :key => true, :scale_x_integers => true, :scale_y_integerrs => true, }) graph.add_data({ :data => projection, :title => 'Projected', }) graph.add_data({ :data => actual, :title => 'Actual', }) out=graph.burn() assert(out=~/Created with SVG::Graph/) end def test_default_plot_emits_polyline_connecting_data_points actual = [ 0, 18, 8, 15, 9, 4, 18, 14, 10, 2, 11, 6, 14, 12, 15, 6, 4, 17, 2, 12 ] graph = SVG::Graph::Plot.new({ :height => 500, :width => 300, :key => true, :scale_x_integers => true, :scale_y_integerrs => true, }) graph.add_data({ :data => actual, :title => 'Actual', }) out=graph.burn() assert_match(/path.*class='line1'/, out) end def test_disabling_show_lines_does_not_emit_polyline_connecting_data_points actual = [ 0, 18, 8, 15, 9, 4, 18, 14, 10, 2, 11, 6, 14, 12, 15, 6, 4, 17, 2, 12 ] graph = SVG::Graph::Plot.new({ :height => 500, :width => 300, :key => true, :scale_x_integers => true, :scale_y_integers => true, :show_lines => false, }) graph.add_data({ :data => actual, :title => 'Actual', }) out=graph.burn() assert_no_match(/path class='line1' d='M.* L.*'/, out) end def test_popup_values_round_to_integer_by_default_in_popups actual = [ 0.1, 18, 8.55, 15.1234, 9.09876765, 4, ] graph = SVG::Graph::Plot.new({ :height => 500, :width => 300, :key => true, :scale_x_integers => true, :scale_y_integers => true, :add_popups => true, }) graph.add_data({ :data => actual, :title => 'Actual', }) out=graph.burn() assert_no_match(/\(0.1, 18\)/, out) assert_match(/\(0, 18\)/, out) assert_no_match(/\(8.55, 15.1234\)/, out) assert_match(/\(8, 15\)/, out) assert_no_match(/\(9.09876765, 4\)/, out) assert_match(/\(9, 4\)/, out) end def test_do_not_round_popup_values_shows_decimal_values_in_popups actual = [ 0.1, 18, 8.55, 15.1234, 9.09876765, 4, ] graph = SVG::Graph::Plot.new({ :height => 500, :width => 300, :key => true, :scale_x_integers => true, :scale_y_integers => true, :add_popups => true, :round_popups => false, }) graph.add_data({ :data => actual, :title => 'Actual', }) out=graph.burn() assert_match(/\(0.1, 18\)/, out) assert_no_match(/\(0, 18\)/, out) assert_match(/\(8.55, 15.1234\)/, out) assert_no_match(/\(8, 15\)/, out) assert_match(/\(9.09876765, 4\)/, out) assert_no_match(/\(9, 4\)/, out) end def test_description_is_shown_in_popups_if_provided actual = [ 8.55, 15.1234, 9.09876765, 4, 0.1, 18, ] description = [ 'first', 'second', 'third', ] graph = SVG::Graph::Plot.new({ :height => 500, :width => 300, :key => true, :scale_x_integers => true, :scale_y_integers => true, :add_popups => true, :round_popups => false, }) graph.add_data({ :data => actual, :title => 'Actual', :description => description, }) out=graph.burn() assert_match(/\(8.55, 15.1234, first\)/, out) assert_no_match(/\(8.55, 15.1234\)/, out) assert_match(/\(9.09876765, 4, second\)/, out) assert_no_match(/\(9.09876765, 4\)/, out) assert_match(/\(0.1, 18, third\)/, out) assert_no_match(/\(0.1, 18\)/, out) end def test_combine_different_shapes_based_on_description actual = [ 8.55, 15.1234, 9.09876765, 4, 2.1, 18, ] description = [ 'one is a circle', 'two is a rectangle', 'three is a rectangle with strikethrough', ] DataPoint.configure_shape_criteria( [/^t.*/, lambda{|x,y,line| ['polygon', { "points" => "#{x-1.5},#{y+2.5} #{x+1.5},#{y+2.5} #{x+1.5},#{y-2.5} #{x-1.5},#{y-2.5}", "class" => "dataPoint#{line}" }] }], [/^three.*/, lambda{|x,y,line| ['line', { "x1" => "#{x-4}", "y1" => y.to_s, "x2" => "#{x+4}", "y2" => y.to_s, "class" => "axis" }] }] ) graph = SVG::Graph::Plot.new({ :height => 500, :width => 300, :key => true, :scale_x_integers => true, :scale_y_integers => true, :add_popups => true, :round_popups => false, }) graph.add_data({ :data => actual, :title => 'Actual', :description => description, }) out=graph.burn() assert_match(/polygon.*points/, out) assert_match(/line.*axis/, out) end def test_popup_radius_is_10_by_default actual = [ 1, 1, 5, 5, 10, 10, ] description = [ 'first', 'second', 'third', ] graph = SVG::Graph::Plot.new({ :height => 500, :width => 300, :key => true, :scale_x_integers => true, :scale_y_integers => true, :add_popups => true, :round_popups => false, }) graph.add_data({ :data => actual, :title => 'Actual', :description => description, }) out=graph.burn() assert_match(/circle .* r='10'/, out) assert_match(/circle .* onmouseover=.*/, out) end def test_popup_radius_is_overridable actual = [ 1, 1, 5, 5, 10, 10, ] description = [ 'first', 'second', 'third', ] graph = SVG::Graph::Plot.new({ :height => 500, :width => 300, :key => true, :scale_x_integers => true, :scale_y_integers => true, :add_popups => true, :round_popups => false, :popup_radius => 1.23 }) graph.add_data({ :data => actual, :title => 'Actual', :description => description, }) out=graph.burn() assert_match(/circle .* r='1.23'/, out) assert_match(/circle .* onmouseover=.*/, out) end end ruby-svg-graph-1.0.5/test/test_svg_graph.rb000066400000000000000000000011731164403311000207100ustar00rootroot00000000000000$: << File.dirname(__FILE__) + '/../lib' require "test/unit" require "svggraph" require "SVG/Graph/DataPoint" class TestSvgGraph < Test::Unit::TestCase def test_bar_line_and_pie fields = %w(Jan Feb Mar); data_sales_02 = [12, 45, 21] [SVG::Graph::Bar, SVG::Graph::BarHorizontal, SVG::Graph::Line, SVG::Graph::Pie].each do |klass| graph = klass.new( :height => 500, :width => 300, :fields => fields ) graph.add_data( :data => data_sales_02, :title => 'Sales 2002' ) out=graph.burn assert(out=~/Created with SVG::Graph/) end end end ruby-svg-graph-1.0.5/test/timeseries.rb000066400000000000000000000025371164403311000200470ustar00rootroot00000000000000require 'SVG/Graph/TimeSeries' title = "Plot" #data1 = [] #(rand(10)+5).times{ # data1 << rand(20) # data1 << rand(20) #} data1 = ["6/17/74", 11, "1/11/74", 7, "4/13/04 17:31", 11, "9/11/01", 9, "9/1/85", 2, "9/1/88", 1, "1/15/95", 13] #data2 = [] #(rand(10)+5).times{ # data2 << rand(20) # data2 << rand(20) #} data2 = ["8/1/73", 18, "10/21/76", 15, "01/11/80", 4, "4/3/83", 14, "6/23/86", 6, "9/13/89", 12, "12/3/92", 6, "2/24/96", 17, "5/16/99", 12, "8/5/02", 7] data3 = ["1/1/78", 5, "1/1/83", 13, "1/1/93", 10, "1/1/03", 5] graph = SVG::Graph::TimeSeries.new( { :width => 640, :height => 480, :graph_title => title, :show_graph_title => true, :no_css => true, :key => true, :scale_x_integers => true, :scale_y_integers => true, :show_data_values => true, :show_x_guidelines => true, :show_x_title => true, :x_title => "Time", :show_y_title => true, :y_title => "Units", :y_title_text_direction => :bt, :stagger_x_labels => true, :x_label_format => "%Y", :min_x_value => "1/1/73", :timescale_divisions => "5 years", :add_popups => true, :popup_format => "%m/%d/%y", #:area_fill => true, :min_y_value => 0, }) graph.add_data( :data => data1, :title => "Ice Cream" ) graph.add_data( :data => data2, :title => "Ice Cream Cones" ) graph.add_data( :data => data3, :title => "Sprinkles" ) puts graph.burn