pax_global_header00006660000000000000000000000064151135163600014513gustar00rootroot0000000000000052 comment=ab61976766ce7935774ea19327dcd572d8dc065e Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/000077500000000000000000000000001511351636000204735ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/.github/000077500000000000000000000000001511351636000220335ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/.github/workflows/000077500000000000000000000000001511351636000240705ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/.github/workflows/main.yml000066400000000000000000000007251511351636000255430ustar00rootroot00000000000000name: 'Repo Lockdown' on: pull_request_target: types: opened permissions: pull-requests: write jobs: action: runs-on: ubuntu-latest steps: - uses: dessant/repo-lockdown@v4 with: pr-comment: 'Thank you for contributing to Graphs by opening this pull request. The repository has been moved to the GNOME Gitlab, please open a merge request at [the GitLab page](https://gitlab.gnome.org/World/Graphs/-/merge_requests) instead.' Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/.gitignore000066400000000000000000000001521511351636000224610ustar00rootroot00000000000000#VSCode .vscode/ # Meson _build/ data/gschemas.compiled .flatpak-builder/ .flatpak/ # Python __pycache__/ Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/.gitlab-ci.yml000066400000000000000000000023161511351636000231310ustar00rootroot00000000000000include: - project: "GNOME/citemplates" file: "flatpak/flatpak_ci_initiative.yml" .vars: image: quay.io/gnome_infrastructure/gnome-runtime-images:gnome-49 variables: MANIFEST_PATH: se.sjoerd.Graphs.json FLATPAK_MODULE: graphs APP_ID: se.sjoerd.Graphs RUNTIME_REPO: "https://nightly.gnome.org/gnome-nightly.flatpakrepo" CI_IMAGE_X86_64: "quay.io/gnome_infrastructure/gnome-runtime-images:gnome-master" BUNDLE: "se.sjoerd.Graphs.flatpak" interruptible: true cache: key: "$CI_JOB_NAME" paths: - .flatpak-builder/downloads - .flatpak-builder/git - .flatpak-builder/cache # Build Flatpak for x86_64 flatpak@x86_64: stage: build extends: - .flatpak@x86_64 - .vars # Build Flatpak for aarch64 flatpak@aarch64: stage: build extends: - .flatpak@aarch64 - .vars python-lint: image: python:3.12.6 stage: .pre script: - pip install flake8 flake8-docstrings flake8-simplify flake8-unused-arguments flake8-quotes flake8-bugbear flake8-pie flake8-print flake8-warnings flake8-commas flake8-builtins flake8-import-order pep8-naming - flake8 vala-lint: image: "valalang/lint:latest" stage: .pre script: - io.elementary.vala-lint graphs Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/Graphs.doap000066400000000000000000000042471511351636000225730ustar00rootroot00000000000000 Graphs Plot and manipulate data Python Vala GTK 4 Libadwaita Sjoerd Stendahl sstendahl sstendahl sstendahl Christoph Kohnen cmkohnen cmkohnen cmkohnen Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/LICENSE000066400000000000000000001045151511351636000215060ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. 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 them 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 prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. 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. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey 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; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. 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. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 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. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. 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 state 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 3 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, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program 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, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU 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 Lesser General Public License instead of this License. But first, please read . Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/README.md000066400000000000000000000132061511351636000217540ustar00rootroot00000000000000## We have moved The project source code is now hosted over on [GNOME Gitlab](https://gitlab.gnome.org/World/Graphs). # Graphs Plot and manipulate data with Graphs! ![image](https://gitlab.gnome.org/World/Graphs/-/raw/main/data/screenshots/sin_cos.png?ref_type=heads) Graphs is a simple, yet powerful tool that allows you to plot and manipulate your data with ease. New data can be imported from a wide variety of filetypes, or generated by equation. All data can be manipulated using a variety of operations. Apart from regular operations, Graphs also has support for curve fitting on the data, allowing you to effortlessly analyze trends within your datasets. Graphs supports extensive customization options to change the style of the plots. You can add and edit stylesheets in detail, allowing you to quickly save and apply existing stylesheets on new data. Graphs is an excellent fit for both plotting and data manipulation. The plots created with Graphs can be saved in a variety of formats suitable for sharing and presenting to a wide audience, such as in a scientific publication or presentations. It is also possible to save the plots as vector images, which can be easily edited in programs like Inkscape for further customization and refinement. Graphs is written with the GNOME environment in mind, but should be suitable for any other desktop environment as well. The operations include: - Shifting data - Normalizing Data - Smoothening data - Centering Data - Cutting Data - Combining Data - Translating data - Derivative and indefinite integral - Fourier Transformations - Custom transformations For feedback or general issues, please file an issue [at the GitLab issue tracker](https://gitlab.gnome.org/World/Graphs/-/issues). ## Install Graphs ### Stable Since Graphs is developed using GNOME Builder, most testing will be done in a Flatpak environment, and the recommended installation method is therefore to install Graphs from Flathub. An official build is also available on the Snap store:

Download on Flathub   Get it from the Snap Store

### Beta The latest testing version of Graphs is available in the Flathub beta channel. To install the beta, first the Flatpak remote needs to be configured: ```sh flatpak remote-add --if-not-exists flathub-beta https://flathub.org/beta-repo/flathub-beta.flatpakrepo ``` Then, install the application: ```sh flatpak install flathub-beta se.sjoerd.Graphs ``` To run the beta version by default, the following command can be used: ```sh sudo flatpak make-current se.sjoerd.Graphs beta ``` Note that the `sudo` is neccesary here, as it sets the current branch on the system level. To install this on a per-user basis, the flag `--user` can be used in the previous commands. To switch back to the stable version simply run the above command replacing `beta` with `stable`. A beta version is also available in the beta channel of the Snap Store. We are always looking for feedback, so feel free to report any issues or suggestions on the GitLab [issue tracker](https://gitlab.gnome.org/World/Graphs/-/issues). ## How to build from source This project is developed in [GNOME Builder](https://developer.gnome.org/documentation/introduction/builder.html). After cloning and opening the project, you can press run to verify you have all correct dependencies installed. You might need to install meson, if it is not already available on your system. When the project successfully ran, you can create a Flatpak-bundle on the buildchain menu, which you then can install on your system. If you want to try the latest development, we urge you to try the Flathub beta branch instead of building yourself. ### Build without Flatpak This project targets the GNOME Platform on Flathub. Manually building Graphs for any other platform is currently **not supported**. If you want to build without Flatpak anyway these instructions might help: build-time dependencies: `meson, blueprint-compiler, gettext`, `vala`, `gtk4-devel`, `libadwaita-devel` runtime dependencies: `matplotlib, python3-matplotlib-gtk4, scipy, numpy, numexpr, sympy` The actual package names might vary depending on your distribution, and depending on your distribution additional packages may be required. building: ``` git clone https://gitlab.gnome.org/World/Graphs.git cd Graphs meson setup build ninja -C build ninja -C build/ install ``` Uninstall could then be done with the following: ``` ninja -C build/ uninstall ``` Please note, that this install might have issues that the Flatpak version does not. ## How to contribute ### Translations Graphs is translated on the GNOME translation platform [Damned Lies](https://l10n.gnome.org/module/Graphs). If you wish to contribute by translating Graphs, you can join a language team. More information on that can be found on the [GNOME Translation Project Wiki](https://wiki.gnome.org/TranslationProject). ### Code If you wish to contribute to the code of Graphs, feel free to submit a [Merge Request](https://gitlab.gnome.org/World/Graphs/-/merge_requests). We are always happy for contributions, and new code is generally reviewed within a few days time. ### Feedback and bug reports If you found an issue or have general feedback, please file an issue at the [issue tracker](https://gitlab.gnome.org/World/Graphs/-/issues). ## Code of Conduct This project follows the [GNOME Code of Conduct](https://conduct.gnome.org/committee/). Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/build-aux/000077500000000000000000000000001511351636000223655ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/build-aux/flatpak/000077500000000000000000000000001511351636000240075ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/build-aux/flatpak/fix-aarch64-build.patch000066400000000000000000000014101511351636000301350ustar00rootroot00000000000000From ff614575c96a6850948ff69f33d66e8f013a6318 Mon Sep 17 00:00:00 2001 From: Martin Kroeker Date: Wed, 9 Jul 2025 14:44:25 +0200 Subject: [PATCH] Fix arm64 HAVE_SME setting for DYNAMIC_ARCH builds --- kernel/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 1a5d32e071..0ec08827d7 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -238,6 +238,9 @@ function (build_core TARGET_CORE KDIR TSUFFIX KERNEL_DEFINITIONS) if (X86_64 OR ARM64) set(USE_DIRECT_SGEMM true) endif() + if (UC_TARGET_CORE MATCHES ARMV9SME) + set (HAVE_SME true) + endif () if (USE_DIRECT_SGEMM) # if (NOT DEFINED SGEMMDIRECTKERNEL)Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/build-aux/flatpak/python-dependencies.json000066400000000000000000000626451511351636000306640ustar00rootroot00000000000000{ "name": "python-dependencies", "buildsystem": "simple", "build-commands": [], "modules": [ { "name": "openblas", "buildsystem": "cmake-ninja", "builddir": true, "config-opts": [ "-DBUILD_TESTING:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=ON", "-DBUILD_STATIC_LIBS:BOOL=OFF", "-DDYNAMIC_ARCH:BOOL=ON", "-DCMAKE_BUILD_TYPE:STRING=Release" ], "sources": [ { "type": "git", "url": "https://github.com/OpenMathLib/OpenBLAS", "commit": "993fad6aebbce34a97d3f8c34d6d79d35b64cc48", "tag": "v0.3.30", "x-checker-data": { "type": "git", "tag-pattern": "^v([\\d.]+)$" } }, { "type": "patch", "path": "fix-aarch64-build.patch" } ] }, { "name": "python-build-dependencies", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"packaging\" \"pyproject-metadata\" \"meson-python\" \"setuptools-scm\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", "sha256": "29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", "x-checker-data": { "type": "pypi", "name": "packaging", "packagetype": "bdist_wheel" } }, { "type": "file", "url": "https://files.pythonhosted.org/packages/7e/b1/8e63033b259e0a4e40dd1ec4a9fee17718016845048b43a36ec67d62e6fe/pyproject_metadata-0.9.1-py3-none-any.whl", "sha256": "ee5efde548c3ed9b75a354fc319d5afd25e9585fa918a34f62f904cc731973ad", "x-checker-data": { "type": "pypi", "name": "pyproject-metadata", "packagetype": "bdist_wheel" } }, { "type": "file", "url": "https://files.pythonhosted.org/packages/28/58/66db620a8a7ccb32633de9f403fe49f1b63c68ca94e5c340ec5cceeb9821/meson_python-0.18.0-py3-none-any.whl", "sha256": "3b0fe051551cc238f5febb873247c0949cd60ded556efa130aa57021804868e2", "x-checker-data": { "type": "pypi", "name": "meson-python", "packagetype": "bdist_wheel" } }, { "type": "file", "url": "https://files.pythonhosted.org/packages/f7/14/dd3a6053325e882fe191fb4b42289bbdfabf5f44307c302903a8a3236a0a/setuptools_scm-9.2.0-py3-none-any.whl", "sha256": "c551ef54e2270727ee17067881c9687ca2aedf179fa5b8f3fab9e8d73bdc421f", "x-checker-data": { "type": "pypi", "name": "setuptools-scm", "packagetype": "bdist_wheel" } } ] }, { "name": "python-numpy", "buildsystem": "simple", "build-commands": [], "modules": [ { "name": "numpy", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} --no-build-isolation -Csetup-args=-Dallow-noblas=false \"numpy\"" ], "build-options": { "ldflags": "-lgfortran" }, "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", "sha256": "ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", "x-checker-data": { "type": "pypi", "name": "numpy" } } ] } ] }, { "name": "python-scipy", "buildsystem": "simple", "build-commands": [], "modules": [ { "name": "ply", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} --no-build-isolation \"ply\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", "sha256": "00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", "x-checker-data": { "type": "pypi", "name": "ply" } } ] }, { "name": "pythran", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"pythran\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/a3/61/8001b38461d751cd1a0c3a6ae84346796a5758123f3ed97a1b121dfbf4f3/gast-0.6.0-py3-none-any.whl", "sha256": "52b182313f7330389f72b069ba00f174cfe2a06411099547288839c6cbafbd54", "x-checker-data": { "type": "pypi", "name": "gast", "packagetype": "bdist_wheel" } }, { "type": "file", "url": "https://files.pythonhosted.org/packages/44/e4/6e8731d4d10dd09942a6f5015b2148ae612bf13e49629f33f9fade3c8253/beniget-0.4.2.post1-py3-none-any.whl", "sha256": "e1b336e7b5f2ae201e6cc21f533486669f1b9eccba018dcff5969cd52f1c20ba", "x-checker-data": { "type": "pypi", "name": "beniget", "packagetype": "bdist_wheel" } }, { "type": "file", "url": "https://files.pythonhosted.org/packages/a2/49/c5c72ebb49edf56bb06d3b805870cf6598565461670d88d292085ac96bfe/pythran-0.18.0-py3-none-any.whl", "sha256": "405ecf2100d4926d1a15640c36bd1b19a560386653d0ee4d5234f9421ef4034b", "x-checker-data": { "type": "pypi", "name": "pythran", "packagetype": "bdist_wheel" } } ] }, { "name": "pybind11", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"pybind11\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/cd/8a/37362fc2b949d5f733a8b0f2ff51ba423914cabefe69f1d1b6aab710f5fe/pybind11-3.0.1-py3-none-any.whl", "sha256": "aa8f0aa6e0a94d3b64adfc38f560f33f15e589be2175e103c0a33c6bce55ee89", "x-checker-data": { "type": "pypi", "name": "pybind11", "packagetype": "bdist_wheel" } } ] }, { "name": "scipy", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} --no-build-isolation \"scipy\"" ], "build-options": { "ldflags": "-lgfortran" }, "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/4c/3b/546a6f0bfe791bbb7f8d591613454d15097e53f906308ec6f7c1ce588e8e/scipy-1.16.2.tar.gz", "sha256": "af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b", "x-checker-data": { "type": "pypi", "name": "scipy" } } ] } ] }, { "name": "python-matplotlib", "buildsystem": "simple", "build-commands": [], "modules": [ { "name": "qhull", "buildsystem": "cmake-ninja", "builddir": true, "config-opts": [ "-DBUILD_APPLICATIONS:BOOL=OFF", "-DBUILD_STATIC_LIBS:BOOL=OFF", "-DQHULL_ENABLE_TESTING:BOOL=OFF", "-DCMAKE_BUILD_TYPE:STRING=Release", "-DCMAKE_POLICY_VERSION_MINIMUM=3.5" ], "sources": [ { "type": "git", "url": "https://github.com/qhull/qhull", "commit": "613debeaea72ee66626dace9ba1a2eff11b5d37d", "tag": "v8.0.2", "x-checker-data": { "type": "git", "tag-pattern": "^v([\\d.]+)$" } } ] }, { "name": "Raqm", "buildsystem": "meson", "config-opts": [ "--buildtype=release" ], "sources": [ { "type": "git", "url": "https://github.com/HOST-Oman/libraqm", "commit": "3efba870c8d2775cb3287122f58530c6fbb20f72", "tag": "v0.10.3", "x-checker-data": { "type": "git", "tag-pattern": "^v([\\d.]+)$" } } ] }, { "name": "pillow", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} --no-build-isolation \"pillow\" -C zlib=enable -C jpeg=enable -C tiff=enable -C freetype=enable -C raqm=enable -C lcms=enable -C webp=enable -C jpeg2000=enable -C avif=enable" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", "sha256": "3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", "x-checker-data": { "type": "pypi", "name": "pillow" } } ] }, { "name": "contourpy", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} --no-build-isolation \"contourpy\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", "sha256": "083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", "x-checker-data": { "type": "pypi", "name": "contourpy" } } ] }, { "name": "fonttools", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} --no-build-isolation \"fonttools\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/27/d9/4eabd956fe123651a1f0efe29d9758b3837b5ae9a98934bdb571117033bb/fonttools-4.60.0.tar.gz", "sha256": "8f5927f049091a0ca74d35cce7f78e8f7775c83a6901a8fbe899babcc297146a", "x-checker-data": { "type": "pypi", "name": "fonttools" } } ] }, { "name": "cppy", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"cppy\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/8b/3c/823fda3f226d84f983e48005527a6dd2dc365220ac4f098b770779d9f4a1/cppy-1.3.1-py3-none-any.whl", "sha256": "7ca132b6ef6187384738804bd3a453d4eab7e3274df6dcd35e5d92aae3404717", "x-checker-data": { "type": "pypi", "name": "cppy", "packagetype": "bdist_wheel" } } ] }, { "name": "kiwisolver", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} --no-build-isolation \"kiwisolver\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", "sha256": "c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", "x-checker-data": { "type": "pypi", "name": "kiwisolver" } } ] }, { "name": "cycler", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"cycler\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", "sha256": "85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", "x-checker-data": { "type": "pypi", "name": "cycler", "packagetype": "bdist_wheel" } } ] }, { "name": "python-dateutil", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"python-dateutil\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", "sha256": "4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", "x-checker-data": { "type": "pypi", "name": "six", "packagetype": "bdist_wheel" } }, { "type": "file", "url": "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", "sha256": "a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", "x-checker-data": { "type": "pypi", "name": "python-dateutil", "packagetype": "bdist_wheel" } } ] }, { "name": "pyparsing", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"pyparsing\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl", "sha256": "91d0fcde680d42cd031daf3a6ba20da3107e08a75de50da58360e7d94ab24d36", "x-checker-data": { "type": "pypi", "name": "pyparsing", "packagetype": "bdist_wheel" } } ] }, { "name": "matplotlib", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} --no-build-isolation --config-settings=setup-args=\"-Dsystem-freetype=true\" --config-settings=setup-args=\"-Dsystem-qhull=true\" \"matplotlib\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/a0/59/c3e6453a9676ffba145309a73c462bb407f4400de7de3f2b41af70720a3c/matplotlib-3.10.6.tar.gz", "sha256": "ec01b645840dd1996df21ee37f208cd8ba57644779fa20464010638013d3203c", "x-checker-data": { "type": "pypi", "name": "matplotlib" } } ] } ] }, { "name": "python-numexpr", "buildsystem": "simple", "build-commands": [], "modules": [ { "name": "numexpr", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} --no-build-isolation \"numexpr\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/7c/08/211c9ae8a230f20976f3b0b9a3308264c62bd05caf92aba7c59beebf6049/numexpr-2.12.1.tar.gz", "sha256": "e239faed0af001d1f1ea02934f7b3bb2bb6711ddb98e7a7bef61be5f45ff54ab", "x-checker-data": { "type": "pypi", "name": "numexpr" } } ] } ] }, { "name": "python-sympy", "buildsystem": "simple", "build-commands": [], "modules": [ { "name": "sympy", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"sympy\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", "sha256": "a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", "x-checker-data": { "type": "pypi", "name": "mpmath", "packagetype": "bdist_wheel" } }, { "type": "file", "url": "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", "sha256": "e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", "x-checker-data": { "type": "pypi", "name": "sympy", "packagetype": "bdist_wheel" } } ] } ] }, { "name": "python-gio-pyio", "buildsystem": "simple", "build-commands": [], "modules": [ { "name": "gio-pyio", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} --no-build-isolation \"gio-pyio\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/5b/51/3c95292f35445e525922c2488d466b4e885595779449bd1becac66546a8e/gio_pyio-0.0.6.tar.gz", "sha256": "bab1231c5a456eee014a27e169ba48735cf532e34b36688b86b74ca3e5ab5915", "x-checker-data": { "type": "pypi", "name": "gio-pyio" } } ] } ] } ] } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/build-aux/flatpak/python-tests.json000066400000000000000000000031311511351636000273610ustar00rootroot00000000000000{ "name": "python-test-dependencies", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"pytest\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", "sha256": "9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", "x-checker-data": { "type": "pypi", "name": "iniconfig", "packagetype": "bdist_wheel" } }, { "type": "file", "url": "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", "sha256": "e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", "x-checker-data": { "type": "pypi", "name": "pluggy", "packagetype": "bdist_wheel" } }, { "type": "file", "url": "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", "sha256": "872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", "x-checker-data": { "type": "pypi", "name": "pytest", "packagetype": "bdist_wheel" } } ] }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/build-aux/snap/000077500000000000000000000000001511351636000233265ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/build-aux/snap/snapcraft.yaml000066400000000000000000000031071511351636000261740ustar00rootroot00000000000000name: graphs base: core24 adopt-info: graphs grade: stable confinement: strict compression: lzo platforms: amd64: arm64: slots: graphs: interface: dbus bus: session name: se.sjoerd.Graphs parts: python-deps: source: . plugin: python python-packages: - matplotlib==3.8.4 - numexpr==2.10.0 - numpy==1.26.4 - scipy==1.13.0 - sympy==1.12 prime: - -usr/bin/activate* - -usr/bin/Activate.ps1 - -usr/bin/python* - -usr/bin/pip* - -pyvenv.cfg - -share - -include - -lib - -lib64 - -usr/lib/*/dist-packages/pip* - -usr/lib/*/dist-packages/setuptools* - -usr/lib/*/dist-packages/pkg_resources* organize: bin: usr/bin lib/python3.12/site-packages: usr/lib/python3/dist-packages graphs: after: [ python-deps ] plugin: meson source: . meson-parameters: - --prefix=/snap/graphs/current/usr - --buildtype=release build-environment: - PYTHONPATH: $CRAFT_STAGE/usr/lib/python3/dist-packages:$PYTHONPATH build-snaps: - blueprint-compiler/latest/edge organize: snap/graphs/current: . parse-info: [usr/share/appdata/se.sjoerd.Graphs.appdata.xml] apps: graphs: command: usr/bin/graphs desktop: usr/share/applications/se.sjoerd.Graphs.desktop common-id: se.sjoerd.Graphs environment: PYTHONPATH: $SNAP/usr/lib/python3/dist-packages:$PYTHONPATH GI_TYPELIB_PATH: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET/girepository-1.0:$GI_TYPELIB_PATH extensions: [gnome] Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/000077500000000000000000000000001511351636000214045ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/Logo.svg000066400000000000000000000101701511351636000230240ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/generate_gresource.py000066400000000000000000000125021511351636000256260ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Generate Graphs GResource at build time.""" import argparse import importlib.util import logging import shutil import sys from pathlib import Path from xml.etree import ElementTree from PIL import Image from gi.repository import Gio from matplotlib import font_manager import numpy parser = argparse.ArgumentParser( description="Generate Graphs GResource at build time.", ) parser.add_argument( "out", help="The output file", ) parser.add_argument( "dir", help="Path to build directory", ) parser.add_argument( "style_io", help="Path to style_io.py", ) parser.add_argument( "--ui", required=True, nargs="+", dest="ui", help="List of UI Files.", ) parser.add_argument( "--styles", required=True, nargs="+", dest="styles", help="List of Styles.", ) parser.add_argument( "--other", required=True, nargs="+", dest="other", help="List of other Files to include at toplevel.", ) parser.add_argument( "--icons", required=True, nargs="+", dest="icons", help="List of icon Files.", ) args = parser.parse_args() # Check fonts font_list = font_manager.findSystemFonts(fontpaths=None, fontext="ttf") for font in font_list: try: font_manager.fontManager.addfont(font) except RuntimeError: logging.warning("Could not load %s", font) # Disable matplotlib logging logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) # dynamically import style_io spec = importlib.util.spec_from_file_location("style_io", args.style_io) style_io = importlib.util.module_from_spec(spec) sys.modules["style_io"] = style_io spec.loader.exec_module(style_io) # GResource tree creation gresources = ElementTree.Element("gresources") main_gresource = ElementTree.SubElement( gresources, "gresource", attrib={"prefix": "/se/sjoerd/Graphs/"}, ) # Begin Other Section for file in args.other: name = Path(shutil.copy(file, args.dir)).name element = ElementTree.SubElement( main_gresource, "file", attrib={ "compressed": "True", }, ) element.text = name # End Other Section # Begin style section styles = {} styles_gresource = ElementTree.SubElement( gresources, "gresource", attrib={"prefix": "/se/sjoerd/Graphs/styles/"}, ) for style_path in args.styles: style_file = shutil.copy(style_path, args.dir) name = Path(style_file).name style_element = ElementTree.SubElement( styles_gresource, "file", attrib={ "compressed": "True", }, ) style_element.text = name params_file = Gio.File.new_for_path(style_file) params, stylename = style_io.parse(params_file) out_path = Path(args.dir, name.replace(".mplstyle", ".png")) styles[stylename] = out_path with open(out_path, "wb") as out_file: style_io.create_preview(out_file, params, "png") preview_element = ElementTree.SubElement( main_gresource, "file", attrib={ "compressed": "True", }, ) preview_element.text = out_path.name def _to_array(file_path): with open(file_path, "rb") as file: return numpy.array(Image.open(file).convert("RGB")) # Generate stitched system previews for Adwaita and Yaru for sys_style in ("Adwaita", "Yaru"): light_array = _to_array(styles[sys_style]) dark_array = _to_array(styles[sys_style + " Dark"]) height, width = light_array.shape[0:2] stitched_array = numpy.concatenate( (light_array[:, :width // 2], dark_array[:, width // 2:]), axis=1, ) stitched_image = Image.fromarray(stitched_array) out_path = Path(args.dir + "/system-style-" + sys_style.lower() + ".png") with open(out_path, "wb") as file: stitched_image.save(file, "PNG") preview_element = ElementTree.SubElement( main_gresource, "file", attrib={ "compressed": "True", }, ) preview_element.text = out_path.name # End style section # Begin ui section ui_gresource = ElementTree.SubElement( gresources, "gresource", attrib={"prefix": "/se/sjoerd/Graphs/ui/"}, ) help_overlay_path = None for ui_file in args.ui: path = Path(ui_file) if path.name == "shortcuts.ui": help_overlay_path = path continue ui_file_element = ElementTree.SubElement( ui_gresource, "file", attrib={ "preprocess": "xml-stripblanks", "alias": path.name, }, ) ui_file_element.text = "ui/" + path.name help_overlay_element = ElementTree.SubElement( main_gresource, "file", attrib={ "preprocess": "xml-stripblanks", "alias": "gtk/help-overlay.ui", }, ) help_overlay_element.text = "ui/" + help_overlay_path.name # End ui section # Begin icon section icon_gresource = ElementTree.SubElement( gresources, "gresource", attrib={"prefix": "/se/sjoerd/Graphs/icons/scalable/actions/"}, ) for icon_file in args.icons: path = Path(Path(shutil.copy(icon_file, args.dir))) icon_file_element = ElementTree.SubElement( icon_gresource, "file", attrib={ "preprocess": "xml-stripblanks", }, ) icon_file_element.text = path.name # End icon section # Write tree = ElementTree.ElementTree(gresources) tree.write(args.out) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/000077500000000000000000000000001511351636000225175ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/axes-one-quadrant-symbolic.svg000066400000000000000000000003421511351636000304120ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/center-symbolic.svg000066400000000000000000000024621511351636000263430ustar00rootroot00000000000000 check-round-outline-whole-symbolic.svg000066400000000000000000000010721511351636000317730ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/color-picker-symbolic.svg000066400000000000000000000026341511351636000274550ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/derivative2-symbolic.svg000066400000000000000000000052031511351636000273030ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/down-symbolic.svg000066400000000000000000000007451511351636000260340ustar00rootroot00000000000000 fast-fourier-transform-symbolic.svg000066400000000000000000000026471511351636000314300ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/function-third-order-symbolic.svg000066400000000000000000000045121511351636000311270ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/graph-symbolic.svg000066400000000000000000000025031511351636000261600ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/hicolor/000077500000000000000000000000001511351636000241565ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/hicolor/scalable/000077500000000000000000000000001511351636000257245ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/hicolor/scalable/apps/000077500000000000000000000000001511351636000266675ustar00rootroot00000000000000se.sjoerd.Graphs.Devel.svg000066400000000000000000000557441511351636000335250ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/hicolor/scalable/apps se.sjoerd.Graphs.svg000066400000000000000000000273171511351636000324620ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/hicolor/scalable/apps Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/hicolor/symbolic/000077500000000000000000000000001511351636000257775ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/hicolor/symbolic/apps/000077500000000000000000000000001511351636000267425ustar00rootroot00000000000000se.sjoerd.Graphs-symbolic.svg000066400000000000000000000065121511351636000343460ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/hicolor/symbolic/apps Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/history-undo-symbolic.svg000066400000000000000000000026771511351636000275370ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/horizontal-arrows-symbolic.svg000066400000000000000000000017401511351636000305650ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/image-print-symbolic.svg000066400000000000000000000020461511351636000272750ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/info-symbolic.svg000066400000000000000000000010321511351636000260060ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/integral-symbolic.svg000066400000000000000000000006351511351636000266700ustar00rootroot00000000000000 inverse-fast-fourier-transform-symbolic.svg000066400000000000000000000021301511351636000330640ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/left-symbolic.svg000066400000000000000000000007551511351636000260200ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/list-compact-symbolic.svg000066400000000000000000000017761511351636000274710ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/loupe-symbolic.svg000066400000000000000000000015741511351636000262120ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/move-tool-symbolic.svg000066400000000000000000000011501511351636000267750ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/normalize-symbolic.svg000066400000000000000000000033461511351636000270650ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/right-symbolic.svg000066400000000000000000000007521511351636000262000ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/se.sjoerd.Graphs.Source.svg000066400000000000000000001047741511351636000276330ustar00rootroot00000000000000 Adwaita Icon Template image/svg+xml GNOME Design Team Adwaita Icon Template Hicolor Symbolic Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/settings-symbolic.svg000066400000000000000000000035731511351636000267270ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/shift-symbolic.svg000066400000000000000000000024561511351636000262030ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/smoothen-symbolic.svg000066400000000000000000000027261511351636000267220ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/transform-symbolic.svg000066400000000000000000000024201511351636000270700ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/up-symbolic.svg000066400000000000000000000007271511351636000255110ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/icons/vertical-arrows-symbolic.svg000066400000000000000000000035761511351636000302160ustar00rootroot00000000000000 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/meson.build000066400000000000000000000137131511351636000235530ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later desktop_file_in = configure_file( input: application_id + '.desktop.in', output: application_id + '.desktop.in', configuration: conf, ) desktop_file = i18n.merge_file( input: desktop_file_in, output: application_id + '.desktop', type: 'desktop', po_dir: '../po', install: true, install_dir: join_paths(datadir, 'applications'), ) desktop_file_validate = find_program('desktop-file-validate', required: false) if desktop_file_validate.found() test('Validate desktop file', desktop_file_validate, args: [desktop_file.full_path()], ) endif appstream_file_in = configure_file( input: application_id + '.appdata.xml.in', output: application_id + '.appdata.xml.in', configuration: conf, ) appstream_file = i18n.merge_file( input: appstream_file_in, output: application_id + '.appdata.xml', po_dir: '../po', install: true, install_dir: join_paths(datadir, 'appdata'), ) # Validate Appdata appstreamcli = find_program('appstreamcli', required: false) if (appstreamcli.found()) test('Validate appstream file', appstreamcli, args: ['validate', '--no-net', '--explain', appstream_file.full_path()], workdir: meson.current_build_dir(), ) endif install_data(application_id + '.gschema.xml', install_dir: join_paths(datadir, 'glib-2.0/schemas'), install_mode: 'rwxrwxrwx', ) gnome.compile_schemas( depend_files: [application_id + '.gschema.xml'], ) install_data(application_id + '.mime.xml', install_dir: get_option('datadir') / 'mime/packages', ) compile_schemas = find_program('glib-compile-schemas', required: false) if compile_schemas.found() test('Validate schema file', compile_schemas, args: ['--strict', '--dry-run', meson.current_source_dir()], ) endif blueprint_files = [ 'ui/add-equation.blp', 'ui/add-style.blp', 'ui/curve-fitting.blp', 'ui/dialogs.blp', 'ui/edit-item.blp', 'ui/export-figure.blp', 'ui/figure-settings-dialog.blp', 'ui/figure-settings-page.blp', 'ui/fitting-parameters.blp', 'ui/import.blp', 'ui/import-columns.blp', 'ui/item-box.blp', 'ui/shortcuts.blp', 'ui/smoothen-settings.blp', 'ui/style-color-box.blp', 'ui/style-editor.blp', 'ui/style-preview.blp', 'ui/transform.blp', 'ui/window.blp', ] blueprints_ui = [] foreach b : blueprint_files blueprints_ui += b.replace('.blp', '.ui') endforeach blueprints = custom_target('blueprints', input: files(blueprint_files), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], ) styles = files( 'styles/adwaita.mplstyle', 'styles/adwaita-dark.mplstyle', 'styles/bmh.mplstyle', 'styles/classic.mplstyle', 'styles/dark-background.mplstyle', 'styles/fivethirtyeight.mplstyle', 'styles/ggplot.mplstyle', 'styles/grayscale.mplstyle', 'styles/matplotlib.mplstyle', 'styles/seaborn.mplstyle', 'styles/seaborn-bright.mplstyle', 'styles/seaborn-colorblind.mplstyle', 'styles/seaborn-dark-palette.mplstyle', 'styles/seaborn-deep.mplstyle', 'styles/seaborn-muted.mplstyle', 'styles/seaborn-paper.mplstyle', 'styles/seaborn-pastel.mplstyle', 'styles/seaborn-poster.mplstyle', 'styles/seaborn-talk.mplstyle', 'styles/seaborn-ticks.mplstyle', 'styles/seaborn-white.mplstyle', 'styles/seaborn-whitegrid.mplstyle', 'styles/solarized-light.mplstyle', 'styles/tableau-colorblind10.mplstyle', 'styles/thesis.mplstyle', 'styles/yaru.mplstyle', 'styles/yaru-dark.mplstyle', ) icons = files ( 'icons/axes-one-quadrant-symbolic.svg', 'icons/center-symbolic.svg', 'icons/check-round-outline-whole-symbolic.svg', 'icons/color-picker-symbolic.svg', 'icons/derivative2-symbolic.svg', 'icons/fast-fourier-transform-symbolic.svg', 'icons/graph-symbolic.svg', 'icons/function-third-order-symbolic.svg', 'icons/history-undo-symbolic.svg', 'icons/horizontal-arrows-symbolic.svg', 'icons/info-symbolic.svg', 'icons/image-print-symbolic.svg', 'icons/integral-symbolic.svg', 'icons/inverse-fast-fourier-transform-symbolic.svg', 'icons/left-symbolic.svg', 'icons/list-compact-symbolic.svg', 'icons/loupe-symbolic.svg', 'icons/move-tool-symbolic.svg', 'icons/normalize-symbolic.svg', 'icons/right-symbolic.svg', 'icons/settings-symbolic.svg', 'icons/shift-symbolic.svg', 'icons/smoothen-symbolic.svg', 'icons/transform-symbolic.svg', 'icons/vertical-arrows-symbolic.svg', 'icons/up-symbolic.svg', 'icons/down-symbolic.svg', ) other = files( 'style.css', 'style-hc.css', 'whats_new', ) gresource = custom_target('gresource', input: [blueprints, styles, icons, other], output: application_id + '.gresource.xml', depends: blueprints, command: [ python, files('generate_gresource.py'), '@OUTPUT@', meson.current_build_dir(), files('../graphs/style_io.py'), '--styles', styles, '--ui', blueprints_ui, '--icons', icons, '--other', other, ], ) gresource_hack = custom_target('gresource_hack', input: gresource, output: 'gresource_hack.vala', command: [find_program('touch'), '@OUTPUT@'], ) gresource_bundle = gnome.compile_resources(application_id, gresource, dependencies: gresource, gresource_bundle: true, source_dir: meson.current_build_dir(), install: true, install_dir: pkgdatadir, c_name: meson.project_name(), ) devenv.set('GRAPHS_OVERRIDE_RESOURCES', gresource_bundle.full_path()) scalable_dir = join_paths('icons', 'hicolor', 'scalable', 'apps') icon_name = ('@0@.svg').format(application_id) if debug icon_name = ('@0@.Devel.svg').format(application_id) endif install_data(join_paths(scalable_dir, icon_name), rename: ('@0@.svg').format(application_id), install_dir: join_paths(datadir, scalable_dir), ) symbolic_dir = join_paths('icons', 'hicolor', 'symbolic', 'apps') install_data(join_paths(symbolic_dir, ('@0@-symbolic.svg').format(application_id)), install_dir: join_paths(datadir, symbolic_dir), ) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/screenshots/000077500000000000000000000000001511351636000237445ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/screenshots/add_equation.png000066400000000000000000000531171511351636000271160ustar00rootroot00000000000000PNG  IHDR<(ϑsBIT|dtEXtSoftwaregnome-screenshot>&tEXtCreation Timetor 11 jan 2024 14:44:02 IDATxyxu?wWW9 ppܠr"32^;ft]wGewq.q: #!ܐ *$G$tWUVwN:}z~SOOUDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=L$ nS"" r Hs2#wADDA躾ODD}K @}m֗ȟ>B HDD] ~9 $EJBD >DDDє0'QB,G,b_,X,SHaܛۋGo=du?QȈl8;HY%`E!'OLXa[PNLJbY>:pK̆X< ;>ok""X'xo:_*;l 3Q,n|&uP̈ya'ۓWO _'n9z]\0_-'tDDD;B >L艵w( 5x :>۝Sىzg ӓNjPNOo&Q Z}i 5]艕zŽ侂O8mKDD'j|yz{„X)G"xN/o""LkV 3 zbaJ|]0ذ+ 4 Ų@-3`l 妤}:t| &BDDDK +p&hCCCMN0A_Q,L$/ýpCO(dnN8a'Pzz33QB=e\י\ =1vO8Ϡ[ 9DD -(=O0'>=1zb=+^> ^>뫟DDD@BVoaEy/[t-G0D֝`N(AG9IDD<-:ml%^k_k?CDDLp$t 7(]ڲW؛ Chge;[6NN 7v8QC3:xeG™Gv_L{<йMzvj&"J4Nttן}Vô3ˡ;b1xkz_y|a' _|(e9;5!""%:t/[n]O?}G ;:||=(< M17[#B9U(:<%ۖf_裏>b2vbU{{o<ӧa/m v([ Z1w1X NUUՃO&DDDq?ўyD+O:(ʳώgfm뎶b%***0ýKvuۦ+/Xw8Ukkkj]gki;/+=gQc@"@;qgekl$爹s . MMM퐤PwH8$%%Ax]F"}U\o#kMV guŽC ~7'%%NNN@t+OozbE}tuo!Ȱ|"_ jnnfNN'] "k#&Z's}/먊V}r8"g@ü1mmmm ;!r:hkkv1z'Žxl^]j0-$ˁ'Ξvl źW|}n ]Gwxt eM믬gmy?(r6V}v>dXJSֽ '""|_/~6VO0 BV%oF]D <ңg_)i+Q_Rjˌh`1QjjjPSSb%GG2z!n݊}݇e˖Ylhz;#Yfa֬YصkWmۆYf7{+V|vut͆w}7E=nō({6ebv) FQl.aNjޯF2EԌcDTŽիo>f@e:u gϞF())Q_bٽٳg`0 {5`˖-xwPXX0L(((^+ ~{/u"!-5JS?̙3HML,"}UUU}|عs'\.WUVjʲ%|hP~Q9I`+`HJs)t腛{FZ4O!%q\xwaXSOat:L&vZ|jܹsb We0o( VZ-[~@c `0`:g~ml޼:t(ytMjy,X[nf۷oen޺Xfc̘<؈gAk1V)45 3MtgL(ZxNq(,,0 ؿ? ٳgCe{6͛w^ 4 xwQVV[or}&, &7f/BշRII 1ydPVii4?ϰqFÇѣG'iy饗j*?<hjj z݌F#vڅ|,X,wGE1o<<>__o~Q1sLTVV'W_}6ƍc塡Epv9nӼL|oR2p'i!`5lotz!cŊnӾHJJŸ':3fի㏑_n'N>rL2=]ܹU(;wN<[n(⦛n ؾ};z)t:\p;wDNN|MW_}ׯW4x+^;ϟ2_|qg6aŋqwb޽xG1|,\[l#|msquӧO:vtM6mNwVBaÆvޭٳ,< F5q3w;κy ׃ͨ$y3 ,t+WN£>aÆ!-- gΜN7oDEEЮ[RPP &x'0e={?ƎKbӦM"f?)>F555xq]w;/JΜ9s܆> ŋq n_xÆ sk#FƗ^z-Ls=BL89OٌQF> 222P^^ّt믿RXXO>fϞxˡÇo޽{rJ9;w`>zRߦ\{gqkҁ'rEH‰|uIq=p64Գb'QlX_> F*?~ ѣ1qDs=E9 찂j*,Z---شi_?яrJ>ǎdR[uyddd`ӦMn)|"ʰm۶ %Cmm-.^^z wL0xSZZ-[=^z sEmm-jkk~cǢ.K 0e<E| Q\\;K,Auu56nN;faϞ=7nx <8:rN/T.<`t4갽٩zeW[&rO$ h޾l'xyy2_Vc]z5@ɗ~E'mۆxG#D8Gܼ6Eu` @#YZ<khA3vzx%"-)"&* iDDD;z&"giQ?>bQb!"cG>Gź}{j䀾0hh!.qQe%c:a6Z"Qy[ "yͰ`dv~9@=w#D!55'H(wf'"uggdkv1bNqz[ eXV۷jkknw#3uEF-;D-h;N\qBM zNO閱&DN^1,!c!""cb!-"""Jx DDSݷ0'Dڊ$4DDD 5 Õ Oxt|%DQ^^NS >c 6HYt:p8 f3RRR +u,ː$ hkkn`(tj,'%(-:$nV+DzNS[vL&N't:a4ixZڰ#IN'l6F#222vW Fl6v?E1gG$#%%%%#"b %%n` MH "Qf2rxKCq y=\ ,oGNS= RSSar]urA#)))%#""VRRz=\.[yr vR&"XdX  +) h (G#$Ibw""I ]0xܓ{ُW$"X\iwc ;E?޽mؑ#GaÆ@S} =̙Ç @ߏsv{DDDZl'RRR0uT>|8233\2"")Sw3gPQQ˗w̙3زeKs1czDDD 'z=Ν7b޼y^5l0?B鈈ÇGRRFIl%:D؞={b ̞=wqWb u{o?!͛˗㫯RǗ[nN~;{}[l6nVlܸh"ڵ DDcӦM ;q'O.>c+X`'_?< :::gϞEAAŸ'~;.`РA8zhג(V]r={6Ґwt<?~<~ .`ᨪBnn. )) >,x :}֭[K!77.\#DDDq-<4l0L6 w}7}٠: ·Al6f׮]Cvv:nŋ?Oǘ[ZZznO _$؈pNDDPxzԩS7k9h4`0s>O<5kt C/_x/`&lܸhoo_|ٌ7b\t @5yy\+VѣkvףGօ(1DPss3n݊ذa~_`05S믿]mmm< lقrC`Zճ2]mN3\yN#h>WʳpV(Yt\.8N477#///ӧOcŊOw!""RUUT^`0 S,]-i桼=4<˚Zކ2>dlA[Q mÇǍ7ވk۶mK%#"hux$AG('8}iF#~aT*""I.gB񐖆eYVi!55%*.͆ffL!-ry>""I---n!|cAAAEttt"""ʾIEuE1hh+ IDATN Q,z u_#w <^(IY#I]4"""455A$҇G`0 6-"">f AG1Aۻ]yQa2PWWǖ""&d2AEPu;^' dYbAss3l6233þMQr`XԖ핕?/t:dYV<`2с˗/`0 -- FK1""pȲ I`^(غO!-ՖN'!I$I 07""(g_(0mpdYV;)V:4KYc[DD}HIўyV <~x꣄5QO5wCg '#rb|(\AFC4OhCDDD=[Gdpð< i{'"Hb> >DDD}Nx¤t눿ev6m.\C"==)))~y[Xd &N*|0aA˾F毵{n?K.qY\x&Md *k8ꎯ,}yaҤI0!/?aÆ{1a]֗`)Q0xh4ްj1b̜9 IzOw$ixrvwZ,cN{ qet:,[ UUUoi&;qә%Ir z}B$IevZݭw~>ʲw8ˏJ ?<˴_0 8}t󫭭E{{;nfdee`0իhii=P{b#QN YQ[[ f}]Ca(--Ul6SuNyyyXp!.>}m}S(x^PaaBi[|8u3eZpH`1@$ttt=\.WПOIIŋ܌S%IBKK/vF{{;N8^` G0\@zb#QNɄڵkjo6ʲ-[`ԩ2e ]vE555B{{;Á#2qQtŋHJJl;Γ`qm6faǎ02d߿7Po00flݺ555hmmuێݭw~>ʲw8ˏJOPV:C JˣGٳgvZoӡc477b`ԩA_-##7|3ۇ{bȐ!~w f+wy-`j}~Я_?ydlݺۂ^߃'O̟?k׮ŨQ#Gb۶mhmmww[nɓ'g8[o^GKKq̛7wƦM Iqwyocohȑ#|'.χ[7CYz;V '.?R+=Y_B^i邗it=]y?_+Vñ'u!rˊHl{}D4 M85ג=E]y{hyvj;| e|xH֕+W`Zv(q\v ׮]C^^Z[[{n:.DԷ1Q°(//͛'b.!""c؇(1Qc!""CDDD %<"""Jx !"" gA_|B$Ϡ<\.\.NgCDD.A "z=z=t:` OaG$Ȳ A`6AXdY$I@[[v; DQNS[}dY~'H <(aGiё$ v`Z!܄DDԳt:ڲc2t: ѨN0ІIt:a`4CDDBEdddh4f()>;$)))X,Q,U)))hoow <CO l@$\.vfLh0ɤ[J[ JeHNNf|?r:Q<0hH ;. zIIIQ.ѷr?k 2"ł^6 <^x^I`0DHDDD] Gyw <n!I{QLAS ~<%"JO<.JDD0LDD-x(1Qc!""CDDD Qr8tϚ1 3<3hnn:u < [zbO8q;vcǺ‰'P*݋י3g7߸ ۺuk?VOcc#6nk'>êUr܆8q/_RHcǎ1miic0}ߥKpYa<fϞ#UVVW_EeDag޼ynRSSO/3f k.ӵرc8p*++t:WTT.\qCgJkVKK 9>Q <vTVV/DMMMvTUUnᦺeeeľ}Բ3gv\GG\.DQT?a4ݦZ~˕tgz-͔yxIAD  ѣGc۶mQ744`x衇N ZZSXXV OͰZX,p\hmmul~>,w)fGɛ`O۬(djy'>;*z=V^EEiEٳqTVVÔPJJc6^dBnn.N鐜*JMMEzzEQSS۷O҂ϫDQT˛`O_HMM 8""5kra'' F#i&lX,6 ׿ׯG~~>ZZZ`6^ ''' :Ђ Giii8y$Ν}vjjj_~8u&O16d۷[lA~0yd f8tDQĘ1c ˅AR.QZZz ۷og؉#ۛ cN3߳k_A}VZɲ Y!Io:]摕Ʉ"33zcƌARRL&N#Fjkt:t:}ot::_Ѹq`6t:ѯ_?;kYqap :=zffR[ok̜9SLرc)))6l:::`Ze-szeXnn.fsm ш$dffѣp:4iRXO"~ߩϝ;YdIKh4Bˀ(ެ^ddzC+1#P0| 1zJrDDD=Ax}KvBgeK1a >DD. k 'Zz!GǠCDD=[Gdpð< i{'"Hb> >DDD}Nx¤tfV/5jكCz;سgFӧ_~6mN>s"''$VXn 0p@455!===,,ŷG7nIJeЯ_?>}@Ea„ (..ƥKa|8RRRPWW) ՊPGkk+mۆ 6ٳ|vYpa'\>Qap8X`mۆݻwc>X,qY\zY 555()) zv[n"z$IRCCg} @шA;PFeø뮻Z>Qvܹ ,|8u.^6]}}=jjjcUUUhiiQ}f :L& 0. ~lMt.]J3"QK./BedeeoW^EKK[D(F>}@g?u^Fe)N'lق%K Xu(ޱ'oߎqƩg* ̟?[lQmmm(++C]]$IBZZ,Y:u*JKKqavm8wz-fFe˖%l+Pii)^x477/ڙ;E-[ K.EII \.RRRpՏsΡ F~z(Z?`I0bFe)KO>Q-ZXu(E:KK^;gf)gkp8x+. N :M}VZ˥+,,ĺu"zkUURSS!"z}0藿%xhW/֍`ZLpF.NͳtyhRCa}BO}/*@XXb--RSSzD:x(.i`|I+=z={9^! ֣,֡CqDyDZ|r[?S#>֡Cq>rJ[*A /KFXX ~PO`=bO <C=u(~%(_0O>zW_^իhѢhbuP|c ż5kr_06#.֡ *++caxqh)(wb.--SO=g`JKKl2_m8WpX:S_HXO?ٳgwٳ.Z\ھ}zg_o/2.\6+<}՗@W?Y_C'֭[sb4h|AB7`YOu ܸq#3fpXK֡xNVRRmŋ/F@q.BT=3>DZ~# 3gaol ݎ.w7WʳpV(Ytӻ\.8N477w9% Q!X(G]N{Wl0nЈm[8R)$C$ढ़2Y ?(:wȖ]4E&.EηS2y]R`(/ldLJx(~? Jҹ3>tj2feZgִͷڨ Ba7ЎV6O8i4Mo|/B;mmCSƫaRrEf:99fkmE;Bڪ҆pS/_^{)B;Bڪӆ3N(E˥Ї@0/IЎV642dWx:}QE8VJD])ᡒ$ln *kC=u1cflOUijX }hbP>Q}!miC$³f3%IbUr||b$I.E#H#״ +l`t785ϵ>YMb6664/'*_9vmmC-۷: ,榾|cݻwOI xtu~~OSo-B;6 M#"eYvy/S$mllh\jooOIΝ;u֍Ac,˔NNNX,tzz$Ilv(4юVm<%L 9MSfktxx4M5ax痟84eB; MYh'S7?hZ؏E~e?/ӔyCɲrp:o30MSeY܌=N¾ؾwphGC;B[]!g%tpj*Pdֹz\.煁G&cߛOYMLc73-]C;& ]"ir} 5⻗3-ϴXW?oIIZo&eٍb2h}O&y7hhGh6$I%N[feSi7/vvv~S1xcYEW/h:hGh63]JNUU7XsS}Gi^[왏uQ1uϟ?ۿn޺xsM muՆ{_%}E9ͮؽ&E{|QŧJ#!ԥUV)Zo.fNyec޺wMm7F҉wUaUNI}(ۿ>{#gs_T~ѣx>^|ZO>yЎmuՆvww_=~~T{/ A9ʙwրx*3;늺b]xǏ?zVȧOwIGpt5Jw@U0x>6O"uey3ַZ}.*;9\.Ce-ojw9p[fwew}?iɓ'$Uc hGGG߽yO>}/X78v}/Tz 5tMQq|WnZv='I?gϞ$e72(OOOݻwzu1@ypcۍeˮr2yM*8=vrܺ@H bŒ=6ǭ!^+gw c-{.{9mͥfo؃ΔE^ CۅUpq&n2w/I.7HWɮI@X*"phMZc[q M艝{{{7 ?nǭ]vo~pCUdʺ|%;Vuj--{{W냑r]з.+#]]UYF]ZRP^7tU1L׻0D0Eޭม s$ם%UqkR)l}>ycoN"LvEczٮUUxcWv e#uU}+cV!|ʂL޸6VR KK_e//?w\geI ]O߯S *O]ukӾK}]E(b!`*Vyߝ%㡮Wky]yc5  d*oU'䅒՝UV}"oR˪FWUN*32t#ToYH uUV8Vw0țO]QU8{PԛOU @_Tp,iTXtCreation Timetis  2 jan 2024 10:27:53 A IDATxyxSe;ke)R ȢlqFGtDEe@yEA@P\@eq]GePEtOɤm&mt~WrssR} P n@ =$p7&!4 ڔ0G@<@8`PԚ`63PSP&5P@5Ԕ*~&UÏئԄ2?CM_Ze$:BEPsbCjTǰ"lsœpjT &T 3&NaDyj(Z6(K *Z׮t!`(Oz|uZGH(B*T 6)8tlSPʆ1U9)OSZݒqy_qר0 %АhyY*-t?zPr_LYØ`( a” cB9/]KK;}Ry˜*+&\AB(C_0z˔ URվBv@&0&9ctSՃ}jQ<2˩V|(%10%ݎ@Ԋ &Ta a `S{Ȕ'brh]LcQ޻>A ^9UcܡKIAw9(we޻{~{}帐 <|,%˜TpO,1L I )^_IvI*xvVfx~nJGU b;*˜U8Hnru\t5o<`0ٳg,^x>z@R BwcaT8tq+ aÜ*2{o3,`7Ȩt1y>㾽{t:%R'@ZZ JY>w7}K猯!M曑J_ڛ2*%jA^-mÏ|kw 1#GLE"Pv1{}]vUx0E'-̨2#TJ PWtC'OΉrPAZn=ѣ%T{V+}/)+Pc b*7{]0Qb2zPر㏒Txg"RB# zK +zc裏lA0TK/ST> `KSY t~_y1>RoY'5Ks̙U.9A Pa 0իWiI*X={^{佾q褽T1_+iĸ$-[֛ /\!J)i4|lR%zTFZZW#_ݑ~֞={62뮻KE;VH%0 K F5UGL7dZ/7 ӨQT7LiGy/ʫAL*sOVh^UeeX.Sz݊.gyA`w+ rr@X8np,h4d2b(""B&r\qܮ{%.m_C^-4@vlIቯI|WzCYYYttn+++Kld0B|}@ kB_JURo_K[*QFFFPursshEFFًڻŻ,_|νC h{4 Z'+++[FF_,Q]mĔ6W阿}jj͛7;Ԑ!Ce˖p7( œV3~IBI p1"|$WTTrss/&|gee!=a|3&& y9`:OL/SU' B*p6n(ͦVZri޽ڿWH=dZ=FQ袋$IiӦi͚5;wZj9WX_<=a~0աj|M bj7zqFCǏ$.BR(q)66XY|NX^@Xu 7/3}:t I:{̙[jĈڿN:Aڼyt颉'J*:n:JHHy 6NHH(GZfRRRsĨK.ڼyaIRXoQ|ƌ̙3,/@lRRN=z]w%jJ'O~:uz)۷sdUGLYƃ1?L5a4k,9R]v9={(>>^mڴ$?^mڴщ'p8N,`5% r?k2Ztvޭ(\28h ]{:~V^㯼,yKOVjjnIkͺ;5dmٲ%{EX_>S͚5X͚5K~i]5.ƕvn_369X۫zB T+V+q}Sy睞UBjRZZBzN"\pA!=gDDbbbcZ{I:/!){ח/*WYmUd?GLNpv/}9X.*lCZO?ʕ+վ}{- F$6{ETC 1 B|ڌFd2d2yBw C"0yyyX,[dXd421P9Nvegg+##CٲZ@h4;@ )E^0NS2WTTT[ XFQPTvEDDHcToWhт!xzy@wX5l0MP6lXegg{%?` xsssU^=կ_?-Pׯz)77܄2B rnh4%6l(('Ƅ'ҙ3g $bJp8dըQp7@%jԨvGRU5wQs^&9w^p7Z[p7Z[k}{݌j+;;[}*ͨ>C/m۶ȑnJ@SD0CE6--Pl6,b AL N!CSwzfP ~r\/)n@UTh$j#X,5 anU퓟%STT'U.WI@Ĕ0a>b]tܹsu@t*22Svw V)m+w8ZfYwuZjFS BePoѣ2e_~jӦMĔ*sAXVEGG53zp7zp\pBȾ_u֗hCr&?ߡ-pY!A111  ̙O?TC5g5o\sϩ[nݻwkȑoe5sL_^NR||F?~[o$/ԭު+%%Ev $`[o+Vٳԩ{15kLkӦMڳgc+@;wN YoC*ѠF1G"##T[.S s!v]مʬVW_Ֆ-[ҼytĉmXiӦ)22R&N/\IIIJIIQfffIE͛7O|^|EEEEiƌ5j>Y,iZ`5k9shZbJ+>g2d6t:Y0_J{O>+I.]T&LХ^DM0!~jժ4|p5mT;w ~^^y=jݺ4qDeffjݺuk„ j߾4haÆ̙3:rHPmPϗj w3*l6+???֣GL%뮻wQ9B4^Z?.\G*'''ǎS~~~6DEEC:xv5kLQQQ j.0{@\ntt~vv ӧO״it饗p|EX̄ &e01QQQ:p@2wp{niرjٲl6~g1zsoL{~du2-v]j׮])'ԫW/-_\͛7k0a6lwhk.EGGbh4z&R)))X=:I=RZ?77Wkfggh4(""2d,(##C6m*4!?ڱc'Sjɚ4i:tE{=(v|~~ϟɓ'rĉuW`0UV5`l6-YD/[o!CH?*11QWƍ}v=+b IÇբE -ZH+Wŋu1%$$[}OC &/m۶Ix%&wSbZ{I:/!){ח˽-myvyVL!U(%%Ev ߿6mڤ={(!!A=?dv]g_|\__dd5sL_^NR||FQ"%%Eӟb m߾]SNUjy4o޼t}y7cƌ֭z- >\tA͚5KoFCTM2E3g=ag IDATC%AΝO?3h5k̙I&nS <}v{n=zT1˖-+t>}K.M7ݤcǎiӦ8p:wo]M6[ocj͚5ܹsPsʕJMM|@SAnV>s:z4tP⋞\s5A}DIKKٳ5vX-[L۷=sK_okѢE:vfΜ)IX,JLLԴiӴtR 8P'NԡC ]'T~믫SN-p5A4j(=JOOTӵkWq!…  H_<~'m۶XQFiǎ:t,K![.]tĉҢEo233e4n:]z9rfΜիW?:5k^YO0=n:z??z(~>|ڷo/I6l>C9rDM4˵b oGkosÇ룏>Ν; 4hn2CIyڿC9rry fĩInAg{נAl2mܸ::tО={wO_ݻuWj*ݻ$i޽ڵk}egmtmٲEǏ_Yh4jf̙3jР4i"IڶmkӧOs5j&Lt}) qYY~oճgOJN6}!)Ψה)S?Y|0A~mѣ+Vn#G}΢~g}OkѢLrrΝ+Ѩ|uM}_uԩ[uU&MҘ1cԻwo5kL:t'xB?zڼy٣-[xݭ[!YYO=СCj֬.\zJǏb5c խ[‡ @@ܡKLL$w)—T0Aݰa4`ȑ#p8B&\ 0ng΅ On|ڳg&NXW^yeYS]pA̙SW^jժUԲeK:zfΜ믿ӳW_}#F[ow}m۶yBᄏX/!wOg/MYO=ĉھ}_(!!Aׯ׽ޫq_/!MN:u7ۿ{niرY'O :Գ! ܚm/UFYj v ]&11|NRj;ΝSbbb裏СCڰaU w[oU]vՋ/~Z-[ձcGEGGKRMA?ԢE }WZf7nO?y?#ڳg^9Æ ŋ mڴI;w. ^5nX[lw}r 7g#HYYYlNteM}]Y3IIl6맯JO?{~WYWMI&*sO8\h==.))I^zi!H]vȇ~?P{QDDM'NA:tڵk.Hcǎܹs5{lKɡ@fv|E.o2 q*GYIIIɓokԨQygϞ % J2w\og}6mt:p8;{5{l_*4kjٲ%! UA PKL2ESL.q[֭[yo0?Iӟ|/%%E)))~?Z=EEEr_uRD(13fƍLuM˖-Ӳe{{5ydZJ+oƌ7|SRn-ZPddڴi[nE={TӦMu)ڻw^i,X˗WƍգG;V?;|p]p!z+ qgΤo~>ի;C'NTNuVoc~Z{y<*2ͺժUp7V:u6mw}W/*^ZzӸqc5j(>$iĈ-ZHr^|E-YD+Wɓ'^ziÆ >{?Լy<,\Pz5\ᄏ_~y'zǽ;1~[t&Nyȑ#j׮yxelrt^_ ^Wmc^/o޻_륦np\.s:r:[իWnݺ=e;w/[nL甤_uA ]v)::ZEF3|½4%%%UvkG=QG bܓ5GDm"\_Фps8򕕕rHK;KK'RU_ڶB'۫ uDIJֺw7!M>C &/m۶Ix%&wSbZ{I:/!){ח˽-myvy,AOvӧ~'K*^yf۷LKMM޽{UNl6}tM:1(::Z]tѷ~+IղeK5hРL狊үm۶iݺur:Wg}Vka ֹsg-\PЎ;t]wI8/}Q-gٳr8ջwoI!vN"##e4yf뮓В%K\rNSVӧe6ʎ L&WȂ.bw}>8N]s5WrrrxbjJAM4QnԢE 9rDCݺuҥKu9rD^z5k諯T0V7ސ$3UW]ƍСCZb &Z wڇ lRE]vY@F|2.\Pff233պukh4fgϞ:7pϟ8 2DRA3rHIܹs= 7n,IJJJRllN&j,۷OgΜQddfg^#mrCڵ?:{\^+=ˮ5?x+ nJII$ \ѺkrgϞRHt预 :T999ɓ'k۶m6l5dȐRi\LzVs M`0rz5LvY@`NyaT5FMZkճd4$4vfھ}:OY^^N?u_~={t뮓j YbbbpB]}W_}UW^ye$=aj}s/grd4=  P}ݳkh@FsGJlo&I]tO3g |P{$kGW\o.tlrģhRQO~WcW=۫zGT_0iii\ΣԨ#N֛[0("""dΖhPDDdYǏզMl6Oґ#Gt %&&zz={VR֭S8֭[=.--M{UϞ= ڼyիK.ilm;JvNvY֯Zm^F)}{wSlz~wݔj>nU+N:% 25$um_W6WbbkpY^KrHzurok[^eE]^u@~zɻGL2?˨6mZ,d ̶hB-ZV ԠAm20Elj֬Ycw.Ir8Km;(cgs^K"׳=ggeMװPR{"+-ꉟe y:v6OfSp=-¸9]K+N|=TD#&x咎Vv3FyV9yNvrɨașN}SnAԪ JptT.䤼ny$ˡ~Y^G7IiuiM~gCfC9v̖Kea$)~^Ш_G7 .L.$)aTZPG ,-îS`7EWqEo{UѹLV9eT(ظ\@!T{.(r1s/|0E^\Ǧ]'㵛,Z6M=~sbxܯ5ɤlEFF)@p8f@tIOgɞ!˞7](|d}-횢ī:Zebu-T,@f6fU^^^ԩf@kwpjCCөeUl_L+Ued5mۖr]  PEGGܹsZ2O[^^߮|v憻)Ֆݞ;utdL\.){bbꩋ 7b`ԨQڻworɤe˖iᒤ_QQQU~~l6[e#}gee_џh4*..NW\q V5+{\/_>@YYYkTtj-y^m{.8)MqlYB0Dցܹ;O#C;rѣVgh4*22R]t$yO6Mkܹ֬sժU+}կH={$޽[~vڥ9s\Ձ{\6nܨW_}U-ZP޽ռyJ6P[ ]|Q7OPn yJ_҉Eyr9vؔ[k )q)66XY|NXzGUn]effj̘1:|6lؠWZ~ACcǎan P3 7mZ)$K:v.G9rO=sZSl_dR z~"e=j pM7j>Д)SfM/ɤ>oydEFFon5tP54gmܸQѺkd |Xϟom۶)55UW~4l0OonI}Q||V^&MhĉKNNjշ~+áAo߾5kvޭ8;V:t$7ƍ;jъ/^TgxWi&-\PfSRR~ixٳ>oVK^z%uI+>}Z-ZPJJw^ψ뺕vCl6_Jp,9\!Lѣؾz{gNr]UOo0}BÇz~:o߾ꫯ%\Rb?VR||}v-\Pmڴ<ܿ ڰaZnFiA \Æ eZi&hBڵ۵tRYV 6S?$%&&u}5k(%%3Z IDAT̟?_E-}vu]ǎӆ W_}UfY&MҺu.K{ʕ+e6թS'EEEiѢEc?mٲP"Xh=Y`yK&M$s=:|=*á͛7K.8qy׭[_ܷo_|~m۶)??_:tЈ#6߿vء˗+%%Em۶ӧrJ]tE={fVZ_~YK.-t|;bb|IL&Bt{"y惙3O.g`UˉϪnn4rx|Sի'"Ij׮+t!IRΝ=Arrr@A̭yo4x&ݳg>CܹSr\JOO/T?""XH=Ӷm[I*ZS۶mua̙3N^{m󤧧 T>|8zE?%\_^zu!C߿_NS]v-x='7_[iq@? v9)#3K&Oѹ?EnV/M͚b!ThnKl6[ԩ7nf͚o߾N8q)..NƍSRR\~ddd@yխ[WRA4f̘Bǹz%zE?Czo>7|kjݺu9s.?KNN$pxd0]趷Gpt85[r09ks`} FIO<.cg13"?r`ټy^Y,ܹ3>'c{+=B~~~+k$%%h4_~Q"aÆt}7В%K?$%%I6mڤ{WVU[l jY@_-٬:LnSNed4{RlЁ,Tn0u~T/1e˖^{M=z]w% 6ͫ<|`TLY)gst> ",<.\ /uuux뭷PYYq3V\s9 p\K h8 ,Z,c۶m:ujD!=֡ŭd1 #ϢMcrz֮A_9@3 ,&f ,EAYHNNF 믿Z\yXjU"8( JKK1sLL0\s 92,}GHJJc=6TEݏF?(ՖV^$stsCQUNv&aF1Isܹsc"a[:5k`ܸqx0o<ܹ=+6sǍ7EQ""""a5~x (0Ͽ@}+ZKlQ5`B IvƏv|ƨz ۶m1uT瞋3gbƍذaÐj*̘1DDDD4f( 7U< ;.X(Xz\@ HP,qL"$"1ꗿ%.P2p 7෿-nm͞k.lڴ O<|!DDDDk8 $gpd#0Y8O Yf]&a 1DDcԧ~ٳgG?sގǏG}^{{;n&lܸǏa y?"""iuBCmnw-LcoCҩD@gk)V LB֤! kfjjj18zhcs=5k0 ԷS!Eg(_q?ZwA6lMȘ1 2DG0v,%et]i4-bL;kX" 7|7oG}zٟ-p5Ž#QvyjFIkQFjxjC=KoDu}Puv Az0 &aF!ABABc!!#IPCu౉'>vx dggG(x翌8~yյ{z!>;\?i Njl~5֡ZBGG;\ 1 ku(p4Z[뛣.|#h*쯿 F驩|l۶F-mYu|u<7*+??*\a~"N=bF/úވ(z$ $2XHƔދ8o>dffbϻQ__EaѢEa+--Eii),Y~8eYu_MjJd8~7()6,OtMG8oez?(ܹ3fX2*UFGGN?cfC^/.4c^7/_1FX~ 4PQz?>7k&\ KFzT[5}{I  &bj]C @ ł8X,~Xu^V5EqLYx1nF|'8΂r^[n%80a|޽ދo=9On {/RSS (,⢨^"""V`7kx?*JWBmj dۘtY94݀, `W1~t_:~?DQDnn.k, Qa`ِ EQ``LSO/Ǻu0~xlܸ%Qo࣏>¡C2󲳳{ܒT\\b1BDDDCK7 T|P5]fT045tpr_p~Tݚ:7͚oRϘCOIχdff8:"*&MBcc#`C9cXe,[ k֬ù瞋;v 777|j<"""P; MM^=GcƛcQCpM"Nj 7݊T'EL @0 z)ID IQ2URRz>_y=9x!"""KWEm(@Ŋԯ SVs@`k1^W ~2 CҠiC۔5c=)CDDD4ڸhrQ'a:*_ 9|@{"v+(끪EL똈A$aP(r; EQ`Z:1DDDDq0cM>x*h$3>jaCR+6(tfɶE14 DM%Ė:HYJ"""xh:0 D=^T>'bV0 0z0a@Ӂ !iMM3a4MbqdD4N', 4M$I\CDDD<~ %F?v HXs0eJHIIQ<ÿ[S: ESA1}u"===֡Q $'',CXCDDD45w,GWqd̓ [SS_CpE WMN.+RFau>ZtI@2>, ]I(\u?Νe`rؚzDL-0Bob7XdXBo p{t ^A庇в}GĘcj1ׯm„hB550y<[SS8;z ~,F,m$<(k?h = DWš[n1 q|L3Q'aT]UQd1}%<=Zǃ^x_=klN8MMM|HIIٳQRRbzDcIpekVrEn[w#kBsÎ ߊQtVUY)Vd$[LÁjѼ8S16Xxq#//V+HQi 8#bH9 e?1ӏ؊ Ccڴi= ؾ};P'I}Dc# UER`?x 7tSz<lٲ^{-RSSw^ڵ 'txbUUUUU='"""""MWEmhZShں U&z ro>I55E$bhuʴiӰe˖A*كvn*%%---g̘3f0 g?jkkv;4MCzz::::;8DDDDDD=qԡD14xʀn4]3Ua[S`"&FA2Ǐc֭ &^MpL2݊Wl6+[x1***'`׮]XhRSS{wnn.Z[[QQQh(|())E]^ @ :C4M+/o3O??W!""()TR _{Ld-\'aTY㬘ig Wġ3<۷oǿ/\uUSNEUUy8L0Μ9/",X9%%%p:ؽ{7Ce6Ε9^x!x 8Nddd )) `XpBl߾wj̙34 _`QWWq}vY(..c=-pc$I[ P\\P(Jn&?d)z0[G ï`bҒWDuv55 9&bYyacwqGcXhQĘje]̙3J&LW_k'tN:DzqFַq,//DC+xh(//DzePVVoDDDD@S{v5P5 E~SO"Բ,04,!= _aDDDD{4yQ%aT\>wi(~Aф UӑdEN55b(uMœwyač?["""">nq7RIϡC8{Ld-\i?"$M3 CÎ"[]0K,?yrJX( @T[|\mg7ÎV Kc{ H7{]70%' )nYFDqi֭$w܁E ۷o(Xz5?XIDDDD=hq+8w+xI#Z= &d:0B _"γd#iƌ iHa"֭[QZZJ|EYY6lQf͚^ WQl5(_q>GZCt*Ǚo>O"7k&\ KFzTq+2šF1DW!"""tn@QՃyoaI)U߸ @{Xt o)m+q&waeyLQ`ht+:]^0$xǿVW ):(\~_UnMdkj&b(.l߾=YdI&̆  I|A\|űioLW mxNȺoz g}Xĉ(^⢨lMmAN-E" >(4Müy!"""%hPadkd A&kxڎE<._Ô+!%'G˰+n۷oUW]^z:t~;?||{Ç~8Bމ't~,]I"""8}h > ONAqOE$a hŏ=UF7 ؚ 1[cѣ}>v̙M6aΜ9;矏PDcǎLŗރAYz~)a s]hU ,rʬC񃉘ayf̟? fϞc0w\,ZXxq()C4Gea^"""8i8rQ L/*X3O@g\}p",oWU@f2"WD4,6k-[lݺu+V^vK.ڵk8*o߾X@DDDD}hjծ"OCRx+",g~f%,܍q&FgIlMMq8҂'/((@GG<Ng#""""0f>mPk}7^ =|@{"vk[dY@a)1ƍ ; hiia"Fn@3l0P < V$əU#9QǙ$55 Lā`¥YYYE222b]ހ./dIΎ1(~x'G˰+4㵵HMMnQdDDDDD45wBIGqzLd̟~.$nuSrLШDL;w.lvl۶m?~"""a ~cQ«k-]=?-V ;.H"&~]aŹIlMM11rw]n;w~;8q/2mۆ뮻.QŞahinp8rƒ lg$@ Pl4;lHNMEɆ MOiMRlMMoŀ먩AvvvI''|?_ӦMO?)S0R"E3gXBDDH`(qr͏ѲsWĘsZ ֯-/t3lMM1#駟},"6m|+կ~5aQP%->*W _UUXa} w4u@Ԕ ubhtu׻Q;G[* Ӵmʛ IDATz> Kp-[Ԕh Ef7=X( DQd2nvyhz0Ϣ~SY羡.4]3Uې̮H@8{KEn&b  """J@uI0Q[[Qr 1IP~c NXeNDL/A $ ---ȈuHD4ZZZ IRhSQE_by)uuc^e@YM_U¤L"QBb"mI$t:,"!~Nۓ(!4ئ@̖GaK~ Q ꘘac1]0ދłzu*XQ__h40 Xހ 6UCͳD _-Y(~hN=%u,>cUJpL"AX,|>444`YCCEnY@DDD4ڨ MvU[Pr?0bl駡hZX2LǨ,N1>k IX/(555rn'2 CDDDǯX$o3C|y)cY `@-cT4ilMMc 1tݖKy֭nTp$LH+YL%߫bdYua0XM4\7|_7Ez0]pK/$I4/TZwA2 jƧX>jD~#\lk+r(ʹٛ^o΃5\^&i#"""wXzm3CUŽV ݃.1}`kv-R'%8&b\{YiAjQ:?>u_ua4󂈈hoխN?pFĘm?EckjHLk2cCӴ1wo]qILMӴPBIuL0= DDDDF7 T|P5d. 8рpz0b,k_EњUMǨҜ2ٚ( 1Vb ==NzG]xuZ3HYVtjrĹ`ޮbb`򅈈[@5P(0ȤQQjsS EL&xyilMM~W]!IWB(nn###. @6- yOI@ Y1a΃QFElP׳<|laۓ&"""vf?,O\mFO~ CSÎKINX3}m&1}- GGCthmmna+Z&a ÀGjj*A:B]&c⁦λqbڴ(&\m~4UI@-cI1O)4pXELp55Q`"##iiiXff&DQDSSD$c_ ===0̄$Iy,K%I"f:pFaǚ|T&kjP~ x+"R9y2Q%aT@SBaIA⊘n4 ]q YiZhie` 4 $qU pU&w2b,9(\u?$kn55Q]ס*끌AhnnUU1FZpȲ Ig"""^nhC2̤PAaIDoE``J+b"1\18N\ )΃< """.>EQsI`=goESSQv>t|ningkj(1Ef+!'ضb82Iab:fmQ%8LۓhHh @6 FQvZ1V`3[S -&bz ~,lF("  ,CDDD4TQL'a|UU(/AL],Kx4{98p ao~> >,nwhh<,·L %*OxVL׃iٹ }IY„oÔ+M'a4C$S1 C4ĘfcϞ=7Bkk+p` ( d Wt׻$ > ch:j~ i?݀7HsZؚh0ӏhWB :t(|" (..w@GCk?`8F Pj<$ ުbݨaSޡ.'MÌ_?3b:>Eӑfs+i3>ll߾ӧO>|y݊e=;O=JJJP__]1|Sp98~8$I_\x<o444`֭uNvc˖-hjji8S0gٳmmm袋i?G~~>(jCE6p_by)uuc^e@+lM]dkjaDBZZQ\\}.|UUٳCa۶mXxqWt8po&/^TĎ;|:ԷApQjj*|>P\\ٳg㥗^‘#GpQL6 | """O./n(o_d &vd_mi$4:Gٳw^TVV"==>VEM=f;mڴP(BUUٳv Kkرc8묳z6QQ4unTHj~GVHX2qϟ* h:-Ogh03Bp8ꫯy $I}~>Ю/]<8~8~m`ܹɁڹT$"""QSq(@4P[Z]K:t317!SMf<ށq39LČs9gFFFƈ>ဢ(֢mDc @uuuxjo󠱱v9994 ǖ-[pWt_WT?C"""JlM4 CM7#Ʋ.6’eښCLKbkj`^Ap7ހDFFFS7(B;,---rJ#0Dc1|wBrr2233c$4 <>(IDDDáŭYX:7cY ɍmKW5I6SH#N{ɒ%x1o<{V\$Do-mߌ3fr ŗf?NM׃q4ŽV /e@̭bQu ;Ն &'\Fqg֭;-BYYoQzj3$ QzIE5bC~0D Wy睇ӧۇ_|("dxcǎPkD=8:hhjզ"۵aMų04=lHr&pe)k*.*5% hDL~z|uu5N>ddee SD!''s͛P_Iy@DDD0f<>$¶9IĘ}rׯTla*`kjQ/\}Xpaya8t&L{3gC=_~oeee H۷/! ; 󀈈⛪n@3`=1T,ReUXڜ@ 9ŦHb3u>&bDKK ~i<3v㮻BNNJJJp"??ُ$q|y)j~^Y23~K̞m*.Uӑd0%'\1(_j<SzS,x^̝;騨@mm-Z\s5ؽ{wTv?hnnF[[6oތw}K./Gn :z0j|Έ1u8o*6E5D5bjŞ={bQ^x=ƍ~ݻwcÆ x饗L'I|ɰ.X<~aDDD}*Ox $MZv*W?$8@n5ua63mhT`"h +++… Csu֢E|rX!;N8$s#QPE|t?[7Y.V'^8e#7R|m0`Hackj4+vL>=iMP[[y^;v'|xGp"":sIރ_:" SCN/=VڪtL;0DcW~>?w}7~a_oGǏY0jjjp5נ{a"Fnvyhz0/E}; -(bx/͏U iCfDc!t]pCO⹢(rAAEVc~@ghϛ:u*N8MӰ~{クiӦ}O麎*W?S<kyn((ۍktzfT5M~@v=S_"ICz=ǚ9u݀(30 Tg 6G z{x\r^?1A "E)@M8ؤ( DQLd 0aBۏ'N$I¬Y/cĉxquiغuG&|3Z!\a~SZZZoX2j|^EEQzUE}s;W0v;bJԼ $3'k:ķކCFJ ?쬝4vИ!Um&zbƨ܄fwa~"c"WBnހ1n;,y06u9s&z-_>֭[1m4 y|ɓ'3.:(y5!Kj%_1du?F)'xs1cR26Ñȱ6I2S,Lf""DL/[AbCCCCZ)q444q`, ~?v=gA"kp5:ygq8㌨#""KsG Hcg5%aɕS>ػ(@ٚ"'B5b Àj$I1NMMMp\mi]Wp }"""/a_3ؿ4"Ʋ.@wAˤd; K=1MmIσ76 4+cd IDAT"LCC`Cېu=݂ qu] &$""J T7xFs{zm3CUŽV ]o\f2.!w D&bmhllD[[Xwx< mCe9-{s08T_mOpQ1SzĘ5'9}NzDLz*+Jl6#)))Tؓ◮Pne6-vŜa0H"DDDQ;- ,&"N4~?4b,+0eX2}]0`@aD40Ӌ[߄oeY8~8t]0 avY\ IRX@*y0LV?Z ,Ǩ(] )|@{"V௫:3;[S{A_&bXpۊa-+=`o+YzzTe 7ޜ< ""jɭJ´Oy``=n`HvXf Dd1}UDPƜ?GwA0;hxpo@}*j%ʗHXs0eJHII5v1C7rpܡYGP;:Ž oDd%[lDD}a"b0?>uI`=gw5&blM=1MDDa"aChnnI됈hh >(>6КۃCw#Ɯ%SQ~lbD8D4l!"f|^?&/>蘂m>h0!"&"f:E@[V?, ,opdZhnw,a­ MŤjrؚF1DDDDD4"QL `TLY)g9xt `x&D  +5M~ȃ, MooA#BŽ + ޅSr`1ĉ옜ODDDD4pOQ:> EC)':`kꉙv0C?{wUy{Y/$! aQj1 "X OB]^|U* ZjZIV,XEd[Q/Gi& $I\Wds{fNf))ᏻ{w߅e]ᄏM;i$$$$7l5]NTha'a*;OH[ܰx3(1s."}EV}X,s=M6rss7ȑ#G}o+V_Asq{۱c^~edggcիW(TMN: q|5 gσ,-uddRx*^Sv&"(LĜYf!)))d~3ާ(g}z!p`̙(**1nܸ3/?;GCDDD1\^>3>Xt) /ظq#JKKiӦ / :G:nc. .Ğ={_+^?dY!w&""Wt7Hϋeϣ!m4,y1KSQg+ =z4ƎΘ1cI/yyy-.oκu됑,ݻ1hРO?۷HMM믿4 ٌ;w";;s݋~fzk`?лwov W_}UVaժU2dRRRpa GK]DRx`{-6.v_Dgv=} 6 =z@ff&ƌTRRYf!-- fB>}p 7loZnݮxY||};wĝw ٌݻwC3*]RRł={lYqo"""xPQ:CcQ#o\Ha|.KSk:лc"㢋.ԩSa6]\ 2=܃>}`gs=Oo>8Z ?1sL\z(//?vM9r/^'!^M߾}ݻwF}vL&|gt_cعs'z?~O<iiigh*>\11q9"Q:Jj5؜>᫬ nd<ẁ @iTKSQtr2e .2?đ#GZ\~&͛QFeee8q"*kذa:u*dYƍѿr{x>}:DQofAu?mχ$|'_ Ap"??P?8&M˅ݻwcx饗=1i$deea׮]… 1dI< 4a$ F]+i}"":c^Eñ U$q8;B0R9KD=wQT12ٙ!.^ZoSBb#6#[o-]wk׮ /Yf+t8DBUUU1j͚9v}bE^ހ;fWRYY~8zkt֧n"555ҡt*? ӉNspdSdSH%7r^ [):z&[:]ij˅[6t(k']1۳g73лwvC1l6P @4Go㿍`Yzj-9ɃFRU5h[im#i{9`<Ǐp:Җ0z͟)&&}j@t;KSQDLG>!Cpƈ#0hR """:p [,z^jX4&b8x^]ɸkpG:,""""R Up&u.B״&)>9 #Bcs5j@jI%"L"ƌ1cD: """"\^ \%?jPVAx [ɨ'>["%df4u+KSQD Q'zߢ"K91*΋Z$`j<8] 1mpCs::LeG!ڬaD1CDI|7kp&۽^oGDDD(,I؁G_Z/Rs;ѷPm;/\ZWDdz2n9xUGB˙)"N"N@FFyg1$""TMGQ C檍GC0~Em,6;~ aSuL "~tˊQLɌ={wa" u"n27$Qª8QkwH}@r^sFFXh:]C4;aVi""*9CM%_!< ў۷oC "NCI&9=Nu(Z]{֕֯Wd?}y5!Efdgij"֘ SË6ߺ3 C H?ք̵^>!=v`j(ˈw'jCچ ]FsfyFԗ6!)DD] 1ahxuBUUh!&bG$HH2јCUՐG *""6^?O Kil//{X㋶+'%"gBb/MW63/=&b iEӴ@d2!>>6 & ȱDN4|>\.rl62(Fe faرUt9!$ϥ;.Uq}A^!DSaH+Mdjgij"iE^0@EdddfE8B" (X,X,HLLBYY|>,;o-[;#ҡQu)(B#1uL/\h\UҞ:e2gA6OՑDDMb"M%an7㑒舨l6娩j nd8"E*jrxW?F372g=S&IxAD:O¸\.$''#111!Q;HII$If(Q9r$kG:""pU SZQq?!MRrw=*X`ij"0 o LuqPUaJ9c"7nnUGZ#CD]1 UUгgHBD(55Ő$ /tDD|8] {>5h _rlAC"+ڟ#VֶDDYt\]DƽaTUdnpdDԑv;L&TU$IQ+;Ro 6 Ə?鐈"BuWza%atǖ5 i3!gɓ<8XX(\LĴ@4(HBDJȲ I7}"2_"77'ҡE(*uA28 ip<>*D4&":#LĴ#a'ݎ@O9""HpT+uC0u_}sTN>hP4g"KS 6u=iL&NhD݉g\4SWW9s`ڵ i@TDD4? KSqhpҖ0"[0R\\X(&$ŚڎDLl0aB`.[bȐ!HNNi סCc?={i(//7SGo{}(".PT(Qġ8Q/ȸy*2!^޺*KS&bZCA@nnnvII  d[84M͛q*?IHk֬e0mڴHBDD֥d0JS]PԠR}F⸱aOC}('1_F^^JJJi&Llsw}سgNplܸӧOZgĈ8tƌ60n 77={lx555Zǎ;ou:X~=n$$$`ضmbL> @Q';zhˉm9{E~~~C!"6 7\^rC<Ǐч IDAT9p iKb[0R *N uuamJDDa`"2`_>E]P[[ 񨪪 c(|t\G%O>h344KSE1_ᅠ3g6ٖSf\qMgVP/~fc8p d[ZZnᆐ]w]Ȳ={6-|ǘ;w.T4gaa!yto?<ʰxphQR5JiZXՈ? <Ѽ$"3qTӝE5DcHDDD EIaڵkq@"W^iq}&bۧXO´0Ewašh:bLo k;""j[LQTk;v,lhCJJJk_}QOr(8UIN,|Un iCSa K!3Ɋ8?E'%0 bٲe7nɨ͛ ۑdftDDtNU{QZ1 cBM&a~9}XIUӡ:rLE QTڰaC 3sLL:ؼy3DQĂ 0~HIDDBӁR'|)H7蒧 w"㖛ÊCtXeL*Qa"Ά 0gΜ@W |r 6;q5Q$y|\pFW5ë(Y]P_Ҩ9)9"OՐoFRl9JDY1CDQIjJuU5Jõ(:ոWa 4R]CC"0gޟӃ!$D:*o߅u>#G#QJk<(򄕄q~vgI1p+a%aT]$ 3I"(1D6oH<9a/_IO/t_~>|8F^zaԨQ 2]$IˑPBQTFKSX1>,4'h`2#EaDD1D}Yqu$ P_k֬o&bWPT@2:īDɛC=RI 978UGfV~'" j6oތ_|7tnf;p^|E߿999{qMFY>}@ˬYe˖n^z? pݭlADDVpVij Gi=o(r<99TM(maADDWv~̘1K.űcZ\wuFիWcј9s&(zlٲ%P;&abZ#֋ް;L¤N/VFQ5Xd0 CDU0a ??u 0fL:>}z )Cݤg6W_뮻wqjkkq7cĈXr%|>yHHDePu@e?tǖ5 i3@S3xKSkNjfU$"Ί=bAUUz<;;uuup:޽{1ЫW/_*}GGDu* KH+#yKpLd8ZV45пg,0DD{DPؠ *$^v-*ԩSlMDDgj/d`Q8{.) H?+x3R̆!"DL'\X^WWHNNH\D]>شifϞ ())A="QS\A9pּe/@W|AE?+~V #+Ŋ+{ubbb'ODBB`x琗s=#Fl۶ \pA#":TMNԹI%O3KC0^0hJ¨:rLu1%ƌce7nĄ "E &`޽o1vɓ'ꫯ`dDD]ǧh ({!m #/B AQ5ZeL*QDL<2dM[pm7ĉm6lܸzd%ܠew^!"Zj\ +<0a/Q|( "D HK 1x9k""\Mp  8/~^À+_~R|8}4.b :Ͷ{Ç%\*따0ekGsCWR}gFlJMѷ0b "·+EWYǟ | ~_"-- Cag]QQf̘;wsE]]ߏɓ'߆7DDEu c>E>ׇY{SK`p $ jȱHDD]?Bh0(4M?`ʔ)~|Xz5{9[g%\b|gػw/?oqDDūh8TקB},?$LcWJ($ }{0 CD]0A "|>_+Q bNƬX=zy~_bĉx饗jbΜ9lunF 8vj{CDd\[R7DQ0ޱoWA@-7#%cc /Mo't""䘈iBË.Qp8" E>2֭5\r߮:|;v*>x"3TQrdu%oFC@ j1}j1,MMDԽ1 A $IBUUU!$I׃رcӧOt#Gcƌ9ӐΘ+<(B68!p8 荆[?cOBb,@6X&b$I<NgC"t:x IRP^^T@YYYm<.\zg:QUN*d=WGbARRbbb{Qt4 >x<@=aIꪽaz\xغu+\.?bAOBQQ o'"fΜqM&S"֪>Tz!I0THW5ë(Y]u$'& El&d$ZXDL3&a0Y( JJJi4MSi j>"r|0 Cݻ7no|󑟟vzܹs,F"I6R]CC 0gd>OёhAb DD2&b h|!/k+r  ^d7|5pٜDDԱJ}F`4tѧ-Lfnp㫺IBdK)MMDDDL+&cZiTU 0_|7WǿnS'`?rLE$aODDѧ֥ #EʼnW7W4RSI b45E1a_`|>|>L&$%%nsN@4x^8Nl6.>/iZf3`a6yD9d <"VVACl*RUCsŞwr/)%c(MDDG+;4 $|u'$"V+V+t:QVV XATBedee Z.~A^}㫚XD30c? rHDm,%%(6 B1_ARRR$BvI"_"",GrdIWeEAdN끜%s`ǯ/Mmfij""jL4d0 LtaIII4 X,A ^/j<rCDUu>@68+KG#!mqÇ!g"IɆ:t,MMDDm(MhO+"t1>/ rWC#+au ]qZ$LW_a;&0S&# 'aM$!7$ )i|ٳgCbYyIu UQTꂢ VF*[>]-d?\9H3#%VDDDFꢑƽaTUdbUnnCe*dYyЍKҫ Ii(uOűR7DQ0}^~f*׭ i3g#ŰhغCՀd&"~-4 ...ҡP(aI >>>aQkxQrPT$ 0.73m2 s u[f8 j:DA@nI""jWLĴ# UU<DDԾNU{'5˽?x|\Mc[d /ii6C1 /I:M&*Nt]d ?Dy5ऽDDmIu9DK,hZ}y?ʊɌ?u'Bx|>St%3¡DD! /DFE σ?' ~Hxu#>>#.83(!Q4 tDѼ=}VfO GN;ޗX:uZ&l\ Y:VZN*G2 :c4,_M!I_:F:"j#.GN;!8ndf 8B0g Q4e9LQ;O;4 v ?p6 &LhuQ1ru'V11v$"j(dpB;*< t CDvt8VF X$]GɛCC j1}j1}h&td\40]PTR,H3}!"":S#\pؼy3 ~@Ϟ=o/#//%%%4 &L@vvv%%%QF~S I.rdddtwq6lM7~v8_PU{.F]v?Ϡ*z-?<vkӅËjӖ6kl>־}1IuQ\$W{/]j:$Ͱ*E1 55(,,Dnn.ك+_|E+|$%%ظq#Oz^x!o>[ӧOGBB`˖--k<5JHHFAArss~Ʊc0`dffE+[ Hcã}HH[w\HуSp/wPZ  ȀIl&d$ً&u|޽GARR]WE$%% F]q[b;`s~u-(BQڵ ۶m 8q"֬YǏcĈMEZ/N{ Kƒ ۷ I{B3O%a;Ն=c`)D KSQTa"f\`x;A }p$F:;-NG}l3x<~,$I<Sudu>Ȳ` ~ J]]P˖"㖛aĒ}zؐ`$DD]@FB~~>[_ l6|> IDATU"Lt=íEu:PxeF_akD|~c+$O(*q? n#9911]z͛pB"77K. L`4wG믿͆,Ȳ?v_deeqB3]< "><P}΁葐]= j1tlEѐoF45EzjmM Mn`;moǿʶ7mi4 >uuu8sZ;Ԟ>c̝;塠]{σ}nN'":KwЬٸ;{fľ}_b"/o@+W{qF:Nk>aʡt`}# t7fY?]סj@+ ~p8TpްSr\غe~>Hiz5]={v#=={w/,f(TJڏk ~?hp 57ֺڷyW|7?z%oIVN(rc!"NJ /|A,[ ƍP_g޼yJBu< "nT q~v'jviK8W CVTIV'}:MaÆ̙31uT`E ,#&3DDѭƥ|0ڀ#O= ns>MfHYl""(D E 6`Μ9_W(((!".\+"aR;y@DJkߣ9𖔄NYCMGUѻ637$"Ώ͛7.|\ ˗/$Ix'pG:Ljg<íx &X"PRhordFo@꤫ GUuȲi,MMDD]1q>,TUŸqxݍ< "N^*ƒ0-cN끜%s`CUT 1fVE"".;#o믿nx}MӰtRL0/pС >}@}Yfuɋo>߭Qg 7k}{$Laj$OX,cbРA_f كE]lٲ%P+]|OW=̨}b 1lHD)R'T TF+3򲐶) HtKaij""겘{'p9 5Xe*===ƍPtΌ$I͉t(D݂˫X $Hu5h _rlAÿAʕ WQ5,2z%[a`.`""N6m~_`ʔ)ƍΝ;qdeeñd[(((dmjϞ=n]w8]e5>20ϋG?׆3ґI U`"&JTUUW^믿LX<֮]ٳg#&&UUU?~C6盈Mq.dzO9p|?-|,Z]4DDm/J$&&bΜ92dq뭷ǎth|QP5O;QR %aj܋M2n[j( :DIg"!lp:o""4i$Q04)oٚQU%hhH?qUGB˙MDDԩ1ӎ^/.䒠e* fh{3u+|#ոWx`2PHzPs(_QHw/>~S4L"ƏDD=fڵ+a9]AC1BQw\ȱ1CDDDMhcnx|05;wЂEPkj7OE]3 GUuX-KS"""nh(*u $Ot% pBW&)Ǝ>A1j@J~DL-[vo"":^Ȣ`.Y6Yd#Ű~{XY&bZ!4M pC]iA犢j|uF( 9v͆ᐶbbZݏe}Sm $&bZ DQ݉$Ipݓ(LQJd/oǑO@ Z.H"zz+zv+LDDD-c" {B8^w3# σy uh:ȭUZ`㑳h> קhA1wfA I鐨UUUA@o]QYY"pY+p蓋QekH=?rZ KfftЁ~i6 !""N $Ix?{E=[; $@(XEԫxz"D\H^+" r$Jtff- I6!99;,O|=$j!0P*`BdNpzU&dRz2\o=4:j$oz^AQQ!?0DDDߖX/jT(PШ]xjNR J/z{xBV0u,#Ԉ 8+; ./P)3k&Ӟt\Q"O"""&djСFͨЖjNpj5F#?sc]@D"=, (,J]vM6ZT!$ Ag2:k$QC7;!dYFAQQJ%\JJJPTT۴47?퇻Qkc0,CTx\&Z@Ƣ4T;P I/cG}J2R ~\ÚEVE~~>DQB6 1$A4NK"֦ڌRRX cnCY-SPh5cepij""K@ qR Aqq1***ЩS'ysxtgR&gU(e˔ρӲ"QTXz{8D^w܈r^Aү!*%bf'OW "ЗDDDI}WZn6qyhZߖؓZ/I`6PZZ JVkr\9DQΝ+H5 <惁EDבuC*4K ~,!WE"""j ĸPwE"]Rbŋ$ $AeuQ`wT*.9+?m[c? xRayRSjKY9-zp( $-_ux>EIVP?DDDDbi+,ۦ»}m좛Kc?DDP Ru8;!LewA' ceDi.1nX/k?W(ĭ$9\"u}]N/94s@D EF, ”ރ +_d4m͝1{܇,$#|JaU0.^|utu1"$@vz*Ot@ec#45Qsa jQy 7^›Kc?DD-"ȸ cRZg^eK ܧ$#OH.MMDDԬiwEXY/yֶ?~KQDAwم`..r(s ́|gN!\%mR{] 7C"* MP)<]Yc'2׼bۮh\v}H2 @FB_.MMDDBiwBP} Qk @^:l6!s:¡Lף>EIF0.MMDDԒi#""jQYE=&5!ca*tr( ,I:,cQF 5B4751DDDD^b0*2@!0?DF"XJ 'MD3 (=Må""""/(әQPaD؉  ?_$,\3 (rDIBDB!CDDD-xe(BEHd{QP(lR Ri X2 QS[[bԐeoێ-`ȣCR:dZڗKS2 C]$I`L&j5 Z NI ^*zh4[@FP"j2E&T=aN^^@G|㐴b|<ǥZ7b<{$I0P(ر#|}}W(Q{d .CD,!H ȢMuC*4Kx2ô儿ݰ%/qCԾU-/5zd)+ǹgw(I˟c} @(_.MMDDtb ȍ #J,P{T”Pq}tg(!WH?A""1D$.1h=aJvVA2 j AĘ=gdDkgA]!"""dY@.*bu`4"9%EGiDDD;bɤIp렠 $''cƌj .={ , | ADv Ki2=N8˖@v-M ^7aÆaĈh48~8{1ddd8.]C?h񾉈ڊJrJ0տ>4qt{%A5H Q;;bٿ/C$<8v>>MNe|xwCv2Sj z\hNdyl]".I6YD1a>T$""wĴB7^իWc̘16lΝ\[Çcڴi{=\x))) ]wшA}1:t(zsDFF"$$r'Oŋ駟o`8p ^{5̛7z!ر>(͛S"rGoUJ)ݍ0"d _~u( w.:,m_QB B}*Q;@L31b'bHOOw}뮻WPPwFNN, G}*UѣGcϞ=1cϟwǚ5k'= [0zcƍ.1ic͚5ʄ ǩH?DF"XJ #G[̢?&""SСCm}}}O ѣGѿTTTG 0b,XM:胈ڎGbԨQo6dee> 7+Ԉ SA/!vlAe$te0y=zqqq8r.HZ|vd>)S 'OƩSl 郈ڏlta{tt)y(8WP**7+#I&#/[QFؕi;wF͛:|d*~ЪyEDDD5xVJ%fΜ x饗 ItR~ =4 Cxgaڴi$ Ǐ/ ##& pY~eY=N;"j_, jʬy7,2WC6)bCY ף盛m_P?#|`B"""9bZȨQ; ==;vرc1OѣG:˗/ٳg6mk׮E=oaڴi űc Ann.6n܈iӦ[njVW4hxdeez)ӇI5J:ŋe&kf+v>bM}RUU BsAnl6{Plt&T"sl?:} 2xo?>D *m=wZkkJKKQ^Vf5QD~~&Rjn d7JEE9rsrpct[u3bZ 5kx k=z4RRR]va˖-E^ 2f3°{nt:tÍ7HKKÒ%Kpy!55˖-9 IDAT7{lZ 1ns=HOO7|> C .$ 999!#_~a{zz:4 :vؤKR;ou>޽/i߮>+Z$&&5/weee8ϸA ܄j}>]YF'"Q+R!fSH  {Ɠ|C}gݻ@XXk/wΟCUP$#K{ N81vXH۷cڴi}`…ݻ7qw+9|0&L~!!!O<BCCqiFwm#$lA}3aɱ/P)3k&< >W<ejIUT.lڵk1vXtM7neYƔ)SvZۇQFm=z4r\j~iL0n0.^\\N.Bo>9{:|jߐU68ܹƍs;09sHHHmBW'$]j3.R)\NE 9^ùg ve$['bF\DDDD̀zZFll,V^ ݻiii;-A@dd$BBBFsvPUUj5zl?CB$t:x7`mϟ?kҋ*@YYP^^n{uؘU6JQǫq0 (..VEJJ ;`L0&uc0 ۷/>s<裈ՉRĩS0`Ǒb:3*?ܢŨwGҊetrۏE&""f3frw۞O>;vx5k+..mPhl СCwo6o GiiوPt#FիWە5}էUC[8Xsxh"?~-BN0|p@߾}qcÆ /q7:A@LL !"D,#(C&+ouX c^CY- ('-k B@b/&""f@L3)++î]?0 Y刈m}R;ylCbŊ={6f̘zyyyؾ};,X{ɶ:ǶbЁ;`˖-MFq3`\aÆᮻW_}e Ĥ|j56mڄc_GRR([DD8&"=nd^\X$n ';#J2|Tq:1GL3QXX~WFrr-h?i=77ixL&RRRp5஻rZGP &&)))ڵ+;rieeeӦ!uuVѯ_?}$$$/K.ERRl2o]r\a*+B%-bM>AMt_ "Cϥ!""@L38}40{l$&&xb<{۶~ԟEχR3]dKS0 PUU>طoDVO=˩<'OÇeO>q}ի$IXh222b Ȳ ^^Yqq8qpq uXv튼?ߧ߿?ƍŋ^Ϟ=W}x8IsN(((sN[[n믿w'|#Gb4QRRnݺ!""{ dP3Cf.R38CCYĝce]H4j%&"""afٳgcӦMذanF 6V.IrrrlV޽;^y_oatҥ^>lSn+Ʉ˗#??۷/6mdokO?{IIIػw~U*Ƽ/OmSqԩիWcظq#rJt@]Bׯdž 0o;h>={;yqTUUʾWy{(m^7[n\ڹ~m]0ǏCTTGy{(hnP@`ST!zX_kV\ymζ5 uVpZ\|]VJ%5II 5* MP\h /~ա,UH\PKS'DBn%"""@ 5Ȝ9saJZ>,="VMR#tF Lɟqv3;E9q@P>DDD1C mt=߆ODԞB̒;zdb]狄:"c~\ZbE" P d2"sZ2mHZ nI f*WE"""ֈ"""jve:3 *Ln6],@AwCY ףEPIo$"Q+@ 5&. peB0nښ咪#2RƂ&"z nVϰ@Da\Z9bYHB=~ϫwAqV|}.V}4 "d}Ȳ Q:Of l"""VԩScǎ&3g0k, 6 > N8$mS˗/1h L<sA;{=Kx?iC>ӡoc2dnV,[ t̟?GEl~5 f n;e*/đGƅ0ڸXؼmFdяA"""j3iC2331sL 8۶mc!==ԧNyy9&O HKK֭[1aN>3f`ժUr9IqFݻ8N)))8x'Dll,a4QTTs?DJJ Μ9e˖pI;vQǏ%TDW֬o%-58 6kʂo =|>]\a%%KDDDD 1m1x`L8ј>}-(p)mSg֭ӧ.\޽{#>>wqJΝ;1b|駸k]ᅦ(2/ߪAS'VǞ={pw~!22ÇqFw#""o*4b ?󹜻XPG<֣cU} IHZJ}XD *DNZLKDDDm 1oq0d̚5 jO2k׮mۇQFm=z4:tImSgΝ7n~~iL0$ȑ#8Km}ژ6-u:s ~.SN7|3[tzCDdEzP=eHawk۵W#则5eR^I!2; O,1Lp!<3?aƌ0  F#йsgqqqBu_Ӧ>u j!C`̘1nBqHJJr eee׍}?u 8}G5jKJ9ro&,X`+RĩS\%E g`HvA-h-FLe;nc7YonBȠ.Hj%U􅈈.LDQĉqUW-Z;ߎ5k4h25:(^|E|xGvv6bbb䕩;iĈCb ~?u SIaذaz7yf@||.XdDqij"""mSgРAؿ]#>>MB ??ߡ,)) s}x4q:qp-wmj.L~~C^" J2]Aotq'T] ["gk$bK!\g%ϥ@L3q ॗ^$I6l੧–-[ɓ'زe '`lu>mSgҤIعs'}عsg'uyyytǸqxbٳgcД?ƭxm봭\ےF%%%֭[=ѥ2Eͯ .\@s!CYͣZ .(#*D!DDDD'&m&={D^n:ફ¦MR IrrrlR{xW~z[HNNƆ ХKp_Om[o߾Xz56oތ7"!!+WD׮]O{{:R9hz4q*--Ł:(L&,_@߾}i&DDDN<`DDDͭLgFA|0%! *%bf'OrVe@;rU$"""5\Su:Z?U> .**:"Y:>\Kٌ*ۡԩSk.z{8mŋsz{(©SZ Bag!_ceee G2w!fzIIv6_~9 ݺ%XY^z˕dQB߶&L-$.IC5WܿEQsO5}>u1rCٳ0g@UU{&z|sk6oڹ~]#?PERsCFרkڜmkHyM-*117pnI1ydoȲp>N"jT-.0rcA,0DDDNpj9s ##hZ-}YDEEy{(D-Fe;ٳ;%2Pˤ#cA*M z \/,C\bEGG#::h1|poE) põ-:5[_!ޯp FZ$ ˆ J@B/WE"""v""")0 EDk"աGe \4AT0WE""""""$.1h]a,eX,*8PpH\ux˜7[$t A/O?Q;gH,t"TI0;E9qsR;mk]K?4*NE"""""vLg]ŪHPk2_xd]"vD7m-_ |"CDDDԮ0CDDNTPTaZ<#M\vt(Dv@e- A2 1DDD,9T,P˜qva*tPxU_$.[UhK2wG #QFfa5DPTugd.ȡ,17)(P*t 0DDDDN0CDDNMP)ZhNdyl]":-`"|(nʑ]>!""jJL(0C"#ZE_|PAᛔrfNaZPZkq]n!""QHK IDAT7d*(DTNv( :$= e`Ӷ,C.Q~\!""L"=,eǟA@I .ڊ KS51DDD!Ej(.#؉ J?$,\aC\_@5&"""j b.3f䗚v1UH2rwq(vFWKӶ֥;pU$"""F` 2RPnD2c ?P|HX TNۊ J@B/ E""""j b.\,J.T(2MXYi_`3s֥M=t"""v""6dYX A!@)8 2mG͐EɮH珄ET@\\Pr W2331sL 8۶mc!==exWn:dff'h:DD,l,8 XuCƯ{2z ((1A""";bӧ۷CTУG//ի"""\h4UW]sN?g}׫۷c8q"`߰m69ԯ_|ݻw#<<fvm_ѻw&CDZLXfE:wd,H1/ϡ,[t ZӶQFd!\+gf "F# [=ǎ>}4n&S7C=!C`ʔ)8u}{2dΝR$ o6ƍaÆ'@nn.`x뮳EtM:tݚ5k8쯸 >չsg= PVVu!>>MZ[f g!2sSGWo>Nmd*4 ,bN/+Vŋq7(Pc]Ѩ]#]tqzINN,]N___\q8|?~<9M6#GFSsW_8xY.uUUUj%V\ łSNE\\n&DFF6i""o[p@B$NR}&x?p(s _}p,)D`r""""jyLT*!C{}$I ]0G)aWcСXn6lHJJmEpp0|||/lmΜ9kQK*0Dgqxnd\h.57w$d$tHDDDD ZRDDD-ѭ/Ν;gWG&+66 ݾO:]6j>(N8ѠM <{۶~1?ѣG7{" @V%:3TN 3ֽK9a4}.0$G- 1ʟA"""VghիqQ"33:N>#G;v //999Xlfs1cǩSPRRիW#,, _}/,ۺO=ll8|0lق|'ؿ?>?#x">3gbڴiСC!"jnfQBEf*asQ1<2~PxUߚ|0z:ݷEFp_Ӝ85Xs^AϞ=k!>>#-- &L@LL F&CJJ ^~e̛7]w^yә㡇_|?.pR$Iqw{xW~z[HNNƆ ХKf3~gl޼FݻwǬYp7Tu`AN *gU'O"#Y"ys (u2 I5KSsU$"""֭^iʅZۭ?N^׮PSsࢢ#,; M$Hٌ*!թSZ޽7U'%-BCAq^ D/(x/8MywSQ6漡66ټ_'^T@T\Z&9GIMMO&i^#\>~r94~c[] jVTT}Rߨ.|ӝ3&2czIRyK{+B%> ݺw=-]orN;%h9ÔRҰ~q3+RyyZFb^y_yvѱ˗iܸ_Y}[6oRMM94?MIHxه~inJz՗tƙ3[wЭZ aÆǺ)!IKK;FR$$ϳ0}e,gWui LS]Ѩwt5i=7 CcmLaV$B@x S{2UՖvmQ"GANM+  . KeAC5ke-r`iOgkA㝚zdnRl@|# *j]*l0%/z:{f߬Z0fא̊b:`dEI0fCdn%'LGŕMsb45jwKΑyAu{Lin1AL;l6v\.H6.Kv0&RRRtY]:k)m['۔d=MWO'$#~ 1tin/0@ Z۳%yU7vy4:'x0UUrmmc~wݡ6i := b [|_v7!#)h ޽{URR8S7rV독&pn&miw q |ꙑAYLM b:VH,Iju^'Sk./dٔ\~zDY?\fHTRm2[oa<ӘbB";f C$ ƻ.^SLѸqeedd[oլY裏ۣؔ/Gݱ@=>8`iJ~r155t/o@ _܀&y,s= -^X={$-\P+W}ݧ_|1rV%F7JKzUju=9DM9yLCSS AL'_ %۹ /hƌ-!| fϞnIMMMJK &rV독&&sN6eM8/8`~3__'[Jj6TvO@pL`ԶS2[nѮ vcj΃K T$^NƖnԈo37mGL$8񨨨H6`IY*7l0K?W$I ۗ엖_]=~t`6aaF T6HBv]iii<+PVVrV4M}:ٳcǨG!ўV$n] W5`C~U>jjjj1tyMnRkjr0kti0x<\."-񳨩k``բc'@7'"HB6MC z{PQQ$iСY?ۭ_};`ƫovhaN{ v;%5۠!a4hڀmI}MH5u-><w_jjU^V~յQΝnJB2 C55՜aލu3V]]vj7f8sf T~~~mu]/]T￿zV9KMMկ~}UmLNSG){{ZX],_u\T-{r^9m24%45_Sk2}v;X7%atYk4n܏ԯ_fؖ͛TSSC͏uSR}}>^rZ^{%~Yhժ5`@ 6dhufImu6q1Q5%ALfhK)cS`K b䃍I & Ҋ-=^ ގX7]lC ܡ Rb݀hBM쥫N-IKbcCx@xn_*my}4WH X1Ғ_ $ML9SmJTdhnP oLSSQA[wH)aalF;{Fފ zC}o2yKjN/~{F> xOg,]_6ԫ#u̾y9#mJՇ,ײeĺ k5ź X˗ݻbݔ޻˴/c݌GǺ k7try<mEMM-\7nuS-'|fm6,;Ec/k5GS{sϔp* a?kЎ;ߪPMcs{fqc:LU][}qn&&4m[4p&#& vWeONorKdu]3lz޹n^rĞOg|t`W$IWKsMCooMsl/zjN;٩դ|P%I:ٙv]?=Kr8X^5PL[m\LLijblۮʪlVҲ(mEEڹs%%*((\R7o\F}rpn]uXu|zMBWaX_SS+6cͪ~k-T^Vn|a[bEڵk*/-,o\^k|SK_}s8sA IDATkm}pu6!wU]]c| URR b/{RmI,h}cߞ}5#Jj<ڼ׭ N '}l~vߖIo`,A_%}_[- ;0M8GBkcK7~͝ݦ&qjG[o&]xtO]rlvQdǹv&NSO#B*ڳ'vvإa)Eڼz2}{嫫ˇr/VU'|󰂘ի؍Ӵa\7lݲMvZR wpõwoIXaneeeXAJm]֮655jY. >c]>\X)Ӵ{ߪ/+0xR}] 1i-***OܚNIT-]_/a 2LNdJ7ESwʣ+aG%}r{(skkځZJg@o>ug7mF~?=Kob/) =Em4UhG}f凱d+ztYUm\LLnӽKJKKewؕfm ƦF6˽Jr,߽Heeeڹ+%%*\BiZ.بꚚz%l={Hv)amZmٲUY m[23/g** +//׎;jTLirX{{^=7[xLӰdkJK_٣r+++Uƿ}}-p1?cX'HQQ6oV7oW_lq;Y9zmmn"?#R(Lt654yo bN=fi6}t`(w뜇SdoNgw1b7 ֎ Wi:tXz;nJOKV;*޺m8[ÝS6}=k_GC>M3զO75hնFU5wMOib6R.% z۴2y=yl>˿o=R.pkHV Hb6)X_[[zm>X~ur-ؠ'?Vp{]tsϩg껢&M?CMSOhpO#Gǡ)nCTb9@qXRҡÚoqt ֦P%A b'Q9e;;w$_+;}T˾kPn/f;4e[z`P ǒյz: rhF~F'geZeth٤OLrPrz:][]Ш'?#rp/v^B)̒{ +ûI>H n?3X/98X/=b (Gbo%QL&d( H 1yALgg`P NʱEulL|"xAEǏab f9g}:K Є pˆ2dzFL颋.ȑ#5vX]s5ܪU4}tM<9bu&*+C4KƍoVے'IO=;8_ӦMv/((Х^C9D:#3DDr4|kȐ!5knn+V֨QiӦ>kϣ>3xQGEt^OСCSjҥaם:{۾}~i„ ш#D7nl_2\֬Y=zsT5|;Gx 8G}SN9EyyyZxfΜyw]D;ԪUZ=;h.{nM2E_oYN;4yÈԙȬ|.]>ZW\q6o=>{twkg:t>l[At:u뭷j:蠃t饗ja՝L9眣%Koԣ>bھ}{2˖-矯?\?ySOզMZU^^^g^誷uVUVŋugՓO>}3'|V݉gѷ~Z?iƍ:餓TZZڲ_wZ9566/J.ZtTo6g{e}>އcsʾ>%%%fħo;d]uUuYg.II+))~|P]t8l߾]WZ(tI-=^֝hz-wyꫯ4zhIWs}PQjYWQQѣGk靵`Zre׼Ե4MM2Ex{챈?쯼\yyyzG4{줸Z9͛7OuE믏hvJIIngcka3]iVKCğ/RW֕W^jܹseχUViGyq'М9sZ(HґG &+--Mׯ?]u&2+C=T[n-ܢٓg}VNS?OZl6_UV-GN4t':t駟ae[02GVqqq˺nfiСxdeew-0V__+V?|0*#<1]d΄#]94g$% 6poO>:蠃ZuR`jj [ݻwB&Lvܯ/VLTV?dI.??_iiiO4I՝^|544h͚5 T]]iӦIJ`8ouu.=߿3RXCӅ~1)1@wa *77Wv{uԎ;*N~lr!zW".bP4`mΝ;xfs؞dIҎ;>33Szj,u{ァ/:8eCJcjk Bfó 6L%%%rK/..ְa*ףGq0`222t駟G2c>|n5tи3Y'>{RūQ!믿^cnDxp84hРӻ󭡡A{=X]wup=z2224`͞=[>>}ב{#1*''GzM?u竼_#rHb$M NK SS5*'ZWۨn*/Z=z ѣ^֭رc#ZNj1??t+2dN>ӀmVjXte,SgtϞ|֬YV3uthz=D1f̘6ϵ߿ͲG_| ?.gctaUz_&u0׿SO=U+VPccc:h뮻TTTٳg+--i&͟?_iii7o911vhr^nIB1i]LO_K9oܱ__[?!ǣٳg2֙r'л4?O?7nԇ~.e]CCCHuv簳gO~e|Eiĉ:蠃Z{Lwy}]pa՝̙e˖iݺu-?y:4вl̙[!LGI#bf>sIȑ#:h18pӃnnA+ 7ܠo6!I\rۖ)C~E|ַvtY6MϡsS46׮^6edIn$T)M5i]kO c=`Dl6 2Dwq իW/o~:u$?֡*ݮɓ'\w׫RVu][gU>}b#ԢE{ꫯt饗j?$JG￯ /U>@K:Ugwas'T^^^'ӧtth…zh"Ij gI&)//OuUW顇U\\HOO D7vX{zǔ"JJJgϞN;M7t.2jܹzh"555:999jll'RUWWkǎzVW]u9Ȱr?AUUU*//WAAꦛnҬYt\wz;oFeeeڰa.\G}T{:h۷/N38C+,XqILI߳WgF qHGutp͘1C/^wy;v.\r$Kk7rSL_ڵk)S'Kx5J˖-Ӎ7ި۷fΜ 쓖\ <8buv'V?mIϞ$}4h/^?O:ꨣoci٧ׯz%Ik%Is׬YB;ѥwՍ7ިnM ޖA_edZ9Q?#ZRT{e} }8={}SRRi(ݧ3***ZUбc$UJHr<^>1e,gr^5jc]gw.A @!"1] t.BEbA @!И1ctǺ#"r餓N<A An[ڳgOC)nНdddヒu3q1] Pฏ\#Fo~UWWk:#ӧk;1cc'?FÇꫯVmmm5kH =ܣ|jƌ뮻4n8k%bIk5m4-YDעE2}zU[[3gSu_}:/jZxϟRGyD~x K~_u]W_}USN{Zz1l]{K%I&LڵklٲV[맣:Jׯo EB.k{˖-z:]'7߬^{MǏ$ܹS>^yM>]4qD}wz;] اgϞG!N®2o-[Ei-׬Y#ͦS?//O6St-nM,ԈTJ='NԸqpBUVVw2 C.+"mt Negg7Tyyf͚2.,0kڿ4t&qlСz7t 'hܹzg4j(͞=[/UZZkݺu&A tAZd^~e|͒E/… 5sL;w:7vz=Ȳ>vreñ9eksOMUTT @,xooƺ)c$UJHr<^>]ϲ|/>}uD<$ԴfEn{$׾E{(`q%AL ~f͚$Cljz*YCآ-$mA^?Ḱz=Ȳ]ܳߦi5͸ $ 1"fKMM=ARϳCz ^P€tkRGojw޹zSU-G!p2.R:{[bAm~ك<+&c֭Lzzkll\۫WK%+0R`8;$ F-(1i;0 0r;>e^voիԹێ|Y WBpR[SU%ɻ웪m6dƵ?VsO)gU1 lbĄ*.Em&je?GRȑ#ow~ev96?W^IUۏ|]1bio%qbl~ښI;NsZc*)g9N:qIDATthfo4%cUUU:thĜfs\_~&Mz]Ժc=wtx1u mZ]?"5`o֠RA{=NyEW$ސ;fF_v{hV"S|w_CuG?V&z@km%a0o΅3팙xbـxMCpF" @$-E5L.m &Xo/0yqַ-oo m5 ?[bBNR` `oYo?1AKG({[^ D;XzpUB$gO:AwKv,ںuofE 7L\Ζ&IzѴuC/oSoq i@?lɿn+Iݖ$Wiozk1_ڿ$+?&~0ϲqb|{x+ƻ]?̬V=?ddmk`!L4tIx5I >#:iث nac@wҙv~Q(LoҕAnOlj`-y WXP$ mfGmMW%I+~ΞV0 οGM[m cLVXno.A^1ik}(&B-E0:Qo@%zH#&~%V}C!Zkk]}!LBEOh1R=c*K[?6 "JopB #wlPlPml P^0C Z ٙLH JPl{}@zۧ%԰%!zHj ^d](!KC ".[3HV/:O(:A~z$ cB] Vo;zOgth6a1:}5 8!L[ڒA~>@w[<M[cnu[ACg`5hBP لـ%!#*^BH1"DAzt6Lӑ0R|cB&^+!V{tޘJ{qKƉ gj{"%Ze:%VvD37=#@#39q#^pe=BcX(NW!Aax>6ĻpnW򯣫FM //ޏ$Hue#%FaJ";%aG<8QHaBښH.RI\0^J0Cg`JFp)^og2@D|\ I*CWp E2.B,iTXtCreation Timetis  2 jan 2024 10:26:00 IDATxy\Wy><B!ba-п\q9>rlB!D9*p]m*r %B-d_a)l !BP$R fc)B!;\RjTB!V/նUM[!B̭Rs![ jXj/t !B,eb\TKm_B!Dy~; &ؙW\B!GOs}妻Yr Wӏ?R'B!l~ el!ؠ>_O!BdCtR۱-:hvJTMwlgB!n:Re-ӞbCT^͔B!bLvՙ*eؗ?bC\B}9>B!JSU \/ ;_!Rͦ\^[!Br7gO}4&C?y~1B!LU?9/_6LwBCt /'B!(/U Bl'ꚩM j\|c B!А^lwi͢BĜ)g/}!B2S w}_j/ ϊ W[/% s9HW!Bة/)O73Մ/VPː?SjLHB!X3M9Uh}r/$g~O)Ә7fhB!X.WVݧ g 0uY{Ʒ+"U5_L8)B!`2u5al o/&S>KB!(/.8.W_l=R B1e SgLZn4.B!@?>1jGSS},7':_şg!!ߜSM:F~QArMt C}+_Y|,ڢ/!Bym'x4+.Lab59O,>mg9L9]7˕zsm|+>OOH!Bt:I.wq\Tai9+h[П*?tsEg>}a?xlܸ E>!BQN>m۶}|GO;93m0}gۧb~!2َ>mooo`}}'z&B!͚5ka펯x/B_0L897~/ Rl7puv:Lu&w⏿ݽ{w\3B!KBMM}H={ 1?ylSgNlsB5n;0sПj-Lc1q@ ➆B!XJR+**>}g'wۙd\LuS.'nG`}:[_򗯑/Bw_|wYGM7nttYT"njO5Ty6 !B%R2o,Zy(~&ivTLv|6R!BKo|5rBre~1x'60Ͼx !BR9yvLVj- b7_BnԔgZחN!B1G!ɐp͏k5 4x<|>L'̅ry}w/fgeg:0 K!B,0qH&d2)w]ur$I|>`p.gO mg: 3PjjTSoNuB!X@tD"Qc2 Lp8ᅳ??]&|[-z~*K]*LB!MB!`׮]ڵk8WUɟ,H8 ί%M糏~/\OcA>snݺZB$Ix ~A0??`Νil& bs>?яBf.*S3\j͓/_*R-h ֭_By! .g77x#Hb6_y[ʙ3gBnt ͹d檪$**Yw!>,vy嗹[׾R_*-rbB>?OEEX/| k<33; i{)YtB|v  >@ 0 >ϱg~~ }CڵÇ=_*vWT*O|jBe(xux7xG&k477_պ0Ys ˽μ/XH۶msQ~~{s΍}]{? ⦛n_͛ٷo>ؙ+uȪU8<>(>(gΜ/| Wn42?SΜ9CKK _רZ!:կΟ?@}}=_ihhuρw\wr s,Br뭷//K<***曯Xnt@Ν;ԧ>5UVݓB̋*;Qa3VyGp¶6ݻwSW2yRee%=---}nbr;vlmrGYV',7Os=9v1 'NP]]ͺu.g~bYg?ٱ{nvMss3mmm|u sB¾bK|]z'#<'> {l6 \SONǂ?"-lppp[n|)!(޽{'>;>-vs h1C\Dzӽtwї.3663Ke*es5׾֭[cĸBo~!Bc>^;gĝ/}?}TWˢ/*CdO_& !Vt:M"uᒶ_%VW>V}% !Ro>8v|!ĂsRu5/yyGoB!2u5R+ʼ_Ef !Bx<$ɂ|>̛_ B!bI$! dr83v>00Mǃ瓀$ !BgR=gI)f !B!J A_!BeHB!ː}!B!! B!B,CB!X$ !B IB!b/B!2$A_!BeHB!ː}!B!! B!B,CB!X$ !B IB!bB!BC)5vZ]eB!Ēalv:;;hjjb͚5x^\] B!bIPJf9p;;;dΝx<_"}B!Ē}Ǻ B!bPJuיJggq$ !B I%'3ܚxMM,-}~Ś*-rfb7I!Dh؟仇EIbiG;s|P_ -H':BD#i{.._̩XZ/F${Kw^_&,ɐJd2ض8$eY|>>9% QX$/'w&v/}k-a! =?y=M)+54Hź[.Vc"R)baDF ?s]l6K,u](@`UDkk+Ü>}nXjjjظq#7twq֭[򟉕L~s,=|"J,U áso'w56@8CMM %3Q0~lfppT*Euub7h>}'|gyf#~y饗xyyƍgbW= !sUJslId=͞"jvݍ磮NB3eQWW磻{ɜas3SO#HsgGᩧZR 'ALHzBwJܾtO߿֡;RHzG,^***BLB!***]'|ݻw/vS2{n|%yP"yϽ#mxEb%O?\D eW/O8f```2~inXq~L$藡'_> ^߿D#''ER)ǑJX0PqHR/ROJX0weI/C=t9\}j̀"o]^,XnXab݌)b1|nXa|ɲL$藙_L[^y U_,r2 a[,0 2,d2[yZ[[3!$A̼ܚ۷%b%ŔJDXH*R)۷+Ծ}3!$AǮoiʥXí]\LLWzˮzd8|b7CP.τ2>x9T}dVUH'0,mӧOϾ`Ӭœ>}>J$h|cd蓃M.#mNJ) 'J)|^ߜoc ?}*#f0R@ 8| bI_\eppxm[RL+AqaDѱH[ECC[n%x=BaSx0گKdt4a9߉PJz0}rI_F,}Y~d8y$կH[[ۤbZj\m^7/]2U%%@)0UqwUz.J2@.F(\pa9B2#G`6QQ0ضm^{-~Zlقil߾}ގ6.b\”R~oNOOlӧOsr1&,P L$s6h˒ʺ9ۥ.e*/Y$WFFFp\.72LSԉ|>2q`g3œEdY#n@#g Cp?mx<+'"*hiiuh4ǖs]ٳHbCi㮚MT„NXs\:tlb)LN55^[#J`Y&=uH$☦7 rv0gi|a6hUP f@jq\6qs9',RKcg4堧I(NkMkk+MMMcW}0X~=@ uM|5LaaL\o9+8ktqg'a q]gwM"4p C2/ˡ6|Um6ĵVUCֿ%8*dXe'5KI%A=D^W_}an+ܲ,V0/fܴp ks4I;XT Ռl6Gy,<Yeq ΐas-zu9yM:a0 ]\rʗ߰#dk٫ F<ʵ*hWu^  rx<z{{q]q4Pp8V6~˾1p75l4k4]9x'^Lw P 44yk-ׯ{s{kuzˠqlv j/ b5@>sꊰ֐9zn p;*|A<'ûٲ:B$`1=Clq+^gP+v֚t:=VmضB)E2$urQ9w}UQ3pq=-n3fx]ؾzkv(4 cw2s0 rLfne`LdVGA;P'I;] ^np+6bk3bֆ{nt.LM~sH_(֭#©ShjjfrHzL3үݰF_Oz0m맬9bw7uAB~yn>(q^~Nw"}W^޷N޽6B#;˳GsFmPNќh k&șmV;W1qy|Ti㴵a ضR q馧w숩8ԅ>ݏv4ܟ ׄf\S{EAa͎9܉0n*Wq_퀌 6Wµ4п>#<'eȮz>°!V}'yq 2e2Q$/TVVcǎiJQ qKm w_9\K?2!bYeG.y x2O/u1dUC-gN*uP޼UEr\H_dvb%K(US. ;ܚ=πL)U|͏l.㸌3-6DPEoYs83שm>0oZs7ſ>1> ܾDg0/{s9rKo*^ >o۴ STlhd}1M*~{]DԸ2֜zu> 뚃w_/Co]Gȷ̷A 8py*4+f~ɹ7Gk=jg3s7yrMw㆚c 7J:3A'ta]^ Ÿ&DG /dzGP#{[` J[ 7PGnսt O(mc(.?2u;nE{x:~ .+H2­czwPf}̸ ZC*r5XC\ y?߱8- s/pqwX aFn T4F O0{߉\ L4M<gߟ2U0V[B]'RKDu:}kyӶ*B>2{ j>>tZ/vιeiv:TfvV\C'`&~K7 xpuΣخ;ڸZ5;:߸uA"kcd]]޿{ܑ>t&H[Gk=]Ŷml۾eM1#t'Cu|D{B Xctm69T.+䪯ǩmx(DX /h'>JՐs E@эPX UD{+\|#ݗԭItV0Tm ԃ!3I_Aഇ`*6@e x?}7Zݫ+>JA8`;w5q0=IW\DJ¨m CП[O4ck> Tݥv =Jv;UX(;eT.S}=W=oOOL)f>Ag{Zv5lfOhq]4ʸM^2Ø2CV݃>J̗*L esj8<]A@"Sع;P~:6C)7_:;/vO">hAѝӍ,s5$smʡnZUP 7b3qc(7;zfu 6Tm)}7=WYfSHbuéچWW5juj]˳:?;]cHva@9Y#hpsgp*6z`7Q%[-۪yڛy19onҏ*mSYOb ^&䕂pf.Mkp7sNE?څ9?[[ZbY\ֶj ϑY}ii!*B޼i𣗻諯39g] { '`yRkp9ou)l,714C=drEJ rGBݸ ]SS cuTf0v*? vpjwM?eW_~1_ͧmQtW> k*qq\3P:F)ς'Ss2 ʖ<״xh-?Fo<\UԌJ)*{O3N rpYL@)otM13,Fx+Ӈ?[t3v̡U[q`,dzSɃsg~#Qow^){*Bb> XmG={|,lε_'K-75e=^yoM?4X[䮭5>Nv$ڋڬ(Z3zچ ʡw>?VTQh?jƭ\k@lF,'26sPA_w#*3JN3~kHa ĩ܂lhWљ a(`%[k DܵǺpgZ*B^:9H"k*P>?a cIb d1bgQܞTLkd38t"'MĽܰM.eJ6UHۜH`4ˉ[ZH`2iÄdpQTS8-10koAXyQw_I kɥoMFP7Cn7b΀G $"A_L)rGD)EM{XôJj(R-1fF? B5WB;|ns4nFbqfQ@}~tWUnD9 @u }]nDBH I&.&usWTCc'*Rpӆ *|oq PdaiN 6Fcv: 9?UVrK8 H$ƤqH&~ځD_zY'iK&&>:@f =Ksц͟jS^n^_[7nbk^>PjkJXIA4% bYr("'7P~n?Cp*U*݋r8^:[ zQk+Sd'c : j}5J'($%qz{{ٷo/2޽{yhkk|8}#ʺi1ٱuFŻ9&e6Mj?T#}$RW0ByO4%u1q+6sp*PU^\#y B{"G&}={N ܳgַ8vXW B`A]w4azSܵuPs 91ԁ]B6k*8[/r6o^ oi߬ -T;ľ%JZT4]؎. ^m}|>R}`Ch#{3-r[|kexha8wk%wiKq|XOciqM%S֍ ijtjtqFΜ9C&zΝ;GwwwQh ɠ:$'?xjrX]{ў(nq5Ts%F~s7܌:*3?zORvW{ϱ)X'{Em;G29 ANu;Va\qXH_+.P OuFFFЮaNS`OG/v϶3ljR80B?_N CCC|>"^x*6n/V(E"mmh4 >vv.RLrFrj0( lJ1ZvXpq'MH0Pq 7.MYΈaA\&-pC<&#UxXV`ʄdY@H8BnيuT3WaWWhE%sA0qi01v6({"L_1뺤R\,m04Yv-J)8~8D+44yǾ$ZBК:?;6Əi(CaaU*3TF6"oi= RWW LD"*(4$|L @)dr RdG'ItArLJy GCWTi222L$jf92 E<NUϲ,6n+8_nB9uYn-Mu]#&^׋R͑C8Zw2a(B:;ŃgyK8>]L$^q74]\> J%["igq 7o":`\.G<'^OkMќ!췸u**! &,X g4**}4/b׵UD}XRP*zKYmcW>[矶rb*Z翷<].& (Z[DYS4+ylilk0LLJSooZ_'~-E zP艎4pߙQ*ZSW%\”R|>jjjND?ћKUGtZ16qBao^Jje*BZ͡3C?0>>+K\ NFpT86l*T*y A%;qeFCicc#uuux^***P(4W;w=sp?mz ;ɦap{fC%f=t [ք΋v }~N >&N}ýJڪ vQo89Awݟ~nOyj~}|̟߱1Z뱉Ǝ A*-BIҚÃ0bgk PQ?Tfk=m-ZN IDATe1F:n5՗^#"ۓtP&N5i&Tڵ mCʞ{<|҉C/;.)dWDbdØs(iC%*TozsF,-28V- 2v~^L?j F#5L"i;?DŽb{#v7ʉQJ4ͱJ}ө̌UDhi &pgBm.+qLD4h9J,izk{3vrulX5h ɌOvS_㺵+x h4gb]%o 3cK5G/Ĺf1rgg}}#3Z}aI%tG*aFmᆍx w00Uc czt{-R Vr3=N!b&yI+"88;~h{ӑ[wvFi^ AS.>@PUqray/m"su'Zna7]2>:Z"x -Mz)TXls bdž m`8E_4;0MahkK8 7"JF+]wRrPk] "]K$"B@2X߈Bw>ZC[H!K3❹:P CUiػ7DoVV鉆 zbbfɉ$٤@V ey ߛ.)24'[kEZOl+;Ve6X|֚=Cof*(%f`|see@lW@.©Dl,黩pa);si*|{H)93 (g)U![S)Jul,j&7jvhp2u,b3=ӗֆ"7B8Ud}?}hl5.g3D͍o!E?ܶ6iosepv[a,oJ ݰ=VIFuOoZǥ(FRa>Tl}QWO%ExO C C#St󾂥jKM.#Z@ Z Ohj=hyGW0ڻyq)#!zi~t oNI⣩H- 1ޅ*6bNUf /s||{m]ǃRpQB֮QfsEٴ/& "blR@jD TfcY:_yrz蝁GQaNDVwE|Nfp$3@Q~*M`/t-H:׉d![]gdrx^$'s>?ɚ] c;.q</d`f-Z_(+vm]Zʈwjӏ dG+Ⱦ&&bQC{h$3\Oڬ:Ct'M+8Φ*=.ؗy=CQAH{7J䒛5!鉦9`BUe<ޅ ޻YNSùt,zwxIF][0!*]ƏߙYb_ P:h\ |+G+\D8 j6VP +)v|?9su؄Nێ{7K$"X? #ʁT\OriS&\fv \dv4} aB=b[W)UN=wC| <| ?92H&WXf4l/)0F\x0+2ЃBNO1`߉ūxk}BOJe,21㡾+[5.CT4&t\w(~|41<3n""i!bdmcP.P7J0Y opG+/x0_Xxxt;lJ瓱mVTkE׳5`_, YN-T%}1r _\KC& ! }3AV&n'pP`[v_&EI(|e&メhjڃK^d gћL4H1Fͯչ^85~v's/@h#*je'IDGIɉ>,Mji.˖`mK߫ ] KDkvxv؀Lx:˝ÚD텶)w1Qd}-> *χTae 8̀ 'S!L| WoB|@ڌJ!.g)i0e/ίvwnWuh.C6jT|xඳEPtVmY/~|s%APzUb##IgRc-T i|Lvl|l׹Rpfx.TJ^xhc 6Rb\)*/TI`6X_?L-y ot+$q p2t/ @k|(6hWpC+ِ,9;ԅLPVܚRj-( yy*^K[ {I4Jh.'FRĬu]bə1tmV ECD~u4H]t$8Õ۬lsiOqe#p~u|*PFKTAfkJ [QT?6E)mO\U.?\`;ۛjBr(3@ʌqwBΌ0tu~|Fٟ զl})RQc7At?Én?s."fi<7fB8_A,z!^UD4PRr$4?B:Ïvb3j"r26NJ YCM|:p?Kmdu3Q:A:3ݴ{ {Ŷ[]J76 C s bxDY~u@2R,KLi-MX-, QmYaMd2]˝ H),l6K4]b14eLfάm;ˈ )+Pp*-(h!%n. acRCYY&rk/7aX!^R5 L)a\ebb\v q}sDO4|mgpgǨ6C~G ɛǺZi0n. yVWWsgN Y_mZGI! N\KG#m>?1m>Ww^u!TbQi z#*nHJw^W ,D<ˍ2G2čp:޺˫bm?-*3_ % ڙ^ MfPzi>}??74M|[nlJ)w!p"+S0͘+ \,У@hs٣Ac"T$0O}Ofe +pZFFFI9!RSiSJݚr 1 8!|7mj''>m\Ÿ\.sm:ěoIPX+mMIhVrsŗNuTW,/tg WRҟ5%/ S> 3d# V|SKч2/Г~PTY+TuVI6_;Kbm7>gɡCk$grrl6{_ߴL ,_c! i=J 58:< JO<^C JC(=;N ڀz;ax 8*ַ{bI:U3Rf~nZ(p])%{ZB"0 M<|itt!Vk>oMul.=#>1_XJDPՑd6:2_:'7t}fNj߉~LV8o[/H\n5T*\Xj1NjHۜ9_t g)%==={ W\|W^y+ 㺮dD_s]Ž|~Vnr(繸lʩnU⛻zpmέFT/m#. &*wѥ$rL^B&,ѫS8}! FS!RM?8w8pJpl^x=2  10ؿ.߉Fc˯["eLT u'Rdrkhڱ seMnhyZD2I$r {\XjgJi#|ʷPN(b^ fX`4C_4{79^'7xn,\j<[Eu^=pC(Y_!'Jh3 JP;ݚiQ;igH1~|vHJ:fpl$ɕ 3-*kyc>,-\l+\ UCPkXjS+{:S%_yTܓi] U+`w,^6b6>(!}I%!PҠz+J56<<_)2HA+^O !^ n6B8~|>x( nX@YXl#$NH&b7#$nDxXnJFSD& EkD '3֋:!B@W.NrRcdc%IJ!nXٹZ_>Ս+>PBq)?HTT_"yy&59z ܐBrDM.i;QF}lц'mU7=AԀ:$ˏH!]w5H&>Vc(;n{ :UBE G)|.(H_{oP 3xѰO&vt>j#FPVQ_ϝD7iDx{U*3v1~2,؎A\n-Rj95>A)n-֩ZmE)p]^xX*._: " ^|HP(3!jC@B$jEKv%pw,B}\ץjqm.\OZ}~rJ-iBR\Ji6TR?YlWii.ek%cVz/{ 4ED-rPЗ wB^DNQzHP<ϣX,;ﰲ?gmm z/ ]΍)ot, %<]aкOjP,֋ԜDz!wm,ɨAnR&++:^&yD?g ϺidY^z%Ξ=KWWB^}UR}Ejڮ3_AhFs;N XO<+t$X+iphū`Z}\/{e>FKgݿuqlٳgP(N,B@oo}ϻ+u};6>Y~2w ?r#]|x/.ynJq,7LO$Z,~ Z2k'pfJ߇jtȵk(wwammb2>>N>gnn~t]guu˲Eu|ߧX*aE,R |ϧl./kx U|zI=jn C d%[ME>Zb_=I^Rũ2_8+([ cgi6蚆4z]|ke2oܕql\.sl$s )e}% Jd2H)`ff\.G?OWWv۶!+~ON d0 ۶i4 ===y?N\֭[8qby+ktugD"V i4;Eo 1.'ek-22x*J<Ѩטh9|=iWO(,J$Ir͝xhRdɖ%!I$wa˲PĞ"^ۗ )JW>]yDyT=q}JhyxVū׮]cdd]QJVZ#4&%\P{(bd4߮\VaAlT9>,qfHΤ8*5-#/`)J\*v<^>Dymǡma;mHM#Ex!7͞Lbk*ƺMչ&ZMJ co̳H)I&bEmf4MΟ?O__`qq=lpB`&B; jN`Oce+ M\XHCg1n`WV0|4/_QҠFH>iHt]sӸs 3!ϧi',iFC5N@LBm)+#ZaFwK'n4c i__ɓ'ywj/y]icc cBEkkH6!r͹2?=B&a Bc8ɮ,/,1m5]H~%24mgYn R~뺜;wK.Zo }>w$X__s' CTZ=4Cǔh]C_gIԁ.Tj:v(wN%nnn T*fb(| n~/C4MZ{K'V~f,:;5]>/o/˰/{6:?9.@@'Y)i߅xV4\.G2$rddzw?F7ڪp8o_w+X+Uf u>(MQu[3DNmD qa B"cP/rcm?`+«b]S_.rJmc'seH N'9$~D γa=l) p<ԅHGp JEb ۤS=:">םhB$yk8]d.)7yh])~6{t$Ιp"BGPl;E͕ކ}<&QhRbLۥTVhW7ua pT7?\t g*ؗ$}򛽺]ڂcZJ|MZl#zӡ\w.b um9ێ@06.$> O!_Mvu.B)+çYjq8"uV|/?yz6cK@Z]Q OB6{D>SD4^ Cwe}٘tu#8AHP@_- JAۅJ7ATGCg>ILq{nq4K'1Y_{jb28 -/$JN" rԂ ;<1P!âKWz7"1Mb_ΗΤ t,cP306>]f/Ɖ$n@,fHcaF@yrRD<*\h{ he+vp+!Qz?9l" >Q|ZZ.xۣS!<ⅾ#rN t+[]5å*iAF8!wR{`!P4M d_8\\bWInBlh:vD8UXm=4Gq$3ȋ=|wwLUW;'bؓP*GdgțMhw. B~':~b?H}Õ*̮!X{N, y{L^;BD3mu䗓sq-2Ӹ}#K#OOszuy t ASBBɸrAz#i?!wݑ_9p4 W*_\Or^~*7jW?JsXtJRM_VyiҞBjOAs V%}%0$\,΢'clGhȦh3p+ڨg9D;]:¶] #JofgvKiⷤ8_:8˝@_@%am+= ]f!Ů:.A3?ƿ:O2=#hQ7/b ~AYݶQOI{4sD7ߛZaKeO]M C2m mWmY|}ڙ^zchXT7suֻԜ֖R4,Xv2uߪH~_b_ŞIũ?d?-k;6\ʥ_}T7'M˫J--Jh㟝σ&5#)?L-O/rޤP(׷a^ :lFϭG[\qj 8>>5``(:"zc/sef(xgPĘ! wo(Hl%BtSA:Bu ]M/7^3繱.R)~<s&_t_r850"O` !@4i@J2xy+GZιX/dcnE}Uʣ/8oeh{@iW;Zun\>kbF+52n'w)q|gyy U)2f=]vn~FVo>omj_)?}hz<&DLC qs=͝G 25pb/By\U[UKNwg'!,  <:gFFwqTyႎ2(B$BNHBI{w]~ܮJUoӝy=O=U֭==|@m:$([wvҖ#Ic,Qs3|pQq/AQ(r:oܢ6/eH?}O5K~eD2I~qE[T d]Y!ynG0 b˾!A;ms>MVh V7|):' 8~k}MQu=)*D$B%%%9_YYI]]縰,Se fOUDZB1;>FIZQT|#G;ڟ 4\sR0Z̲jvDyc0|ҼJ '6j+H4ibt id *L "GEaR)$asN k׮%cR˖-9;MдUYfyEC0o#.沲$G#6xCcѲťpɅK/t1Hȧ10d4W#Q>pOEwmE !>tM=iQ\\S"ϣk#` =ϋI:גP-"m܌4Mx <---DQؿ'`uSj[cko/h RS4#]Xa$adDG ջ ҳoACJ I%ct'cAxnb?iJ\XނHܞ.`^O;Yaȑ#AH$d2=Juu5,sotEsqP06M3ݍ+XP\ϓ;ASTI4$\T=&BN֮eU]YI"/I]d2i P_Fބ󴓰H,Kmm=K) C,n5s!A$֣H]a@oYp'Mx6.?>tuRX@G9L& 444M&Bk4-0߇,KI:9ǜLf&eɬ,K穣݄ܵq ӠTsg,+>.2d2idYBy!G< ovQ`8">Ye2:gHӎe0ML&M:BQ3g%kxh3d˲MMȼiWbFqKIhiirM(++cllh4J}}=,Y& F㘦,˨f;%$IB4d<1ScaHHKj0[+1*H>$#'2sd~ƤY,AtOdfsqE܎brl* x7YQU MӐes`Gnĸv.&"#0*+9dS9NI0rXd "DP.<>w=ݽ  Q^^E$$eL:KSO)A+YgW(媟%zȒD&`0RsD¸ b1{/ eY N3cl= wd_N \r~R1˅,+h9L^5^$EƓFKCƄ{;I*x$ȩAC">U{E?5q躎i4M]]]](5hMӠX'SS[A2$N5~-"(gj^^ `~q-5@d$"Hn{0bhS+KvQ}a EWP3"*B]׉D"G򛦙 -T!o{VħZo`~E!^2BEQu˕{4͂"44¿0eۍɠc!YxMb 2nYE;XT2㣨P[P_cRf pPdpxqGII1$366zN"JXDz|;$E %1T c'Ho&>e"Ѿ 8$IbYvd2I}}=$QTTĜ9sfٲe(6S[W1:Nc&@QpXE*FQ\BXMV:`.^P>RkZ݃YgI&t x7 d29ΖbJh(eQ%Eb2 Jt%T߳gX,FGG%%%ݻL&$I&{q\.H&H&y[O$)'>po$I:,K0&ai4LR;5U‘pa٤Q,d@F$,$y>rPP6ǃ˥&pݱODJH$ h_VDE®i >?C8@{ׄ/F>Ǔ;Weee Yᙌ C`wOdvK#{-g.nfEwв,‘(PF,4 I01iݙL> zL$ dD2CFz=wZ$ 4+aD|躎΋oH:+},BSOc:CHeYd\/"Qa7d׋m"򊼫òuDR)LkƟǯsEEEyo6MQ$I&,x<RTŎgG$P X,V` ʯ( >h4%[v*-[*nMӨe`ptx9Y D< 3yWIG:.A+*8vJ2ʳ!zRCWmUEI_ɲiRyل$ M0 #Y%ID"Q㡾ᡂL& l fBO3BpҮuvs[إ>@CƈEeZD$$yzL$sf˴~4yKXǐ Jz ~<!eزH&ShvRV1DA (褡, !!1tL^` (hVqX ,F``;}PNo\.$NrV]$&`zCꓲIHQx2I|z¯JKr ypsJ|b$ EŢtu9ag2$Iq j5PeY+=!J`"<Fe:u֛׹I$YJSI{j=ÑHP('!J'/iqmA:FU5 ֙L=)l9`YX˲CtƏ4 `ppH$b,Lӎ\/](OikYCCC06ֱ1FFF Xr+2TTT;39@ @EEEXԾ9Ij߯nO;Hkr*zf&d2Iww7ÎfTX͸hI8;&4$Ib]]]Dт !zϼۈe͚ͤv8p$`0Hqqz^vC \C]]y5Mۍ륺5kL:!8Xo;w̠hruo&hƻn\.ψa)fK"ZރV{r3K{'郿=eNn0uuzxln7iRhN?ڵksR7ofpp;116::ʎ;|\./"d2QYYyA,b``gr-Fo =ݼnYfq73L>*PܑW9sȮ]uѧiV75CK.%HNm_qTTTi7np,I(++o瞣Gyy9O??nFf͚5dGˏgs蠶1G,DCCc4=== tkvŊTWW;fGfBr*y*z8]]],\c~F&F&OCbCgw>_ӁP(4)9YY|y;hjj"  kTR)9rQY qGD;tR$I"Jn:b#AӉP(w1iػw4;::FDQǮ[EUUx<7SRB:vbxMݱo߾\ԝ;wzqӶN BrJ_.ɶi;9ӹ[0T={pA{Px<αcǨq̦(TUUE8?: Kk(J$XNVى^lljlu\{馛N(–-[Nڵk'\.U`z޻9{2y;qyXw.ߟ3b04sIZ<a6KwTHhwL@L}>Ng-g(=_Vܻ?i-TUU1<$Mz7{d I&My6l烮댌.uof…?wddیg{d2tbo; ~ПB=Rγpk\~*|V3 ejh Fe2='RZZJ"`hhY yg2U DUU/se7`&P袍?.--eɒ%̚5;m۶kq!G&֚d3e{׫a̼,eee477l2֬YìY.:q#Bw.ړ仛"ӻkSpqH| ]\hM*"HJV\o\3)]a snE{xvyոGy$;W/n!=HT"@ B"":?XOR:fN.y$KTW\15m&@@l?__*@ϲh"nJ:r\s5̛7/,|>6no[~JJJ{x.S]˾}GAQJKK nB&#ַo5\3%eԧ>5%frrb.]JUU> W& :O|}at x'? {/۷o{kn㡇?)?y7oztM|+_!Hls=?䓟$_=Gٷo/뮻{!پ};EEE,\kײiӦ {z+7|3<@,HӬ^ϭK$]Go}ns`_էL&sFKKz;,^xls=lܸۿeÆ ر$h}C<\L!-bܹ|S'? oذ_|˲r6ŏ D"{シ}'_:^{-w}7sx;x{|A~cw=::~{F:;;Wʏ~#r I}Yz֯_ Yz!o7 z衂FuxsuW^OSnW^y:f͚E}}=_~{/q 5Q}4l.=.r1w\:::E֭[re|g@kk+Ω,A!S>A IDATsnV~<UW]E" a8ڵk@uj MMM,\b<TVVO|͛7).. _ . ~444{^v=gEp)H$زe 6l`ժUd2vG}իD"[lݺbllÇcj>D]]Gճ ɥ|=z}7>ZZZx<=, /R|>w}7wq[l>?LKK k׮e˖-,Z-[rJ~?gfŊ|Cꫯo+<~;i_%$:X`ws(4͙3`Y p^xCcccxYf Y0~%; p嗳sNٺu+oƁxijjbǎ|sޫ޽H$Bgg9g!q†`fr)_σ]m.^xB;O>${駟FӴs.@ExUUky衇n`˖-Y7M\.w_.v"~O3_ L{9FFFr;wObc&i5\î]@Qkxe˖H$? ׾FKKim63KzNӼ[455]+P(ģ>zNeND}BQq\Y.yr,˗/>7of`` |>^xᅷ@$}v~}gAe^yxrv\󫯾 /'W_}5geʕ3 MMM|> ;v젻~aߟrXn<˖-˥3 B P(p.>Oo|Yf۰a;w̅5ڴi;Nx zn&,Yw؃j?r 7OG,s=Wpm~z?a&Xbg>CEEۯ[X,ҥK l;0k,ٟ7oe:'֧!4T_W\wutM|_4M.D|#aٲew}b ༘ 3ٝ{)o}]`99wKW?gߋs8s1Ny 1W ŋ({bڵqzu0n6^V+L2yN\>3Ѻs}g\yB @ B == @ 8GLk:;;9x ֭@ \T ӚznC*@ @ f B @ 3!@ @  D}@ `"@ @0B_ @ /@ @@ f B @ 3!@ @  D}@ `"@ @0B_ @ /@ @ԩ.HR$ RcTIp( vz.c:!8D uRDD"A8FQBȲ$IS]4EeYI2$3::J(NuΛlHRR)$DL&3E\hvq3N;`&}}}x^jjj8墦K__iGӀlC|cEY'~ӟr /p{믿U000@II Ph"B!JJJꢜazzz(JOOp??EP>}!9ÄB!~TE0B!(exxX,&N?X좨/$IBI S%8$IiO&I`#;}eYoƍ,^xZ ?MIR"_Lɰ΄$IndY=3 e QU]O(.*FdIF:ELd, sti*d2i$IBӴ 9Sbd7b?N"+2tSh"+(5,~##J|.ErdYcccDQz̙s^v^R)m#UU $ cbQ$r"TQwvSUUEOOɲLqq1Dd2y4P(I_~m__]HoBOSt]G `zs!3&˲nZ C4MH$B0={f@'⬻qjٴR+GSNݶZZ`j>~b"gҥ旽o<m2[2CÇg!ZJ[h*i:kMvdS@"#46*x<G"`Ν^X'눭3~pB;crEQXla/;*^/z+or̮$ITVVr7裏^t"nUVqazzz$I+g}yk޽ӮNNFiag|p꺞kkq,l$IP%.x<\.ef[-))qӨ:GєeJѮH0x=ϩTH$R $⬤w^%IrBzτ,˄B!,YiA<xb4MCUTUc C!F|(> dm ݻa^hnn R8`BLQ>I=IH;J?1x2f>q=`l#<60 [>o~?\k HH̛΅@ɲ'L&3-ĩJ4͜8 b EQhhh+dtt4'\.P@ @2ta$IUa-1>L&ҥKϪlQUP(Dkk+gǃeժUPRRΝ;9ryvuBp2B_$Iۇ磥Md2ݻ1/_NQQQN o] A$apA6H7|ZGm[EWWuuu~GϳeYOz]/,TTTEx|هW{;.?rYר wQ+lgÂy(*r1gl/ ٍb3R@$-[eYڵL&f֬Y,_͛7ߟk(ׯg)0 (ccc"3 DD%1.ܲeYD"|C 72{l^/V2Eb+5񕗠ݹ @E4@,UUX`8 -@Q)jTUdEA ͬJ, mh&AlCˤj.{Bvb4Mò,tNf=aNs(ORmlSYi)܃R$:wE)jO4˔\NsaVA$$2ReVi%qiǟ :񛦙\y^$]9pd/|,T z묨]WO&x~,۞n]jj& 4 b1n}ڐUVW6p*̛oz)~:dvqvȼyB_iIww76l9\./f…cf.tDeJq4zzI&ӘمH|h^OD4U!!jkKDbI$Ȋv- zIDFF13g_Nyy9۷o !/bLdxxd2I2$ hoo'PTTT=𕐐g_$EɊfբ@ Wx=$<﷠Q~j}&2hW/Me^VQtt+ Dj❭e,Аs?QU KUUjjjH&b$c/pC z4B0&tH۽0wSM<Oq˼[gFMMHHӰ?\}}ζr* ΍l/CP{@x4m۶Dg UU z ȑ>::imcV)/ UU76p׉ =̲P1qI^;$45Uw-́?t2.{9zXv;h?N:؇e>-C;_!242apzIۇ`p466Q7;ɩTWi1P|~;zw#5|<fWg.2¥C- lw<6ĻY, gp4ܲ0Siz\/੭DzoC 2MpP7}}#|Wov;<$nܻGSiL"Hrt*JY~-Gw@C0tR@an;K {NfSdԠwE)nDF&Cthd4FydlB_PҨa "W$i1y>*bn4@@`aboI=#1FvNUXtzےdǖnX b8#]Ls@9k;VYCô\X }SO4CikYO4NOx˲d(>ƵR\*S[ߢvnH-[[Iʦkoȥ:M*Ĭg;k{OReQBe(PЪB}CScYmmc^$Y2ge2p"?K&k]@iiN8pLO0SJ2;7kL;sO F-UR(wx b¾wg#ڎp.ZX)Q\ D޹8L $)R_f IDATCD,y>LbQW:ٸ'4W"_s,w R\Α~Khxq&Bwexkަ>Hvıgrlf2U L:G V+vkjQCuSToNbBV;b?{Y}_\Y‹/e%{zzq]R)XVχA(A C'N6]f; L$#ƹsǸwwf{@as=' 4]gtʆ|^HǮչeNL`&&cAFY/)J]Չ,q]Eifi Kg8m;D6C#%N`/oh6k |~z.hL>7 kmR٘L*xh_JnB6>gώ$HMs17-9$[1wF,ec:]kcV iBSsH " R֢kMの*<+ƣRJjv'N GZ?2>AЙ.Ljnz ލ|xVdI'3Gqyi\{^fpvvӟBΞ%N1zϯݓkgq Cqe#t"a)%l"J*I;w$M%ƒ*{d-_Mp}?BnaPRWy(Yr?:zg^BU3IAnNuw)I)i4{xOG2P4ȦUƻRhhHi!R|>(CyhfwtźhC`rOF 􅀑inY RuAJP^+/jQ>>s}m;.//2RRZC'ѭ%@}Vt00"O(,ԅKWx}4jfElv=XD˦ nY&nIU5_1X\C#cw3)SH%JpK'jIy4 Jmڴ,3}yP|@Y$L hgjW.L2Du\Pi^mF 8䎏eRaǓ(W ;<8b7Ň'*3qJ:J7)a&I !B<܍O%b} 9udPJ?-6zOͶo⏺ӎf-^IscJӍb喫,͗ =w b)%MZwsq+oEWC)\ '1 y\~ܹsm˗/ܾ}jH.EC;|\t H)+?(`YC0,EkAIUgn`X_3#kyo~J Ex CcrbJ1(Ǚoe ҥ4r9t1:^:!ȹ]Re(mr[aT-=꺾IPIEM7tR JIs5/~5XӗD"烪\/ӛs %l㚚&L%Ν;˗Vk9B6uZ-m2-&&XPsj2doк\]fd$lGʠx`4Ec$=[;i<+h2oΰR/VH)iVk}F<3,T|r1M%KRo>!ەDO^C)#;oQ|j>17c  ^_t,ˢX,:kkkO?/"ju#Od2-A&߲EH2a9Ї`>S"Y6oM|L~ÀQ|H>9Q@UU$8q"rٟJ1fq63%;6w.~Љ-04j[Bbf2hl:ܽhTZHߧY!T@_1:N:s":ue<gtt-#Vc;=qބ*o{yc)|i,Mr;^fG~&vd`[Kq)Nl&\?iKE*@/zsݝUC)k7,&k>CUU.\iE? 4mK_j8'On [Lq](ϧ)WJĴgDلRǯ"FR#4f!r"C/DEQH$ _ .( bYwޥ~lr.p\i'Sat(,,]= ^}uMS54G?{*S*UlO>3~}:ߔqu}#W (pY2 l0ԩSRGK׊ >r$(TMbz]@}ʕ_wrzH &*.AB,"i&9(A?ҝhFVHm'?ayy_|qG4^~e~U;:܍[c;\xӧG7J纬/{tjN둾ɮ,tJ>#>7n299k>fDsE47x~l c,\fa,--ydd('''9uԖLiXa!p}|rv8_-f}8ќC>8뺎i[B[٤D"4,4 ,DMQU47l٥kYdrMcZsi.Prvk($-W[I!t序,]A}xLf,Y/QCH0ƅWxBMl$ɍRryN:uxZ˖Su]j_\K4Y} &ӭo ~~%"Kj~ EzdPZ(jm5rF_Q Qَ'133C&AUU2\jq}ׯsҥBvZD\D]קRS.tzòvT,;hE! w.HR"+%ܸ1KWWDŠTڴ]9G<+WpHtx9^yN-$%e=-(Ć wcǵy̝ IR.̶yVy?8q""CS5fR˔_fEͩq q`󗖂HRpXjR-4X[Zя~ݻwy6<$4MX,{Q Ҿ8Ξ-Ue`:#B}Hz~~jqM`:o~2F̢eJ LO[C6n6UԈVU_<:si066eYtwwo9r˲ *[H)d}#[!)J TAz;oW"i'uܾr[nw+5V$OCƸ؞̗T΍B:_|8 ZѺ{!KS8Zjs>l% AtnfP9s7#W`OOz,Ww$_XJhZ3[r]\|RJrwޥZRTVLMMqmcvUEV,=&e4U%3PB_C\ě'oe*KB !MD&Mc$ kdH[uZ;9v c6g7gUUm{2V'Uɣ1ҕ[]:ٱF\Si:ϛLNS]$669г{Rl{s={SS&)k}hN|͔87V{׀z:Du5x堜f/%l/T !Z@iPl)70ԨAr;lFWtg~Fۻ/zzMh%\ZNyގjkjTѠTJԸ#M_YzhtpI +4kǖTM*:9<Spx6(5\V3 69!0FY;៮]1FR0- Mc) \N *̫g)\[ݺnDS#|fz}N&`@~t@7@bZdW9;tPf 3LOE͇X]7XmgRUTRf\-GŎ|aYp,of>̘8g+S3;(zrw9>Q! >B寧kjGP$3Gl6 1݅Vmj=1tu$OFs9XEKp'ʷ08ٲD?IS({,ժG2;;*A/h|hO U$ƞ2z;A:$,"nYGMFk{HH A]d{TM5-]rU5(ߙ} 0gT׋z0;67n펬0]QvX:̕kTw cv6ǻIjSJ7ESŧrZͧԠLխ5`u5o43ŚPdL*bٶGK}@>=h% h_wVk9~!9M}ēs9y4h?s,{r9(AÑ#A@C+}oF7ѾkvPTdwwCޓL6A2&4C'{ػxO\GTRX!!`x:w:H'ANcMweRq=e%%")FvlK7Bnbvq}ɂ1|W;B HttnTqCf:GA~G,b8KkF O\JI,RCǁ'hE(4FJCjq>l(nArΥKˆB&{ߗPF*4b12}4k5=f{пC)%NI)F(Ni̸L'=ME$i.h.-,m{vg.'۪ORRwTx\Zŭ[} 2--Mj,l\_bTδ EW8%ʷ'3B@LWXwmVniuO |'O)=.s)'Am?nDzrrwIGB`(q g>;G(aUQZ$==)UEb}Pdv3#NCp^]-b]]bax֐Rۻ{{{;k8<ߌjXZP]65o;JnlڹI<(4v6-} "tU'i$YD/CI&4J$‹/6ӑdz=GˎQԝ: ?Q" <=yLRn |nݚر~ C ;MçNN5sήDt$xmכ4wiTiDP8zh}Ο?%緼C'?!@QY,( fb ]͗j *=ViFMiO*o!x|ϛ[%$Om [d?/25S0~` 0TN<EQ6܏;2̈́yJ$fT]|<ZD$RjˌIV>nnbp,sd5 3O[0WW/Vd_UzDƆѻnjiVW=@s }}<@p$u$pT8$z> e"~H쯧a3r8rpǩT9޽6t8X dN# Wh)nesASui9B2DQt]'J1::gNh pC%..Gslߎf{Q 3P7fB4ӠY⹮; |/bYᔉ§w2QNȟӟ;w>ZG?֭[{&1];051&YSqC'yj4WB_ϰVwc"} r%lg?RJ* 7oIJ,{=*dwoh KH -:W n6ATWȗЕh RU}lYl/z ^U [|7EC|Mt]'տ_o ׈(n!RezzRͳ|ϣYF;3DSy6͍,<^J( Fo7WRroIHm`j ņZ}[kSs'Gwl]iz7]`(m`!h$_~{ \*#3W|4>FccrXqd21u]zN:FMl099ӧCA'fܸqY|O>a``7orm2F  &''7nqC['95&a} Rr1 ca`&ڦzбiعaԤa꺎i;HgݯKk_#$ejMmߴǙJ9 =TSSO<(膱ea6( O[wgff/}K:G_'*ccGh[!j'RfM~4H< K֛EF􌝔"P_LvB f.Ft+] .]Ҧ0`ddR)q^{5fff8y$}}}r9~r= u]n޼˗KHg}aj.t`.'NT:w#!Wd2LOIA,ݔ KT?,Jemoy| ~avx9aOufxeYɟ Fl6k2[Ůt"+8~5fmЫmB0C˼3E&+jTUcOg(o%Vck,bFef*4)޼GٲvZ|4_&_sϰ|PUqǁ sF<,W;tEg(5Ta ۳1heO|W,^R"ut<d+`!<=6wwxHc)J Z*Îd2#D2y-YYYyLͦõk3 d+8!z׶Ar3}R}kYxJ_b7KS5I$,S,.wb?A?qRfIJ=NaQc}~ҕkAm@e157Vx|EKQ\Z%ux(VtNPC ])pS M.͗ilZj<47m^I&hvPPJͩQlFmY}nz; W!nB0XG6RUxx`yFJIXeJjGGq\>mΝ?|GJMqԉPc!+ ݅E^]n?vAi51Jt@494ȼr딯FJoe-MvcKǫ1z :nWk$ D  iDIs9!t3 .͕[*gs)7'UdVVRˋ%HD}p\@`&GGz-P8Xբq]BJ\/i,uQu};ZJ_wlF%zoI۷IczDk,ݽī/[LtCi{شPcG(~r|]C=$ (muąXXWeRgy+g1཈ ?6_^-\Ԧ+#iOBZtq4EX&dK!ik-lPjୃiuZY+CHg<εX5LOBYQR8kr47\E&JU4kdF@<:<bZUƐy,^S@2lSz PfG5܏jjuBj"{>"ACS8#_s !:P9=ReS'bɓAN󃲼=yp~7p!EL"8] O=Ӭq\2Bk{C^';4T:Jv(8Cq,$A*}t%6;2Emzge TloXicn,,S?>|.>(24J\4#4kd^/"p[Wg,;uKIi}M^ w[", wm-Y 飤43CծF.4ǃOcuHX#MRR/} ;79uj&dES^P-DiVbQ1N~jZ-娘GY_Q/ha{QRJ&2)-ݷѳ_=2q-vH!l5E01Y,7YRJ-}7(yV<d{:Fսr3##zT(W^)j=ڏ|%NaĬ;==)?^lX4oQ5ys6RyV$uGysp`Ec'Jh.M,>@fȟ!j"\ɍ/a34\Lа~4~Ds5f"YK+ssFgekځ8NL vM0(z=Q@r4W˂ӎ泆2zH-|?GP[+E˗a4mՃkmy%&'d•I)\fF< O'?䴂3zݛET15/eX,ϼue|I'B8!u@9l#]s(!&"IY!x|1.]CE¯J6;K/N̰TY{MrY}۳o)l68'&4{|=Y}%Ojwx(_ɡȚvı=;>>@,nDZUu 4Ys LkkȚrfJ+/e_<:~ELLl&cv`ͽYJ͗B7lLMDfwB߷L}n ;_]ڔ0T&<ó;2uIb@0-Aj2A1*,M'^iÓ,vkXAӍ^$A6mm`f@=xuq-N?@+,/@_Aww7 P4FGG9~8DsuT*_I>2Mi|356_M%OTA}|+Nww7?>$ի3躊e&B 0Pb5t.HIqe${.G13| #u7n`ff%0 cCK]4|gjj%܇Qy,//hnBpgCc>+?mu7ntZR,ķqZow]۶7`60O,v0K}no yn޶-G%vDILWH&[uoyƘukaZop?wh۞kֵ}=CW=1(rc{qr,//ϑJ׽{XYY l%nݺE>PDww#T*Av4+ҢFV|zI=IoWi8zt,B3(R)^~e^yN> RJVVVGA^T*K/188ȯ:;v M;X1jͦ˙3`eXl˄AAOwOE4:VOP*ըӉ2_Xd$6 UUell,vxBY]]Upqo200F@#$ˑ:ֿu9xy*0/-҄C CXmg gV&<&cْ'mI)7 )QLr4c#akc>Y 4S m .8N׿5F }}}=Ԋ\!6sB~_5 !4 wWRTznBWfffe#i<ץ^3JA֑#=89B<NJӧG;J!ߤdk7ܻwóH'xT>H)QU'N( ##AH$`qs8>6vANv;Ho!y(֑})UM,lNƨU_Fj"B;ǚ\/4*H)T*RmZ9ͯ, ^5n1P7Ch؎@p'FJh0 4!2IRRՂUYhEt]̙3pEΟ?@TX,J=IC5HI,]ᝣ)nǎA>@ȅ ("Zdp^ZM8?p*C! 5yB#kR>WZ+-?FQٸݸqX,FXܲ*|nۍիӼ1VV pI$rAz&`~~ 7 qر~6RJ*>7Hwb)y1ML&C6%JDZ,.0 sPR,4s4WAi0m1^Vwܞrko)KzXF,<4Zt8p! ie C`Y?~qLdrroP*62k۞z{{ؤH A>^w#AeA}j Kbruӽ#8>r Fri,!##V, 󈔒B@>\.S,)@9 qRN2'-J[#ۍkd2 ΞSSL>'ܽ4v<_]1jpY*]Vf|4-hMFfRb7<ks6k*p-^s553%̰wi> xS'32O\"l11-ѣQIeJehocݺS$@˺u ><‚% Кl9nΡ?4MqY DT`pc,3#nޅUx*/M.eiJ7pQyVnXQjcLA3e%_%I]HBVg"fkVibfTH:X́&z'3͙)s]=ȧU⢱#GH̱.I4$Iqc#n,XIGG?Mg:h>pI((=R{^ȣuMsE dw8+*`4⟿Ʌ5d#qY'H]MJTW/')ݾ Mɲ2/IjƤ-2E\39d즭 uDs\xL z m=nD5!Oֽ֡oPU\D9DBoH -\8* _fh4f:Hyw(,X5}dYqRUULSS>|_o;kJ` BR+VI=)$IY2RsYqG )2LY]} Z$y_WJۀ~BIYUӇ{Hj6Iw,CO<ֺB44MɤZx$h-ٯPZd3opVx E7ō=N8VPiN`yU[ٳNON( b?ߐP$.|/MMtD;Ѝ`{N0kǩ(￴ kin&g?a&,Ce\3I#x$x啣lUx<x8 Am[A&?ˋs0PU@OMPUøqՖ$ V6^PFϣEc@߼-p3 !:f!bQoQtdi3sK }@ )vA{EHRa55ː"+]~.?̓$Ӝ?Iee,Z͐Wa@8 g@m- $Q)dY2Nc3t4Wq\,wMǓ]:_"OlY-s\ٸi)N;Sh;|rg8١[hC=99&;d4(x.-Te^WoX©StvBmsd2IKK YQm---={6'"'D.AbBK>Y LäIwWgqaVʲ|jrvh4Xdcmk[[eY$KʊY?qgѢodn]V: r[qshN+~{'>8e'bb=SӚ?_F80>떫5CIXt)-&p8,X5k :?R9s[nYǒ%UZWsw/J'̳W-~l4o ukqa,Kxux{pyn'kt(~P쐿\pad-adWݻw/tMhkkNB%%l۶K/&c}dJՍ8BA:»܊wA,ii,,tq ΄y3ߩst!r?nic5(ʨN/_˄2xꫩ&:|`tZ&ڏ݁fp(b18K f "O N`Y2W5MPij_Au:ˇ/ N솣ݰdps⤱[^fU*|Ne|<펷Qug&pB$I"JY磤ӧOe˖p8Q^> 5&$H ؾ*k#*,ȉ9I<~ix8B;Yu,R$"VsHDyJs2XQ;Ö-ضm%JiiYb3 %-GeJKK2r""ۋPTTD:FUUTU%NxBQQVd]inn&ЀȮjDsaOжVwu8ίcJN2D$<Ϩ߲w:fʋ ݸUK|5֭`Jp X[ qy^dYFUU$`Y5 LtE2lS=dx:=e鲂5MCeNgX۝VEh}t:Q%7\(,aQ| JqV*<[={h4JiiidbGk/BS/*­ꚩn!v˘ەUKp]gC5aKPS37 K2NɭKohQ|.RdIFbÄSa)fGee%,Fvrqg;03K,a͚5=ztgi twzrζt L)qiCy!ڏ Le0Myj{2l-]Ϯ].Cs*++x,㭷B$0L O{{;6meYj>/*H$k᷂,+,~!N3ֶVdYm].8xz|CHIf}窪rI.\H0̊2 VX\C@kxEOR455xl]GӅo-upO__DOQwm ( [GD"7+VH$zǷg gU&t/v+Ϟ~[n_q7MH*sgwL/IPW۰e 530{ [,.;#Qdj4C'|bo1nh68;xsȩYqZ`K.ŋG&駟gΝDQL$JqAb@.ƔJexu14MqvT f!.Bkuә>[4|YkeYK]jiD(..Mɪ:,X` &{;H$={ ښ?Y4:::rM<0UvKK F^\)֛I{{;l޼A2ӄa$Ibڵ}rF.c44vױ@ x&&LX'ݱn[^N@:&SZZ(8f :9򮐷~MƓ0 tNTD"AQQ%ۭZnuA57-_cuj膎j $xܸF?+~+f:$_|Yu=;{MWWX,;hLt:MUUR#;HWI[FqYnv ,.tp\A0 S D"ٚ,2yJKKq8Y;0M@ kSMӨ榷EQhllzcgE pY__?#}gRv߳,7L&1M3;SSS@V&Iذa/D]I$9Nֆ磬lU( :q+:JGr9R}x|XVm,sW:ux(--eϞ=tvvV.@YYEEE޽;;sm.͛7DWWm3$QXXȆ عsEMRZ*b?nRT}%Gf[[ejA% eYu1EĮAeͷXfB*Ӑn<E7ׂ6nܘ evb,T ,رcxp|t936 k* LLߴ&Ak麎v<}WGb|vuVv=㲬kԅB>Hp)D6!Hӧ3 v }4y}wppݻwr}08tP9H.5ݏʉ֛c%-W<8q~:x5SHǩSl J$ڵ@>vI?yË/ɓ'ymUl o#G8~˷R擠K dYRY <mmmTUUp8lq{K,k"}JrhHii_ 4XGGVGEQf9p˗/'L /p466cǎٮ cǎ466]\yL0?g|rR,Xᠢ6\2ٳBQy`pp~?ݶ@ @[[X̖{dYlsQQK.eO0q9  N*x,{M$Wi6-7v\.WN~J5[^^NN֚NfFmjaz?>O<9+￟m۶t:뮻By:Nlu]g,c|ccҥ rJxN[V,K|N>]29dxOF#~c\3g$>/Ȗ>G|abX̶rn7TUU6aٸq#UUUe0 MӲYa< rzAev|ijj,'0(gz4667Bu~֥kQ:G"h$uXr%?pNʿ[9s _+ ɾ}4گ(mmO\}477m+`STTDqq˳f`vdŊ3*fʕy'yNqq1(b[`9{,֭?_4bh4j[^/7tt&JJJl9X"z[ES}}=x^ g\uORtttPSSC?\uU.7DFGQUuܴt:餼kѯ!iZv:p:,]Edr"ƽ^/w9$RbZ6WĕK}}Dkyy9~K{m-p0000Ka塗$vfQ].˖-ܹs,Y$@oo/nU IDAT^dXhQ6GL1 h4P6qaͨ=_5{o ?(++]m*Ϻer~.dz_bz<V\i[xYxq3E$?=V>HH$b˔(JxhllIVGuuufkMt,dYphѢlJW~r۱˂o\ʸQYYS}3 _mf2mϡp8[8t萭 6]_`0h{jOOɓ,ZȆھKAAG! 2h)**0Ă ?̽;g`!,SQQx[h.\ݼ"譔vNM[d G;d϶>p8b Ni\ +WS}B0H9H&D"E! fsemO%jr)+Ov*"dg沯'4tT*E2=gtcX ߟ="p8c1www_43 lt:z4n{SNsN^{59 SGyy9Vb֭tM466]5'st:M2ٙB0QÁZw OhBBם9J>tf'ш>!濯@ @p"@ @0B_ @ /@ C@ !B @ !@ y@ icqIlG#-[8r䈽"L]v|Cn駟eя~<#_F^vEN>mKm˓'\z3eΝ;7U ٮ@0w}7d2;v/?6gΜaϞ=\{c] /}}O}??AUU?NAAeu]Dze.o |Mhii᷿-<7|1{@ x!yzٲe ]FmSPPc=vokkc|Ye`wc޽'?g?ollu?}S\.EEE] oe˖6=A p)zzzhhhwܑ#=܃뽜Uf~>O*/Ž;{xgGm}vv>:rg~}-C=Dgg V0WG?i˓j<;wϸy7/*3|{dkCu}QNocǎxf=s7|Ew뭷[oe?~뮻n9я~g>n>O;du~y~|_y\9 yظq#m<w=֭_|z'|rUU%L^cB*UVMzIMM _pܹ/}K\:(_?7Loo/T `??xGJJJؿ?EEE3>nr@[[%۠/||Q߰tRxߜ0:LU|;ݻ^oc~={W[gy'p:WWhv;y嗹=N"(;?C~iڨ'dW6B /dH$xg7ɺuFMonq/_?T#ٲe!LYr 򑏰m6nv6nx.D"+fxB___6>j6hz1yg8t't_0Q&~Ǫǹs0 cTP({ᱝ;wMӲ իiii"IRuee%^d2I{{%>9{eڵ۷>^{5~aoNii,R EEEoLzd2'?I'?WU1 z~[r/~/| B(@\M&̙3,^mRlذ`08x\xOT&~Ǫi1vd0 ]G}t:q\S*k$nWB P(o6~|j&G?Q~O: |e|>N:Eww֯_^zI}gQ]]a?~|f(g?M6M Gee%_җxy嗧Tp:LU8n[F2~[<^gώo2eϤ^+ !y먪D"v޽{m?cp\S>>/_SODkk+= 0l4MbCCC hD"W۷Kaa]Haa!۷oD"+"ۯXGo|_"$] N͛7O}(b{¥0{a=|>z+_W9z(}}}|߾|>>}G2007MB7o`͚5򗿤s|UU'u[/5O>dv Fx1/,,f*o<ܹ}{Ȳ̢E? կWU>}{lݺ۷gˋF޽|;qW墷woo7?iTU%K4+dѢEY'a]]݄mp>|/~?RKaz9/я~*nq9?^vUUU_0ܱF|n=c@ q%"#LD|ALӜj $ip`0jO(B0Y*LiA*"MHӤiRdUUgj9xpݸ9'dПC *τL(mGHd2IqqlVmZ `\ٮ`"Id2tbb1g:95*B!4@`N+?0 QL E0f:^J!r>ikk"_`T #χ)NGoolWeR kABМHٮ`I$3}B0yEQf*y(x<f*200@0vUO0}"3Uszzzy'"~cO Krt:QUd29Ud2a’/lA >ń%_p$mF<&|e oD"JJJf+Xl!ˆby'B?OIӳ]Nt:("Vpq\(}"N[egpp0yJ2v5W(>/ed]vL>0d0M]sZv.Gs y'r-j4Mk-&oS4X@0;8Ԥi<ON4i$ɜMd``Ml/[4m_Q4M8.@t]ω+>Jl-sD_q3yb1eZ dl/[UU;FIӶgM󻙐JO.F(<%7m`*[4 YέmBUU:::(++uMӤIXxjYn$ ynjAuZ[[)//Nijjbڵi!r^ d2>ORPQQA0ə3g$e˖RE&aϞ=l޼A^}Uonk/M8z(UUUz8ǎcƍ}oM("oSt]ghhz^$I0 TU%t:  Xv;l:k7ע0 $I3˒On2ML&385M# f-v Pߏ,˶[u]4I$m&-BUU[6 #+f<0 QUUɓ]P(i,YuMN0i:5( /B1K,ee*rAn>̹shhhP*B,\ Ww1h,˚a99y%>$IӧO( k֬5;rzow.y,vm]?w,d͒,{9Ru``UUQH$ݻپ};`ͣR8+B ;[U\R~x<>U!{_i8Ng6`Һ[7 3}x'fm|x̆Nii)'iooga4"N۰5FI$林tNbpǃvOyXudve㢢"JKKq89?7ԩSrʜ`B?"ڭrdfH],ٔ8O1ѮڄQ\ o )M YL(t|N7nCr.݁r-oB1 $o<Ԣ/[fWBiL^ʰ3(D47 p{ڀi6*W躞kFaa!Qp8.VIPY5 =煾1׏h)RQSVlm!MN&5 ."CaVkC$H@_򰅽B%tMV[y6)7$ILOxsp8())Z*GtHDI4{4k낸 ΟaB9%+pxmunBL5 X]auEHdtMI)9&`,JŠ(H z h;l5l#IWTbEuu5Nz亮UVeseeecdYbinn&NaÆ Eiq)mFyyyvv ~O|932M^9˳_+跭jBЄt:O<ҥKYd 7nƕݻC^eYP(>jA*w[Gn#zo=ώL&eVhmm%d]U^sr.)2$F&4PpCobT/"`,hMz#ƓM|A܊gVʍWp`&݃d^RI>K۞~B R;hmmam,k%/t_ ã,cn&-Ci0aQȍ{,w]㇐6\ n3=?A:ziP*N Ci*j cD7!eSMwJb .$IBO0AXiAՄdrM2q̙mmm~oy0lQ}_UUŦM.ѷ\&-74]EUUΞ=ݻ{\2 }}}7c~rF<_#戇مGr ݳp<@.+ z D`7$s'_׉ Ew'9I}wu%NJ=2G-S]:?8's(4:[qPBLwE IDATUGⶏc6`a2kP0qW(gO[myC7XX[cDU\ƯP cj2N,z`oJ:0fhdXQRpRvl"KNY甹j}IV0C U-p :u*' ,пp0Ced>x/?:}nLKjE^֖8*҄ANY5?{$Gvi~ D"EUEQl6gș^<>Ο2Ok;ޝYn74MK@4!Z"˻Q2, >37"=w~'FVv{h߃#sJepWdz:Jp_s}.(olbut?-&1h-_$hIvkFZO xF[c(pU*#Zav *^ĪMw@ߦ$#1 .U =lKJ,㨙q=L-#?88Ҟc=/^ܓ͛lfvvvG܆a0882b67oޤmIn5M#LopE666GM#Q,o8v"̗dUW# ,M<2WْIpZ͹e_EBQ)9?yn-/xÂg tO_saF-&<_h7$*R{g!cjX&ŵE}V*X{!u'ppf&moԠ*/-;_*x~`~~^ziG]u]'P.Y^^R;numj9Rwλx"$[;ݏ댎Řj BqvQ >?'}b8Ngb/{Cit2R%V/>B~7OՏ귫ANPFً3ym3ȿ.@Yj?M{FB%EǧZ![ͯ눃an^ Lٔ܀] ~y5!hMlyL0w.FWqngmDRbYRׯ333l~ 4y7r=$J)(:th˚ccc${:so!e͛7w j4gM'h{5R/_}?!,cnM>^t;'AQ [@xPxIz~4mw_Hxv4IBt¹`cH8iekY3WDP,]0!ی|/GyfqD8L!ٖjxX=댌piޓn @[=FC_= x}L"OYUWqMB7M>ظ{YB# $xKx?|)]Ug(9 C7¿^;lS%3N0#kuRff<4cuuuS/ZN[,yΣ 9snL~Fς^~+ =&۽1^ﶘ.)塞k\P-ލBn =[st+; Q]$iC 6"l ?ZI G.3OR `О4H;_h *B 7=/eUmb! 4">=\dwN:u淴HS7J)\%ˑdvZhL)%Dn|1<4g/Pq]X g#kiȘO.f(\EMgS|렃3'8]mB!`89sft*ރM+}(>犤A>rX6/Ljzí{۶dppL&FlT*G}D&yJ˗/Z;77G&ԩSJHs )ˡiqWS*An0s.rR?6*o_Fk]}<!xۢ=&Yn8l im~?ۮ߷, 7<eW_2 ȕ=C{e<C]:hDϚ^RV JCB * J)ǡP(m;=Q,L&C2dqq1t6^A:ƶm(X]]{v.ٷoLnVWWCwuVWWY__}\ =<\} @)R J%ɘu`|#D I;d8 6h ;Դ}aoc69l>uYzdu7PYݬMwBr`Çۻ'nܺ;ze>åyض}>7:$?#8p'_f0ůbPLhc!gDZlQ.R-+2ey"\lG /E'q_Gܗ/;F,q5l_o"6}΃ <y\ È?݋躎,..dX]]%c&D>!Bߏ {!D}VXYD ߊݓ]6e%i`<|6]Ds(qZPWm ҙ0j^ PA {~)O*mK;[{=>4 8QՎtkϟǮMf!H$O>B@,#Hf)k$Q 55ЊbCC*d)w_#3;G3q !fll ۶Cbz&~-0 c] [P -Ӽ@peէ-74[ W TW? re>%fyHSc;rZWMX?罌A"T NxV6CQfviv:LU<6;"pgrIWi4H&oseuё4xӤ&JѿdӈdYt]Dz, è!et tH$QWχr:/ %z_eΞ~,5Sp"ҨR"gF]BW|8xX,Fwww]R>---na؟5},s"n\Vʇ=,o8rnŋ(ɃIe&!(zHBm!] N0ȵ\cBLwflۓgܽdpKn;tOZlغDOgQ3`1뒋Og@F mhn+=%Ku͙H07N+N%-?ײa uY]]%Lk,@P& /^ۃ>N2mmm sYz{{IRibYV$_]ҥK٠a.p~9UA fEƔW ޡP hwc$t;3Q# WЮ$X[;h58Ѻ.;!=-"g'#Xf.=|YRF} }lLb9i!@~ @I Օ-Nٍ0 eWWPF':Oԇo޼ ?LoA'JSNH8mmm[ iɋ봶233jm1KO:ڢPTp?r~ՏПؗѹsdKv:;EtF| xͤ#En<8µn!VW"7kVWVmya7P( Rf-|j:06QK%Se;AbߡMڒ:)D c4aeet:]7Zŋ/\dX[[#=PǶjϯC}Mhnnfll,R6^JK/dDRFQpAb_ ?$H}\څ7i%y"o|+{-> G()>.:4[Sm1m'@޾)/&5>Zpr$T~ LMp-R Ǘ+/VWUHq]WrĆ`ژR~Ҧ[333+Ha۷\.:iX ~\~ڣm?PZ1mAXx"]|5hKOi$v$ʊh:rbc5Rפ!Ϲ7h>#Oi,gM> $Ҹb=vH&ɒ@孈<~Uڶ8yTgZˇU< %- M=)^[@jD89 H&9rB,.J(>^tkhfwkT:Y&N_ S ~9UR(K Xl^B@O?~x3JU@k |$l>P :4 AT+ )x QrC4;I)us+.YMNEi6Tbuz,nx):Ik.V\<6*j_ؖK M*|GDK{DZw5ŝ 1]4#d@g4q]2F ~_mO{ic FTՀ},+RF?&''9rȶ&@FteDJI&g]hRLnx\_[ͭzXY8lG IDATnY'+y>Flbmu LJ)ySm&F!$X1iDt qޙDAUi޶Л1Y,AìJTw}J%#4Q VWű6&Rº2GӪ 52_*J)fffv[JIWW+++%5M{ww7ϟ@:*(J/bn3Tkł ͒H$( NMiN,,}4mzhm@v&EOq)F4o R~n[P ֜lVk' Z;՘^!͆wHg1)IŮHQ7/WcE&bBMA:S]I(xM.P)k{{?'JmۑVΜ9ixG*WJqva4MFFFzq<4g/P~F=Ð;xA *ӄ>GK肣.ج<QXG,͢Z:zdb\ hh5Uybe0ϡ&#-s.%!mu tHU\&tn*T7"DtA+Y} 5Yč}`~:(6?^JDgwuJ%~s5;ɨ6MMM>|]׊|ŶvzApUFGGwxZ+S,Cg5M7n$C#9O/;ΩN! rN$pI7s!rk]c#n#Cu PV\l_rI[lgfeZG_ڕMҦOcT| ~uKwg!w<f'ےOs\wTbX13 Ν;,//?]f#}yMGǐ+ W#I LYښǥٹGr\W! =_L@4!}`Ю_@#MN!E 7([y-PJ\}Mh-mh϶YN2f+(![폥Ȧ:&ǹMH˱׏YqT&pk NZ,td5: !XXXRJ. ARy7'Jյb\.R,Mdaa!Lpϟ?O6RS#!deee۟  6|cN<"dw9rDi}}}A=! 2*`zH)}n;@F.|nn?)?5PN6=Ft% AP=*. T<9D=x}u@`DMb6׮])dY}]i L&x6H)|LPW4y뭷αcRT@aQJ,+debICy':}M9>|5xx_a4Mg%-BZY6yC =J/@BkAygan.mo(Z |0g3^BKh[8AP/4>-r~aB,yJ[e>vC%@ȃ'}[9~K7m( iYpp" ujz_4H/7 x&hȗ}RlA0jg5z--Z)rKSiFtAūvDM-ПBB@:477Gҧ]a}}d2#,LdbbApuRԎ7399__L&CS;'q"===\xtnեa<8|;󌎎իW`xx[p~gX8]T0bq>nlcb2L%Y1s2TkƧ0^¸aDEp5Ƨ3EF@qL݆UY@ʌЭ]A4o,!Go*dӊAS+jn g!dr 4J`Mbll=!Gٳ߿?H$B}bH9*BX]]4^2+WH = /0o߿D"w]LtwwR>O5KtmjJ30ƯUJ5ȚFSLyJnvnCJ4Ejj{  i@VI#B|ktF,"yPJn~falMFܐU""ix.;*tbr:jaI]\_7 Dܸ\`O#$H"m۸R{V9pHX, ~,ѣ>}Q`۠N#qFFFAA[[XD+Bv]re!x[L,(3*uS%CT&Rta fEJWCZ{9=ZO}) kJ^0)_LYwvXu&ע5HZ[:P듶$KQ |⻗64AKBVS42Wl6l18v؎gՊd~ͺw7}]ijjoAJI<'H<R G#-ũuiU k9mdUO a{?Gi#y;*6oZуR# ܏y3Z>pn2Jkj& 7O6 Gh`P+K6)X2 =oi 7&hB0crƏb%%tV-Qt^%sJ[nJ… HR0k5bi200jдjHB1::jCh BI/C,vR5ͦOcXJ΃"2v#}45aҝ jn?pY\G}(fHW#L}.wXhd-!):~xS!Q361XGj[K03RB& Jko=O:&S*B{ !hnn>QڶmnCap] "O-gYVοIPhd@AJGSȉ}] :)Ky;VAg_c| |/FndShKJŪp)<{S$ R#PBk6KHpc7Y7!5Dsacm7c4ͺ~YwmX,rHnCR atuuE5:LLLGJ#o eCtP0B6/ul8_MWw0-"o\BiAh Kp6 tݮ[t!tbrfk/wtd!B"qi!i"NZY P Xxk:x.dk4!XE'TAKT'B R)nܸIH$Ʊm B^R8C,#ٽNYZZT*Cad2hO@A84IT ~p:(~:rvrGcizLgtf>KeY}9d~ ?D&(Sɧ.e~8 m<[DI`.balH_~1Q&P v!z /Jբ٩Gׅњdy."y+hϕKD jEmS,F{oS.BuoX3BrH$x裏vL7D0o D mѝ%֝Yivc!T\($e_ ewdLBL aa|^(7$֘d5x@ im֙s7{yb⡀X"wnmYH[.Y,;͹MCU⧍  kݲ}r!XHSo6u(J)*c][,x]d28ŧWF *ӂܫU/ "t5q%ɢPmQ*`S`$m>^-`p1BT=:.ȇwH*zݠŒ7kP~mA8.dWN,P]W|6S3e 6P,B3!%mI|eS>g RTPJ>z5NYQjOSx7PIC _5QiS@M-yTC@LNNrE:|})%y@[[?я6uq]5Q355ީqRGݏapΝ.> 4=m۬Q(A) y “KI܎߿meȞ:#K9OЭ;AXw>]tkh@_œx6ˈhXM{1JtgqJ ̰B\f~ /_{*KKK(R>:Ro[Ա u( %hmHA~=38ٝ"nF8v/!{{ߓNԨ0݈)>sqT*qMnܸDe9ZH$u\y\~|YXaEziWJQ,9x`$ɍy{:t(NbI\;u!6En=L#LNNs̙ |g;wT33Ǿ(ЮUuɪemXϡ]=;ѢC%yr{QAw?1\zt_Zy“W_ƍιsXYYOOi۷L&ek$ zzzX[[5Lu* ~O&AoVwid2 =B/'?Ҳ`=" e A8z( =JGG όǚ޾#$|>rUXxaxEμ?xC#)\wgOl:()MKQmAwUTpѿL:vcWҁ 7TSO487ke?::'> O-3\f,"y---b|lR S 5Yd-x> |ٖDiIhd7P3'A>ͮBwKk\7O"`;nӆ{Z)믿R~ĶmR=ē6a4t:T@[]eyynJ%Bw\x~;8aRrS IDAT1VVVhjj =Yf{m 4~{D"ATbeet:yx(###u]/Q?IIKD 㽟#+%*xR#K+EJrny*A[~8psTp =kʎZ;G:6Κ76| F HiH']Qv#cͰG>%grf9~qKG )iWl8~kcUg!Tt:M" LL&IӴ=ib1* md !f~~u)JoAM㿶vjH)ioo{jvllO=߿gRTș3ggrr%mmmde x`UJY_ xXMHK]>yÝ2ތfI~[IJMU[+?] _Ǩ(m|\Amb?fN>FghMo =~ v\V_ 1.+%X̵D G]:΅I_$iFqQ8p,R?H1] Ln@㧶Iӡ~RJ^|E~_]]]GJ)]믿Kz]/Y󌌌4;aN8Yo}[!T*JH${Ə'G>|C:w"K^h72.:k$h.Z:^6`3^9l'1'x-C~XH?x-5e`yxՙ贈n풵ؒ%n6[ 0 䗐dn&$d /Ϗ;Y23OI&ܬLn,$  6Ƌl[w[-v7-Y[ GVU]]]Syw3TE Q*:B@q ϒ\š}" K@,#Ы3bagrIfıc(++sիW_۷vyaVZd!c`` wҒ"O9,P\\L^^^ߏ#//r @ihz:_UrLzpU. < O$h :s s6̚n9IZ<'M_{ڃl0}bh/> kyEnOLJ_i(1/;D \m:Z`Um:lBWc,eMD0!Du[[ۼO +8,G}v\.עgbDќOZZd/9Hp+"2zyD*9R^wyy?žcx;ulž2Ú@k&PpaaQit&Ic'@kVܓ?ۂD,=2Ru%1AҒ Z@ e6g˲,FGGIR 9SS]]ͱcK %Gn7mX+kQϞ@t4y3V ^@vMXZOӆg|p`dv_F ȳkIDG&MX shd0ۊاO@Y9"7e8HQ;!H”%$ QYYМ{H$< i5dKL@_rPWi?[D">`UW%nG}yRn/ ;QӲiRj6C.7f&PJs#Ꙇy](IKW}:g kVMb) TkצSoY+  !J=2kMTKF2u@H$hkkc\~s,H$:u)YYYcs,'-]d/9Ok`?nWĚd?u \HZ66舚\^ү/z9]T~;"<.d r#xupz =LٶMW$IW$E]mEu"v,J(a6ݣY%Ӷd璔)xhnn0t,ˢ_E3UUò,f,zg>vyUr ]Wm3qL@4a:_UbÆ b1z{{g7 )))NUWWSO=5 !97(:;v_5r j`C{I3!5ADbl}h-h^ͯZ4Ϯ/7fFHXǚ%5'%P>7c~^+iNSY_a8f0hc)[#4+ieee\}1::ze߸qcnUfN:Ej 4̙39w"zPZ0v Rox'"/ln)V\ ;Ox 141]^sܴߝ1vʃ[߀¬ۀajK#g >6 #پl۞pڶmLkyn 6$uaqz NWįv^QS=4S&&gNAQ0:@ iړ֡li@o\,T*a?dΗi \LSd2Iss3yyyNhԩSb_A,OS WiFAAD)eYR)ZZZ(--%/oKC҂}50EnG 9bm(|=uUPT8ؗ8,c>ڼI]s3~Y6JOZw&u.^,T*XgYpxڞŦ(]Ԕ$qb nm÷NjzM)DYE7?y4~Nqv0i]L8mQ^ mNwwwײ,9s 9}:444;e"LaÆIA̩S&]0 HP__hc͔fƍ>}I à4),,^ҹGCiA/ގU h~| }>qk0.ȭ~4k|_OV6`X_qN ǖB=7{SU"#hO=NH2r_Cv۽w X9x ib&Hf~kkL^MaX6qâ/mX[Eb7!`?5X4߃xJk7A 6w) 4-^KRѤN鋑0l6qްF4M,歷d{}#G4?[^/{!qq1 D" f``E2FQZikk"MMM۷jZ<ϫ.gOQ^/۷o'LfoxlN2dڵ95Xzv%nU2-s@24U*_E_X˖D Sh=]XJ۱ AQq)oXa0)qcsh<6 ő$<;F%ئ!vy0ǟG;G-W`&/L zgfc Pi~2dll ˲x<\.hhmmHX, ~B(xL誠ЧUF&zǨsM&!AnMΙuuONWlry [Q)X\.WLУ˩8 qtUNX6U =], ?ɶm$===ض墠`0@J" SYYdzT3Ô/yivF466R^^N4ett+VPRR2ꢢ"ã>m|> ioon0'JiΔ(~9{,/2Dm`0HuuE',l~iy~3:!eL&$// 9I51ވ_?tUtI ٶ4QN#u[" Y%nrtOm ȅ $-~ˇxTG6 -^U0.{ &ܿ>>Ryv(D泷QI콉ub1zzzHRg{10MM__߄)S݌RYY(;9sTjCܶit+~'z]tO_@(\1#j=>3VU f:MaWPwByfKH& `6^`0H*~Ya0~?] àf֮]d"!^K(̙3~YEn HsN@N ˆ vSWW7|˲hiiapp;w.Je E<{9^S]]h4c=Fqq1'Odǎ|ϾcKh6"u0v_e!FЎ>̣(7s./߭p[_"?8~M]An۲s$B~Fi:I..L 'ڱC;¸؁^ |ضD"mۆۍ֟ܒw\TWWg{1㿜7*؃*dLƤt7Khbfs˞\빷 Njv4E!_mM<ӝo QS)$99bCG7U"1mن 9H&vq=[ԦS.V!C>R֐]X+c~( hٯm&ӣgn𩙶AZ|vs[w !˕Ӂ|d_ml߾˲bKaa!^7{ѓi1<߄L-mǃipmv:?<3#B'I)..v'ܶm0n;0D"R؛]\*OZ&?L}):;;az'͗m # //}4M:::(--utpmMEE\-bhhχqt;g&+-->n###ٱKszε B2QFb``+WfZEWWWv0A~nxf8e|E((;H:tf暍h.bضM"m +=s̤)bVaWNPWM o!W&_6sx6DZXIb^0V 2lMGQU|jAטI$R4MN:AN֙q1ԩ@۶DAqqAM2iBzSMMMLa088Hii4M&T:nYVN HXUU)++ xe[l-P(X,F;&gϞb\j*cNR444P^^Naac9Ũx&krr&Hzli${2<<̪U(++3!Dva߆vK:w5'[T]* ]ڐt%=΂y~UsFea+jԜya9Wwvǧ쥜󷋦ieO'IS4g}>_>VU*\.9ّx+!mgǡ9Y6WUUꨨ`0X(**>koܸf)**rdcV"HJr ٓ@ ),,$e{1}>_$6YEfZs {n!d.(N>޽{ ̢DOO+Vpu2󕕕;djEd3d&Q%;,{njeM*P3 UmI1uי㇓e?Q4MӱǶm,"1Msɷ ׋&H$b9uw^SuZǗjvxDy<֬Y23rlyBJJJ&t@}fVN^mR'/QDX,ܷSCUU4MvgB i.dBugN/Q~$M$ۄ$M$ۄ$Ir0$I$I$-C2З$I$IeH$I$I @_$I$I!K$I$I2$}I$I$IZd/I$I$Iː %I$I$i$I$I$-C2З$I$IeH[rwc=v}kmuw>x/𶷽[net]$exfnSm;w|gVq_v%i>'Oi<oWoVfۮ$i!@_ҭ> iZzkYnݼ_'? e=g.?ioo{M TUk\Q^-rf6w}7O0k׮Eu:::8|y4%E wGyFU)_s*Nd. %I|_~\\~勸f<@_&.<wuW?ihh_2T~<$ Z^_ѣԩSTTTpwm۶ %{~a^z%/Cx ַ222ӟGrz;=}Ql駟__{/XO3F34ov7Huu5wy'7n,?)?0CCClݺ;3rJ G?wqDŽ 2wwz)(++oo஻1g{MMM׾vܟ7app-[pwR\\|ל@I+KSJRb nen/~Xݾ}x{7 ~as=|[ߚq044~;>*++w>]'|3|ccʕ\qD iO>$_Wm!xE嚐3޷m:WU^/'A{y{{:J~}}{TTTpww< ^駟K_o~|H$Boo/ׯ?NUU{/n?s=lܸjn)H|3qux<}￟Gy/| /?kNf@IYuGү~+ _Gy{4z{{inn檫"O~RZZ__GAAټy3g``)[n[o~+X,Ɖ''tyD_|s*~E4M?d?϶m())2ORg?㳟,uuuTVV&SO-{.M={d/f7qo;TVV|1b> ફ" R[[e&?яg?)))ᦛn_59PCKSz׻w1t]cر}qWxhhh,>d{T j]ܗz/io.vsWs6mā+tttt`YքHo)֭['0 ^͛72"I|>w `Ba><<#<‘#GD"g{/va׮]檫[nɎ dll 6L{󵶶:r=#nlž}x^M̃>ȃ>~;xy^9_7x#ҷto9/0 ,V)ދL?uSeһ\ҙiNx\i%i.^/c0@ @ 253#~>| mbc(|k_.}s|3)Emk&9B%id/9b׮]b1^xN>͞={Xb>/gn1{Ass33[UUU>|8m^G447khh`3z=˲8y$uuu)9 <S[[d31~Ν}|gQYY9P@_r(x?\sM'F4o{4顡U_Dz2>Lwww6Urq5۷_PJ33wW2t>7M|+_ĉo}k(]۟fy<N8?wh믿|m[^N8 LAAw)9-oy W\q~;ə3g'JyBFGG?8N̘y衇p_x^/yyyqu}8pXlF9]9P4@_rM7DGG7x?o|x|cd|lٲn; 7'Mۙl0r?Ok.>OP(<=gu.MO>p7կ~[ocǎoo w]<#|s.o޽|=yp.OE{Ї>/K>OSmuu57}/su]ɎH'|tMK_R6M(r Ȍ^v+@ҴP-wߋqg+<9ʸR}?=/L>3"-svmd]o~3yҼM?yKBOL:kܗ=+3~fclx>wG_߿/|ANTVV _rt$B y\'}cMzw.ZZZؿ?|,[L?M1^YG_siGDя~??RZZʭ[VG-]z۟&I1^/I$I$-2Gd$I$I$-C2З$I$IeH$I$I @_$I$I!K$I$I2$}I$I$IZd/I$I$Iː %I$I$i$I$I$-C2З$I$IeH$I$I @_$I$I!K$I$I2$}I$I$IZd/I$I$Iː %I$I$i{I$b1a`bin^/n{W1MHs!ۄ$MĥHKL,#*PBEAث&-mcYxp80P׻ث6gMH!ۄ$MĥLK eQZZZՑ !$Ib.͚l|6!I-6q9KeYz)//o1.r^/===Xث4#MH E Ih )MK@__B^i B׷ث2#MH M Ih )M9nppP(_U9O(bpppWd^-MHDKMH~bX%{hWM(²,bbʤd^mMHD&d0EEE%p8ث1)& ۄ$MmBH9*HP%\.H${U&mBZ,MHD& @?Gb1y+VZ4P(n6!-&&$i\l҅dH$^mێ/SM044B I &$i\l҅d @Qb6a8<)ٶisއEɹ6!I!ۄ$MmB siMWn&:t (Wfe6cIH`,a`X66hyPJ;zɇ ;aM FFݬ*֔6 p,+שJaa!fO\T ˅eMSQWNvmr6܌eYTV<(bf9ж X 40-W$ϋ)n4#j\o W*F8=DWOӽfzI"7%R؟ [m*+2!D8}r|8,1Xoć1A8*6M  !fuX66m8IAV$eƭ"?,hQ(˷YQ| /2_TU%LH$ &l ^7AQ=JӁ2aZ6 !ND%MEҴ(aC^\xζLH;4b͇j(\ AsE?òi'y *$ X_"Fo%`nRtJN8vOv~e~(cT+(p}M&8]_ϥ2MǓi[ʩ- ,g&x={ ȎOYv-pm۶[ |%B6TM3C<~6xc)ē&'[8fm|.t`'x$w!Д*GΪyw%6.,tB_&XB["I $^'a}W,<~.ބSd-G_--l0+ ^!*o&Ŧjk\Iktd?՗2/wYE1222ȊPTTDMM ``0Haa!+W$ imFFd+0Fi(MMyMtm.Ldpp3tspg6!QRRB(v  Q+K UXd4/OIm#xvUQR r+)K]!6@\Ӳ8HoY:ʼeE(Uy||"O!WhK>BPJaЃ[WQǭSQ xxB@a3΁g8U u9BH$I/F]TP62(i /;>lq'U~PC7.y@@WЦgO( ڔ9cc1g^$jAE2(JR &Mm zV/4,Jܫ xzJGPQ0BBS\.m^)m&..dH>|[}?k3d11 h!ZUpҏq%X6 ]aNuGm*.q'M!^],JyPWbE:9 ƿӏ >)}Q"l$&,P6Vs̓l(OQ@W`O}L@ [ }cxnsim eq#L[(7쪢0F&u!R)gYU\bZ&/7`oey I&E*tvp8R/6Lx |= *5TC.K_h61/N @ENj Cw4#8WӄCN(|$E.}ylיDb'h*l^mш$(/qw7RIܿ6:~/nl`5ʹ6%XPDץxj{GTeK殇{rL#-EKm}dZMf0=\^SHM cU>?S3IymHDЏ`kf{ UCzhtm[MPzyA~b?~7{FIT**êPN3ـ״,# 5]URWU<|Tn ۶K  ײ_:e-*Xs]HZI,;?ۖM@^_\? A7g;GH.w.c]yF>י=Yk6_u^"u[ ._g݇ubܬda1Bξ2ޅ66_ljJwء?5^qA!=6WY:O _id/M`c+`3MgZn] Eio4Ȇ/r0w1Fz`~ %maLfd,ziS^i6ϭ_l}ŧ uk^ze`cKsl_SLE6(AFrM$$vo*X1` " (ex]J(/Mukl)ctƜz;ѹN~.%A=LUVSRZkqűfDN1F#47ٻDE+((\eor~ ~ #X m5 IDATYͻ +O lf.tPM;H _sCzjK,[ns\[&xgˆ0> U;өK3 Bu )ۆ+ 8}cOS~ߴm=YRb[&Lˤ)Ba*y} 8KXTv1)l"!xK )j|rSO14J}i}nM[96P\ bCS˜憊>~[%(2E.GچeJ ext,ۄKWٽ;FΑk۶il$jqU_B:_]A=AyG'gUYH[eUYƶa +71HQ)&HnZ,֤kφeR<;=OC 6 xyƒk5yYUR5&ϞPI\3d/MA`~7b4Wx]guUKq'{0-kLzO+ kRB9M%Ԗ Ūy{tf$AFlHG<sY NXJmKx@jC)< %sP4nh\8c/ѯښj2fN޻L=>R+0 +SS|Sx wP)vNxO]&i&XuӾDrؙVA>S"fpWh9Z^Ķ}#$DI5bQYm..󬐓ieǦE>R@cN#96IT3 ɛ<~A4DsL$#K/W_Ü8l,{֡.nmmG{ }JC2E5K)}q s[OXƈ=-O2f67K!hڬ6W֮1#SCJ-d.0Gھ\:F2nR3O%xEE\<91:mw"uZF|Ken1#0JOyW9J%)Z$"6GtDK % g4$?v-UL :Le/t\yXl؆|-V%<&gUɴtofjA EaøFۣ3$+5@zP+4pNW.{Lwۍxer)7dU鵔24baA͉g߇jC0'CEIpQM 9ɯ>3(QZkW!=2PI->ћKTQNئGxυ ""6J$#֥THo;tBr X"U!aXZi)g9֟%+z3\-P5 VjmHlE/AVcuufoʣZ2ܛ#;ɍ-@MhBNXRZiɜ҃$[? )\g7C>`n"m_VW5jm*Ϝ5n͉rCp΋cVazP}LӠ++|Y؋iWmNs*I;w &gFa(5ykbrKQ# kr$#6N$w UYk8a|#=i̘ !6xm*=)MGv,)-6h>0,_ ]|5AG5.͖0s8|wǹs2'? .\1N*IZ1<V.e+ȘsPם?'jn*o m<#|=)Y*6qm8q5W-,kL2k\)EN#drY%>YbX-d ~ǧ=iU_BւF2" gҘƝۂ?\ѣ9a"<{g"wߧR( }Ym]-]pp>(eh9p\݃uىb\3&WG&І4u~)@i+Mũ\U>6!u-wo5'LiuJYA/y&OJߘ%yJYpujM[w.%R-9̡#%覃eQdR+GJ"|7o"$LRT<&''6PᛇhZ(B1pOa0]MlS`w+ ]?xNSM̗,UZ*;KB~Esqrq>JۍndRJ}^{Aq?L ,Kfa8dvKGc&N*/3UM1݌rcdBٕld3 mSgUʁ\_:wυ?yrå\w8|D"f$;ϣq> Lpug30M]=/3̉P .Oj|"4A?o9X4A2.LhYmJ$ ratt%*e1R٘GM<ԋGz%Cݒ_i95"B`6fL&C㌌ p 5I\nAVdl |h /(#%LH@} }KP_Q Ska|r`6RJ*vRD:P(<1r#npێǭ2I۠+Ÿ zSVqGr|:3jZy8olێKQ vc4w[ ](v{띏7]ѡW՝ |6=fSw oleNlχKi8ܳ^a1@81n1,fJYl vc1g2/Qq epC4++vHْ7_xN]=9"MxW8pǎjQױmǏcYc憢Y/|@Gdx(Ei*-Ym.ɄvR6?AҽJCH ^VwN=" 4A6nғъd2d2Μ9C*¶mZo}d2H)y7fJ:VIZ2Lzx/\R`WΌry#f'|0G:v~W XJCǒ4t_뻏=&E*nv}*9t\_Uހtn[\?0%ZR[ߋGi )`~U"$˨mmT ա;+g }>9B<'\!wt }#qK+gv'r?CpS@ %ņJdn ='BM,Sg+lIÉF4{ljDT*EOO###dYt}3DڮOW~{Kk+GJN.8;˹& fHIڦtԚSЂt )m1TH1ڠTyw9%we TE u\)ߘ"h[C14šZh2) 3(by.'}n HGS:F_X;ȿKL2g6㫽Fwffp(b{(V>ģש@"A?LWQt%?8qkag==|EN'1ZS Ή@}Iyr4&.%0𻠘 dPmyOw: Ӛ3Jv}8WQDבR2Z $T Cө8U*ml{x9!6izmN>y n;-7xret16Yn(:Ruwę]G\{@ ƌ 20Yk4xh@!puP (SJʝpW \yML*9 d'E#K$#*MmmБA3 žb@p'MRsW~bH(G@rsƋ9 V,]U h{GfwZd fnC&tVbX"`'MRimի$8vrOߗL/`lWAŒcse_u]R1\>p{Q#$mԕ_sb&O1muxW,))( >\xi>$*&Q}CNI wf#b I\iП1 4^PB@.a2Oxpm ŷRB1Svᩈ;; oޙ ug{M;'6m=QÙ!K8$hlҢ;gs 6~qa*DDZnqb w e/) }L$0YD<)ÈP!46;19an0M($=>ojmcJ_cU_$V>s,L.J:Ӓp#"a"R;6ᩭ"85o)֓{mmB3U$O&JHk'/s7?.,a#@Jz:(03Ynni+ks-߶՜&atmf\X# l4Gx {|xiZgb۫X&g1[zN}:M$}c޿hЕtg\|? [I[rjjQGsNeeHo$.BiCv&%_H8џelO]Bm|-WFJ.U8џ|O'ۼyL]p\z;꒻[\s7W*H7 7rdެ8 014'tM.V^t_Z~3{={=H)yw~ǻK>K:aOwZXxb9fw%^Q߆9QjzKWxiCH '4{ SHtq2Iũ) S9UdZUOB*{?݌>YWlI \)_>+>!:a~>ߟM peJ`hW'cmVv|=9t]cs̉H☏i݀s& ˀހ+So?kXm u}C}wx9tB$9i0r۶T*$I4M0X\|ݶeh2ܹ0P\Ps ៾-}d)&UBW#F2 7'3QЦL]X]%Q׹x"iR,4FKB`맬x_aM^ $A qU"f[$+)}n)%:3֝Bt=G,' 1/p7|A^\WH)4;ѼJHOlIL.Vyq,@]1m? h+sa1ɛ^'Ƹ$kt!fJeoj *D2.fHސgv$ܜ䒒d\̲16_,/̰N-?{Mp"DBcoRA\?!sss`Yb H>DL C\-H#O҇ܒL5o^$'43 > 㖮$NrL>0ﬧ5,ɤӌ*as(7lm݁ A# n̔89zؒ,q[5;ͳ8^\'^P3թK~zgqNH5y+6d/6_qյ0ohUbϯ-͉#X vyV 119<.lR"GBÇd24G%ˡo#c߅?*B>,_Ze +]T[.{ 090l\@gO'(~ 1<_HVDC )]4J5C>z;2 k+# ~s"DB?b&t - Oƨ;M\9 d\ͰKv+{OA}%LmRMSY޿B?A_n1W[X]YFۣRJ%cŲ[:Ʌ*q44J Pnը eacc}.^x09*I8W:P j5 hS٭)-Ll5S| /C E='1C> $Gv`+^8/d49mXFSJn>s1{е}V$ x'Sՙ-E .'pГ(i7 67%idbi<.\XtM3CXF<7Z)i:џW!|S391̭m@MߟT;xl/b9.GRW\0Eܸ-SWvamǶ]s1,c|,.mɂ2HAx0Џ>}ڒr,Ct,>Fc3wZB\0SmoD_! x+x$˵6ͼEX*K5rW&hm{8UꋤLV:Y3mWitM#kg0 T^+W+M.ee@CZkweu1ET6ǃ_ uo7&A0 ɩmS/A_]"};B?r n.KG|="Km8>?p|26VBɱ t&RgE{S| nJᕁr̙)↺@o}~ I.O-Ʋ#4rQ z:,5)Uz|;C9Sڨ\eF7 jU ˻Ԛ $sVN[kЏ~v"$o\tŇw3^n$E I8`nJCmGB/\bIU5wEAp8NwP]ͱMoea(J,5Wx p2n‚n':.,mIL-h ߋQ_IokJQcwv$VFqJJchrIIWVri2׌~vcg!YLk@xujt+&AQԸdr=Gb >E3 ;k4 7pj gw,U#v$KXW]1 jmV73__$vCS{{dq7N߁9k:x%ݢ Fz3ܚ+v9$CN&C9a[Y, fV.e)|)`*r\5{/CpSL+ vFrሯЏ^,axj%M9?b 6Z[G{H; f2LmZs6 :{K܈WY/j &Hڡ_3hBRCZӥXkƙ-?6l,չ-՗OL-V7dIp]LD-J L_fs}KsX̭i._g:i<֬pqBǍrk"fX얆LPjweF񇾶}^P_!` (ڔ<+FA<3خ\X$Cu/ 2@ 8ҾjΎvawfJDMCؖ&hN,⬗d6CqPtn| }pc#P',NǨS}"{`yy *aHG,Jc_z8= .+h۞Ç?Ջm;Of?}tDRM1S#h9_ YCU=zi_9&GH "h Vg q l#.97 ǠCR+'$/^l"͛|ᇬ Zl:-E{1nSoskW2>uT787S5wV@詟 mj*0[wV7T*_yRJΝ;GyeBw#H!iMф g36Ϫ{ ZEN\dϗ\,b#=^B;х;[,Sg'͍s)w? %v} jׅUfYs:1NVN&M$F}>SE;+g܍;8xέ[O>q4K9B@",LJJRsLU1ב^W8ޗ!ftaSx* q'2UiU.y&/_f||%d2|ߧ cSSSn_i~& ]'ȑ\^ B*UۙTw@&"Vy$i;<~ *#ⶁ9uعxf4m e\Ro=8a;"Gl-dIr` 0+[ո!c^CurƖ@S>؎f9de _kV~Ӊ>CJ2.]ҥKTU!3339rzAPTXXXTMp;cAn/hѮBeF1x %uN7]JBi qB:l6gffA^ښ8++LMM=VԄaW4,d(ϕ8hz &/ vf)rpT,4զ {/w<wEah8c#||G:JcFG:FG:bmUpjޱsb7118.GdKw1h}%W3ߗ_#$+`^;] &YyZCCryRSvG0b'4zzB7/~7ol6q]  aY˻{^? 96$1CP!k̗#KxuTח,[S}@CǢae%٭4n~?J Χ=# R"l299sN8 vׯٳgWw]!.+nWv N2gHzCSu0\g=i4McDq;O/`~ƫ &#w !4 |?s]VeY5Q&LhvIt]#_B9'vA ,Rؼk"&9{K:SK#:!(ncb\qarIWr^0C\dWGK>l{7߈gHC_~6Mm2 [l4R/[[)6L'6=x ab+@p;FAZk ["t&:װv CQ` G'sO#i_~_xo|0 l&:q,/ysw,۰9u+kdcMuqrP!D2__'ѭ \s a+"q6s9H\4Z\*,M t^'[fXdM$f8 zY4❰xI7^B3KE gv@riwucJCh9:}vx.ЅN&f2khX,G &o^% 5NcAOB:*S[pVo3q _4xt8M|Sc mmm=hbb%HG,z VAmQy5oЪr4?se Y4E~,"| <=^ajgOXfUT9Y:ClRXvXYR2>[r9<{ mg65zJQ&&8LgB [ˆ+vW );~\)_?cAn@``p̳󿈈 Eâ΃0;ȵp "!<+jn~%^:A{1h4 ZByxk7ttuh:=^l=4#$ː=BГ9,6qy@gF}9bT_Pq6#i/$ |'s !9D&77o~. @lY0[(qkaZ+HmN;w !>j4փ="6e{z. \T@R94NILBf+xArELPT|uɾ{gi1JjC0ڻ2y2|sT~Ħ)]1v](Ma~yeRMP5h&tJa#炞DÙ!fjsM˴oXZk(e՚.bCtw}Ş!tR} rCcA#\h*Foiv31+͉z/<B9FF&`_:7b_#yG oE g_ͣ|tH6udbNOA0l8=X1D<غpf +W9uM .s<7rp/'̉ML ?k#)L!nq$iJ^R.&K(#(o"/,'2mME.TbSsC΅>gTC7\\ckZsE(acDnPn=@ke}|_J/6UXj8||{|i u ѭf?e7Etr,Xs+i f+C|D?ZnjNTWJq0Kq|l h`bbKnrB|@='$ tm$mz[. ~IK~vF<\\3\P{ά<47azMC +7,BױL}~ye۸ X_Y"D0k;0{hm,GP>%nZe7$Pw9114\[ &.Z d1B K)ZRT_ Z!xvȒ?ȿd>6$f=|Tm.Oa /#V{coR;=x D!1yK%X@ $6s-ߏJ>Cjg]TN+6:򣰳mtZ_RsCPw|,Ɠ@Rj_chbd=~:O6?iGR8Rc[b4:L٩/`vX{v&ԚOo}IP9Dן9!t;9u?嵞(e'K_Uqb-n1*e'Do?'ɯLV+O\//o1 eC 8@/ߗS+j48'Z83+~M3/ r3#vK{a ׭2[5IDAT|2a~N>ec&V||{Z{T[.0PgGE>3s6|EN u~H< |U[$Xi.\nM.\&29m|鴓Mxx .Z9z/.P3 K֘w(OkFs} nHdRY%;h:SwɤML'>/~OyʃꗤIҩJč7iO,,,͛X\\ -|WRבΧבP6%ЛP? fo.y639ZQ~DCT& Эvk/cz~=%f \Z 0CZv'уg83bXwc `:`?mWJ8u{{U +Gut`e?Ho]qgֳ]X,#؅ߡ34ҡq&\ݏ/ғ}?Nkb``|ٳeYƍ7p9D"tuuAL&L&!(҂wPFR7Y5z3_?=[d!0;+Wn"WqN]Ǒ|J~:s1tXx0 =c׬RZ@bHRb{ML7Ma4M !4Mɓ'qaDQy\x? :|.mB@Q:n 1|zF{QvR[Ƒ+q>~t24"kfwN$ /x[kl.qHtK&&ӭ%7Ms8i90籱!dYFWWJgΜÇq- #JGYK9VJn;̗rA7W3twEύ`l DT錓BBʕ&U-^F1ځ,:E `  @.gp__n߾Bf Pc@}Ƥ#vA!.-~El9k5V ;V>8A?o-; UGw * I)Af?v6M ?dHwajy\x| ~ZaCC/di6 =ID!q Hc X8jz|pzt5M(JG}]1??[nAe߿xTiEA, Ǐq5۷ݫVX&ߘD^8z|a]h ${7_[Mj tmiirP(F IP,i666i|ViSUnZx Ġڇ]]I']xr#|$ ðPU%q`o\XYc0v%P ;+e HR ?•U@$?=+e濹$I۶n?}"l$. x<_px|݈W]wUc#$"S:FwP;0 ɐ:ΊKH ¨9ɏD"pI]`_ΆNۦ5oз5\q>c7O#Ϗ~oYE޽{{niO?sssصkrߏPcccPUw{ ~o_L"c?d ,:]^~/X-ǎ+(Y֭[$I4;.BAT`-Ozr .v'άEE _"d!Ceh>#TYVly/6Y1B^0KI(}Ukm L&@@@ 4*ϫBd@,֯O]]P  +719jtVQDdȐ!mX9~A߯lg~~<ӊxx]q:TWmN<^;s?u,YTg:?^ ,P(ѣD"Pa@UUdY2zN{Ab4z6̏ M r-Ra@uȲ 6i2 ;J#b#GYB$I"K[]!$t.,Rgދ4|V>޵KTo ֿ >AVSX\e@ `mB"^q>7,W l#;s@Ok3˵W 7m}W:"|ͻoƻ]]]i*7NR04___ߦi4Rl Aui$ D"@h*\F;w`ddXC# ܽ{t$(a`qqd2 X>JدѺkfNN籘kB *.k%%;ɲѷe \`;>[}ŦN5`w>!D:!P=j>(JNd2w|ͻ\Sދclw \v}yɿ[?я~ڠoI!kyܾ} 4M\rH,Ӧir^z%;d:F&=],1;;q$@[2 ^MzI`X,}N;uֲ=|:c읱OX!bhS ?otjJrr?E>iS^ג}Pǵ묖}Q .~ر DS( ziHӁ.X,b}}sssa肒p5ܹs'!Na$ /_ƣG2/NzVD~Om}?@Yҝz{(;76_Ɏ}yfؘ֣̉)(t]oXǟ| ^?|>eKM!ɓ8w\A?N㷿-+G.]Y:u ,scc}4pu?J. utکDup9111 X-g#vjUsJo}Ħ;h@fjj'N|/ɼۄu[oo/E!M:xi 躎{A4XnB`rr= lբ/ITU l;˲ݻw0 R RCL'5dϞY iXYY qyd28q{SSS<Ǚ eH4Pkvwjp=\֚~|ӧW|o< HEh[񣪪_5lQþ}0>>X4qY `޽P%o&EA$8p@=ס( [n*b8ka>LDww7VVV 2;}ulf pr2 .]Ç}Iuؽ{w`f RwΟ?T*@FZZZ˗144|>iܽ{ }%lb^Ӵ<~_ӧO/|k7J+vA5^ak3.S׵⸸ա#mpn`k_smx,oF4vkaҰpMski߮R"* """ ΰjw0{ Zֳmz`vD遁 ODDDn5zW[w_M1OW}Jnw߶9@3,!""" gK}ݭr#픫7|vն7|`kȷ}pv>tnw">Q{xW*|>ܛ~`Ъ;vw\w˽.qCʠODDD~6~e<^|6e;ݮ4EKwO[}Kl'""" r-{ww]O]}|~ݲlzb DDDDӆmww{WfYWV7Nْok3\;[%<o9!"""aUvJޯ.j @XKwuӜh ^%>5oaGDDDD1_뼁hgmEط3dg-ӈ i|yytm;n NmFDDDDTzKaj4NG|Ve+>QUے5#!oþ{zXw>^v輁 Cu ־y>\w>Dpl&VZ j9xpڌ_r+i;QVjvgjYnۄ-ֳ>(a}>QT5rճ [w}hos(87z;~ y|a܎DDDD,Ha W=ϕx="""" 4C}a0o""""zr2Zܦ {mtxaFDDDD;Ue2b;ama8HhN Ak'g"""" NP<i#Q#tbeUruBDDD53wTm@Q'keȀo.5#DDDDFa a\l0V|vQpMw ax'G""""ݶ w%"""Ͷuww"""n{7͸=:ێ n v""""  `mJDDDTw"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""vM>IENDB`Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/screenshots/sin_cos.png000066400000000000000000003310051511351636000261110ustar00rootroot00000000000000PNG  IHDR*6hdsBIT|dtEXtSoftwaregnome-screenshot>,iTXtCreation Timetis  2 jan 2024 10:25:17 IDATxwxU;M.mihRV)[PU/CB"._DqyEEaYAFeMGJgLLf?+y<ߤZ;9 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""r1 5 jWP0T_'"""""""" 0׃z0П?\6=_""""""""Lpסe}s#"""""""^-̫oχȜzZ֗`;DDDDDDDDD;d99ԇ׋Ǚb+r>>5\j86Te] Q$""""""""ǬSe] *U/GL:V֥ :DDDDDDDD԰$0TWu湝.pᤵGDDDDDDDDcm(hkxhҭKwjZ~Òj5 6t砲6!}mODDDDDDDDT[j3^fIkC5ُBISۭ 3k3p!A#Iw|=q -ktִs`ޘ)iTXGDDDDDDDDQ˼m ,k:mJw l֐X@iiߚNDDDDDDDDzhK{ *5w@i,tׇ>c,9ik`YJw R -lv$$"""""""" {iie*wRVg.e>DDDDDDDDD֌~47jCzVCfK GH;T@i)4u^"""""""""kX i*4HZVV?9 :wH)4nM@iˈL"""""""""[X2XLDK\V:.6&47%GTQm UFڍ5 +mAMYR@i*";DDDDDDDDTYh}S+&tQ R "mPmin5pDDDDDDDDd SeKkJkt݃JKR )F kjK:Gc!澹0>ܪuXAMiMHiK@ƏIDDDDDDDDTՃE&MX鶣*96m%i׽?J"""""""" `Rn C5a5b bg m6c!mj4RtIl#a嶚 ga,4wkMH)v+DwQv[B<Ώ@NjG[14ٓniprXTV6SMWi*C?ذa==z(jZhJ"Z(..Ff\];P⊊S窡CP&TX 臒IS!n69ϖ˾]mnoc}]-?#%E:m^6lۧO*9n{"" 2܎Z:gСÕg䥱Kƍw _)zedVw *<6uyU9])~Xud2J""{cPIDDDDDd^qq/7~ wJݐRs;ՃKiL0nS3w֐~1Rfgg/djDDDDDDDDt-Jw ݫMMhjDSW0g|4]G3/ڵksD"QDDDDDDDDJ2Onn2OYiUϽ;SBMwQiiK)P9*%k_//Q )ڼys_]Y3ze,vT1n3YX;?~p3xKC1Ff *sTYORLpwW]TGX3W%`zqcsUje:eD X}}\}J%aHIDDDDDDDDF(6۴iSl6ǚqQ*D- U56/NXڳgωv~p<Ȳ޽{O ~el``>tٷ-fˈJkZT_H*q4%QA*s۞{9HRZ}RA4\ۣ}())1Qg_R퉉JJD֭1edffZkDDDuWGSVК\}AZkL0Z}yDDDDDdOSLApp0Ҵm^^^C\\d2~4|p$%% +VAP^^^cjgY]Q_.:lhJZv瀖&o>W端BPPЧOcƍ׿Uc$""} 娬DUUvԺP(D"юU_[wT>21cc4lIh-5WIDDDDDDd֭[ѷo_$&&bҥ:V`` Nӧ#** ͛7_Gyk׮t^^^z[Wݝ;wHHƌWjrL:͛7GLL ,X`s? HOOOC˖-q}J%͛: 00z–-[ ^#~R;vDPP۷e ͅ\.GEE* Eaa! =NTs/Sq̱g~W+0KқmYTm,;eee9>}˗/Gdd$k.{HLLСC1fm6rHt1?0n݊Çk֬kpABL<7nDǎ 64T4imomڴAǎ/Kcظq#FT5njưapiy9mQVV2"SV6kGG궹|1TZÚSwHlj'!kT@ZZ<<<0oCnZ&Ǵ4T* 4H{cbԩ8sL}6J%lwH_iiiBJ \PXӑ20vMKw澱[ctYZu"\}3ADDDDTl/ٳQQQÆ F֭[Pz&lѢig}-226m|~iӦƃ>85{|Л k.S7Q`ȑdعsKkٷoT*uf9Bros Joub#_^FNT8p)))@^^'Yf?# ɴsihP|Nom7DTM<'O6] cǎطoKL:p5رM41TmXPPP~.66"ǏGpp0isǏիW#((Ȫؼy3Ν OOOlݺjeٖ׌LII FUZZ <OFBBˁJBQQ8=z{SNaٲeHMM߿?z)/sꂒ߿5L4tXYWfHINV1|[8r|||бcGxzzbǎD.ҼaSQhBW^yɿ!''gφx ܾ}}{ggy'Nǣw޸x"z聉'b޼yy)LÐ!Cpt&M /F 7[Np= ##={ī$߿kDDbȐ!8}4ڶm׻$t >>>ؿ?***\]?4iKɓO;@$a8tVzj97o˗f1O? \-[{سgܖY͚54i _"''Gג#-- m۶ѤIWT=z%%%8pKcȑ/VѬY3YO?6m@Ra5jk/v"##C!??_VPP#22VǮz9IX:UL-Ssto:"[}6sT\+Worr2m>9ZѣQQQof1s4iwʕ+qɓGx@;2 cǎERRޱb1 ={@P`z8p~-]///DFFb֬Y5ȜB`ok\.7z]TR!"". 9 /%K5k#Gh(((@BBnܸ(g.}]o㭷ުaݾ}l:p@?111!0qD9sQQQHMM5dT*BVs6rkfr[ rw]YLHJ+33r۷Rjl۶ мysDGGC*@ll,N8kB*? ""ؿ?>cgϞ>r'p)\m۶EݑٳgÇGEER[1b鈈0 ܹsQ^^3g 779JJJpaUaڳgJ%uRRԄ9+V>}K.>裏<3Xx1 ?&L,]O?4bccw֬Y`1tdH UUUf\ǏǹsC ٳh֬LұnFb:(Lm4RXɐҽi& nܸ믿?}IO>A||U333CLdtRbbܹ_J=<<#vO8ݻP(O<ȉb:֐_{n;vaiܹs'tQaڴi믿rqqqغu+BBB\]IJW||re?1M[ni_|S*GHMM瑐?P;'m0c ܼyݕ}ϣҙRUO>X\R~͚5HNNƙ3g,s&sr r///|Xnߏ˗/C,#""&MBRR;v숇~7o޽{iӦHOOヘ>|aaaxЮ];묪B@@RSSqg|y @]_L'$$7_~큁3gk۷ [0FDD-??VBEEF6mڸ$"r w +-N=믿^Ò%Kp ޺B!}Y̛7΄t&_zՠ-88)))Vj2[IY#C=K6z6ǂjk~DߊW}BW{D1o\] A* Ymq}ԩS1g-U^^3g 77 %\dܹP* СCZ]͚5CϞ=R &)''G;?zBB/ʝa޽(**r)Ξ= ;wDFFSO=M{Bz"o>R"J [U:?tC :mGm8$tՈJz$"rEΝ;]r^OOOt%&""}=P(_iwIX޽{C @$aHLL4S9`} v?vuvmۮstK-qDDDDDuX,Fbb"r9nZU* ׮]C||=$"7ei!g:'燢"֗v[WɭD"""""jq:u‘#Gh]999P(ڵ+qFܸq~)BCCѩS'ddd/@\\$"缼RWi xyy٩*@$jp6n ???h ((oB!j5F@>C\\F 4j-Z09_~_qA<DzDDD Vt\c:[Ǻ}:ik~DߊۧVٕډBDFF """"":E*P@ @sJGy iX +x[  -w!"""""""""r,DDDDDDDDDr *T1$"""""""""cPIDDDDDDDDD.Ǡ\A%J"""""""""r9DDDDDDDDDrbWPjW@DdW%Qà ,Z30w}Z͐ @ 'K""""""r$6Lj(J(JT*Q]$ ?""HZjKDDDDDD( *;ZRRiJH$B&A"@(DDT7T*TUU %%%(++T*B+aTZP}JBEEB!4iL C(GYYrssQUUJ""""""r3XHY^^DDD0$zM&!"">>>(//׎(HDDDDDDd/ * ) WDD4AAA@YY6$""""""7&h>kȩ]\z 1$""""""{aPinHVQUUPȑDԠA(J;`XIDDDDDDR*4nե\ƍQUURRaPYMєJ^^^. HT*9A* >>>.m@PhCJ"""""""{`PifD%GSK;^T|VJDʈ܇D"Nx7J# 2iB/v=mo """"""03C3b.Ck(,,OV_~vBET'W@uΝ[7Ǐ!#㊫ˠ:j똫ˠ:;\]F\Nܝ#FTRʑvn+PXzwTbo*E(OtoasG1tuG`\W^XBai%NިK$DoIhJAͬG qFM]W`Bb  DDDDDDDDT?qJ~ R:y`2;VFDDDDDDDD>8ҁ)v XݎID֫Rr4 [ƩEȼ] D|ti!]B!; """""""[1tUv )5ff! 6œ5gq5`ۥ;y!^9J""""""~muDOR㿫b£FC2rJGP9ye{")) IIIطoSMDDDDDDT[ *`;Ғ7*1g9|-Iy?;"Ν`ܹN=7Qm1tՇKo6qsΡgϞȰceƍ+Vش϶m_sy뵨ȼGjRj,˲cEDDDDDDDJ;;xW>N1D$0jTXu{.\@jj^ѣGE-׬~!&&* /F\\~-Zɓ'qСږHdJܵgk}w~8*Y6sL 443gt9셋K>F 1&KB|wѽㅄ`ҤIѷo_b:tgΜqӦMT*ѯ_?R)aڵ֭[mJ$2r4 9礴Zn)avʼ޽{~syPTؿΧ_DvmѢeK""""""2#*,f &4j5pљGEUU֭[Z?O>$ ={]v5/=}>}… ے믿=^|9L~ǵw؁K.ń T*HJJ†  ޽{q$r,njcB  ,,-"rJ;3,ZCO!OXϛi˹<<< /`H$;vѾmڴmیnH$mŘ1c^~e 40}Yzaa!-Z+V <<K,kM6A" ##]vD"30}tzrh@L&3طP P۱DDDDDDTqJ;"7jS !:5BTE߁Ŝ*6*VlR#!! xG1n8~RYYYh۶-ի />@bb%YYY"d_Zr}ip*G߮VfX7nɖ-[O>$<= mkQigJnW DR+|˭wҹj9*a䠼GL&LJٳ1i$$''#&&~oEEn߾֭[0KkpPϖ-[dB2w|ܹ7n***дiS̛7Ç7~DDDDDDJ;ey[ aeQ }b rxG0`/?>Қ 8qCYY&Nxװ}v>|pI!**>OoCEhZ'2 TݻIIIHJJ¾}rΆ'ģ>#F`ʕvֽ"ĸ0OVoJauAbtoizGmϜMrwcwޭ?b$''C.#99maaaz}7oތ$D<"b3Ϗ8 9 iVߴiSې裏gr ]90l0m[\\z!,^MtVSi7iisEZBϞ=wߙw-ܹ<*&) mU]xw}?? =x hОׯ6#"""""O8$4BJZ)Nް=FfRI%_˗2"Ǚ96SXݴ(P[e̙31gnܸ0p@K~U8s >RO4mo{L/BW*UXܢ TTݝU!B2z@ pqDDnLV!\Bܩ*B" 땔+YP (Uw':f="r<2}?&-qرprS~;QuBoks6xFx{\;MPvJKKg/≝_TQԶĠOX;4nlsRVᏴ|SE]`VT,DH_$Do\zB$drID R±}4Ngl )L, FǠˉՒ1YW6/+Eud_[KNjA `΃޾y0 w ŖcYz,'3YpQX':Ð.ܥ "dH,#884s6o~֨‚?P?!8Q077f #[W$ ,j5p:S8v] E**Tc ն z УCbhkߙJJo^<?~ 0RÕlܺub\*w2 W{gUy.s!0D!b=BqO4dEd(rUP~΀Lݧt>p >k-F吢eҗJܾo{^8A _X9ID  0<1  /%FDD ϟ?T&MH]_$~||<||Jc]ǥ|lcldw<<,aj`B,ٖK6\mgqNz8Z=E歛h6.ǢE./F-R9}^uN]{rAemeR©L*DDƘԪ/4e`@UJ5~ܟove#O^UeX{ OЯ/ftBc!땔yA Y@>VX9+;4b $"ƌw}ж^cǎH$%BQQzo$ΌPI(*3ve \obHDgj!띺Z;EV!G2+*Qtv)C1ct4} H$BP I02 >>>FWӓ~(ՖKbyzCRTYҔkway4|E2.h6A>R4A^ 1UJ`9-8)Z`ZRKH\c,%J| *]&c;X}WV$DzpQ};رcXnb1x |;v,! ,C'h=RlĨFCRjI.tpoxtDS4]X@FqNg`yb954++NUb1y@^$ <ၞ93'3r5l8J(ۋw0x~x+;fS\YW+UgQ^4* hPj٩yc^ k_ŗ[3PRuoGqK鈘f/+xq׸\lL,Er{19?|$5W&V=`y3 `p%yxtǧxݻFi*5>n0PNxmtktnU+7:łѭw"*5>p /⳧\A%o߾zm>>>&h̺Qs||^.PX"\1)Ṥ:ّ#0oS:gm)ŘpJ<9pEkW|_u,B85}Mۣox,?ڌ2?*UTW;?/PVċ_¶9ۂ}=0{B u B<ʰ#*Q< :ӯ zM7C_] >?CNu 4TU ѐ}/R熵[H+ ti?jNO}qvݰy鏼sxzb[{Mߎ^؀F7 u!CyVtlA^Tcwgtkk "mŭTOc ?]^CFAXy<]:g,eXiBfA9F`DT,ĻâHbbo xfhKOmEwgaϙ|@D1$""ղ<9乨2TV BIW"b8g|ꥎ艟^MtK$"r.ه/rI$bzQ+Rj/KPpZ=uAVA}x`%nF|?ꇔ^|rzQ5{n0"m89G2)_0wkg~,2[Zebφ9Eۄ{c S)ዩ@0J ~y2u"rDDDdgR<Po1C)PBFDd *)R?oEp)WꅥNCk? gn_ǫJ>-J;uQU=6 okS(xnI"rEYxϯQ_8°s#+_>Ei[\TjL__П&:VM6!H( nΗW"-朣DƠDxpHL$:vwy=a}";X}4,8płڤb!N넸{c"ܰVzmJ<8 !#ZJ9 0i7UeYo,Ӷ 1U _ĦYzmM(Jv IDAT<]^!P po{aڒP(ޗDĠ.K}X_9袪R)QBd礴Q#Jr*̒R4ֈjf[i ۠0>8EUYcP |kܙo o*J9[d󑉱.wϐRC,;C +_(;kι*A%9Dw0zs8Lyo<CBM^Iq'6HإB^Fι9x麩{fCzmeJkW/eAwHES;"^Q_?ڿy?YJ"""r(8^\Y.^u߫+  x{5ՐX$)!m.~H#"~V۩<EU̸I^ݿ|\\˺w&3zi Zu*A%9Ԉئ^۵<:}\uծH9c]TQ5`Np1󎋪"zI:HO\7K~Cmж\bϙ|UDD+Xzz^[ e*Đh<{^[<EُRN,"7XDT['#wgqvV *i z̿/OouQEqBwqUۀߩYV$"Yq1cc HpH J AqB`1."ےzLP,\uif} w-۶23wsb#=e24J{ci i?9%":}#~vJ|hVo;H֞c"ئpe 'ɔe}K\V;X$"""IRDz _ۋeEрWyMUBvZCCM2""{+;]#Ev.eGT %~?Ik%A8%u9*nA-!W )#z`<4)XZN"[xߞȦoClY`2羰)r酵B*SFp7Om8SـzrI:>yT SȔ兺ᕸX^mxyLd>]Jn;Eڑp qbMV4Ȕ`no8?bV8,OBtN9_=.O͋ǠPo=1n-7&]C]fBDD C*g(T ǾU]= oق[2e;n3s#-SFƚ+Eccz_||篸tItވd.Ǩ<֧RH[#2ǭw:8HI~ܮbύZpwiBxc£pU3}F}WV .xh2cL?\]I2eDX$""rPj[ɝNoLX%xe̪>;*!=[۳+ǁ ̭"&EXO Ófɔc"K/SF=ׅFjMGE(U%Nb3-bd?+c y؟,SFSPހ?mGSs{ʷRBp_}~OSk6 #JS| m~vP(VN ~ᘼ4xQiP^Y=ۑ&SFD} yqrbLu/h BPʷT7٧ob<2""[w0 oa>!2e$B_& JJƬ:BC_<<#[ /Kǭ2"o,T5iCB,RȔQN`db\c7F"E0%h*B, G.)# bspU2ܧ3p'_,C妑)#0h_'##,TaB`!.u?rmlhM!OVf[ayqWV}{+cYlÏF'"@2 36Ӛ㢼b@2 xva;}uzJ""" _f.n16m@e1qʔ3&sbN<:2""[SXW^?(Ƣ2ed;5x%!U]o)a-QRW?\4삷6D(SFDJ""" 1^m˾KŶ }uz[ ʩoMz8QoᄎDԟumB= XĈx bOًX05 _^5=c"=eȶ8kUŊB,<)SFDJIkWϊ0OyLm)|V?$""F-{6};/ 7;s…ΤU&SFDd+nb[9!"f bȔQ@{P(V6ke̪[_݂djvwV㥥2fd{b`!4V5vpIPiEj?SF׶ /5"9cGؔdOψSҦŢ8,SFMrKv_=&GwpDA¯&ZQ?0Z}4NGFE 1bqܩ̗)&Gb!za$xb,!v RJdSymɽ`8kUc!Go0OȘ`҂ͥf;kKY$Ψ$|ln}q^B܆px:HQDԱaq'nꭿpQ1H"J=,8[Pͧseʈ#H,@uU`{yxS$n f^;]"`毷+r# (إBqĪy$WbB!̼6k0R[b%M'"2f@,/SF]㌧bRTdQyF^YP*H+.yh2)Q[,TZȻ{D$2x;kpdUwfU?1~0?D$&&ɓV6&Je-Alh12JaT;8CrMP(fcñ "G-,2%ߟ~xd[uZx'{Ao)8{,RRRZҩS`00cƌNjXbZ)3rt2/#ɮ.Ɓd3dԵSbΝرc&OlkY`` bwkr:1hw9-_BDdQ_=2!Qd-L@glN5z|'](N9kUxaX=}nupQB]ڔ*Qx*a26ɵoCaӦM0Lx3˫Mjj*y饗:+V`x iT?,_f^'O'io~m;w. >c<0ֵEbb"m?>?I}g)T$rdύZfTz :R7;5ٓk999^͛7 +Wlw!C`޽ ۷oǛo/-O%18~ׯVիPYYW_}K.Ν;bMOOGttӇw] lذҥKJBJJJ^:r4lZNfb1/4NPv7>X>9o~XlPXр/8&< Įq4e- G')~F1AݡQ+za;|$DdX4ڎG)AXZÐ ૤DU՞#22k֬ch4vSTmã֭s >X`\\\؈bbb7x5558z(0m4xzz"::#Fh>wvv6BBB>?~۷oS( AV觾+zn*-7۹:î~Ψ/o75fVDBє}#%kUN`نDd=2r*ό'FR^g6kSєfȴzkUkUI[Eiƨ->x>+V*K}uzcʕ+Yf999 _6el=4hrJkB3z!009 _K/$AAA|uh]X!ދf735ޗ,nŪBvEvNi=6mg";!Yse`WfUi0ɾt!6s?Fs498kUxjnw7rxJ f_lش~bTX+ 1g Bf~ n .Zh4P՝5*ƶ#<a4וGBBT>'h4x4*qd/TEEE#Ez"|z\ao?b>h)U`kNy5{I~zn;}Ѥ0X~o]ŇlZha>!3ߜbߙ<f&Ifma;#sBȔrViPqC9WY]s d%2 ф|PifbeE?ŵ嗿%bbb3 11ׯGEES3W_}SL+UV  pA|[œ9sejƍM7ׯG]]qOS۷Ν$''˫K8Q_ CG@a͐Qz=|#_q!ͤ!mF=K7ԺBS7gud>?tYSKeʆRqbOu-䑘Ժ6oM&|vp.q4wԖc|u"U2eDd[8L&rB%NW4WjR~jLgٳޱ<==oV+W^yk!!!¨HO<O'h~-88GmޱcRq-1;RW#'N;w}ʏ:턄< IDATNj>]&w,TZqnjQcBXH[>e`Χқe"}oyh\G*O͍ZᔖHajQe>+_BD6J yy] 8ӦMt???Li'R*x5~6nYfƫˬ.~S$"A|j qGؑTz!̼HsG]pѪP!T. +dʈzZW-igѓᦱwW=:Ǻ0[hpQ)͖#ulh;/>ٓD[` ~m6]67,3CFaeJƵLה`o8bN\'?***Bqq1 E'Ew";;<ܺuoGRceT|puWs워sx~L"=;9:I }06ˬyRǞ7Ouklx?"}պ=*'cFKW0É?CQNN#V*t&zf^8^+n޾[PCŘFFȂp`boD$RX"ʕ+q9( x{{O?ŋڵ裏ī7n܀;6oތ' nF_hhZcѠWwNa]{F).U%&e{j`|y<9CYX Z>!enbsCcƦXЙB2R0c.RFZwFU7B=p=9v ԯq귅Ϟ ӚcB@)lL]]Oܽ{hb >}c֬Y6?<fΜAh*\:"gO$u:#Z|*Myq1EktT5b|"nٟ}yeBa3I<`(xS?u[y~\ B|9mߚ {FNLɏJ+'Ӆވ ?5~k to""~zcÆ  /`ԨQ믿Xmm--ЙvQ}%d^zL.bO͍ڲ #5>;Dd+u?~ҟ)h9x2ҫ ;=nBHhy,0NBCJ+Z> WDw ۵U1Z<6=揍Uwo""{ =466v?uzCC63#MHQIDD haPPF;]ٴi~W֢f̚5 8wyfzHӇ`4ɶs(nPߞdJOH v_(@~YLQGj;{$f*T ZIoPCYvY4?\Wf:@DD JZUUUm^khh*x{w=oyEPZ/1##?8V\k׮յsF;rW Md2h0?=Y6Iռ/;%yՊk}~?]؞6\;xw M>?\"#w2%URc~h664ۭx@p~Jsߩ$(NXgcѼ 6$l?L.faH.55qpF.( F(|BHD4l8BvGM7} mZkIII8s ׿{"""'OFRR.s4 8s|s G^؋Z BΨb[kḄJ˩lL8te?G17j~ sH奸v 46&e ! kkLcvzZFlvEmild2aԙlN;Ӽdog~y:ן$hn}eWf RClޮGlt:cuk9نc)(*G޽0m9Y5K.ֽ `Ǯ4ܮh)^s[0:qX8κ(#= eee/w**̄oBɈ z& YVEE9e[|;Z|+Jt7QRSS#2{)))ظqcs,##6l?/Ξ⫯s=ۛRn:!!!Ldzj4a; V4`ׅBo c҆ qaBL;ha>!V/RR,θ82'~{Vr- _m?a  * ]@DOCR5Ogw{9"y9ixdSyh;8Pف{ӾU*P[kkXYSmm-R8?cݺu0LHOOslܸ^97n/~vf1zhܺu HHHիWrJ*hyב^Y/e ˍkٚc%.hv{|y b#ɘEqŦ;W^YٞG f{U7bׅ3".QيBdWTB ??QQEDh"+V`Ŋ϶}gs=zts.mAEC6Lӑ3, =6fG$JG {[Zs|}:?]>.Zv&CAi pQk;82f*6:ּ]TWsѪ$k\0e/tXk~#*;poJBFN7EEEth4{$qVilD!l=<*hZv%q4 \e_Bi} (HT3 5sV\l٤`*aKYyA FRVEqq1JKKNJKKQ\\ Vۼ,dVLMF' s4m uƌQM$' x8zJKP_+H0kD=0/.-ˠLcJS[7QTprrB~~>GVQRTT|8995JGThI&bI9歺O,&]lCdmE8_xGhJ ,.WlR66 #+cB ZnXi S \~tu{T*Uk jz000L0L&{_k=ٹ[K)d/TxDq֣cqxlz(>ܕUe.S-ɽJ!,jb{ JWݙe{Pk2ʫ`N"o| Rl.d*$ Dd{ӽY& yP񆗗)YUIU#24@)tKŌ#Km3Ñ- GY$!sLPOYj+v)+{Mu*c_/W1+"b[Th4BVh4)TXIDuK)Y}F ׯ@~~F _ʅsYi&8bҎ<:=T(TU;5$DTXWc)Bly$ސtWԨ*7Ǿ:J$"K0-wxdʈzj˙<Էn>4B42Iw-SvH[eͱ'rX$BeHGWsx"r=W'єF WwNs8N<dUB]kF4d;by p"2͒&:1/BlfVZf[X1er*Z;yTq0[=Yl76If9R7벵S^rwVcѸ !QL(X4G>?ᏹ왴Τ>u&YPo`^?~4-_]tFl%ۗu u)K"d̈zBo0yo|xJ]p_p!5M\lݍj$Wʔ PIDDDiBq]$&d./7 Nq4 mKG8Nn2eC=uJ1JZ ׃uFAd-R]"cBB/9ͧ C|0׹}P`d:Ό$G8a4ꖯuأ,PU7T(Ja2-T=01jU; 9{`BlGyGvrz%nI&W,T]q&`&K#C*Ͽn41R) 6cw{9}YuBL:\jID}":J͞Yƴ~vbߜ 9*ȮXƖѐ:9wG\ !$e$qQOWyPtސNa3;t4\mрY,Rbӹ0dJȌX$"""&6k?4]$b}8*j,tً9cnl&@;{. K_07tQO4#YIgή<(=Y\;2eCd>,T*,oBlY7n`aXh-:}#f_1k~d9Z48fD=#3I؞2j&[s0Uu-i dT5?Q2]s DDDd9eFwX/'7L4ؙio8 L^m. Ĉxސ*4ԧڅ< ΞBlGGUړ:ߜ͇No)"` 8kU>^zC~"/%Ufɍ,op_6 ꃝI0aO{pvUqZ[єR(0|۞~^ ۶l@(Z5w/wްPIDDDv)W2*t#Lq4ؗu,)67N氙Q7G/ ^3cztŒߙŸZiB|#6`o"B%٥O)0iobBld6m9pVY9B,1|\{-.i 6}|ܵݾ~ݑA{"<\,[JdoX$"""c2[Έ#N J%JF\,JC^mY#nBli6 ɚA.^)FO>0Ӿ[ܝy9Qv +OdOX$"""s)EuB =}ϴvj)t`Žt6'қ]I83;%s"AN sc{6ő vq}%S~d=n'ĶzdX$""rPF.&vMtatg{wNTa~XcotbLnʗRb!&]Nɴﹱpu~317\μ/>;RF!*B"w:fc0|S7sqӇE Dx 1mDB0&#|Beʆz*oKv=/Cg:bh[ z 9 DDDJP 22#F O؉7JQT z6. \ ؗ%ツ= s5")ɄݒnK"˔ ƶ3y0ZM#:>-C+kq*FIƬBl&m,T]~Bx@F*6we\ \^,?@]^khsPT_)#;؛lֳqb3f IDATvAorm{v DDDd7^2-N`Nή)AJiYM(qT7MBNh>);5]%Ėth4p=.mф]IMJ"""'eqxHL0 pPӦd]2˹:]*Dӿؗ}Y-b82 gycU:ȿns9i3FPIDDDvcN /9 %*wg^o;x\x]ǑkŝAԿ̿ ۶ pĸh!5ە%k.GA9}a`I%3aqvnM)dd9mG}sI#Ysp\dʆzzv 36ӿsNdkOWMdJ;,T]8R]9}3'<^N+K\Lрùׄ ٶI⬂P?g4oy~XT2ARz Zc5g9B%*.lL퍁}ɍLv'⃠VLk4UN&:ʹya2fD=S98a4뻙;& b$ ȶ-4tEu2eCs,TH-6h{#kˑ\akzh1yl^{civzv.rtѼT-r-2b;8 DDDd\+AEiJf^!vMbTeʆH~mӾ}1&2湡( ^ȵ*Kog DDDd#FŮ7_2dەcQ|mq #hhigPd}uCs]_2Zf k;؛ȶPIDD #:M_m͛Nt֛}v_.N5|4o7e]cqڷSyUptɂmG& A@d/X$""rPJÆ EBB<>F&NU)Xo?OWp{8ۮH VzNGg@!&mFmd4e3bY<+d4ߍ=H w eʆgX$""rP //Ő08^̍ h3vT ۷ o{" hnRgL0L;(^|`w5%KJ"""Y :#&%H9KY$VPWiV6ӆݿi3A-S6S7sNT39)iADurm C|dv%q7>*fO몔 ,kAAoP2b7dʆFI}s4]>`8 OgP+UuFKʵTJljӿwq7*fIoodٗێ̏ Z2?#Jd̈ȺN}+|hqm;$˟,´{01h%&]rq76*`G}>;}v̛7ի|nG7p@{X6Z ϯ-ՒL@!]Mtx$nȩ-+wN>Ϊ9PMo7MQdX$""5kGACCBBB[oaɒ%۷o#55K.~fΜs;7JQ^r3P`iў0H{vUsY$9\FQlGo4 }5郕`w2*EK٠FWS7Z!dX$""dGw޽{>㪫^YjUhvKB'M/sޛeyUuzHor|g o=JXLLڴ{=-fr=6_:w E 2eC5*zøqxȑ#?ZY܎h2a%PӾ:Fά.­sًjL81d]BDDDj¦GXXX.[L^bF_~/2z}n)N~NE3'jjqЁnJb7ty`tF9ɑ1-FlEmMtfl?9sg(.*ĹhN(9#l=U^^6iwN"4hjYU 4]agzj&4UPOnGږ46P^^&w*V72ЈsY- | NnΜ59.*z!<<W^my&Z- Q[|9^~e **lVT;wĢ#޼m2PVZ ْNӼ玕/s536oVWg6w&%%6әK+MPCBYoNȑGyܮG!yvI{r^ ރ;{-S K6OEjEsrPQQ#GɝխPECFӕe•+v7/_7(++Os/ʕ+R:9Zt@ttYϭT*1~xDGGudRGsW˅Ą`ma8Py<j#]in`+lw3<<0")YUͱ#׫03.DƬ7F W77'2N|:Dl,CgݩqwP.BTz⣲..OkK_~y )z 2.$0PkwڸF%Q/uaǎ3p׮]ؿ?***PQQL>^^^ܹs1uT_H$ 'j 3bm_ċEz\˪`o"[Se9Bl 6ib9Ex K\~PNۥjv͏?^+AMȶp7Q/ 2W\Avv60xpۇu"7n܈h4"&&mι. Zesb-\vRWٽV ٻ~3j8Yml02LV E/6鏬)MhR@BH1ƶ$Kmlkl˲yttslYg-v[~ȱy>Vkܽv\rS#|G 1aJ`kVleN^C㏮÷V{7o:87vG'쨬îX##`E%JII3$ab 77ׯ5I9kMލz\ )}p9Qj-+U[HjxWr. :~ӊ>O:F5U{W8hZ}X+B͸kmv\B͎w-"""Rg:03{ a0k bZhN |o;9Ohh!>vlψBŤP4ˍN1D 'u+=|lL*D%D 0UNt5a>P4䭻E &,}ĪJGjdC)wִo_fwpWX۷QA)bu&*d&@$>SBѐ>vf&͉D%)!tOޟ򦌈d$[>YM)zaw8H}\pLj;QjJMHJ E`pή\u٦ϓz :΋ָԀJ"""R>#91A E=yw)TjʾMq68\@D^뿆.됰&W{ *gC{Ibð>z~"mBD%)F}ە+Β4 J DΊ0a$%qjH Vj?p}|z( uq]X+NdJ-XeG#lP(I Śxa"?$.ŔݩP4DLTB>\NnԄBѐ2SBBj҂ҶamDbW+ycı:q=)Y){6ĈCvhbfRDDDzm8{I(oՊl FaGUgap "5:>sڷAǏwZq#VXaw6;I:6֯P4ذlX-h9~ͤ Ɉh}|NhDrl ָ4aun{Ӓ=+CT(Ofam4ԄMUym8b EsgGBLbfm# q-'&*howFlےRXG-NBѐE",>vլ$訇3ȏP0"֑ bRNi^CabVQ;٤F{mhlU(wD@DDDd9P n wSgkifřK FD0t(+z?>jR/鰘U{u^rHkJv.b5##9dFfI)LT(Á^/pz"WjT :̉V0;Zi;Iiie}?&8rXGViߚ"'Vc]EhG~|ЯyiGUWvKUGدD%2 >ǟ&6lS:7AH[Lsr2Ҕ0=UT5 "e|ӂ1:ԘҶ{6hwMS ƭاR[vZ7bS(gLTѲq#R_.;D^ w) y+2Čk#5N&5*}gE !8bIm&Q:,`4XG.VkŎ(zbNJٯD%-at Ӕ<@`d$G k-{֋$`pz"U8&ؙmZѧ [E}JwoOUh[fYLc\M6Uq E9$5Tv LBty #ѝiA'oޕA7ڒDa4iSy]?lvVb͑ D1QyKm]+ĹSHIr5e\P8"56aGUC&? :_vz1r+/0QI!؝|crSf4Pu+GSyqmcLtiEtV kMˍJ"""ZG{\AFuSӅ6n{OeatMq6Å+š<ċM`B,ѫ&qcJZ^LTѲ}N_z!wG!43r( K`D ke/t(L;NKJ}*'6P4䏘$""%g;Q)#Ӿe%ɹV̡1mQ0"ѠCqN>p 9&92ST"rs8 ɽIi߳%|EABDrٯYcaeD%-9y 5q$eϥ%Ķ㓎zٞKY:P}+Hm*n%NXcJm>ߴ~сȎ\P4-݉ .Up\Uy}*5egn :OB}lŽLKJ"""Z264(3fL==դ`D-0T*:,!y@vivAΜh#Z>rštq6MLׄ kGoDDDdJkpzs##W)[b)r -) #݆S]Sjٰ:QfY^W!*PlR#LT2`a/WQv4LC&)oYg IDAT4șSOk9&L:F3%ď/t(5먟lR]y.qt+ @DDDp\J˶\}#P4-Q,nS&Vm]0sg\Gk`Dg#&%IݲW">"@X;6(Ę$""%I=Ft:`U@% k:)-XZRYq"}kɑq\DRBF 0Fy+0پD-O%u۝sKƦ/ Z$m3?w~0s06Ĭ֎񁝦ۿO6 `lڀ(D%-#3rvB@z&?KTi T+X&,^h[#V;N5]kWMErf&* + &'ud;Qt$""Ew׊Kcš~@Z) !MSrrh)ɜɹ EB QQߏ)}l6Qu+|`0_ذ1oު~XmjA^,uSLP"c EC SUQ۴sDrxWVZ"o^q E;3`6xO98ݬ`D-y7RamӚpa۬IاR[vD &8ĩ`p3L݌nGΞ q EAF3Kän;sŪ318?0QIDDDꨔ,yLtiETWJk8h!#I W(V խšo0ANdъ( cӅ:i1QIDDDkšU_EDDEXoM%%DAOY"Ucs8 #P0"(I-X4خP4;2V>SIKJ""EӃz\ p\hii `Zg|3={vC_TGM\xW*tzgk-rR،<ѝša(۾DOY `UhRC>r1WH-$""(,,DJJ 6mڄD|so / 66زe D8ýދ/Ÿ~zH$7=O剮&v!o C\x&: r5ed@W* yp_Xm&*dz]߈ Z8ha$""Z ՊbDFFhooĉ^sqwE?xQVV>ott9rdf9pQƾ\La ˸}gz.)yC|SOi1UHILtȦg. byӱOL>{CӆGD"-QlcSZ -.7 o#99QQQ\?Ӭה(..bAll,~#11t7>>OYU r nq ANTV>Z"WGoP(%vt_֊!\n򮂜3ָu2U] FDޒ*^`DDD [oPtW?ϰl>zX,J}Je[E",ؤP4%WOeGhTNN9qWٞpu:Hȸ65bʝU0CӅ5Oeu0z'|DDD ҂yyyp8ho$ˑ444`˖-1tuu#~m5q EM6$poEp+78~j`T>ζ [׆`hUf1XQOrHҷ:;ZuMGhkFGGao':J""tHNNjrq5 yy}qqq<c4?8֮]s\.zjtŽ/E}?397x ׍)65qN|3r=H =z7nWc%`Z]AR6 HΝ;HZZP azRX#%Us1uϴ﫶oZ;-+-MOǕo)™3{n""%O{o6|IaԩS^۷O:mKo" Q~LٝcQw,ȋ^)Utq6▮O0bf&a01[hh!A"k,LRƦ55]p(E+OE^rHXQIDD@EEEx'1رc}Qy_P__k׮d2a߾}~:}Q˿=ojb͚5عs'rss*;v oꪏJ7[E $o1n01 z[ǥm56I;2`crFpXIL^鿊Qat4V7lOǡkm FE(ɍO޿>nlA$#|+*??7ހ•+WswA`Ƿ-Ġ QQQذa}OLNN"::Css3.\-[Wvv]y6I9>\L0`[Z&'+棢N8^9&¹!a8'Zh0IlwRnBqu8",A.8X@DDt~a =Hjhr װ^jBdPNwjz Y0)*it(OHI \S(Ңa8Zxnl{Fіp OxA'Z&*hj׳EIO/j) y+-т(q`Fi5L+nʥ`q6}{Hz`WD̉N9+Cs_@4LTтX8}q@XcJHȀQ塚tLTwj~"!-oOC4٤6M3۟pw DeMU N2]+"*q60QIDDD RQ'l1ξ\^ 5ac*a޴XJJhChHkwʂn֒2i\BdֱW]`gJ[\AjSzH-\yJ"""Zy(X9[EZ"(Ȍf;qUt{C֘y\?IoĶuڱ:(Wwmlhᘨ$""/Bȉʶ~ϡVeXI,'/f#WO'Y:,^h[V'%l }= o˺HL{PtpAZ8&*|•+k@/Bc(:&˵ i ֎uDKcژh6АBWۉ~LNy! :dF 6 *#u+N%fu}\YoeU0"]Ep9qY[}h$"""/(ɉ Rh^\3+mWGO-r$'PjKCR(2&*+4ZbEšNt3ѥӌ^\r/W"(ocP4-ÅCbn^Rev&*d{FBUCV1QIDDDvo}HnT*"NU EC !je.MeGpX4`PvA,PBѐLzl[m惉J"""8}AtaBJh픒mhP(R)/ krR7R:we(b E[ք#"&}[[rp 4J"""7ZZrxg#NBѐ֯Ct0Le'zn R7B W; 4E~`=8ƶ!bpDԗ+SQBttjZP%n{+oFn@VkE$<@t%!O:>AZ)%&*h^N_rtb$O$;n9p۷ XCش&B|t FӎS] FDޒe,Ga\zƾ\K@>RFv<\@=!4vk EC !*̊') ѫ5nfrW.bDVHh!vdDlfkDt+۟BӵcccJW, tojfnd4a6to2#mD51YmKcsXL1-rDW#XEhvŠOaTtUa3`бnD+. o&O(L«>n*Wp#QQyHWZӋ8OիWT^jLLLx}ǙRn8px?#t:dff`˖͈ul\we_`5VLj R79_{m݃.'Nv7 kR7y HZIQ E#(Vkg-L( i fʣLpnS~\˵Feg=.dъF ,>vzng}?KH`J-)KXTtr;-]NSh~v&)Udll >j2ɥ"vVuSo_? N r_.nw[REIq(e4P)X_:uH lR)o֊?&S]MpL) y+#9dF14_LTC_7fIyM_S:au/aʥlыNhh!!}p8Y"n֔~7l4&c g/)yKr4_LT)ǛׄxkO} VpR+IMSv劉)T_leöq_28Hn##fB0s0W kU]jDc?6cniF_\n^r"ӞLV$D =)DX7T>2qWZ#"o,Z}X;whIa|rʉ FDZD%ijǧ5~@[` dq8@9R<1_VD?oK`T(Vc(:&, ~͗1DVyMV DDD4ʆ~\&=g/rJNxH\/\(\r۷۾S&>XhKv D鬪Ԗ\>xJ=>#G|AC!I=p!R2 Qb_eS)`RSE"8=N*#4؎ 7i!hJ4DGKGaG|&>h=^u FEؙ߻>==VƲ9͍~ȑ##G?T:nۿ{1<#x믣[Јȏ[U?$W<ܥP4-Q TB% +CmX+'YjeU(Ov7@^ذaMR&LRtAt:۷OVΟ?_~VghB__: <3ذa|\:x뭷0667|zFzziVsž\%˵օ'"18RUQ5a FE(Ύ=c}pϨ}I=Hݎ7f B2tؑv˩ ! zN׍64cS[)͇NeG^]+Ԏ~`xWa4KLRXww;IeKx뭷K/a۶mZxYYB8pC?~mO+N'?z}};!2i Ig''>HJI_Q(ZNV z[5n֖riu br AfDVD2&*Twu'){9]Xv-}YwwU:Tz) pYRNիV";;Q?q2.i IDAT9E+Vdwlؤ7bk,DY-bE&׬-OF0> FDjD:|=xFB޺Xٿ_ᇅHFFnT  >99)OG: X*a^];asdؗ$J%HȀQ`4™K FDޒpMr2eSjXL EC޺kEk3O ,hhCuHh[Qf D1Q>^OKkhM7))i֯''08ȧPj&_Z)))"33s+h>N4 `iVof b D^JaۿE}F9&_Q!$%GjʸdlZJbW#Lw]M FDޒY@sc +$z)^yPita -&""hooڵkg|}㰰0w%%SޙGyw|2~ॗ^/KӮcRצ5ሰ 3ieqEG=E#"oȈ٨w;\88YZGzpmT "O&.̎,tzlOIܪ FE(Ύk{4{hq>l6Ϻ~3I `έz= l`裏=܌;H]۽f$.oٟ ˅ygqE999 w]|_U8:rD+ @aR6e blY~ZYm/>LwːLٝ8!?afE$d ʪF8].!צ5 6ax| rh|^1wfꫯ _{g`g?S8 \%/رc8}4^|E}>,gu')N'^{5 y- <صkw2*Ԍ쵪$NZ%jҡh YtpnTNM\l9xGNe{&*}M;7*!ouNmDXcfu(HĿ7Z149p|iBBd-h*ef`>|ݻ* ł|;xڊ^xa=*'r 檘я~YFs;{y#VO_.(45 AFD2ת:Ԑh!Qy6ͬ% n֖^F 6aj&R`k:eˉSw#*ɋu}>DQك~c&)}CXXyqbJ=CyVQQ=t#J E\DBѐPJxTt?eGàQ9E%]!\[Kj'YQ0D  &lZRlq! M) ~bϞ=w^&)}HXX~ॗ^R:"չvCshRV~Bp\?І+kEńҶ:) 3#/z7N5?Q>(̑ݝiN*oq#n#{!4yl[sy].^G~?,p0lٺٙ<Ćګ˥fc )+Nt5U[$7^$jzw+-&+?!kC|FmZQ6jR>Yż.njE{~yӱ?׉!T5aHDmSWQDeG ʛ}au@Hl6ی?]]]HS\xD8bbctskmYQ0VAmGUg`&u[* !faۿ}Å+ A7c*[۾InR٨P$;bI4* ?}rGPI?QEôInY02!iSe,IHD\Ek kJ=1nT(֚`ƈߘlkb4ÁxS&ضmah=7LTR% +Cjcz)%R׊>պ:Ḑ۾50ltP0=Yl;>.I1ƧMEGGcll'ҲM ݾѐfOv._?u[l9rP^^v P-%Xo'e%fu}\ـ<(ɍNwFv|zy[ӘѪQ \ָ[[ʤlV!@lYݞ~U (IQ0*FIn4;T&2EELTKRRT łydJF?O]}~y_WK ?@ww7 o~o? d_.UOov8լ`D-yHGy iY$;. f߻hEC'|`nqHϡVqaAy D^^RSSTbAjj*TMd0`&`4p _ 맬;}|х 㭷믿W^y}Μ9_[_~8]:ɨ˥j6ǮD 0ulWE.pIX}V 1AXP44YɈUUv4( -DQ8Xȅ"!'~/%%%y%((Hmtgѭ8Y[,kᮻ<^e+~;n'wGG'dXG-&ٺ.!FNtPQ߇/ܝpd䭆6X5>3Քj+*:B"o̍y ]뵢{+ﱢ4> * 7X'Ob޽3?ڵk ̄B˕mZ 'B:2ܥP4-Qbry -j/5$+BҊI\JriAa8dwl׊ 8D%iܳ>x7?ϗ~:gV=ݬ~]q*ɽHօ'"!Xa"'Cj, +HݪaH2yZX6mQ0"NfD%iÇcAII w;yv;FGGgR_u UZ-@zChjNy{i߈ uF8hljJ (#[-_d@"Wkjpx !!˗/cӦMHHH3EW-+V|[^Wf﹧7:FX1C'&0-Q|tf*.ĀQ/ޭg0L?Yj`w:1Zs\S0*722 ф;OJ}xJh4 IP;@)tΝZۧV)jt0]o|ҁ>8EÚn?ܻ%w1QIDDj*455x뭷~zۈ7XTXojjlv'NzBh4ߏ8^FF, ~ ǯ^vY?c #)+~8\7%+;ӗ-O(ٹ{>_"}6hTT#/oÒ^%]3{ ⪩iG܊d,Њ5goAl`ج_zCC[\!my,[= ̫PsnxxCyLT}ss'}Ql۶ /1::_|qI·z100HOؿ? ]/tڵsbʡM??la- @^J''35obt*ٵ>NHT~2 Ӏ a65dwp\0 0w&.."WGcZCR-i3cv- .oVo|Sٽ>NHTV໏1 أ4nT"8q?qA͒7`X~\~cccSN}{?u=o1 :۬x0yQ7-OeE;h(v U FD2܅cڢ$ypq6Zcqw\VrR-ilEg?`4p%qxx8ǫV·-ڵkbDii):;;f7|=[8?CCC^]7 1K84܉!o-]0, uvc]Zslv'N6+ }oӚpDX^8DD)?ϑkP$tTWW8wZ[[Q\\,s!477#::ګ9(7*Qb?N=Ւb&*@NT&fAiɦMYѬEr%mh^Y J""Ҕ+Wѣضm^y^?~_+SJJ fZhh(n!t;Nmo"Wqu LRB|L:pV9٤FҶbV(AX&V3Wt) -|luLTt:W_EKK :t|EJJ ߏ{mCCvvCh[!H JYMj/j :=K Hʥ mI}QHh!JrbCcS8߲?Npp8杈h9 cʕKz=PTTW_}ǎ{<Cqq1kIn"5)LȄ:p`tj};wRhgg>|㞥IwN!ouL}TX[0g G}C.5!!2I!hl\a"e2)) .ˈ=zp\p8/~,M0E$iݽVD2"-Ho-)6W. c["hvW:;\Nn¾$7FHTU0"R ~v>|WT{8<~իJGl ϥ%YQ0<&8je¥NaMn@V&m.ʎAAHZ`0aK:a4Ej2g/c4nСCxDZb (//O<TVV'DBB+obDh[a&lX%Vtq6)V8 'jBѐN7 k%9 `EG% ߶HX=~.ULT`2%FOon0`2 ÀeY$dY,+ w}7~zNww8GEL ^SU] sH*}c^=wY;BFσDqw(G\ۉ&Xm})2,#bDd%̅aXFk7r WȑG0LOOTskxjuQBJ, %){1ejCpL&`A IDATZZZP[[ \$DdjP(\l6{oҥKm{+9M*ڻQpü j갘po-A0U>Ш] S4҅":v"H%*O4lMI': RKR*JDDD 00cDV#00P**X WWW!;̟?o6^~eᮻ7B&+ᶷ3MBB|՘Eӿq V;VX.bDYYUdbʪ~R4rY_ۆ7((Q Պ0JPZ0XV.YIb}+w܁|qqq+r[]fþ᣿ą|!,!`.LSmoD:Rb[38ᮠRׅ 'R4d<k.oA}j"Ɂ7[g6,ݛ㫷wNo*'O"JDeMM ~| 5W'q7= idu`noMgDWZe)+[mr,?H{ O1ӡuZ+DEY09P|,LJ.LJ$G ΝիEmo?j;X/mor[-j4HGRX)Q~vaMùCM\QҪT7DJTwNo)šVy !lß4JH.֢*R4YjK/g4 q6 'jUZ qbǩR~9}O*η^T, 2Pr mYτBZtx`#-Y0C8lAeM0v*1X1"2VEA_ mՌF>,rc{£DEmmaܲ?,x !GȰ<`o-TJIلN\L6 _%SRJ2 ؙZh53c2wb :zKIM(0QAb%>3,^/FVVְ=z/|&ӭr0LWx{9矏[,C9x 6mڄOgBؼy3RSSqA?T曷yN:T޽`4/__1!V T+RAz(#3ue0odrnY)R$d$}eۺ,pLdtq?#~zzӷW՘={6fϞ JuwCJJ .]4xCiӦ scٳعs' -[`Qh3޽{󹹹!44UBFp[[w h꩔$G*3Ddr9Q[;π]hjM$>N֋ S_s#L"EC? sz2 D;27n\>I&>('{Gzz:֯_(SPP'yhh+--E~~>bbbs(ɰuV !!]Y\jks | 8%)_dUsW98/w,Lvv"XBt!&bYPjYpWhȑG%G;>V`eDJva5k҂#G 77K,ۑ hJ4455oGLL >۱k.;wիh41c~c֭8t\.ӧ;6l؀x#-- x嗱{n塱Z xG6lʕ+VqiX,~X~=wΝݻQYY `xqkx7}!33!!!X~=noţRpivu]7="#'&"Bܷoa4yaF~)rrrV5 R'`֭~~~~0 x7Ν;qA$ !ge>,o_ͻ]ZZΜ9@a8w.qcҥPh4Xr%wX~=sNڵ rz*z-4 ((صk3Qӡ۷ojoXf ]qq1q} 8ב;qŋ?ޓ"yLウ i Tx+;s o-j~57"| qd)EBR%'3aEtvm X@Xβ8QBmP&*tQ׮]Cyy9֮] B+V@&!==[l0@FFgAP[VxP]"Ww܁CÜ9snrOO*7裏ϟǣ>ʻ{RovBaa!-Z*XV̛7?8o+P\\b`Ç1m4l۶ riii,axKٳK.+/~JTWW#44tTo{ő#G_^޽{D|y|kjjpYzlݺr180gЀ… X~=jkkQ[[o2Dbl+T0Z6֎Sue34VȈly3w|,{[[-"0땸Kh7.c$ΣA:Ybp$;6P\M/@HR!9>8үCF4&"REٺm߇+wyv \ztR.HDDDGŚ5kꫯs2PT~.]Ž; 7o˲hoomMfΜ OOO:u O>$oߎk׮ CEEv;bccfo^e%` j}wMuLDzCZZrssn:ˑ{II^ph4"7VBBȾHBȤ+ğ?xMgWͯRׅ qɭح8S"v.HI`Fto":Ʌz1͓{x-=Jq0,NHtQJ8r9nX,FA}||W_'>EEEOzjq}}=^u\r6mŸaCX?{jqqlٲeȉ& `ٸn0 77;693i"!B`ڵq'nϟ !Clط;|/v8DdߴMZ{ HdDfb;pT-DRr,C ӊ %"EBFBؗՌf2D r \8pow=z0}tݳl{%W\nse9z/~<䓰rs2T݊f!&&?N'ps rrrXnn.X$:u*.] ;M4:}:gKɹssz+[=B,[l%r9_g`a7 JAEX7` !6MN)R4YfTVImd`ntXL"EC5=xk݄D=*]Џ?^3gDpp0PVV9s`…O>>da'iK,F*9gϞ oۑ7BmB=O`Z*$***? 111pwwɓ'%BCC\lٲ3f3gT*W4ymܸWFDDĠw>}tDFF/2MǏ(iiih7!qa^/U]pmQg$EaߩDI,V; =MYD%mo;VĂ0x29,݆jjȑG%G]qfa#65CĈXg5.w۷0Q *}]xwoz(TWWf7 1N~!ɰyf.FUU?􈊊Bjj* 2331c JVZؿ?O?ء׿w/3gy4ibѢE7m}Xt)QWW-[@Ӎ(x'!q mo'hۛ$EB t0YqrM[W6qsdn{&翊`0`6VK,Ks-&  ķ~+v8ٳP*P(d\OG'Yuu5%Zrss[oaժUxDj---CV7=Yf WR,Z:-ڻOE4ٙ~k|\kt1". nk̍Bܾ?wkk+Ν;իo՟JJ _?߷{c1 ~7\CQoxN\nn.l6U]2:u ePڐXV|HqMja5Ղms)I)!yW[yIJh7yug7 qK}xkԯyG o6j56n(v8Q`Zq)T*,X@p!Qǯ@Ғůh/lG}y3lxkԟRZ ^2q2yGUS$R4d$hh(Q9#$$;vY2 Ο?N̟?nně⼄@~j%%q3˽o&YETM2 Wm0H+bDY $| [-)2ɂDee W꺆8H%*;aL&q\.G}E-3L^ԗLVK,7|W_}U^Oq= R3XƖQH? . ;Mn^V``B$*ԕd q6q5!^Q֎SUDBa0 Ba0 iRjjj~q)O"A/kh$ Tf5>̠H2jĠy"EBF"@V n j"cg0fAne#"JvL,tZdL&*++Ao29oףRr 0X0]CۿduwэkmCMd@IKoM&6:wCB.FۿE8Xdi3L6!~sJJd唬'& dJῄT*)QI29]_nUh}0;H[߷&WTc_xYv@EUr4m&CAɦ:2R/`LyDa&4 r9d2L&PUUE 1b2PUUWwY(i݋DLz *hj kV + 32,*sk5Ŕh?!H5n2!Xɭ_M)Jl6d2bfcRأ2r9 ף2$F6W:`"8qMnJ(0Prfk#R4Ym]]m%GQJ2ADRX6j gxK<"bTd4PrUd2, Z ZMՔcD88G%!rk̀햞,X-Ž3qTb//Qyis~i۷d7j{Lci8%#bB~ui' :|[,DD@t L&,m|(II!>9#ò~OJ@R[Z()ijB~uڊ@-%VQE7§֎וq6qE6.4QE8;-} !bY׮]C{{;o_SSD\\ܨM&AH+ey30`2 ;SĨxm l;vWH?KĈ3XggI8aeP$Z"}g$Eaz~@ъWZ%**0XeP{BqXr%BBB 8p`󻻻oC#""/Fpp0{=yߎ|y$2(wl@aSHѐX)Hd5q&x8Tr吊ҪvԵykbhq@`Q 42]*n[AD H#τWg4\rxCԩSɓ޽{Ԅ*<ؼy3222:::0w\ގ=:^_<|0ӛ?|%KJr?)s&HH )iڷ }OsGH)π;,N֕qVʼngD-p"B_/ԩScDGGc&99BRR4 z=v؁ :t; }(1%*eE y&ՎS"F$A:"jJ,Lykk(Q)%I; =D (QI!Ю]p nǞ$d2zh43!BMhj),Og{qѶ7dC=DT,,]j + q `vm1, dųSײβ.A)D%!2BW^EDm111lV/:;;QWW/ӧaF-vBz-τF,TM"%tc!k¤=qm9W;mRȰ|. K WR7:PR%R4YAoiNфBH?_2CCC~zl6B8' _uu5†[nEHHRSS.444`۶mhkk˗1sL|'Xd_!CPX0GMΪ.=atIEJ]⎯wPQn#7\孭rRxI4j8/DPO=*:" 1*⌔h?]e5βQK9I]V IDATBH? ~zd2T*"k6L 2eʰ̙3 J[Oyz_ 6Faa!<<`OC\l1ZoޚjAu5ULV18DefunT݀~jkGdS䀯}Ct=~ Ku7L&#9;6M@MCײD% omQ~~dXMhopzO%*]uS7'.*B"ghFFe :;;a2=dCJB!>|xԩS][[  +7n@jj*~;pM>;v@||<Ν;acdY;_R˧y, jj۪N&6 0`ӓj?m-Vv\At$L3F#V?ye#\0ՔT][ יwVkk LL_-hk3AZ9jZ$έw=APr(QI!… qA;4̙3Chhhup=`˖-}>Qop 35kMcYUXNfsN_O-Hčtl(뫊-#f"ے QQ1U&;ˢxk"(cؗ o4?wܳzxɸ~*06>ϺF;Xty!1da/GIkk+L&=̅Bqq6mBQQí]~_|^|Eڙ3gc; h4;$0!$(1Re!G0(jFt%{ qBEY4XNR|y.hlʨBDlڴ 7nDZZ#>>O>$w}݇bܸqJwq*++OO? ш3g"%%pssÉ'O>dܫ2(-k{P\zc+ݵ"FER!f7.\k2 }* L4O?!jLu 8Y[g vRDI q-TQI!܂?O,]W_}{\{UUUtX`㽙f";;111(//ŋxb%Bкic,kE8K9V`)זUO>$ ),jo*98(QPY]Ռmu"EC宒cI8wAF!=JUTB!衇C=4_{?**ʡa=111q!!`.VrkՅx`r"H{V QFS; *xk[ZUr!lmܵ3UgcwQg$G";>OJ)JB!HQP7^ֲk4 \Ĩ3@N_O&t[P)>qF)%]w5

S)n_o㭥PJ2, vزYd)DF#%ȸZ0O)^҅OOteV0)YOP/R$EX,EG\۱| gO1"2QL7[Q\%R4YnJ"RJIDLÙkS2^ZeQRR\Q8F0H[ˮ-l⊄a+V%R4؊ޚqmG3<ޫd LC'hfUHѐ`wb~;ԈX:%ㅮ5`Y&]]ZF(4;U[ "R4YQӼ0ōv&ɬ.mԺi;]Ĉ3lvNj[7ֈ me)Y=_~iAV"ND%!2Ad2,䕘,v8DBVF@=M4,ȩ$bD T}*۾W qr.1"2 T4U`jlj|Ԙ[~A΄B![~a-%ߧ˚ngv-- ~ z*DLgATsvʼnR#"Z#x; B!dǨ?_>9l%tZLܱa/J\pFtHB)S`yZFUgW$YP\َ&gWBIj?dz>g}9b'O~? "B& ~F\ojpcIZM"_Hg6QV[[<]%R4d2I?-n)⬸YS]!&Ԟ={w^455 {Ş={8pՅ/  "5HMMd@6!DJ#][#MXMrv=?%#xTQI^bP$MdiqpUĈ32t5au6qMo|Mdddmۆw}9F|W/G}7no/6l؀gy榷/d+>x 6mڄOa6v2>۞d6oތTwHMMEKK >ԵM!3`wv-[(\ NTIvKdBF]H]o-K_&-v!D$e{xx@'v;n݊ݻw h4(,,PB)>{,v Be˖!88_O/{| ^z%<3xg_ | 7;Nvv6G54صM!N8pl%tY"ECȓDS{@⁸n'Kyk)JaBƒp7h?eT,%4*mZb8͛7sk+WONN+pkIII7+W ??x70=f3JǶgd2lݺVPӼǼy=01xa6 66K*asNl߾r|Ts< vmB[0J{On'kK&dȑGY?^㎏{f8V?%rv>ry\#bTd2I  ~/բ S5܊)%bgjso^z+D$atww#-- vZ}vwu eees=HaCcc#Z-裏m_:{ 0qYtvvb޼yظq#gBgdm)v*ڗ̿ֆ63nCh貚qF۾EX`:/*DL>ѺPhj2 X͋xH%*^lRbWF[')777<`Y,%J%xꩧ /PYY9༎9shmmݻqܹAB+66ׯUv.]=S_;*++a0 xWơݻwsIqa[;wb׮]刍իW[oS-==_}j5¸4 ((صkSRӡ۷ojJBNNe믿v ýދ{֭S5[5?DXX,Ywߥq'Y];.>rM,2 Eh-EO!cy#":"T}q&c eVSJ)Yߧ`ĥN!J7}t:pU߿}Oۛ;wǎPTꫯ{naѢEsŊ(..~zMD< =O$q v;/_2((eeej7['x |_qÇcڴiضmr9b?x7{R/{ҥK/PYYj⣏>nWWW8<}aDDw^uk׮!77 ,>lɓ'Q[[ g2RF{FS; *0wQG)2`n=vbL1QU;^/HgVu`䭭]H;KK·Wpge5C1+'/LթQ7edQE%/RSS;|l۶ oBl|9>>>0s該$[QӤ.\,XWiye̙3VBdd$?/ +**`VTA\poRLJxZ{{֏K.aǎ~͛7eYcW_}SrzpRSS'O+9vy/"3#UbC$*DYށj-%©EX'S;"K0>I0Ş `sz #Yqg% D8** 85}y8 66Xz5x  ^/š(dpzf͂RDII s\\֞W7n@KKOYo0 {9l޼;wUcL=0 aIU~kxסi&L6 O?_Ko>v;^|E.?>}Ymz!d( `~z?{PQVWE8cU2ȹԂ; ": 6$bT7gͬ.D N5ؕWsm]x{86/, ?bZ\\52e-ݷPo¨!_oE?4 ^xt:`ӦMUZ<;SQ Xnx \,;&JJJ`6.?Q T e޽|2xZML+W@"88{W,!1 7/:7 d)ѼҖ*t5 q ӽxk$_;[POo(Ȼ[DLŠ쌪~R"Bjc]$bDfr/q0 8pJ%xg^^yt:h4ի;sT |'Xx1RSS)#=*mJKB1<= ̸8b˖-1cΜ9R|p_֐|}} ZUި_|w nNo'IkСC7$&& ]]]zsBxX7O4OdVb7q%qZ_ы Cw̱j~ʔ1"EBFX6{_HV`܉[L\_rp+[؊̙2NjXAߋtG.6E"FEBTFF`۶mxwtXn<==q!<</V^}1w]w!99uuuHKKCEEŨ?q^hh("""9t:/oȒj56n8`׿5FbΜ9xw[W6qsdn{&翊`0pU[3n8v g(%q>v[TZ1;Ŕ)> !FD®_f,so{ If 3C܂ΝշExCRT ZXk~olc;F51Kz'd+W^y$fHBqa+#u 2,8UW&bD2Ar4䈠jmHlN $*k%)e 31$ T^l6`hq]~;/|:Q9I=#ZHOO0Xjy"&tBkTU s;>U4ᦞȩ[[E)% qpzo7!ϲ9R6βȪ)3qڅ(뷝v[-bDd0$T*ӟgᩧa4B(qFu!GφvHEqe;*M5'B\OKhE8+" !jޚT'JTB!h| Fĵݶ_M]cMhFڑY][[-Hv>S3ݯ905,tي!~(ĸ[,Tu`… yfXnVBHH7vDRrpAg3Gl(Y: ~KbSyڷ-9pAM9<A^j!>|d`l ˹"aFd.~\oB%Q'ٳݻcᡇŽ; 몫HkȐ!]7Ԧ,>up'[1y'+m΀ %̨rQm**dr 0#2~9*BߣCܯ٦*oTvnD],TuwL>6_z}UVVѱuDRjrIAm9.qP4SMeo˾w{'!s]Ϫ@Fx(h*8JhKFVl\!swb\my,TZ2S 2VcxgaÆ2h?"kwU7Y{EEEZ^*RUUGGG#;;콉6?ˇ0b"T$F3-4K@O t(7o(T ]DU 3"sd^,TZ V=JJJNC\\DoӦMؾ};JJJP\\m۶ot?")h4Ƿuu {H޹s>>>l5*++w^"$$nnnxgQPP{Y$2K uԑ^P*E" 3jr.6k+mdfu3;#{-ssDp!v0lie-(I~iwo7|B[v555رc~'AV#88 .DdAAVCv˖-Z_|!avYǎ[0 Xz5mۆm۶~GŲe}ǏHOOGuu5|}} tؤ111~8pz=t:t泌ʆV;;kwP^^CW_Err2<wKjkkQUU-PY%iAvFe!nf#_¬\01'*NKì(<]l\5Y!fq7BS#q,ii`o$ pV3}Iyę,TZ;w֬YWWWt:,^SJ^wy.]+\r?pdoO?'Oᅬӡn2WtxwZPz!==Kލc|||P_ ~~~mX```FDD[Vcҥpuuł p%=6h,>i8x rc{KtfcCFiB-uB @elId(+-Bhx!T[6-;M{r̿Wuu5J.]} RP__V̞?!PW&un*W46ײjTWWu}y{*^J+5k?:u\t #F5kE:f#66۶mի8,]TaÆfbccۼ?3VSæCE%0d:uYSo;nEE KujI{{{ce˖!&&* gΜ^ٳ1qD|嗸qb  ^w}ǏXdvGɓ'P(h4ꫯpit:L>K,1~6lٳgQUUaÆᩧs988̙3h41cpKo-K,ŋ{8gܸ;..şt:l޼s='Wd2q\wul56;r'7?vOgqٓ|I<r aۍg13 CkѣGG6dǠAdZ`PU>(++÷~s? {AHHƎ$2Q^^"Oo))JaɥKBTܹsXbVXV&778p \\\ZձȽ9oVl˞1c`ܹظq#8EA◿߃>Pxo>=zo{9K0DjW|L MdL؟oaiip0S?Mm"\.>N*.!6=h4dhjU!඄9T L)]*(J+ڴ9kFFӞ~).^Z\p~i믿P`կ~?(//~Gx7'7k׮Ň~@#;;8p{_Ɗ+۷7Б>&L0TdɒfJobѢEWҐ#GW_ŪU0vX6>š5k裏\)W Šo(**BQQ֭[go*Rvhmls?裏|rNKvvvطo̙7|˗/;;___c?*U~o-_/ƞ={6DJȺSP^eYW3S]"ĦPiK=mTtyG튡⇲.I uhqO7P]_å+--mOc?ƪU9r${=?ǏӧqId2 ?‡ ScYYY@aa!z=Kcbbggkfddۧ=fY0… j>?WXX4+ZҒ%KR_7Ծ1jJ2n` 8.#/?<n757WKpEe xCx4Kǘ'C1yP!˾/ř x5z0Ӓה^Sʡu=.y,TJĸ;((oܫQk 8vL&CTT0eYX4>wjkk4ب^##}a3 22m,![Ǥ (ԗA+Wg}Lg} ˑLJw[Z<.EOgVc2㉈)fE#*w'ⷋ gіMtg1<۸w4 _elVBaF{([FxBclgTZuTq7|YYY  E5kNj}vؒi Bibb" q? UUUf\e /X/6~6Zׯ77lV(|Vf͂^_f˭u:]ƦqIDQx `;uQl3G\v,;2* }ۖU1FJɏ t`Wq˞&^Ⱥ2}1`<`zgTJx׌֬YWWW 6 /^ĪUÆ }ϟ?~GMM RSSfTFbb"^y">>vvvƽ$;BBBp1|W3f :Tӱo>k4iQ]]^zS?Iqlڴɸ)tnK.ŵkא_| 3+Wvhilqמ<.HZGaퟌ iȭ.mvY' sĭxaTkWu&mGDXkJ*S_tI742J|.3G{AkIY_-j3u/>ʒ{gk@Ӂ:Kd0zh) $~ߵz_̝;NNNHIIA^^ _ט2˭^z%̜9555HLLDXX~m={6&O<>>غuk>uA@x'ۼ ѣʕ+rJc[VOnMG^h7#G6IP ^-1[sύ ,X`Wwul66ͽ75P7 ;c2/b%̊1g>ٴPNIoK̪LGJɲ@.%{S"V5 u _.o*n3ŃOBaMDِ0Q2s5MvH`6u]y?^/bQB{_F ,[13 yĚZ-ΚliA݋J+P(8lʒ%KpBxxxqqq-~DD;L 7p% 3"s=8F,.NC&SNe6BhzcNؑ^fD$&VRQǷ?#iwDŽpq7gS,.&nT*lٲ61uVŦVgtu3{=$,|Y9ハwYE=;J}ۖg}& ?~R3]|,gG "s~qV|;ŵYl0 v:^e`""^JTѥॗ_`$5d۹\mC"1Ч˿w' m/ פS^EB1&۶E:FKXX:pu DDDDԣbNml z:/aFd9c"Ӯ^7C,T  ]lž|ԛ,>Ikf8E|'"""eBns*y<* j7Y ^pVs7]˿?ZBe(|S%i38NR8ֈ3L MIȩ橙""й߻zf.ٔ>jW עW\Q˾eD3LHoP*J2gqpQg }Sd^0#2lY={lٌ=ZH.&+řeCdXZDdqJj,qIDH)W`!f!Y9c/޽"]+NDmglg("P.&j "YRqRO$"jdZpTb!s tF߻LR]C{{ukP/C qK\MddzΞt.&2Be) _~,QQ*ׯ\]]PzBW%Q pozaؘ9cĢgoMg \m+˃N4 )aFDeF 'J ub IDAT%LjIi}|q5K""Li|17#O *aVd])vvq-*!] dU}r~8+ΰ1?T|I(]}qʞDD(aVDօ3*bL_-BFeDِ9"<@dG|,ޛ)ήroy%uH]&ĸ웨Y&`e&AgK a,fW =\mKO _SSUV+888؏z^K.!==#F@.!|2fk!##h4prrBpp0:zJ2guáƓNj+p2bGHu #>۶mp""j] )gZM(}F12 k8#5'0bQm9%;<Â%ʆ g;3+YgTڰb@xxa7㌰>״~@BBƌUV `0P__mw\.;#idYiiiƄ Xdd:0 :t(<=;Gҍ7 ?X6 ԐH C# >}%upw0+ꨩ#BqEtVƾ2%iY-%/2)LQ8(0-hK9mH;'"HePً]tؼy3{XL{{{ 3Ѿ;?~ Ē%KT|ٲeJ™3g1{lL8_~%nܸ///XBXb[[[{ϟ'͛iӦ UTꫯqpp3gh0c ,YxM]]6l؀gϢ Æ SO=oo.ed2]P4>zW\ƍ'ЕdP*OOOLJJ7 ddd\Gy?!Q6#h4N܊mCKbW9<6Y̨#rG2-?e9ɲ(8(,"u΄\7=ȵS8_ 3"慌 7Jq8<$̊rX$Ann.jkk1b/}0`DEE!>>k׮GxAx{{W^ƍqFDDDgAlATTΞ=> nnnm.m|___\r~-{? ϟǘ1cЯ_?o&ƍڵk"00i> سgOGXf }Q+W4,NLLȑ#ꫯbժU;v,RSS?/Y#F ""fS"tee%tH 69|Nߋ<"}$ ɥwZMfT+ť_IN1P|"5)B.& 20?tۓh\i@qF% g'##zQQQP(|gήaIϐ!Cheee6{_Wܹ_',, @LƜ.\`,6j,V刌4Iܹsu1mڴfGnؤr%"17lSfEX|?};E~~vi,ZlŖSٸYq{L%]BDA>㾾Ⱥy!c%ΌqF%  ˑpH}}eKVWWeaZrvn8022۷!1n8@Cq[&͙IDD]' *w'@J+^Mڼxr Lf͒ iD?(,4+bR~("b5kz=4[HHHFT<11[ ϟ?a/ \.GJJ \]]ojICٳ0 dHOO^oB `ӧY@a;wXoۨH8fMYi QS]#u*Df224Bk+q% 3"s;0}t!nro8|Iu±˅)bq%ʆ8QhOAzEDY~S3K.ŵkא_| 3+W"::xWxaѢEݒïk 4 ϛ7ӱo>k4iQ]]^z[%˹|21`@rr2`ԨQfkȑ(,,DII 777ۣ~~~;v,QRR޾ٶnn Ο? :ݙD鑔t9aaNÂ0g0nco̠( "s, M}+t6"k񸸵ӨW8қKpx:8aBlK9_zḦzgTR3jq 4ٸuLK9s&jjj0ՙ3gΝ7^}U$x駱tRrl߾7n܀ڠ4c„ 0a֩{aڴi***P\\ GGGxzzƏOOOTUU`0`Xq_#$$HIIiw9%)r>p?glҢA 3y7Q$"f'\ߵydmKpR{~Cd JٞߧC׵rQ6HNNFxxx}_K{ ,X Z޴^VOn͆ ѣuV!rJ\k:r_ÛB@\\\t4[ӽ#1tVᮮ6m8pBDF ŵ 5&9#JuΠ7`ir.> 9Cc}%̈o4~4=,-ljkx `ę*mXll,mۆf]CdBBBw6 ""[e'Wb^8acl{|<^x ֮]bL$&&xVg}?>ԩS~CYE UU@ǎM)jJ<(롫S 룙G -y-eCԷ*ۧͷOaPȸHz*:)77?0^~e͛7ȑ#裏6駟O?,>uTYG Ŗ-[z G᝻ v4^#qf^Sr=F]h,u&CdU#*kp%L}X~'""u֡ocGYw>/^ĉ'_cKdT %#ĶV(#j;PT[!^~p$F+j)`z)YCd}0+T}sD,*;@2IÜ'3YZݻwcdB|…8{,:'|cbرXee%5_"[hнB\(j[X!8 #zKECtӣcH-ϓ(ORN(sƞBLp5Āj=Sشi{9!^UU$W³>w}iiiݒ7-{bo(jMJyF n(2?>9Hg+-u̿"k4#h4<b`7~0#BejΪ$S*PfL7ir{XRRzկF"6Wìf3Xh 3A׿0|pId2Ih'ƍl !:i#<%n|GmKϯƱ{ZMDR( tۑmDi999ZVTP*ZRP__K>1\Raܸq~?$$`gg5SZZ kz=֭[˗7m,wʕx0g;mtغe{.o݌mz{O"S ߉0{ę~ը mtB+jjjP__ς%R JeLʻd2CBӱ`IB.CPC}Ld2Ν;+VZroW_ܹs5kV LBg`zT{O"SeC >Rx #ᨰXԲWQk# 1:s-=-%%%6;r/%~ysc;:fS*++CEeM[ 툈:GAALؾ};^xclΝxk]#G4;DѡC4-ko[b(D}#7~ʹ qI=EDFFkvߢ"<@LL NA͛ؼys35z;Xfe!N\(?$c uEdx?XnԾŸ-)\JJd2ܟy5e\Ad[6 -Z㨩w }Kddd8p /^====#G͛xW1en@ @ uĵ,cl?\¬"Ͼ"ZM_]6O]/Ƶ tm?Uf7}|LXzꇹƴyC|\FYmSxI\ki58x@bJ0d2j;b^8|slͷOaŰP+UfFu\MDDDD6g{dl`HQT^_R'AYcJX !>uݞ#C j%N0#"ꈟO' uUؑvVŒ DDDDdsvX28FJO@貨ooDiZB&Dz;tC䰷k8Rc6VV7'YgoL&|ˑclңc࠰3uZ|sG 3[zs~%DMr}},UZc[ Cd3~++ pˑcl3[' Pڕ~y&3Xl=~6W JNtGz΀GsA\| 8&Q6D݃J""""Y? M*H0GX!9{wLW. Dt νeCu,TI{t]=d${"NiR[9U8t ҅v(#"9ˡ|yu DDD^8~$d:^haٗI0=a=Qy!_8N%Cbދcf/>ۓJoꌫ8zYOlz2|zV-|\uJ !LRuOw]ؠ6 "[|4t:VI]%6pJ!,TQᠰ& CYpD_\=^kl;(yAilku|y"-?bON ZDd@!䣨J,TQIpQ3 0ௗvIm˩.? A#“SŽ*8˽*߂V4A'L%㹑YeuUX|TlͲ=X*z)^~Ǟ.u:Dd}6gZw42'7SJ.Y{&SY'qoJ#fC!k*Tjjŵfd[v#\:cUϔy#""d2BC 00nn-1 (//ACPV}L&3dw- Mv3? c)8}Im.aɰ~>+ߓӂC(0-71N.ƉEBl89# Q_1+=x2 ffuZ|zy V5ߥz)L_? i}LII \]]WמsNCii)ݥN,8vw.~цĂ;,Ğx.*EprPb1v:/`ڨŖ ;m|8Y g7XËğIut$rK\4u팈P(P(RBQ(P*wQvr%~9bYm)%`{ 1Og<6Y|@O![o@3rLER8K`oǏD}M'#dkv@ 5)º+Xg("T|@f |"""ꣴZ-T*iĔJ%}zC!c1=P}ri4edv'R ~J{IQ)xeaSoNfIB,ԧHImŰŒx/0#ٕ}(6e,sPIDDg U0z4LDb!2^u: r!Ń(bT=şwFEM.wƗӑU$W?*7{G,:]ɻW$zUcӭBlvpFIQO'DDDDXBG?;h?C)v x}!VX^I*g{R؄pIDXv?{RpazDY?%}/+⨇$̨a n- jŵBlObKhBF!Fo L2~8+WEbr 2>n4;Hg}~-+wMOxvL? ҷ3y7'#Q-w:e#~@;t.Hu kߵ߬Y0dX}"RS˃u{TqBPdo Qp T@2 ,Y2 .mlMtG=4b9H}`ƝshTQEP O;vԳ>+p쭷R@@^x")**JNRrrr{կ_?թS'G_x/ڷo_^,BKƷ8=9S0[-4$ĿF6=3LﺪkPr53ykOiqQ#Ƭ}5-WK}jqhyAhMC[5tW d#ԩSo>͞=;otEZO<'Nj՜Nx/_z(ԯ__m۶uxȨD S>gcPEƚspMQ$#ߦ0 *ZP^xȋIiR|]|ġ~ Apw#ޡ` + ޹PI}oƷ[^&SgTbq6lƏƎVZW3֭bQNru^׮]믿RUŃK[:rٜv``U8pQ¡QWALզ굟tLO'Tq^@&}8\ (nuh;{F_/yVvwD{]WF&g](E?-Ejպ6&GMY]wJW FeR0W_-_y-X@ozEiӦMYӸqc+ _OS3i{s|.]G}T͚59s()))u9R۷oפITN;vLYyfEDDh ɓշo_۷OիWי3gTvZ믿zJ;vT*U4tP5nX/bzڵkfܹsv:WħMZSـ}wd?v_CF\Siڞcr Q˸ŠHTzC[XS?3?&/^ }YlW8b1땭5nEp7A%XHhʇqK w*bJ5[sz!͘1Ckm6n׮]jݺNIRӦMstެY;<<\~aũr}YZJ R߾}cm߾]^^]ͧJ*pҸ/ۢ$ AVV^dU^.MsգyELwt>q(<t̵a4ڛ|xL;XY_ZtlC[*Z iF%HwSZp͵RL&ZjZj|Ojj֬F]ve{hRJ)<<<}zx``.^d2i֬YZjy)<<ܡυ GzToGrr~9۠ fz%$^ks} ,}9IJ虙ҧGO;$\}U pmu7fS,ՑسRme|JkϸyqLjJ.(GK_j=clʏ>&U P E7&ר|էO-\P˗/W.]ԩS'-^X>>9e [BBBs }BBBҷ]M?y򤤫Sߑ3ZӶGB,oNMV,L֦uک]EϜ2*"TvٿE;FӖ^u pM .-裉44(+WA?,W,yu{V~/6zu||>P '䋏I-̐+רvZu]t hΜ9lO?nA 6߯-[8ΌTHH*U*/h\~znWm1ɉi:\ަ3 i)}wC[Rzyo**?.mӖ וSb&=՟Gqw7P0|ț"`.}`uzہӚr=۴EG7g5ѡ5N[*3J5T|wUzu]s5X,7oJ.5k湖6m(99YǏO_T端ŋsN]vzu_u&׳Fs|X?ʖ-lӦW~z޽1bMի+ @_,Xsױl2s=JWZWҎ?Y;.mPEX9vW UT ;zG^HҨY8LT{ꝅW1@yozfs2LDW,z~l%G^|JPM2E۶m+x饗}v]xQ.]oN:qرczg$I^^^4h f5iD 7nX%IϗdR~ ke|5S-6FmJ<{iq'h4R[m9>]q̠ Ÿ tR6/I5R2U/F6ݡ-J0a4'zmSmDtЍUT2CP ঞ~i%%%i޼yZ8q^z +#皖oqhT%ՠg煣zwb2zmPUˤ)g_|DdPUc{u6:١}hZpm9P\=РVmж sA7fc]DH%N*BV*TPP~'۾^^^/Aekck]:׷gPEyw.)VnRf%$&{@5XYR6@n,o7ftnO1aAm>8õ4wp;;nzw|4ã AP Zjԩ|}ҝ_^&&]P_Җ۪4܋KI5bRCzd-StlXNv^eT|SS29\yB_thZ6@ i,o~P8jJG8:}Ž? *;xٌP+XBP 8 +nҾSOm5K5:{ơSz-UB{v3 = /d+[Z7Qb4v7vq:N^Oqf2I5R#wS3lqߥ733j\$uj\3g# nJvٜ'} *{i3 \vhjc L΂ ֝}{iմ+yqc.uDWtAUebjܖZˡ=< Xӻ<`RUf>TsU-82=nVn9AWﬧ>m+TF=իVk IGLhPUKIҰӌfkkRK<A%wڒ)zr YiY5v +TTABR!ߺby={Di;xPzU$3ɤ Pۊ'>ҩKUQlrX{/ph\Q: aAP dcDtgmɖT}VkPUI5z-;ݡW;ݭ_rG2rK ze5Ut|Tth?{9F:EOT.^Ӡ5kI`}q62A% LzRC{լQ7T$C~a>8HWj`Peu0}t a=4m:lPe5h.%9-ӳy}XSy{1Ujzu]>g22HY i}u%vF"r䥷P}jqhجz{B}RL.Gb辕o_}i:THqwmiSUۡ}89q/z IDAT3[[VԴMMH =jF0C{b _^W59\9E$F9W.nzZ5˄yMJ?@%'g="'55UƍSfTjU}:vX(Z&/>!ñ;X9U'hqǶ_?ȰjiM1taWӜ-~:|kYs23 ֙+m lWYiBH ]uzUwg7|+^f{~SP|jñjiMOzPB<رcu5h̙XƙfS߾}`=Z}Ν;.](222P2M>]˶fӄ t"d24z-21H3*R~~WV8\'K[*ɜpb`9-zG"BpLլ zrU/K?ܥQ&}zQ;pGRp[~40Vecܭ;z[ qOi/k_dkݟQe Q*ȣAi֭Zd.]m~IVu;Џ?(ɤ)S\[hw'2RX,0`Nj@ewI=sζdҚ5k4a„"1ɤazgXE41&As&[R5U|Gз=FAh^8'jW,EcڨUDhc]PW6G tGcuϻOrL}:='B&2JnGo_^#\>׳Td2vOe8~ku)D# k\R]v)UΘ1CݻwWDDDz[hhx͞=[V5WZϞ=u1=:ñYf駟~R߾} ,}]iF7pC;V3gԙ3g g( K'Ъz|tm>w(èJHVeπz5(ʖZkHZ+f舺fr\y~-5]-v8jjᘶ!3aIO5MurU_NVou$6E^є-Cd9~Ѹ4w!K.V=X]V/sk{S@r?\Q=k4W* MKϑ?Vw[&Jo]_ 3}>&YKVfẹEEuBJg˰j Z编m?\3~ T&_x.Ui땭cfE n£"WkUS9 gh=_4CͶgX$55Uvxpp&wTl6>}Z+fu:[K&MFetyyn:EGGn9&I~,YBPY}JfKt<|>?V?V(O BJ0wK.^:!^&Z7r rkpzYxX֞pѤl=%[djRʥU!_!}w9U W:}NP•WQ>Po>pnxʥ40-9M^ }v^8]UT10DeIQWuJsIY>\Mvwa,'QQQرc;w_5WiZeX\.sǾ9S1x@!!Y>ƟQ߉HGQRA!yb&O͛k֬YٳNٳg+000s}Q#KrK&5)N>U#>DCkGkx=G% Қc>uG|^|iNU埴K+g>rY|||oKIIIlɓFREHzַ6Nkޕʝ+mH9eXtrbѫO!WT|TPDի7fh߾}ʕ+\ˎ kתRJNz6($4TEtR=%ߠ@ϾW_{M~7m߾]!Y=mbnM6Mۧ ~mpA{MO?-Zٳ5zh:ٳ*UTʕ++2I&oܹs5x`L&9R/$)$$D[N߾;3O{=!!!ևU6 H4T' Ax԰z5^R#U.ʥŒ.nBRJ>}:}T~g=Z\N:iڵTRy9WX+[Cו}b]vW^ϫQvޝ̦ڵK]]ݻ{ZOϪW^kTF }cڴiV5x`-^!˕wBP @ѣGHfΜ_ҥ5mڴl/P{SmܸQ$i͚5jРA5EV'Ooժu%+WNՕǎ˗vjѢN#Gv15i҄.=7nz)bŊHnf}Gzw0M:U/VDD$iԩUʗ/p5Jj֬)Iԩl2IҥKtjĈرVO?TƍӎWWlZlnv wQ zJ=P?+{Nԩ$ :TZj9L^h)ͦ)5J*뫯C=d}嗺WUVM_g:}~B@~TbnݺE\Kΰ=???5n8e˖Uٲe3=>zh5jHk֬Q.]TJ }ڶmojȐ!.CԨQCSNUhhh}VnF=EP$mxyy饗^*jb: GP pL2͚?oե jѲ%@*(|||4ྻUnLl"fd2]oJ*ooo] fXmtA%%fe`)))a ǷRJ EGG+!!AF"d6 K$1Wڽkѥ+,,LFHbD%dZotJ0FTP%]NRlL,ѥØf+))Rb`t@Q3 x8EG]eY?w^nSυ t岂J]$&[?ٱZmJLLtzNGQQQnSOjjcǎ˟3HIIQBBԓTRTJdt)?GѥHrrTTl帞P+W* 뿆q]{ڽ}>^vݼw;>ŋm6[.^ bbbTfM(?id6[Deb͗dvNt50l{?6M6M^^1jd2*55UngGrzl6e2Bwwٝ>;{?E6M>>1f]v^e*Œ7t;zݚk/)VEfn_vm6m{مg}dKs?(*****(6mڤ^zbŊj۶NjtINթSG*TP=i&˂O秭[] }eMm߾]7|:uٳgvjР~i%%%PeEP @ fSjҷ~>}h̘1?~ѥ]VsZhO>D~u-ȑ#Fra Frp Zfy-ZH/.]RJ]ؽޫիWk̘1Zb#FhڴiF7r! 0@7pV^xڿAΝcjź[eX x2\;/m?Ͽi!/^hrry5k]\uj֬Ym|^~e9rD*U2:d%%%)444-&&F9r^z%X,ҥn&M8QڴictYpsÆ |r]ƌڶm[A۷oW`ݻk/)VEfn_vm6m{مg1hΝڱcx Ad2iܹUwRJRhh"""t94i4tPK8s挾;b6%IQQQKVpp% i9s"""2X,5koPVԶm[?#*(/y{{E!!!jԨSx+VUGѵ^kt)w֔)S4c Kٶm$[n䎏7$x:uO<-[H.]9sp 8(]tΜ9m6ñ:ÅYZBP @ tIUPA^^ TTIN2*x9sHop%pw8p^}UժUrAN8J*i۶mjӦ*T˫s:x-\PݺuSN|P .TΝ. ɓ+f8VR%>}K**(RSS3跏|||"xsi7nʕ+gt9psʕ+aÆ] <ٳggyF'OVLLm&Ţ~)%%Ɩ/_e˖oV߾}SFo|w.X@իWŋӯdܹs^U\rEwu:t蠑#G]3fhƌ2 kMOWኊ҇~:TRjҤf͚j׮]F7~z :TҥK5|+UVuh.^hDY ,PÆ ձcjժ>ǵqFZ\~^owDP @ ԡCկ__CeXt}TիW3I@&I&LнޫaÆ{_|QǏWXX%MYV 4H+VЂ  6:Mp)mڜIRݺuUN. $((Hcǎ /dSgϞcqdG_|nIFRڵuIj׮Znmt?.]޽{kԨQJJJҸqԣGu EPB[ .￯~A:uhtipc[lܹs%]ɓ.W1bʕ+ӧkQ5rkFk֬ф /***JիWםwީQF!ԩkj2dʖ->}70b.&:nkOro>n݇\xqfULLLu9^R$$ݽmfwKۗݾڜmv <\ GP p GP p GP p GP p GP pAիɓ']AJIDATr_V2et n6lRT{1iTfMM0PjT@7Nkڿ[^Z_||}}o߾e63<ƍu߿PkT@?׺u7H.]{N?7o(**JWpW֭Q * ڵkÇ^ХK /(<<\ƍN:jժڭV.\vxJȅ_]AAAС͛3f/&Iג%KoذAΝS~$IqqqzԢE f͚4hΟ?mM~~~ZhCۀBTM8QM6UٲeձcGX"/BCP PtiM}8􉌌ԛo={e˖zrػw$iӦK-\Pzl A%Prr |P{G}>,}eV  .hu-($$$նm[j„ Zjf2=V|yIҟ$ۗ_~0T@;#Iի!C(!!!s֭֭[ӢEt:_~[-[ThhhJɓmIIIۍ7V@@Ν+oooGعs}M*snsu֌FsVwNɩ-ӮSi#WzI?gΜvݺufl6O s-ũZjFnd2ٹs܎;.ՅsR8Ϊצg7zimrўvf -,}鼟٢::}nΏPKsBg:o;[]0t5e9kZ89@23To?Jy$v~i?\qZۇi&SZ:tIJ߮ӹ}EPӑy|)cHPڜ9fn<@V\n m%aealUVKٯnjjpk2a83Us2-WNSl:(ɴ69N!1Q Ոʴ#&H)Ώ}K5ҹ_^FU:jn/9V5)]ꚕ527FTu*GRqNqQi=R95lW!ea(:[r}[TnW8/#fK!7^V บޝ]hf4[)P;o;Owjwf!|@~ ]-4LvՖI]Ob';g:\9̬W Jb:۹ )3 ;F`uQٍ̬='dmA%')Ymٍn4sV)%Q_vaǬFVm;9a Tf 83Rz#F VX)e?2s]9%[^`de~BJM)wPoNm:Ց[-]V(4˂9]JWd:Yt' 2aG?t7aeng W\$,2} \Qi99?2'mm (3듓c1d^Ya9m"9y-Ss{MVR2>XOPܖYe% ZNGR꟟2̔ؠRXܞ;?(>2Vsz<-p嶆.I? MNDWr@DH]B +DX=PԨ ?chH)WVaeN7pӝ'xF=$(t>םxLw~>и{ {Pa~-CJ3B8w =@+0BBIa[AI%WAnPЎP :qqEƓ¬ݓ =*LS| @)#4%xs5 1tǚriY2Mqk@UlI{%!+ _ (Ӕ^xbN+]Ip/%&tFPEr A { P2>P?!H%5IENDB`Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/screenshots/sin_cos_dark.png000066400000000000000000003152341511351636000271200ustar00rootroot00000000000000PNG  IHDR*6hdsBIT|dtEXtSoftwaregnome-screenshot>&tEXtCreation Timeons 10 jan 2024 13:17:18%8 IDATxwxTe>$!ZH7eQuA}qPqoWʫ*(Y)kׅEX@-H =3dɴ$rs9;䞧DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD&Dv)Q G>Qk0+DDDDDDDDN 0/\GDDDDDDDD]:lpтzm0eG:k$"""""""]9kϯ:vX0"""""""""oMh^h|X^4XS@C/"""""""" pm:l[(kB|~""""""""d6Tle[ BQsK 9T`ւ`DDDDDDDD9̈́m)kmBp^:rp^;Bך[Nۿ-DDDDDDDD>-:P5,5|Ah1jh*[RzょDDDDDDDDD%{ﭟ* 5HB6mo󈈈+tܟ05_Fh ZR4"B'!вAgKh Z鍷<~DDDDDDDDDh`ѕQVF[H=J_mDDDDDDDDD5 . fXɠҍ@C`Bhzs7ҟm. .T!㸿#'^%9s=H_#->8o +!L EHٴ2і93ۨIooB2@jh:B9J_AHHOA@߰uAeCJC?e #2oh`)yh>D,t=M7'%GTQkx %7˖LAekGS$Pz +_n igքQ?2RA[0CJoAcMJw-p)ptz4'gʨ+=.Rtӯ湞ֱADDDDDDDD G= v o\?:DPє'9Zi(JքQ;2JC큄?A%F`n Cɦ?a?7a *cBJw&q;QK5$n7~NC\\D)ذa6l؀`߾}رcG _ p7su{Baa! ͛7_FΝ[|M\~=t:]=""gZP(u4Mjk_-HO[;upLGPsmJ""""K/aϞ=vL& M6a˖-t{GAѴ^gkD=""$ "7qhNS-Ӣ2 t4@EQNDDDDDnt.?qqqO뮻RE$""W jJ9j]$l6X,cfI B]t\S&&7z. =lBTmEDDDDDDAnn.̙V+Wbʕ->׾}n:믇ZƪUo^{5L0vZl6\ٌ#FxהNÓO>#>>;w?3ghn?ƍhDeee@_~%x ddd8k]~=1qDcʔ)P(={6n6ѣXx1l9^?+ |8vժx㍰X,xGOlق 6`ݺu0a-Z#G}틱cbxG4yM7Gٳ뮻`3{u~aɰl]O2^a(,,ĝw ͆'_ĬYܾ7L4iN8 &4[ҟ^o FcKO4Ӓ} mmrHR_wcw[{JȋǏnCP8ێ=궯 _QQ$&&f233W_}5 ] t cl6Ν; *woK/6>@E|k[/@ٳ' nwȕNkUH!2[to踻ݍ;uw͈*&Ž{ JZxgC`͚5شiKZFBP@=# A>r̙3ڵ+.s8wN< 6x_FSZ,{e믿Z.99.L&LZQ~=Z qqqA;_\\,K0֬l612AeK`o&BNj_rssqw 33;wƅ w^8ydȯ}vFX"*/`cڢO>|,ø+0uTt \s <8K~ѣ߿?QUUү0f+X,߿_uΙ3'w;7n׿l6c̘1l_;Fp;?lT%5˗#;;t9E:t(dNN&OQF!-- pYرVjeN[sVWW4aɖ! =o ))A?7xFBUUvɄ[n%ߞiFt9S(j:ͫ Ib͚5Xt)֮]X,Xe/駟v _yy9㏣k֬ABBbܹxk. 9^uZVPPݻw_| gb[o9ϟZCsN>SO=>|KJJ‡~laّ. Cmm-z= 5%5 {9]ӧOGFFt:t:3f`ڵ55CPqZVhYW{ *s&L={[oŌ30gs=7n\@# Baذa-hIDZv¤Ic Æ 1}tݧb |HOOĉPW^yeee:us^{ $a֬Y2dF#ׇsbƍҥ z9s栴uo?8>cb̘1ïk-U>O?SNuٕ.KJJˑ"L>eee. V{Emm-bccⳤFヒSB`Μ9:t(GyEEEEӧODze"^|}}v,Y 6ŋc:w+l,/׹=RiÜmVV֐#mi?߿x/]ovנE7oFw܁sy}v|8s N={O>$fϞQFs(//ƍo:o߾WFBBF:,_}˹V+֭[CTO?ҥK}rssCwިCQQ~c(: T??Z?0J°aàQWW{F"" N{ٳ7n\!pgh,bڴiz͖0 Xvs:+sySh4tܙ!%E{999 رk:l߾_qĉ0ThVVTOkll,+,u\W]u R:RGV:aÆE$vY/_DUUƎ^x,gw}_~-L21 ݞWXʐŋѧO,Z,#33%%%x'[unjAak3 O}V2n{B|ɓ'<={_9裏㫯5\lI&j⦛n/ɓ'p]wl6;G^qѻwo(Jٳ- =f: 7܀#F`„ 8|H+UFc?z,Y$u8/ 0sL\x1%ydZCBG':7.m۶y:wS(4i^xV:)--ܹsa%аyPRRҪsAMnNߍE[Pб_t)f3͛׬!ettv!|WE&LhR0uT : 4ۭӰZ͛7:\pÇǏǖ-[}vo' |й?SSS /`ر{,* .\pDV;?T:$"h4ԩS&w%jXSA$YhL&~5kuH/bDb~03 ڂ6Ȣ֬Q|ΰ!ePTTIзo_| Ae}}nݺwEII {9;v |L&>MGLVTT{qnu]3g%SԖFF:\x3gt˗/V?ӟuV=:%&&@!e^^ק$""nnj3"]Qb<''qN֩t|k֬ӝ;X5 7n3<2qݎիWuwyae8CJI|LNNo޽{ɓ={bѢE;wA]{o;(F|gPxѹsg1w7:t(Z-mۆ~ɹnu,w8ȑ#ǎ ޲2k@E\uU(\aeAAlٲH!C0$"""B9s&󑑑+VSN.hL+Wt>o GFnn.n݊wڅ{w}7nl޼Oƀ\UUU5jΜ97|NJr|͈Źs+%ԡj\xwuWxWC^ӱcǐڳܶQtKLLF7(%QEkXm!%͚5 iӦ!--m(I>#/qf3zW_}u/A֒%jMʦB:ݻ&D7&&?KK߂F%7۷oŋqmEj"TVTTmG+Wo߾ضm^}U1h$2$"G}(BeȲc˖->gx2a`Eku8pꪫKeffb/t@qq1`ʕ/S jzNɄ@(ݻwPVt_j#_i|Ш'! ;#5"2eʔ\l66Z|9T*1`L>+VhQX]vh4n8۷F DPDBG /2.Ae]]]PyS}DDDDDmfþ}\Kte׮]qȑ`IDQגC:gQSSĶjjj`ۃr.?Dn_BTFUHDDDDDӑ#G\7C0x`l޼'N  B`?~*"" zuJ֢>HUEm}D%Qn^""""r111x뭷`0 IDAT!"֯_Jt:w}(((~ݻ믿Fii)O>شiΞ=CDDad4!rGVԴא2dTQQQQҶ~}IRpW#>>zգgbAllVL&Յswڭ8qiii曱~z\p)))h4acC:h4j(J̀TQ#"AaΝFAnVVEmm-L&J"""""" )S#t )Ct$Q0C㑔.(bccjVQ0,5):*^BBAC:.ҥENs2'"""""" &~E{aQ?DDDDDD|m hJFRFJ"""""" :>Ȳ J2J:DDDDDDt *T*#]Q߉DDDDDD *=hX}3*~GL4)סCˑ@vV.# 2кixcJ bD2pkY* z+AC=>fggcӦMn^|E1b,Y+VYǖ,Y_W.M_֭òe0h 1c`˖-e{/}]( #t:V^n yf9111~D ޔmNH$%ɓj=Q4 bMJF![]5r-ٌ~=V+z=>s};1ڬ-.. ,[o͛7#%%jc-<}QǟgL<6 =z@^^Ǝk֬ދ"_p9v`޽~DթA;)fȑ?>dY… O?dYƉ'qXQK!"""""#*LVy<+béxNHu}J '&#;Udw>^kyf:u ҥKaiQQQ4(JǑﱎ*,\Fee%V\$v튒f… 1sL97x{,ܹsM޹R2a3Y^J [Rx)1H=^P TTT`۶mA;ɓ'><_Ȳ9L&t:h4$$$yشi^z%,Y.\p9Wii)bcczDDDDDDD GA2uN2FooL~:Sf H~ޮf8!5*%Io[<ӰZX`AM8hfύw;%5vXE\po""""""j˸FesO_IUSӐ-w,+x.XetJ""?`ժUXz5 ;:q .1xC*E1c૯bԨQE>#ZGᅬ#"""""j/T~}dg{ʪz s>_+z=rss;/-܂g}E۷o8q"Z-x \{6lǃb.BmkZ*[ǂPo#GĚ5kzj1",^÷~F}O<{ O?4^|E̘1 ´iGDDDDDԞpw)B4@IVJ2#N\>M)XEۺu+n)))Gx} r[oAcժUXj]p ظq#B^"_YBJVMU\>~;O<9,> ѣ}plڴVPPӧODzeGDDDDDԞpDeNo$7>kE'N]0qD0zhhK`3"zWT}sG hܡCb۶m7oތ]`0ԏ=P [a7dB ɹŒ%KгgO}f3^y*:Un 7ahPϫ=&jnCn5VZ]<ؔqZyB_rnܹhc?T*~$&ZeCcPvZB @L f$[!9R/|T]pNrE7#*! nB%b?#Je0lؕ;5~xFNR}ĊS*|&$ɱt qV0#a$CD')QH.I=!XTT1tS^-Cцnjj5 ~ٻKut *CHyhڃoBy'3DjΜ9f{jEYYY@|$;vڃ BUuCXe}80&)aLRL 9pr#P5ªY-J9(QdC[[PtJjj8QsQLi"] 1 =>g,թ*T Vr$*Q0)Էt&Q 8P [m "&&y€Ae)0gh񚕊Bh >HJ"6_͛m݆un]–ñc^>P}SQ%Cۯ+%+/tRayc!U Ow%אz_,Z@T5x,>ҟ:z-gQQQުsPdXP]]HBmLZuwSA 0 ԙ(Uz"%jQco' h C`};$M_k-R(^gȂY2@Y 3&l6+ ?0Cka~##W@4@uzC8z1`<ðXf f͚7|PYYٳgcȐ!X CUoY]‌d) 4p úAgܺ:"6ړV( 8c uڬ)7![/`OUѧP "" /_*psxTR p 8Wz\hn; v-K!- `g1M ZKV`sI<lF?CQUDנQ! a { Ȣ7iGWqPQaPFo*%dȄa;ZxbQ(/`+&"3f 틸8TVV &MҥKuVl6TTTǑ#G_HH2PV8p!LpezChTF#pte+H1i )%CQ 춏`R Ee>TC$e]F2' *vDq޳wF6+4R@ 8׾aDwY)>ptpK= r}E!@ s (.N| Ik`KKi\ s9(2B3Ǵi`^tFq7"-- ZǏ~!'@A䂲e.] {n97{0)b})TŅu #3-NG%kkn('A9K.kT p.v h CrJHbPIDDUUU駟\PW~w_8g!og73_:OCz8 9 BTP7Tg6y9$ T? a.e]'?M! 5Bg"C]/zR_jB_ Uk<%\)Brg=K;(0g̀=ToRϠ7h,|H qX(RB6̃;=oDDyY""""7#CTwp@0g6k+TR\RMPyu ,a ~]"`qQm?;yN Sf= Nv]KLV4pېRq!ڽ/5lLg/vPXYI `ko *(p&Bg溞YF4lF@͉*={fT%[=:ҐڼB=.C5s"lƅ"ȌEEnW@ْjH`L.;N4_#)CVz k`X$>&;H ~Tg φ=!'5[ *}g!+D]d V 5gߵ]C}tTE^bo@Q1K`eXIDQF;o|w+F!Sɗv NV6&`8N܂oAyn[XC!ړ 3 Z1$""VTAz'B84f\!a%@TA .͂ KE" )DDDzU&H|ʥYHAq׬AJp )a% #S թPM0Z{ [/#S% 47 )-!?לN躙#=yCJ9h1 Kx%Y;e]E%,}k>MBA%Gx/PRڞ0cnmVoB9P"7ae9"FCMG!o?;_ @ťYJ0; Qs!:6A,5* YP,ad]FԱ0$""jJ%6<<{nf(VH+@>Kᮁ~K_[h6n3UKae2Ć%qQBDaIa`@G PQk -|dЮt欩3\VѕnUz? Yˆ:DDDf_|o/wz+{ _tݱS `qkd{/tPl]dQ s! :aVҜTP}"T lL!7.**2=ni>R X=<+d8,iaι: p`PIDDDWgVmވ0'0k /I鮣He`MJԧ|D:%@ 50h]Ci_\WA@3[CHlC`MwݐM=KgE  ֞ri3`ɸ3BEu *($FH.HG0Q` t\}+(.PQ42ߥUK: a\&^.mJ_l;K=A6!t*i'$]gXeaKPw6buB,^\nr lGD*A%ΩJk=*#SSX2'74@Y3#TQ vh?`rbi2QR!K\Yyu`?)Z.R:n,a_vձO}K ԇ$Y{"jTQHK :u I1XSG<ĥM= Uą IDATlFhAjKZ=t*ڭDۛ`O7۠|ҥML˞( Ÿũ, ;"TQug>I 왐Eud"jP :|K "*(0] @zXvpE:]ѻL.6YJBs=@j`PwU̼ܨPÒ3{~"Q D❃:|geȟ0k$l fB>^քu¥M+?#TQp(vCwXgk{.8rC *DDEjk2OC|h$sy].lxu,.HD,9bud ?`ByaްvI8dE*"jO /yg!i_h՘!}+pQAmM%F#/Tǿ R\/g(*Cqao #jg8$£@ PU w K[f9RLK{(*yxFۣ>UMl`OȉPED^ \۵rhڲwmKpc;;Taɚl9ͱB4]PQ%@:9`wi7YYD(DDD6 wvmf0'R|i.mb]1T'PE!"Y)ed 5/DD~S) 1K+Iv1Ұ1"LK0̔U֮Ҧ(ݍ4P}ڨ9 rm`PIDDDa%S\4 3ԸUJ )JֆQR-K>֥MV'UDDm0.a'F-HgΣ8ꖺ˖wvoguH 9dL~Äof2B8y3d&0aX8 8! q5۲-/[tWcjIsWUזSϽu)lvѺXlA.eZFm۔Uf:2".\$""Zp;\[Ȝ[!Jhm.ezWwRbfy֬"uXS7ONZt BpDBۃB .??3kȽRtd!.eD=PIDDDi'@5׹saVoTbZqr)taGb tg)ty*jA>fVNC6ŚYsKIh+JW6Q}JDN>DY DDDY0 }םa͚n@Ӿu bo:YpGb_2`> G xn"oq)#"q|<_?5B:ڃd% H\N֥2ѹjmg](%ॗ t)#W@"""24'7ع3[r~uQϧtyUs-%oz.K7Z*2"" slzҝ= ĕs\Jhj>E$6Vdso "j/ȼBj. ..TknXTHuAYg^v'!HUe 8OhB7ի;m Y_$<Wc&.ea\$sa: q\%_< RFAH=g4 %^R.;EY `q)#"tbI2%&Z\ERB>{@]nw/ :oWC~OΥܥw}PEk6hPIDDD6!؁Q q\Wҹx1UJh 8gd??B+%DDɱ7 J_Hk?Sjlq9D};\@f#Z;0g@oqh@_%Da\$"""'ԅ.q(!\Pǎ+%$}ξѥ(S&/V@c@QXFCʚ0t) ;VE 9XhPIDDDCo7fVYqy3xyzx}J̬vnȥ(@\'}-.%AM8Jg9bدx3 4å DDDdoK+ fssga]$|VK49s/!"(ڻm Ak\1Zy@N>v^ uJh{ZKe; JH֥K.J ]v W>h-z=rDDD%qiۭE;Y*19G GbV*sDQYVbriߥ2A?T|8Cѹb]X5=bF|Vo##Ȭ@o}D46~ZL!rߛ -D.B7֠/_-ٿZֿ +tqQ6[JL\㜑:Ѻ[\c$iwU֥-*2_/Uw R`Bw-H"Ѻ*!q,v{Gg21 _J,[3 K )bK"yCe8 /#|] ,C,cp~!բ/cpN/Hax?>sϧbťn35_8D~sҚ_+ξ̆w6@Swu7"DM@\;_ >`OK yȰ JH,,ӚF 9'~Hjsp4 D¢/%Be /+O^гw,]pYW6lSO=M6a}rHi7DZg^ә]%$iK!V7<&kzF90t)"J7( *1(`skRv[M9j0Ä9 -_J>D^κzR^UҤgmN蹬<GYYqREn9 D~vZ.d3Na }Zo4:=w)#"J1vSLO (@Ԃ|UU40}1}d*3/AD'؀>"*S :zwEuu}G'? Y`iʈfZ<XNt1dkpP"^xr~^Z^Kfӳc+Dj!tk_:R6%wrG#ZnѻBmLgaG;apcq2ɬIi;˃cjX'u ,^?OchjjLiO|8xp򂦦&,[ k֬ISf&л⢯ӽ"mv~a ?pZ(+j/\_wUZEaC |^HJ7iл+1s wUe-]WUBT7N>^f`Q]8;Ѫ! wRhy"ң"iDU\L23⢯17dG/ǃuy\/~O}S0X_5k֠?O/:TZnt]믿>qhO?4>)3vCs &ՓB/|`書;q]wa6Nb![ 8vUnKJN!0ZHe;_sFj$Vbu-c7%ĝx%67qqNi~+1-Zɔ=gV0~.L˸Pdv‹:@Ƿ.CqP@SLs=S|x!>O'@ooo±/Ɩ-[׾qoǏc<׿*~;g~z|ų>gԧ>57_ش@ M6oKذaAa7t I"˿`]u]8?q7{d zAEkԗQyjd$wSN-!_u,ߢh"f5~(7!Xt{\ʆ(ol a.|8rugEaTkxxwwFo~1=vn1Fnju]o|p+V@ww86l؀/g><>@ |=^z%-ݻ?quoSO=}chll4[ZZ`Yk׮I%Mڵ"eɻD6l؀RJЇhllDss3o //W]u`n݊>?~vMM iG?Qlذ7t҇OJ̚5kJ/DcɻcsOS)ۍ٫rmr_YJ)1@ZI}Hk}HEkݥ(4\9G ]."|S'i̊{Sr7eR[!"jE#wU%Be 07d`y} %,VjK. 'ԴEWW^{-ySKjjj`><1{lI囀9=@M7sFuMJd~ֲ{F2Xet1+"XbAPW7]gA JK+xǐ'|O>Z[[q뭷< /8.% #|}E]V,GS+-K4MM8v,v?@UĂiB3*7֭EfI1{l Ģ!爋Yt+突!Ƚͮ2#D,`'XY Q`` *XoJ.4qOl;t?s #2|~?l[l83][};qoߎ;c㪫q5׌$J>7}~δ$qBb.eVnsG҆~F&%|g_VBV2؁+'" +! ȕJ-߱OX;{Z2kSka.";IlV]h>2"\LpGs/.`xx=h{N}2o' w q7(R&W Q?@CZ%w mx7)hrm O_(iAdC&%4S C6Vźـ1vN)JL?2&?;%ZkK(Cp2Er?kgflݺucpJJډ cLgA(ޞjS.l۶-ONx4 5Ր9j_5{SInVseX;OJ)8~C%ףVr sgwVbfͻb=Gf0L8r{z܆GrX Q6!t?1TWHY.`%T0BnSr֓2Kii)JKKq1 աjܟ744```7o^0YɲgGB#='VŬ٨<{Hf4쫈ּkM3`V] _&FD6Xy-!s7eȶ~bAh$&̤I=f:QoyJz46X#@eлPBFVI[mxF; (u-8ч>S RYYG}+V}}}-[y]w݅ `0100o<>wߝ?G 5`u-oE a;0/'=Gh3nYuuō=DFǑڀA'JpZ6|- K}YȼѝPI3*Sh݊`+EӺs9NJ" M6رck188}sxpwc׮F=z衄-ԩSb [R '.8!M)ۡ"Kqe#VH_>xs(ÉlF IDAT%&*cȆXW+`Bo}3d8K Z؅9\WS*RBM}@\ |ݔ'9-҂ċDي. @DugK/?x t]Gee%Nxa뮻5:%5 7x#rssԄ{*P7;CDu!PWԕxSz"}=<|ZܲX6'7e`?%#jA:qܑXYA3/8InfzΦ;*Au#N 6hBn◿B{p's=+V$e @kgyYR٥ a4肤;VB]JA\h4`uS ǀXU3fc޹'HV !hlF4E^^^~?G?͛7K/|2_bmm-oGō7ވ OUu \s5_bܶm;zrB@Ӵ1,^biCV8NQ99 ʙ J0PPX<BpYJ ]ĕpj#pm!:ptqӾ=%ivq`FG7aH)lJ߀޾ > AVbD{}/[}=8}TZ Ĝ+ *G.?kfs/v9;kkgg㰨{BdX`%+r6ID\<&z'h"z#ZvmO~VTニB!~1=VYw܁Aݻ79,$;B,LJ!̐{𵼮đfh=G'atkEJ|I ,Enh!Z bh]f|ɰYuk;*/@/DDq"Ȍ.m6<x衇pUWvv܉_W#?… qW ߹;Ӄ>xWuV>|Hk֬}9SɳڢvGi7ӟ$Mh~E#y&n@D)V[#v)݋\_& ̂|(UW)jкqrѺFBVz3643pG8 B, `Y9=!f͚{ c~'T,X<ȘЇ>Caܹ޽{q7bisyܮ~qC|ѱ0I>".ƛW '(^@8Q rok!s`.UzXUi7HFfhwTN@!;3&g``cxg3όJ}8-[w>Zu9PrCHѶY7̊u7m,VB V(!v3 _ Uύ.H u@=hH̪iI8vD4*)2VԂuF ykڹ}(c[ޡFVhfL%DbY& LC:zJ[,L9hΪ f4C.eC^\B94<<| DD٨k",)Gd$fWslb( 5b,< tr+;F۠rH+Ct2nE*I:|pp;+hF '['c͵=G,tFV]8?aZ-5S ņD)?N@;aF6uU|.%D>\;+0QVB8Iݔ9Rt|AwKIdic ձt)lU-? X ;<; qUBbR4YP'J7h&)~m wfQv8\%yi#zH-=Νt)!*_0g|60C+ams71B,. r͒a0jv)#*ϰG[ ` Qjqr_Н ODd7yUUU0M ]]]3DzÌQJ}O BI]{*^23+B%Q"< s)*hhշ[s1CuhVno]̊(P9E]K"l1ާi,GUU%;*uYU\] ɅJ[,Tڅa*  PowSzYNwC|KYє3T':#!2r11ai_XeDD^1k_׼Ͳ,_Gu;3+.ẀaB,.,4Fx_P.eC4sU5 bAou/!1j9 2ZlQP_e%DbQy⿼˸i\ROD^0vAڟWs*4^?h};GBfz$z%$6 2@4F;;bK˝hJP ̊;Qp2IŞ(swh X3 '7(P3R*Vq=.fEļPTbeߞbV: NC"#U5PFۛ-Tڥ@ *K(h5JHUɒ08͉ti-s1GrlrA+Yf)1{zвJ1+zn{tYBKC-PIDDDY*]iƱW&Պ5 0ZT[#8D4m9P_vu'g_m8 }[wίK+ӜMuVbQް,ŅJ"""*r#Caȷ/bY%n0:vvt4`r7 Q҉k%!4M^ַ J\[v^k{Ky,j`׹ QpB.QbF6Ir !\pndsv`0:qe+;cJbPcCbYΥlR DDD5uqovA0p":2ڶ):<"JQM-VZ8us8twdFk* 8# *(kXvz{~%7xzn%fh"ՀL#OEA%(1=~a˒@CzwNh !z6e.TQV;v` hR 9ZxYVEKD|={v)*lΎT]gǢ814Q0#JP2TGDDDY\y,ۡWZ˿=Ew.Y2D4Ybv1 ,gBs/`*1'}S U_R6DDžJ"",i,Yk/CUeO2*[|ȄCʿ+ TfGI A;XMtzv}]c,G,4 c7 vh{ yр{[?2ݾ=lTB5{ѾCylV Qrqm vz%drh.UwC@`4V2H=w40Nq9nء"(|lJ""",| F?kzɝ؎ID^…J"""`dnsc`\_Ks+s&$sr_+ YV*uNi]K68v9%0 e\ʆQ4 WkYnTK`Sӻ-mjӿ=E e/v""t5^q(βp @UBzޮQ!8Ge6.Te))%ގAI ;*")hCԅjg6"%.U <4}0eX/yJle#ӿYzl{ ].Te))%N83v;B ѱs';֟-wUzMZݥl2e)fh5b}Jbb/r_Ktb~袮I#Hbw7țPIDDD5A u\EL({T wRmM 9JL64}0e$g1Le:z O;)-cf/.p @璁W]!1Zy_u3EޑZGr*Ź*]h>!@#s0e"|XDzKs eɹ6ާد DDDZL¾btVdX%KK(Ah{A*s ة]X8{]k̃ pA)q]<=9(`gh" S=(cɼuGўpq0W݇sp4 wUp<.4q >Ik_B/ќڬ吚ߥl2&J( e4ӱRC?[;nOXoϰMJ,B%y *Zq?㋻X^w ;.]̽2ł1ʾpKQT}@8_]v\.6wp2cQ㐰è0e'9hGވ)v'ʾ:˿9FJO> SbfhKM*(Y%Ko)a$[sJm,s]ʆ(h1eߞ,[}<X4ܑ6tGo.TQKrImcߞw5|J/q/!"! 7nG˾=r숋ݐIryCQS [Yq&,\$""R6?Ei}pS9|ǹM4pye^baߡ!{{ "ҫXM^J"",e6H DDDjwd˿Ew&)]hq&BӾY- e]NvMߞ1g&ް#/B%e,j8D/m/_藔s{ 0cMj~X%K]̈(|o}{r}wF˾2j7V},e$gJ`dNKMv]7C(۱i& ?In'>ɓ'OOu#t؎iZGzΓ[!6. ՅYI9}xH LiIXivvPɟiN@[lbq9=Y)#|'"`V8yM;*oo#{/~! {Μ9spB nz6VႸ aӼ~.VG,0/qr3K %,2f1ܝiNۯC,eiA 7(!NLOiDDDӰd|s=܃-[~_?A?iүm,Gwpw[!7XRx$y{ktb]Zx}{Z<;X}޹dS7ܒؒ P{zi3:߆U~U0_I_+J""i?{,$Ç#ȸA vVVR%vD IDAT{A!* ɅLD{Wbf)wPKCdnd^K״;q:6{}Šo_h0K."qhVZ^{-!K/EEEc088kg`P4Mvͽ@wܕ\hzХLL$}X%ݔ" ;oNNAcgQ]j]v h֖XB:A\}ؼy3?G?CFߒ{lYh,JǮJ8QF3:F|4QYE@_p)Yޫ,<ݣ>)av"H#ϥl&DDDS: "HvNe˖-عs'ZZZ =֭[kgpyJ<acQY ;ϹMtC?;n$fsŬR^-[z!_;]~ā6`vCXFD!*F[.gF DDDSdY0~ J /|p'ІY#  +qIh @QQ?SQJ""i8s /^7o('? NzB .5w8W n WKzb43hpof&)..Θ;Mў\'z?>YJW#T,PP?tgώE --q5= /Gk^BW®->Ō8rd.0玑ǝ!{SL^^/gB%4+V 77cڵ׾_ǎҵPrDr0DUb) xߒYz).yuIrڷB X]K(AGEGAP;JJX K]ʆh|QIDD4 ===;} LDWW>O/7nĒ%KPPP)%.\{o~|gGdRǴH7&q%pXY3[RuʽhJDw)_ Y%QQ.PYƜKќ Gz!;qd9~|.]W$NwUrcd)m DDDtqtMFnn.Ou|{'O`bo\;Yej_ش *);.TȠȌ/41mb 2n׮Uz)*)kej?y۝dht?%j(oy"nK*M޹Xp bŵRρ]Zx˙b7Ejnnw!ܹshmUq!}Hkڐ:X>^b8*e^ R!J|?Ĭ"5ve^b;˾{A.e3ބ!r|=Wyl.5SB%e\@.e3>y1=w^)>2w"ڧЊq&Q_.6 %DS# 8zGf}*P%zP) 溓 PIDDDXh`ŕR ^>45R&[!Spj;Xwf-תL!j |SC*1NL…J"""rUl;;'C.%$ޢw:* Ay.eC\AohVHmd ac!%F_3+ (p\gܵh%-.4w cMh%D^"zkt) +X}-!u*! ;[;XR6D*.T4z%nC@ \ص|hlzA%IJ74"Qc'Ʌ. +Tb_&ݼR p)*İc)PIDD w݉֬Yv:㲊Cjl}.f4 C@Kch>kAL Y% U;G=Pu ba'Pӿٯ2*iœ_ܹt弃/z!Xn04c#7XF|7`&r}J"""Įk]y qX%jOTBMhx3\j|݄nNoV>\G>oؑnLTEeߙ7rws꩷DeM#dM!\ 8xLTz{LhFL'k^$c=05ECD%i\@OS0T.1~c-ǩbRb;%Ce&XcXӤ gfO19"IVScDT혨$""e' \6{O=`M `@ٽ-N&O"fAOc)a.Vԧ%D9+Df FcMK$""eg5٭@3=jd,qzqlZ!F.0!b%iߞ1Ad4Esf&U)}7H'&*h]9M,u7IW`pW 7Y"`وf,b#?\nTS*"O8X8XK| ;Y;ZS4T혨$""e`EKus^!62҂u,Y,{#Sv}@ ˮ_ѱA[ѻp Wկ DDDoqۭ tgE6f ,`߭1E06 y$\cٛfk ѥV;9JjHL1y\S4d`D,kC-fJ҄J"" RSr@vձff⹘5{ꇰgI&*hLS8|]Yd %ߞjˋlruH wSzx >}-"GY  F>Lq,O%D%-u719 1YAN{Z&khhM%RT{=&9y|̹6(bݰ hZ1QIDDDRJ^WT^%GfLr&}8LD-4p,'*4"޷ ۫$٧Sl;4FDՈJ"""Z_ܗbʾO^LZMkhJGXl3 !Y0ZE7ZB@NS4T.MHt-7&*hY̔MVL_5, K>v}+d )u~,d?l:{j ;Z^LTѲP˾D/ y.{ϰSOH8B 6p-XFDhzc"H 8BE+kaWkDDD fǒYieߧ8K1k}:N=9J1YF RHȱfTZSluF ` &ƏvoZNLTђ"]F`vA0G+4Q,5n6;-7C@t4:^n9uѢdPP&5{Dg7-'&*hMғ{YeP*zXj r`D_@T6[OYUJf~͡ MPa\U]aN+B@SOB& RkV#TFJٷ<Ʀ5C咁05J->h75{쇰 +ڣ/ *LTU(χ?zm /_YN8GRigR44v>S뉖\i^2߯Gnr3= `xj=՛DKJ"" ;ɱd&vk f) u32,pƈZ0QIDDDKFkޟ2oSODM[M,{# :7dIG w@5 ҆9ZRa'ZBQk >Xc7-&*hX1qC~-H8׸SeizHHS4T&D/Sz1!r㚢YfSDƹv^kV-&*hXZY~v߰Xt5YL O=q)- .l 98/ *UKUH k`9O16eDDD$$ XnZՔ}BǒeQs<fu-PPR:-ٯ+c-vĘ$""%!#Deb8,+AbݐMD(J}{#C vAOU !/ *Sg7hS- HAdoRm0&*=efEE/nU! RXLTz>hߓ* '~S)j4jD%- u@L9UI{eP3:",eq7 -Id aҬp|TzH`;Y_@T$""Eg6VWڡ\M-{cCQ2)=ŊnDA/F;#gT0:)'*a$k,GKJ"""Zt}0Cl)LTzZfׯ D5ECU ;LnTTU3xHN9[|j=Pc֢~YoNlwr*z~D$tD ߈ $`E+6[%7D(djMPcB|>\wzzbaG:(w⫍qOA<ϐ6DcɊ2QIKHI gk eZ!x?9MS1 `}.19 1L6â DDD*?y;Z.,)mPdz8L_sb+ʙIz{ Q)ڛ M9`\HAdRiVyƨ()1>GE69L};]l~O_@TCy04EDDDD T[[~H&x[ߊK.O=z!ٲe .Rvm袋uV?g?Ylݺ5^0 .b?nNR w88@A8wL`}T_@T&y*>ˊs.Q2\D&O_aaY4)ĺ(PS^aL˜8X>HJ""DpwرcH&{g|#1/"nK(^ +ȟ 11IV 0f' 0}#r pṯ$Tj(+ MPE1D[sӾ=E*0҇!2[H~! :>%J+>$""Zn<#(?+~ e6FGG{2Q4T7Rܤhܫh)/<ň:~XN}Qc@M2)!Gv ,q=ŧCMP%bhZ[[100P 4rʒxWkuuuhnnW_믿o|ažӾV4}2`wYMBC)>:g4C咁kkFw>(:݌ԠFDDD `&VXѢ)5Qя~GO?_CSSn6|w<Ӹ _`Uk>$$^b(W9tjJ Kfn&ۄ5]tSf>g;ք>GZ)}r1&*ik-ضl63R袋}~;r\~$dЕk>g/6Mۮ]xc}ǎHf$ !`>bS,t1 w.4XsV4ݳmFp`\cD g9xQ:cYChfO*G`xI"~Z,|UUں:X媿=ډ}E#q$j ZQ;#!ֹgNj&2K;փX gk  JىNj73Pih8vVZU+V=zXf ~a|+_~/taG IDATa{я~?۷o?ka /e9?4'pl3!y G[?_Opj c}G {}s=icFEu+`k׵"ayhk RfNE[LnP__`fp]cmMMoc =Ʀ][_:<dD!qz4kx "q-@ `&*J""ڵk -طomll#(^y| _*){ߋIܹs%r-iHL 'K-f٧CK*@2HӾĊ(&a0IyF`myRn2; U$Z($""Z۷×%-onر?}_Egg'7#Gիr9o /L[l%\ᄏޗR&=kiwDT5: \_:pcɈ}r$DA3-QlkFt + ED%9O~??7|Yߑ!@k@+ѱ06 4dGDgDe<ѐWg+ y,@XsG'HFb?/YQ&*ܱG%-XN)q4E1Tzwʚ(캕!/uO-Eeߩ$ O8}[6Vt3qO """;.Mx:[5~sucS΁ gJ]OZPB_IV'Ulrk@suD6ccMHT.&*hAf@s3tS9s޼E-F@S45ѹ0o/QwPLqMx<0 d J1!.cr1QIDDD 3S6rKU޺3‹lH :Elµ5[Q(wS͒~eW%b*P+AS4T $""Q,^upF^O,T6aMXn*IN#zbXvǒ`r!Hu4N'3R !`E7 ǒ ;Z8thmmE:QUbFU2 իV!`jj #gPrM˜^*3}޸&$:!_W^d7+,V@6CL1DYO)i#Mj#j ylLcPT3Vc+HyGͭ*CCCHR " x~?7`Y6~‹\VyΤRȁDe[ orG!2)@8fz;Q͖j>r}\Ql>Q9Lîo>)"2~Qyg_.hfp vM5/*NfPf %`lvM#d]cM@uQ*Uy.x~ʹPLTQYp'`^<0ٗ,pYQ WSv{BTHt:H}{;q,i4ES@5{Iqf&*ia$""/Pj"͌Kx1 Z4K-EOً\e-ȃ E }y&DDDT}GQۊ /7QԋIh.˹[Zœ]`(a/1S]&}g1QIDDD%c_%t$LfKIh7IGS$fE#7yn4 n"I 3ѧ/ R8\;sӎH;(|LTQċȍ/b%ҧJO1\vp5dMLS4Jfq=!,w.@JR4X yDDDT2ZZE}*ۛf '!i˿)};XUv, a՚!bJ#̙I؟rq~.-*0P;D%**>:g>\G°Αx}̙'WSLLTQIFg_.H=D2= ;wOVt3 xM3d;˾̊vUXS0 gLl` /)zdf*DDDTK#n/P r鿳bY a=B:<\M}4{iiBe,ȡcޢC(@EtLTU(0ݍ7bZ򜟯?%r- O0P Kd`;8Sɖ–SYy'vtcPn؉.&*Hħ;YnZZZ tRU4qйMCmm-|>/\.)LNN3b~m2~c)<X=hoyLo`T23vh]؊#"Wp&[(`sW!k@\$ĕhDSrdB_PT2ag` Š&Xѝ"/a˖-hoogRP(vlٲuuue?4Mb1B!&4|Bb0t\_?Ƕm_e?inTwp7I|; 1s)=EgLk i=EM1ԡ1QY&(]  p8 |>_s~TS&ؗk)ߝMLnc]vlxք:cI2Q%j}/!D>y Աka׵h[IJ BXnOV7}䔦hl zǒ:٘:XN?+{˜8ZT*&*K̿۔r˜۔s&BI2Ybɢ^\IDmeEժӾ=ŊnvYA}UZ⡲3QI᧥p7p7s&NJ(n0G9eob:w[I_v}hH'QV;[H YMT9%aC!-v-˾E-6GaLk#)`"\k ;/Qo墬,cP+Q__'xBw(DgC6L Vfie 瓻A;i% رn'v OKhq1Q)95QݔGJȁQ VDg3r^d7+,V@6CLq͏*qWdY<Ӛ#Yr%n&lݺhD/?ǏUIKbPIhDjz丞x+#R;$crMo 7܀`0mcŊBطo<9ָdy4Drj&Ayld3x#QiG1S̋쪠kE%v 0 .3'I@H S&F%0ȵa~Ŋ1QIgUÝwމl6 ;tUV哔/nv\# jN/4Kkƍme?VbLKTz:tC°h߄huaaٷUrzVؘcEmG79o)$rn)>LMM7OVtMC%3<9emoÖ-[Džks=swGwXWpkl[bص##ge.$6Ć79FL(e8LF,ܲ$>A-d>T W3㽀"FvF1QYꪒϽk0*%\=O>+sՅ_E@s|~z|^ ۶ՅԄ\=)O'/ϔr9|C=ρ®kqM/y$1 zr7L:6T4u*a>CHQVJJ'ZF@kŭQȕl}5]_cDez:?9.rq-/`֭غum0 }ݸ{0*B.zXaKDJQ VDG$K"vM#d] ƨhI%^4 \{|";)@sZ{#Dg^0QHVvdeMZz;v}{n݊F⥗^SO= ]]]򗿌{##Fs<;+_~w,S$V&1^VI0QΉZb(D&rn<룀ߜ=%>HɁDeg3}_v}+m1;1QIRW2gS~tuuK_.{5\|ضmos$ Xr#?!LO.A 3ы\%+ _kTIY=P٤? YڱAZs"8$`Ljz"LTV ~<{9<sN׸ގtUNJO>.14ߎɏ|#ظq2G}v9HqNv p< j/&H&*=Ìv$*h`InJO{م NZ;  rqQɌDm8)u>,|1!LSÉ.38Qhim߾o~u9}@dS!?8MInd&8{q~Xv} B9yN&7R+lȃ ZČvۡhȭJ<0 >Ӣ+H2d=yYp':X@IzɁ v  J>-dEgTDgc3Δ~7vtce.30HN&H=L V03 ~̓70r&*wp--t{RRΡ\s5c!֬Y[o/ikA*/ݻ|%}9h2U{#D{GE%3D?/ Z\vwFWo]8YE0Sb嗬h7D%yڿ˿%IW}ݧ!"m/<8YϷb=Z;clFHeLٵfJ0ȶ^? kb5ybJobJt@bh #LT`<'U))%$N,?^%6'iq$*EG|/0!r][V-62fS,'$F .͐h0(!'aGDa:DDTѨA.dsr_~sq]w-a4W\m۶ /2bze~>nϿ'lkފs~B!YQϓh&!?@[#[ze&zH7ƈ\5Iol=PՐ oعx[gX4Cꀸ IDATT:aM+ܑ_bLT&*K699;v`ݺu]A:pY;) Yx<: ajjj;u_%__n/~ }?>|k_;-ʊl,l[--EȲ0tMLTzEvۡVHDvLcT bfpajVT== 1yBS4Tc) Bm~DG3CrݎD`#"~:d799d31MY<"Y^7s| 7|R_(uⰙ` '! ]͐g8H@XӐf̂;|+%ߥ)*ąg:&*=Ì"]c=҉Sۚ#`ΝxܹsIF-ZYH_(;qlY ꎭp- OZ0{K6ztO=D k|gBK7L'O0CdmX)q;q@U?DDiP(pddׯ믿'N[gYFGG\siѣʼnRK g>}OϞ~'N\HﴷH8(Iflݶ |1Am[oXa~cc7OcD'`ZE6ncv֞sƠ@}}=r6n8g+8f<`DaWE7|jjj 7~)-?F#74MB?7\x*DDiCCChoo/۷n@oo/z$%ƦM7n܈l6'pU\.'iQ=}L翖}/#IRj}@eDm/ c|Bض**~-}Z譎oٳciN:fv`/?xM=KpszX;3`eWzwͽ+vسgrHgawGrr`(BOuR$""O{gy0Gx`|ϧ~H$H^xꩧT*۶c``p<4m=B| ! ᤣ'A UhuG2ڀiˀ`6٬~{}²,rs59wdl V ,yA$d?s,̺`YTct#Qi'¶m,$""O׿0oEr-׿${&''bը'> \tE7?4ͲWyAԿ/,s2QTySa k 1g`18)Zù+&)=ŖJ:L855T͘$""O۱aÆfw:twnbH&xއ+V׿5v؁?Sv +p嗣Ǖz0@@VO0T6ag`9w==ȝDrS n{1|#%uyÜ zǒc!r5+YS4,&""Oyߏ;*x g?+gbڷo^طo_97|3BDY+<"I /f4E0㽰"(%ڒ'Cp1w҆gҋdI%Eru84iIFVlQ+ qVQIDDr?~no[|[Ÿɟw..GΛDs <rU1)!rSywu5Qu- 8'Z+ܹl"HM'K^ftU+ODD"/_~9nf ⮻ /|\s jjjtJlY!QcMG"69`Cr5[iooʍk zm^UYڊt:; "044$m6oߎ۷x^A⋸[{|:7YXJa"fh7pW#3R˾'<'@vpcdٷKg{cMY}AQD&c1__PMU&*JtADT8~۶/C0pM7a۶mK}@1:&*=CMTZN VFcT4ZM)3; v&`j8fw:[J?o""44qe?{ w]v\fb]_@4p-ޱIJooʾҞl y \chHQIDDf&7W_h4AP~ mc ! {Ѳ2Milt{͟яyNWoSNc@C̎f`䠾,fb#Qiźgs`10]bG7 NZ0{DKo`dH !L J%a&zk$bE;k1raD>H$R"" ,rMOOcbbL-)% wtD"Yp z;,"wGG[KLTzz0| ]Xq,6+}' lw|E6kfw䉎&`wB{EQ2 ~j% B$ihiiar Ԡ`ð, m3YIY(@o;>Ϡ=ydBTh޲7N)cz0캖fRQPcِ⡲͎cu`U& Q)]if6 N0}z%ǬO @~`IeTSS|8~zGC^5\~Iy|>/n^x@Eeoj2EZLC-ƁCeZ`:_CAx%V6k ʖ1%8R"3IjjjJث [ګЇk+r~'000;a*+.{TFX LϱtqY)J}{u&6D61~TS4$TM{dݰCvƈh9pGe |?lPHc$ի;wIJ~gBE7jLu0lQZQs,~&Vv`B,97^b*jVT:&*Kp/)5'($t+ ]2̇ DhipN'w0Sj҅hWʾSS`|fG2 $}'|Rb-ObӶ:!JVz(Ql6lԋxw$h8Yh{_/ْQ4}QRR&M,5xUieD4KN^~q:^u3J& K.ҥKSoBBfshj;`ƍ~,ZH1g[Xf /^u8«7bĉm~66n܈~gظq#,X0x7ol¢1!䲩b zxo!4Wy196 ʝ(mwdU}Q M94RO"੃ݟ%*\x+x׋3gΠ>:pB0`e+ر[n֭[q\5c7uT,Y~_}ub~{?<Q^^~DŽ^FE'ph[`Ǖ.$OLlV/nص %+*$a׫:ޅh%++ ͛1yd; @!2.]ڍQ^/b~gokUرc

#Gĸqwɓ1tny~!55PVV3Įz+ػw/ƌop8s VXcҤI7$ sO$ۺu_ŨSO= `„ p\زe z-xq7oFKK jFlذovܓ'O1p@477ԩSXt)~a\uUطo͛3g"33OƪUcǎKCx<6mAew}1b.\?8x`q+? ŢWf/Ɣ)Sv֭-܂'|2w\QQ<}:PUU!C\cv,Yv_|x^477c|X_ v^'OFUUn Ap-nSW\\ gΜAZZϟɓ'999ذamۆ466bӦMsܹVٳgd,\~vBAA.]ѣG_2xN*c…x7a6p80x`|%W_}@w`„ K⥪KTl&3&@h8;1uFp!(ZݧgD|;@uC+V0)PHsNHsc#;;ƍCmm-.] Ip뭷b֬Y(I_۽{7v؁I&ag6I\\'Nʕ+-Z>v.\1c`˃`oD/0zh̙3VBII 233q 7ԩSQ\\|s΍Wϝwމo۶mÓO> x1p@ 0@oqmWnV`ӦMXpaph~ԩSqy<pM7m1o<?o6~aÆ 1 A\O 5P;!Mrj^J5wV`Wj& <03]5BXr"xV ݀ .ApdE00(]hFej]=qD{X`cHOO>|8Pp6///Ν;1qD,[ :u*|>y˗O>Cz4?2X,L4 GO>l߾wmۆ wνi;A!e$MWDWI`E\L 4 !]'X.J⚺>: j:H$U+yPW"Tg^*ut޶[G;uC]s5fv;8EBB^y8:RVVƘ~ DʤItR|xg5… Qt:KIzŠ?K`aOz(2V67!? v~FO?H4Zrrѡ3O}aD7eU8| ]`6BsZ~Ǔ#F/0IK 2U 'Vpn{GHUz8O Ե@~ivp!ɿĒ7 :QJJ FU?V4޽*u$募af3x6B`0`ڴizؽ{B+1[_"%7jM/#0`2qO)E΂y4#[ٛģAjPx<8<^z`3BM4 سg<Bt("AFP_k1zUR%SMNu&8|N:b&5ku2ٜQ4;P(HAt=5(:,:.A BmۆٳgkC, !:fn5: Pw~d-x~Q5^m!1$EV !5Kl(>z ҋ {{oA3*[.t֟;5!D{>O!>"BIEP&ҋztX?N)D*0 \HJ>Q֞5@^vmZYлP2 ^oȪR( 53+gctN_u*B@07JTF)f;wިǃsESm|B6@z.n#}yXarVT? W Q}Jr)n: p94 !. ҥic ~p8&=!D[TB_cHOKCfFf8KW.{cMWU=u'kKCI\ji(!"ee&i&m!7Ch<v5Ӊ$ICZZE p\p:G{1 "II KBSWWP 2u F]vȑ"}C,754%6- 7XWCl!~}뉤O'Ah!3C˅Y|OAX)2ݰ5(QӉd 09tBzCcc#%)uH$}2C!D dvϏkX_ H^@L 0r05{ LI V$ggAeQ9|;@JGlOy覫Q2JшDFj$%+ Y$PMJBz)^V)+HNNX_X&Q"j]hZ `O)QIKJ6.0*Bi0_#198&Y`Q@& vhVei/II!.TYŗ,;m\$jDXҲo}҇^}i%;l}h^Atsc2"GS;I]+u>.1!B́ZPDCԳxBdKN;{01; ?A˾DJW6\ބF`AF;$ץ5tQy(F!l6l6JKKeÀr(fffbȐ!577]7x(+40h,B+EtApl %eg5Jgz'i6~F:!T: &=4-&) Ьj]D%!rl23s444Gſ67qw#)) 1nʕ+}?FǎíڝKJUK\-F 7% Pwd-?$R,r' CbƓrcէ$18dR- @3k歇trRd-DoB!L&{=kIbַ1Ǐǔ)Sp=`ر 5oFݒ"5c(k ;9)ܘD(QI!tҜ9sqFEBsua֬Y0QGe*D%1S$L@l(W7mP5 ]r:aAH4yf5]$Oh1Hô \6JTB!҈Γ|߿?8f6ٳgoĸq Őt=~eo4DOUo:=I8VI{ך̓!Pw\n f"$%%yرcqw?a/^z ˖-ٳv;<(w.`@Vft3@IRCcjaV@AkG{~TH%oR\5c&m0=؞44/hkY,hNW*J?ҁt$Y"θKT& EZ pUaܘW \=bAb)Qkb}|H:?$~֛.|}Ąz!IVBaa!nX1g\R16vjoݻw'hm-);w.n78u!۬0SHHgE-..$@NaDK7|"iV0P-8'!QIibdh UؖS/-B.ͨ$B:iΝXjy\}p83gك?SNo\wܡ8ᅬDlݺ_5;׋cҤIxG}IHia)SD]P$jrJ(#Ap08N>4Ύ8aɠNP6j$ވ'F)P %* !wƴi0`,_k֬$I}ÇDaa!PWWYfE'\.n|ǐ!CÁ㏣&nRYŗbo! ]%D?F0 J~fSd-_( B.׉&K7-pRڰА:DJB!2mذ6lhNcǎEլ%%4C fUgU_6OOH,젤 WռԡM,S#bq@j MF cSFEb!:(DF%!B P'li /K$H‰K `a״W?U]@$6A)Cz#~B`5E6D]}lS&,Z&!}igx+yPO @O(QI!B"SJ]G`"iAV !teTROiIH;A7t%]R?DZ -ZիWcXhh}݇kbڵ{a45B!=S8ICbd/RŘGgDmLOHOU*4),IʦH)Oϟynnc޼y?~ .M7݄$$%%o… {$,deecqFlܸ}]ۄ]9Է(Ǩ멮mu=E| ̏_hgGUC-7!~~,k!JCTR(QG͞=⡇RYK,%K3gf ,k>>y?n:<7o9TGiΜ9x뭷f,^HJJc}vꫯbƍկ~ظq#~wmBHWw=ߺ":J 9yFh'hٷ6 (y F/xubHߺa'Z) Bz#Q ijj &!2o:(z)7. BVVƌ5kDuǃ0uT,YꫯPYYkxbEunҽfϞ3fsyfkG}{^vZh&wKl o0@s7fK֑βYVL*FOD19mry{&rSCǴDt @ R#fZB>j͸ꫯ*Z}[+(.ƍþ}裏N7:@,P;/1cDu>s#11c׮]hllsqc4kB/sI/vF&: #ar&醔.& Yd .(ҧ5` d&f.(5oP95;#)Q(QGHLLDqq1࣏>_W\ QvZ[[n`Ŋc{= ǃÇ7]w݅AE엒gΜAzz:,Xɓ'yM6?߹s'VZ-[Ƕmۆ1}{Ξ='O,ΝGزe -[wu<,X `̘1ؾ}{9 =sUPR}WkoM5*DvvspA KlL˪*1.+744_ss=x_ 7܀SNGqq1|A̝;TCEEpwoǶmO^u 8 @yy9,X<.77+WĉrCСy?pA :&Mž={m20}tUUUQ!+c3<4K?.At>68$Fp~%"^;6ɖ\&}6!x 0_p Sf΀ԂB3e(UR-^Q~bYvG݋&LY0m4\{ٛG ;_j>}0dvލ9b?~<ظq#`ƌ_2dA]5,7oތ|TxLRg&kkk_/))(=|p|5jRRR ~-Zw܁`xǏq!%%%M|kX ee$7u0,2%*uC=%*ދd3^`G_,Iy`Mg4e&s@k`Zq@XW:iJTw`9v܉;wbܸqxgpW$}Lk8v|>F {޹s'F#LK D%/2/_%K(fcL&yDTv[ں6rrrsܹsxq`hGy g &HˊcF%N /\/%*1,tBHL]Oyp>MSB9Xw%'*Nвo=Q/ٔD3?i`kpH !?;iCxD5*(ш/}He00lذyYVk1GGFN'^`0ϺGvv6.\{Oq!Cp@w }#6mݎڜx Xnj؁]Eb":CNBmuF͆)]pu5:`LB/t/~uTXV "ѓ}N@#!0(QG͚5 @K%K3gμsVVVbڵhhh@aa!rssQRRKO>í_9rrrP\\fwqҳNbIp?Eee׿޷oV^ ߏ뮻k7pC0i+WüypW/_+WBe̝;EEEp =F%$nɲ9 K;צUnSROD1WT˄ {^JtQl#%QH|JGm,l_}Z[?ċ.~o5\\423{ w}6 K,c nVC#*.DWD봵KkXD{][ZtγV5^c fgwM%һQ`?ː݌믝O?I|` O'xv%^S&_Ç!`WhA̻qH7a"ܓM(:Տ1 uh s+l:F!O8)쵨Aj. !JC}FtʛJRꈔVHR#@HO9QE6"qKhq@h>lyPFerJ[oi3X !9l-46,h.$YRj~zjYcTRWݾS`^HOւ4 cz{hX{%7-FxC #"mDeb XBP!.aJ6,|񐘈Ą!YG@G˷Ec2?. 3L_M`#Cc2)Q#b!\ܖ- Oh7!B ?OdZRbqj1uSNůM0$f%rM1fpR ˷FQ`H3uͻ(QI!Btf+%*D=;o ^&1eTy!!kj$~cFlm_8<m7zJTB!f8(Q+bA(t̐5KOllq}뉺Ez%$ bHQ037xz`h i %*rX=NIOkM>LT6$qtFѐX1o= XoM 3w&q!'TQģ2(2M,$v$P"" #"jBK $ڈڣCE\{ 7@pHoQ>Ϸ3GSWhISէ[WdHޞ2 TÈi/QHN뗦M0$vb^%*viq~n;:%=!hKeŮ]{pi#]D崂 kYjr %WhH ,+E9D⚺>,dzmK 4)yPW7#zO^Q$1~B)N.v8種ƩSwGqUhtV1 7L,#&BR-h7c\UQRWڃ{JTFI$8N466R"tߏF8NHtp%!'j"6"16שTէK⚔V.5Bvd56@hRk Ġuzvi#{tBV-~NaL(- y~A `^ξ6P\pS:A 46h !Uu`^c2wT\(ODAE/u4XaDͨ$B!S5* ,z!֝a75d]@C:?Y]F%σYC:àSM^s/AJB!3~np D1eoUN9*rR?ȉ'%*ISש +`EzzaނB!$vN79LVDID[J VLTLd<`Mg4RC"hw"^D%!Bm$$M0BJ+.N`mr"qM]ji !1I'3@ݿuEX^3%* !BHg,@f6CU_p,^~o(+nANkh !߬0^^mkYZD%!B^]W9NfP5-lireDT+7oPY<m `5 Ĩz͔%* !BHp á|9hkۤQ2 &IH˵G))tBڢN @z!֝a#,~ kF/&@T ZMtsXb"8+yB:EӷQ2 4Kh%kSSZ 1 xGGaXl.yߎǏG|_M7j `mCb%ʥRF|.{cUuO8J Rp#]wYhѢvX,p\())Q|!I 3bMn fX'4Vy\1NHOӎ`0஻»ヒիWcѢEm2v?B]AA1k.$%%!##, l09ݬ&Dk( EC:CTu=EBFѨQnߺW5h<h !]Hrh@NAJT8JTƁ6ؼ`ƍ5y睘;w.l6v;͛;cǶ PC2''Gd osss| ł3f`׮]/~<97!Q d#hɔj<1`d-.0l%M9(Q3}b^+e[W D@zMcyyyxg#6Gcٝ>lƏ~#\s5ɁFyy9}]ٳBD 477u]nn7͛at.Ptg?cx0o޼v]x7;|Yfa̙:t(, Ξ=͛7wk׷oߎC$6ꫯW^Auu5qW@Ʉ$Ř,A% ,`) D !>pܑ7l=ҥ4Hm+Ƅ/9E 3 ^z ԝ7Z_?@F{S(Q'xGr`0Gu*Y)z)7. BVVƌ5ktCk/FYY.2֎()("~`ʔ)hnnFYYc/'\6IMM ;O$?YYw[e={cKKK`ݺuǟg >7F9c3993}}T%$\%))r#vJ@ؤJdŷ_C׾NJJɩ0 6I@'|uW!?sMfHa1ۗGBBDQDNvNmowD3 vjn{%'*ЍbJd6}]z#3<LVΝ;S),,ĸqo><$dhB:O?ży /V^/-Zߧ^CL2۷o3< $z?~|1cr3q``[NN>e?ܹs#GJ#ì/vaASFtOH&.TÈϾ):ڥ3eU8| ;ܗ M;W8hҘHisږwaSiJ]1 uH_ѿppr'3uc#٪_p!۰m7ST #dᅬ}{1-B3nL& |@'Ė>֭͛x0m4;>9}_H! v%R\Ԫ\^WG>^m-Vj},hk[EEV"KB62$CBI2gǘɜIB93뺼sɝd|[Qalٲs&NNSa cGb}I =Y|«P)R!l/ ,z{09=~fh`8L'<:WV0iii 6׿5?=ȑ#hkkC^^V^cƌrޢE0gTVV"!!˖-ի ݎlg?Ld{l2hB'ɝz;g֭smۆ^{͐Sll,͛*$$$`ҥ={?qM7ĉؾ};f͚+W/ZsEUUq]w/}-J1H4Cɿ/4 gƺun:̚5z;v Ν }6l^{-.] ahs9}߿q7.Ë/UU ߻W_}}bbb_w}7.r\z饸{m. IDATOW^AuuuM* ӓҕ4ųTcgh6-eG y  1ygEH+D KaC5nI ?\Q@=ۻ<3ffΜ+V?iţ>7n,Yo|زe ?=˗/w]yضmV\ xWL477c8u~A4,Z+V%Kpz֭[yf,Xk֬wߍ6ʯSbǎAAAv1""GFF*++|]wq-[湇uzZ6Awgߏ;S_gddyll{{%\]v,X)%~_p~:Yqq1#F@QzVm޼+V@VV`رӛgzG>!{̜96v6:ߧ?֮]v|{g1wv{ȑ=7gnkk "e>p;9?O k֬S;ܛ(B%F 4Z \iISfB|[ 4?WXCi4׸u |Xl\iIK;v !LԉJ%%%yx:9 We}BJ]va׮]>}:~M[EDDp ֥G`ooN F{?oa7x wHtб /TUE]du7Լ 0{l_px-{5Z_suGEE|ox AX RUw}xPUvw܁;v񼞴aÆ PɈSO=5B~K,wLӟ꫘.V9>GWV˷y{8PGuu5x!ɞ/C~~Nׯ_dOqժU=ZqCp(%n9޺4~^Ɖ~!W-Ŀ37hE{9bZgYZ pL{(sq44wyr{Bw,&ĉcK 契x|/^Fx:! F{ 7Ok}7=Նȝ?o/r.s\4_oO1=;@7b…Ν;YСGo7(ׯ_//_式>oy bLfbRWgGvlo^nm@AC+7%i8roswp-rn{;C~(MӺ)ٚ5knׯa_#" O؁vWg@ȋRKbk8SۿK0}-1΀Rwȼ;,.`!3nWTѠ4 W>PGDNUU|op͟b=fDÑC9ELN]ibRj^83;@ӿcwODrkgz8+eRŕ43M@Jo1) m,Fx[h=ተT})4*BiX$rlw2+ۿHwۿ])ӇP)'lʦ6~> 25/|ֈh jñG WooCf , ww-{$1(Pf@T=>/|ֈh@N؍ۿ &r5I0Qj[L4m`%O~jry C] N&ꂅ>p8\AD~iosJ璈C@j|*jfw oN5L=4WϴﺃzIik-uBe544D~i}SJ}.:ȃȈ 4WAnN"gN۾ 7p7Q'7^M䃅>r\CSS A4d4MCSSr}=S \u'-΀0E[KA{-.ros%ODKLjZwČfhR6DI5;`p8 jjjz?R >DD%ařOVj^N/j͗F/DPr7$FBC( ם,"@wp6#m5M@jLg0? (3/)DDDD?GA$G A4Wbԋbqu7A7 ==v!mD]>o4@o,T:g$n&jnñ+e: ,vS`q7rN0q?^K" T-- rMK(аPIDDDD#ۿ &\ HBelFÕwA!|}PTԙc^H&PDX$"" Q▯߄:uyNi@JIPֳPJ 1Oo~-V4'AmY|VQ'XLX/pe:Q`(Dize7;N6CHLiR24{Е<P" |sNpqLZ3c%D!{W!rSK(PIDDDD%eUӽ,{ {%Yv"f:sّi>,}烈|4A2NSد`L KFś hobNKٽg5jpO 3 3 1˙/Mʆ(a B5'B%.=;Xj(W$H5_ӟҧw6-ñhĤlVBأ 'ip:LDb5 7eT0.8Vkj^7 i+b}68]ZO_Rn*CtJ*! >H (bRy 죸8(oTUE\\% !NBoa*/ud3 }֞n,^8]Ŝ4a5-նo";]4P,Rߩ>U+4y0s@АP-u z~!|V>-(9%D>* N4)"sPIDDTUŢb]w` fC3 q5 ˙cWndc5mt>aR6D!hi=H DDD!J4 K/+]("B8Xs~SZ4bݟl PކBF7oAeR` yX 1>,6mڄM6aժUfCDDD7uVcIoJKi5jA;r_]ACQyKa" ric p"/ռL)1ALUU,[ \s |}TL4 RJ|}(+1o<1Cll,6eoΜ9ַ~}]DDԍ-7Oa.ñ?zWpdǭk Wlñ\ҤlBБk" /\QĒ:tpAXqN5smK/Ŏ;O=}L^Z?9䠴[nH)ݻwph7o8pjkgĉDEEm)~ !`q5), DYy9Qg©5C_E\@t6hgIMZ"y)Y|bכtSGOPBVXqX,뮻҂gySVw{EQp㪫Bjj*ʰvZo͛7 ]vE es=8q"jjjsFDD_Zƍ moo7Mimmży`Z{a͚5{._sAtt4ߏ^x6킿g_RJ"G4[(:u*NH94MCdd$o>K.ĉyfdgg{Vp R^/hluN:We翐tRZeVkG]V&Ukph(@Rg΀tSѠ J <`bVDB%ddd ""ùsz<{ō7ވbܹx'-fCee%Le˖aٲe8x v; K. GFF 1g8{yv|*L:wu9;v~K._|f\yXr% p%%%z|bcc*l6ۀy)//ٳ\BDt4jnbZ AD4*eD ܩPR TPhñz lR6D!w X=* #Xx1N:Gx's@}/_zׯl۶ +Vwߍ2$''#33pͽދ'x>,ছn5˗:u*ႂݻ+Wo[l߾ƍ|݋"--=RW*8uJKKCQQрEDD*c =H1'7 JqJk<ñ =.aN2DNv=(;w8$9熽cǎ((,,ܼy3VX,ùΝ)U\\ UgB\\'frpݲe |A=y{Çpi̙شiẎb.]ױ{nOOC]]6ne xoZZZ " Hg&2 751)~sr,~k ,:[FXK(ɽUtP!&A:D!J2(//7nƌS6퍈ð0Ct{yQ {64ٳ?cQӑ]H )%IDD~ bXOHL c-[N` ]fkf) %D*R/1,5DC ,TҰdֆ 6@Q<#]F[,{D͞=aaBsHKK(/b`WeeeuGEE矎UTTT\wQUUٳgNm IDAT+W4L^x*t]ǒ%K0a8n B9998y$n݊[ɓ7n܀t:qFٳvqqqHIIASSjkk9v;bcc!2}ȑ#8y$bbbe%\.6oǿX仚DMH2)/˹GA>cRF_W$CL=ݤl]v-et$ Q\Qv;1i$:tv:(r_믿[ZZwo6b…تUjժ}}|MӰnݺA[Iq\ӽ{:8c17n4Ď|r>'"8lZ($$X(uT%+&xBBn^^gZ@tz;,5BD#TBe1>Be裏pg5DSx ^qKDD"Ĵ O::1! 1hdǫpOh#fB36"-;wm,TRH f[[[cڵ~ȌhݻDZZp۷초hơ:1ayfd@DBM2.5)!=q"dD!ڶ eNn A2iիWcfB7cC4TPPPd|嗞~ݙ?>O_u`ɸ+|ǶD!c=!qq&a hIQcjF94{C,6.Ctʠ4]x?z"C6< " fRbn^DCJ"" 0w\ EKK V\on)S̙UU׿p~E8 (^JM#:yIy<= ٱH52fGaГ 1!:Dp TC\ 3!?;6(X$""Tx=/^UVNkx ]֭Cee%Ξ= FYY瞡"3@KP1}W%pOPqN􄜩&$G}] %Ct% JĆyߏD!=*讻nOç~e˖>&LY }###ppC6fd|203#D̨O8D(ؚ !C(P Sy>k,X>>ԩS٧|;AQQ<( jDw w(e8u+a?lW\fE}!:D#VMLJhPf@T=_u@EEeee#GzxxXf!|,'2"zDCL!TAĴ."dQCCX$4 ,VҐR-Sc޽=~ҽT4455!!!9qqq3g\Qyx7_x.>Xr%>cНX,喯m\&jE(+OgU4Z4:b?@ lF%b5"6s+x j".&Nج<.5IWw"+J~ժFCGP#iI'!Hy Ȉ>?;CQbխ\.Օ4d4MCkkhՕ4h4-WRvp8xtӧubFF>ꫯFzz:֭[u/obʔ)O{F)p&howSѽޓȗ>; %ͰO|~J~byu%Rڡz=˹Ѕoi Hotɇ.T4bň nNR= .uޢ~@XXX>;h>݊O19t3CU}vmp886E%"3m߾_rs5/@{{y.]?6Ojix_~.@ff-}egGTCLXo͐O-kB N[FxՋpƩwZcy?0rrr[o|(,,r߄wCXXN*.ƍw^ ""?OL~1j(Ov{z̟?sرc!/m۶ WHDA$ ({+8%ӟmc="P"Cg58KQ!WV;`F'9*.PUUz?o>۷{W{OMӰuVl-Dݑ; n!r!Sc&DAat8!NKnmXc'4sHmժA; qI'$.΄zpMm nQIDDDDAG-^m 1/* 1c1ʺQq+e&5n޸F-DBjN">;N H+0uy  *(8]>ۋŔt &̤'q( %β>]!O@H32. (hiBm.# xfq=*((]ƕ{ 'fpka gm1 L t~tX9D(h7Dr4o` B%vجѝhH)#CLTe޽-Hz#,FWZj@u?,6cofq)۠PpcGW B]bXU Z K]!e\[+bȰCLĤlh>oe'AŚ `WM3Zcl6{t I@Z!$ZpG4X3"ʹcP*?jgܼl!P,T(EQ0c4\q|df`S y&%3+V'unRBDB%C6H{!kR2!,%bCH~q cC̕=fݗj\h8Pkv  }{g{6*(^jIJ4#^M鵜Urpgv@8S}(ӅѣGAOh:ۃ%@դJ"""" r_j ^mJ.!)1"?;ˀA*rO !W4is6z! g#g0/!"Tr[᪻XIDX$"""зB"7gN>!FՔ.AVhO*erU`Уҡ%M5ԪOWIѠs@2%,&%D,TQS4 /U9" 2da୦pZ!q d21nZ ((OgcEY!ث DDDD:ݬŚOW Q+7Cf  .dd*\3 1- ek; `>S͐gJDD,s.ƏG#8y"""ɓ'wq M8}4Z[Cw,,3 .Zh^RADBBT2jf8|3<jl2cûp6A=iQH@LL|)\l,俏XSGQq@=\AJ""'KOuuݿ{cHOi,T7xw{nS}K =!@51 `a˰t8!@ڨj Eg_Fz|.\ y62""m_QkvwZi˙q$C,t M*4NJ !16 b|9(Lc`5,gbB,7qqChbG:p>AJ!&?=PIDDD!Ʈ]裏BQ{hmmŋ/ל1rHo}{(++d/ϟ & $>5n}#bx" <3ǁٓK-n\{+X{"iisakLʈV9`_!$ߛ!%>CHV]iRB DDDrqn233|op8s[v-Ν>_ ?|MII k+YXer8 nxn@+%\mpf^cRFYl)-P:?ǀV, uyի.i ቅJ""" 9_|111~EEExwLήofϞ EQs~]}v̟? |Hko 1⊮H Z%, Ɩk 3L<Μ[5+"a=w@Lˉ\s;fP 1/ۜ|L$R!&0 Ȫ ł믿 (,:gpυ4y:D{='߆R>,/^{6mu][n|7n\6+V`…HIIAii)6oތgyp뭷bʕ:u66n܈o())O?m۶N ͆v78H$JބcގgJ -e!솥DWri#q< Plbf~auu:: DDD!Ju| op yв qf}#Ֆ .<444෿-x \}CxcaԩSN!++ NΘ1xԄ}{x饗puס #F@EEޢ"w?]vfW /x+//8uTa@t'$M[[5 WTp!bfJX %h?ۧh7|~!z\yx7k.}p^^ŖFL<9A^&}%/N:!+ Wsc}{*!K&eta՟A9DJ mBX?01!>p@񿙖1 62 IDAT߹_\Z:+;W7L⌭~GG62"o,TtAi8ivhJ]@={6׿b„ N^RRn BCsʕعs';? ˑ#G55+vw)H+[&C_%PqWZ!UZ\  #S=1-k!,PKKl jlVޤ(蝪QqXZ,rܿ|]wM!֎ጅJ""" OaXw޽ Cff&7quaŰx뭷z]?37w`50!"?38&ⲱ[JKl0Y-PnXVI _Vͼ~l-Z.Wh2+a!fjnRFD*c Ҽ4 8^ <$9 bg(NotOq[$,, n:kQ_?NCUU-rOa8q"~_CUUڰb ,^_=y\uUxn{z!&.Lhp;?~337 M6bUD) ԢbOkhbTT]bYD#!,IXΜ1qf2Ls&yk9e>3 ,?)Nh>ɑk[yd<z[qmTr &|IU墀6|N/OzuT70d^ާ0qɼKdYAe]/d^?H2LC,Ys>_dedul] -H<ޓUOySUy[QisՏIVqI2ʎ?jwDz/[?4&Y|ϮiO}֚6﷩"CJ;w*{LV4MM:U3g "3Jxg$ʼέd85PYͿ)IO1̫ޕvPSÜlL^VL`^e5vY_wSe(` e]QY7ԉ-oxӴi4o޼&y>ɑ̔&%NdI6JgmVIES%=ƾꨬۥf}#w=4aIѦ8󃾨*.RזP=J۰a=ѱVRYYt/%-/i}3eD{j8H ld[2͌+yfUFY$3Uax@QtH_K|az**^P%3/)(§mqdty~YTARY/JI39EiSU2e+##0= ߇k^p<;ߓmV\dzl*r6UzbдR~=y)4:kYo \ 4d\UeGGJy).pimε(Dfvk̥)7(+'Xy 4C2/%9Gaȸ[8&-z=͚o<3՗|_+0ϗzOZ2W+2.-^Y˷6Ƹd^5@FD{# Zy9 NAP @3UVVůYhÆ/j>@U'7 cUӐ1ۏz~R{겉+Q(:M92^\[7]cSUZ*-lLq@)=yuVޑ'E}Y*jXm"'yeg4 Oiq|ϯWhO]63J ?Zga=+}YfL@{ryvkSUZ4K^$k[>Jy@U=u1a15qY/c(AP z?[ZԣmOaOQ\1ԓW#)}Ucb`޴*yCՐzl[lSU kzy =%Nv2)Fu~Xւϥ6 T5$d}70'S$Źe^3p2^0-YU1gU +駩-bRœ SI_Hrb~NX͊29VA2NqA$T L d=Ŏ(V@$|V:su zȸ2BH$K])ZY[6i=N*ت*s| å+dj:N1_K!JK{d)mdNTeղkP/Sv?I7oVLLn~7g}'xB'PH||m>Ueqz'7|]}3eL\u륭 UEgҊKWQm*k&ÊMSQ_,*ܹIIH YRY?s8piȸYR; 4d;\(߳FQjjM{OW\q\d0 Cgօ^Yfij۶.\;8LW_}zQqa[nQ۶mƲ{c52SdNVޫhޕɱ2'stoNA|s'k+=)clU܆dQI?SQ_ȊkU ~wʗ|bb7g K} Eѫh'iX)ڝ=?):1cnA'O3O&Mq .Ђ ԩS*\.qM8ц/33SӧOWJJsYaÆ[niZ Ko/"qA*u6a]d4LF+_';pٌ2 VK異wy Ў/R%']!zJb* 88'/d-Z8W/]pϙ#!e |WVKJC nv]}+˪ʫJ˗/WNNNe[AA^}U]~2MV!?P:u]wUe߸qtYg߶2e֯_kp͔qgVIB́1U˸nT[.2HJqu!ґ:>IdSܗOȽm1*rN*x/ޔTs&_JuP܆ɽ?d}E׆\UNgȸ?OJsOY2Cd-,߿M:*,ݼ7'|֭[W}JLLT6mjuB+**߯ .@lOLLԭުٳgk\bbbtEi…rJٳGcƌi`;"ߜd#qr ydu)U >'#G1}d>\ƈS. IGK{i77K^~9˫(92J :~}*9umZ|UzS}~)ozUM-Oylg$hRs#e8˻!eX_+?:Q:͒|ϬjG_ f8 n;Gj˖-aj߾:o\nn$C͍[e˖i{4zh|>tM*..֜9s.NTcY{=1BgnR7^B:Ann2JC eU Q2ʇɥ'TӲdm#k7!{C_J\vgTcUv$KFEerI8bZJ̔劭ʳ%nM%HkL#W]F*s+idwKe;"o,1ceMRB|9'kVY+r$e4'IKK+vg}kV4M-߮,"aRS6f^~JKFfԧE:e*(VU |M7N}&N;CEE'WᇕvW_vi;vm޼Y ,о}$Ϗ9~x 0@111ZvϟQ:s4rHiF999Zf|MIR~taӧ.ڴiGպu4w\IRNNJyXǏ*&&FO=[o+11Ar~i„kU\\ĉojժUԩz?$uMv!';8pP=*hԩ۷|pFqTLLu;)kxbԾ}s|ns]/k'ʔs-ÒN%K=H5/hI2$K٦.^^W bbbrԾ]Ki|RɚJ.+'um(0Sr<N#W|TDrrr4`*YYYӡCju\MJKK‹ {w` ձS{Iґ#JIIё#2NN鐐CJjf/ѨQtg/֑#@;:xuשK.1bM7ݤ֭5rʹMΝ7xCc^Yf׋/3f|8ر#5ݭ%K護W_kϞo$I.hǎ'IJ3"K*N3_ڼ_Wh5nMj3iӗ*ymGY$U4 (̕;+l6ҶF%4ΝIjӦzpR2zqr "jJՎ[zg+WTAAP @#1 C111*..__#c-_<`S|||u;o]-ĉujƍ$&&J{ ֎^zѣGtR]wuz##3F=,O?YfI9nݺU+z*;tPy>xoH]OƱ]ұ]_INEa$A%d޽j׮]e2M0Aw}nV>Ӹq*Wq] .h_`]V۶~`ݻ])>''G޻wo}u>O|> &{;C-[h׮] \Ӟ={t!=CL4gJ˫,swV9P{o׋84Nz5n8˒N*_?ytMz4a 8Ps$iСںu/IWLa%&&*--Myyy}jڴic8qy懹6;wxxbuE|2 C&Mڵkw.vm| 7ܠIҤItgرc2 CiΝCWZGsբE3gjܹZj$izǴj*}2 Cg}.\h[D4k>-Zci֭JJJ}ސ`K:餓t1ܹ3`/~ )33Sa(''Gǎ_XX%KhرZhbccFy1oƏǏK 7ꋠ4k}]=WaaaUjaUe֬YZl+Wj߾}UYn]/K! JڵKw j<4M}駍R<P*Hb=5|9sf#W4 @3vu+TXX>H ,J2Bmٲ5~˲dFW',~R+552`4]@P @Km6uAzݥ&ݻwW핓cw)Ch6nܨ͛7Çw^y^Bp\JOOWvW_iƍvTRYw:|YB|>mݺU!%̲,mܸQuԻmw9Z0WVr.QrU4] P֭dwBi,:IMMUzze %'%uth*hN>$y P;iiqڭݥ ]t8e qӂZ@]>|Q Z'z=4 :e0dfu7!P>rg8#In[eeev!rM<|^c~!vgGrV=eC^s,r-˥2"9#9%0T)0Tir\*-->^_^ϙ5k֜!)_WR߽mUPe嶻`CjΜ9ZhyOwy裏v &lIDAT]jȐ!9s͛^ g}V\rrrr.Qk׮.Q$##CO?K/)''G۷UXXhwyp3g*==]O>Dvs]k׮?_.K֭ ߶m[Kھ}nZwqwkF^צʛJZiӦiҥ$I~JJJ_Z=rss-vZwy*((l[b>#]tEzmr顇Ҝ9st7]ĭުGjĉ***l_xU:v38C]v Iedd/&$hZ|.;VC 8fҤI*--Ք)S* ֢Etjٲev0NS>}4 ʲ,=ڦt%%%!$(''Gm۶*D)SX ,D 3FO<%9s9[ohĉԩݥ!JT粲2i@@wVV*m׶m[ٳdžmbcc5k,^Z]:umb޽*..րg䶻֬Ym۶k /iccup:4cǎ뫝|pM7Uݾ}{-X@wq֭[\-_\k׿O>2MS7o:8Yaa:t蠤|?~܆mJKKꫯjر;wnk׮:t&MdsA%-eYzG]^{5u]ӦMw]"4M͘1Cgynfeff*33rvv64!q%۾}{S(rq=?Pm۶wܡ^xrX&W^kΜ9ڱc233u-hڵZ~%"Jy睧zJ?7Z|yA%-Բe4ep z'uV/lwip~K/$͟?aÆM]`ܹUW]Jv҂ 4w\KmٲEǏԩS5m4iϞ=z5gDĶo߮+Bwyf̘<-[L>ݥ5+^M k7CI9A%TA%TA%TA%TA%T@5ydT@~_kƍڵk}sՒ%KvnV^x 5 Њ+4uFhBP xǵchK4l0u]*++ WVVKϗŕ*##CoF@4 5fIRZZOٳgkƍ![dRSS5lذ*F 6(''1K *T@֮]yVZZncǎiѢE=z.=޲,1bDuz7%IIIIkÆ Zbf̘֭[XSvvF6sL͜97߬KjzWtgh* ?Q;wʕ+aÆYd4dȐʶ /P+WTnn,5o3͚5K~z\r|СC%I]tQ|| =~ӦMںuFO?TijȑzG+)**ǀ C=$%[zuѣGkԨQo%Ik֬QΝ5zh^Уjk5uTjڴikU4hRSSlٲc:v[nE׿d bxmStReggW.%$$4zT@bbbjZl,O<~[+W_]v QF?ԑ#G*wI/ŋ#Ӑ!C# СC#Fh,ˊ46zT@~_)55U>$w[o|ƞ}6lؠ9rdվ+&&F3f_| %աC͛7X^z^oEhL?^7ګw3oq]t\\\\zW N;4=>}UViҤIЇ~X5u70 M>=`d7NWVVIT;|peff?٣G]zڹsuEkѷ~KH4uTÇ+))I{8Ǐ7[8ٳiIŒ,I>v J@"4n[sѦMǏ+77W~֬Y;vݻ5ydXB/طsN.u 'o=\=䓕A4a3F/VQQV\nݺiҤI:st1^ZIIIZt@u׮]Kt7/WZZV\~RʚƺnA17+SiAAA-^>,cw5RSSf͚3$KJ*}X~mC VlGP vT:v;zi/{T:++7 hlG%'ҜT͌?]n@ @P vl素W{3 hi*--Ú$CkjN *#Z5쳂^r5Ry@ݘRI^ζՔEV5n>$ltݍY?PkG%~mg^b;ԽTF ($xxYqx<ϗT:Us2R՛S{T TՔ({W쌍mZ:UVV^x5d(IAeMoVu*̶СCƊ*`7˥8=&XN}2&՘Ae};6˲W/+.i*!!A^/.þ礬M YSCk֔7Wl?o߿V۷o6mJLL<ʲ @QTTDur)11Q>k֬$*p~Nˉ2pAep=u P۳gZj]^o%"EP *!!AC Y?k֬URzU5:Tǵak-Ae}5Mi)Ge}lJHH8-!!!4Gꏠʙ)UP0xjj#>Òp2ToJRX#5u *vw w=hH=䓇rR̊P'rvKKKSFFFiJJʞ//SBɊLe}!aepˊ6hn;F =b2ToPmܔa+ľ|-V@m!dͿdv/ĵB 7חeEt\pJaT'TȚ?_ V6vfSn T m \$Ãk ծnR nUHzS:r N-խf$+zTZ Bbң {LCƃ]qþ%g >.*Wr?qqu@_2xNPU7gecquT=*sJB-S߿We~X @o& &ַ7el.*ԫ2\{$6DP 7dҿ^5 ՛RrfP&zgeviK R >F9̬kH)XaTsp59-[]`G۲>!{SJ*CB; 2-"n E? . v&cWн*CKjcڨecj "# #72Աu +kL*D[$!dc=A5EXY8Å" &jP;&}5e^;XcEHN]yP)YjQRJk *jcE iOP' N * ,9@nJ5mGն5SShq@HPj@6DHYSB +5D=м5T ?kbkH)9+\k2n:}@c$Aa]{h溶qZWzc6S4OeCmWw\]m+qu!zM6ֹhjc>5p$Fc6kwӐ_}z\yé[}˹f=/_ΩO訐RrnP)5}X|D FShշx}N" 5RJ95lM1@'*¶5^3Z PVЎ5 Q!:qqp&]c 1CĨ (+4@)_Csxt24ʀBs ޜ:Xωk5T' 9kί -W '0%F42Bzݚu8鯥w-YZL0.R &E A Y{ 2>М7IENDB`Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/se.sjoerd.Graphs.appdata.xml.in000066400000000000000000000764701511351636000273010ustar00rootroot00000000000000 @APPLICATION_ID@ CC0-1.0 GPL-3.0-or-later Graphs

Plot and manipulate data @AUTHOR@

Graphs is a simple, yet powerful tool that allows you to plot and manipulate your data with ease. New data can be imported from a wide variety of filetypes, or generated by equation. All data can be manipulated using a variety of operations such as the ability to select, cut, translate, multiply, center and smoothen data, as well as apply any custom transformations on the data.

Moreover, Graphs supports curve fitting on imported data and offers extensive customization options for the style of the plots. You can fine-tune and personalize stylesheets to your liking.

Graphs is an excellent fit for both plotting and data manipulation. The plots created with Graphs can be saved in a variety of formats suitable for sharing and presenting to a wide audience, such as in a scientific publication or presentations. Additionally, the option to save plots as vector images allows for easy editing in software like Inkscape for further refinement. Graphs is written with the GNOME environment in mind, but should be suitable for any other desktop environment as well.

For any feedback, questions or general issues, please file an issue on the GitLab page.

@APPLICATION_ID@.desktop graphs @ISSUE_URL@ @HELP_URL@ @HOMEPAGE_URL@ @VCS_URL@ https://l10n.gnome.org/module/Graphs/ 1 - Plot and manipulate any data of choice https://gitlab.gnome.org/World/Graphs/-/raw/main/data/screenshots/sin_cos.png 2 - Full support for light and dark mode https://gitlab.gnome.org/World/Graphs/-/raw/main/data/screenshots/sin_cos_dark.png 3 - Generate data from an equation https://gitlab.gnome.org/World/Graphs/-/raw/main/data/screenshots/add_equation.png 4 - Edit plot stylesheets https://gitlab.gnome.org/World/Graphs/-/raw/main/data/screenshots/previews.png 5 - Manipulate imported data with ease https://gitlab.gnome.org/World/Graphs/-/raw/main/data/screenshots/curve_fitting.png

Minor update, offering some language updates.

Minor update

  • Update to the GNOME 48 runtime
  • Update runtime dependencies to the latest versions
  • Slightly tweak the system styles to match GNOME 48
  • Updated translations

Minor update with the new GNOME 47 runtime.

  • Update to the GNOME 47 runtime with support for accent colours
  • Update runtime dependencies to the latest versions
  • Improve the canvas rubberband drawing
  • Improved equation parsing

Minor update, offering some language updates.

Minor update, offering some language updates.

Minor update focusing on some bug fixes and language updates.

  • Updates to the translations, and added support for Hungarian
  • The minimum height of the window has been reduced significantly
  • Style previews are bundled with the build, resulting in a about 600% faster start-up times
  • Pressing "Set as default" in the Figure settings now properly shows a toast again
  • Fixed a bunch of issues with regard to equation generations
  • Custom equations in the curve fitting dialog are now stored properly
  • Parameters in the curve fitting dialog are now displayed, even when no proper initial fit could be made
  • Fixed a bug where importing data was not working in case any whitespace was used with the delimiter
  • Fixed an issue where the custom delimiter was not always in the dialog shown when importing data
  • Fixed a bug where the "New Project" dialog would not trigger

A smaller release focusing on mostly background changes.

  • Help has been ported to Yelp and can be accessed from whithin the app
  • Numerical entries now show the validity of user input
  • Improve equation handling
  • Added some new goniometric functions such as the hyperbolic sinoids and their inverses
  • Graphs now fully supports touch screen devices
  • There is now a warning, when editing a style with poor contrast between labels and background colors
  • Curve fitting now supports duplicate variables, and has gotten some bug fixes with regards to equation naming
  • Many improvements to localization
  • Many updates to translations
  • Update to GNOME 46
  • Lots of background changes to improve performance

Graphs is now part of GNOME Circle, the codebase has been moved to the GNOME GitLab. This patch changes updates the metadata to reflect this, and adds some small quality-of-life changes.

Full changelist:

  • The label and title size can now be set within finer increments
  • Data that has been hidden is no longer used when setting the limits automatically
  • Slight performance increasements with regard to preview generation
  • Updated the metadata to reflect the move to GNOME GitLab

Minor patch with a few tweaks.

  • Some strings were updated to use Title Case
  • Local translations have been updated to the latest version at Weblate
  • The highlighted area now deactivates after performing an operation
  • The subtitle now just shows the location without the filename

Version 1.7 marks our biggest release yet. With a migration to GNOME 45 and a new UI overhaul with a revamped style editor. Other major highlights include a new curve fitting functionality, the ability to open projects directly and support for gestures in the canvas. A full changelog can be found below:

New in Graphs:

  • The style editor has been revamped completely and now shows previews for each style
  • Curve fitting functionality has been added to Graphs
  • Graphs can now open data as well as Graphs project files directly from the file manager
  • When closing the application while unsaved changes are present, a dialog is now shown allowing you to save the data
  • The active project and its directory are now shown in the headerbar, including an indicator for unsaved changes
  • Graphs now includes forwards/backwards buttons to quickly navigate to the next and previous view
  • The smoothening action can now be configured with a choice of two different filters, a rolling average and a LOESS Savitzky-Gotsky filter
  • A new Yaru style has been added, which is set as the default system style for the Snap package when Yaru is used in the system, offering a more consistent experience
  • The default matplotlib style has been added to Graphs
  • The title font size can now be changed with respect to the overall font size
  • New scaling options are available for the axes, allowing for radian, square root and inverse scaling
  • Transparent colours for the curves are now supported
  • Superscript characters are now supported when entering equations
  • Support has been added for cotangents, secants and cosecants for equations and transformations
  • Headers are now written to exported files if present in the originally imported data
  • Style sheets now allow you to choose whether a frame should be drawn around the axes
  • The size of the title and labels can now be set seperately in the style editor
  • Importing column data is now more robust, supporting expressions as data points
  • Pinch and ctrl+scroll to zoom gestures are now supported on the canvas
  • The canvas can now be panned using two fingers on a touch screen, as well as with the middle mouse button
  • Toasts now show an "Open Location" button when saving data, bringing you to the saved file location
  • Translate/multiply entries now get a red CSS when input value is invalid, while the corresponding button is disabled

Changed behaviour:

  • Several linguistic changes were made, to get a clearer and more consistent description
  • The syntax for equations has been simplified
  • Delimiters can now be specified from a dropdown menu instead of having to rely on regex
  • The used axes limits are now saved when saving/loading a project
  • Settings related to specific axes are now only displayed when the axis is in use
  • The preferences have been redesigned and simplified, leaving only a single dialog for the figure settings
  • The drag and drop animation when moving items has been improved
  • The headerbar now follows the color of the used stylesheet, giving a more unified look
  • The shortcuts have been modified to follow the rest of the GNOME ecosystem
  • The logic for placing the legend has been changed, so that it now properly moves away when it intersects with a curve
  • The behaviour of the "Shift" action has been revamped to be more consistent when only part of the data span is selected

Bugfixes and changes under the hood:

  • Graphs has been migrated to GNOME 45, and uses the new Libadwaita 1.4 widgets
  • Number inputs are now handled safely without calling the Python eval function
  • The clipboard implementation has been rewritten from scratch
  • The scale buttons in the view menu are now properly changed when the scale has been changed from the figure settings
  • Automatic scaling is now handled properly even when the dataset contains infinite values
  • Graphs now uses unit tests, reducing the risk of regression bugs
  • Part of the code-base has been migrated to Vala
  • Fixed a bug where "Skip rows" did not work properly with single-column data
  • Fixed a bug where rows would change width when selected in case they are adjecent to other entry rows.
  • Graphs translations are now hosted on Weblate
  • More smaller changes and fixes throughout the code-base

Minor patch with some bug fixes:

  • Fixed a bug where "Export Figure" and "Preferences" could not be loaded
  • The "Add data from file" dialog now has filters to make it easier to find the required files.
  • Fixes a bug where the first data point would not be loaded
  • Fixed a bug all styles in the style manager had a check-mark if the system preferred style was used.
  • Fixed a bug where the legend would not be removed from the graph when toggled off, until it was completely reloaded.
  • Automatic axes limits are no longer rounded, which used to lead to problems with the scaling when using data span with a large amount of significant digits.
  • Removed some shortcuts that were overlapping with typing a capital character, making it difficult to write a capital for the respective characters in titles and labels.

New in Graphs:

  • Data can now be opened directly from project files.
  • Adds Dutch translation for Graphs.
  • Adds Turkish translation for Graphs.
  • Adds Swedish translation for Graphs.

Changes:

  • Graphs now always follows the preset system style, unless custom style is set
  • Action dialogs have been changed to follow the GNOME HIG.
  • Regular and advanced import have been merged, offering a single method to import data
  • Labels are now set as an item attribute when loading a new data set with headers, data will be plotted on a new axis when different data types are added.
  • Axis limits are now persistent when saving and opening projects.
  • The axis limits are now reset more intelligently, resulting in fewer unneeded resets
  • Graphs now uses GFile when handling data and figures
  • Multiple refactors under the hood

Bugfixes:

  • Fixed an upstream bug in Matplotlib where part of the Graph would be cut off at scaled resolutions.
  • Fixed a bug where the styles could not be deleted
  • Fixed a bug where the styles could not be reset to defaults
  • Fixed a bug where the legend would remain when all data is removed
  • Fixed a bug where Graphs would crash when the name of an item is changed and a new item is selected from the dropdown.

Changes:

  • Add support for .xry file types from Leybold X-Ray apparatus.
  • Add support for single-column files
  • Changed the behaviour of the canvas so that the limits are not reset unnecessarily
  • Started work to add localization support
  • Refactors under-the-hood

Bug fixes:

  • Fixed a bug where data was not saved when multiple files were exported simultaneously

Hotfix update with bug fixes:

  • Fixes a bug that prevents "Add Equation" from generating new data
  • Fixes a bug where a change of item properties was not included in the clipboard when loading a new style
  • Fixes a bug where the "Restore View" did not properly find the correct limits in specific situations

Major Highlights

  • Massive UI overhaul
  • Save and Load functionality
  • Customizable Plot Styles
  • Loads of optimizations and refactors in the background

Detailed Changelist

  • Removed PIP mode
  • Removed Toolbar
  • Sidebar is now responsive
  • Rearranged button layouts
  • Dialogs now save automatically
  • Graph now has three different interaction modes: pan, zoom and select
  • Operation Buttons are now enabled only when items are selected
  • Operations can now be be performed for just a selected span of data
  • Added combine operation
  • Added custom Adwaita stylesheets for the graph
  • Added a function to quickly change axis scale in the view menu
  • Added a notice when no data is loaded
  • Added functionality to save and load a project
  • Graph ornaments like the zoom rectangle and selector overlay now follow the system colors
  • Follow GNOME HIG in regards to keyboard shortcuts
  • Add a warning dialog, when opening a project while data is loaded
  • Separate Item related options into own dialog
  • Added Toasts on some occasions
  • Removed some options in Preferences, Plot Settings and the Item menu
  • Added a Plot Style editor
  • Plot limits can now be set in Plot Settings
  • Added reordering of items via drag and drop
  • Added an option to hide unselected items from the graph
  • Added a dialog before exporting the figure
  • Support for Panalytical .xdrml files
  • Improved clipboard behaviour

Background Changes

  • Loads of optimizations and refactors
  • Migrate to GNOME 44
  • Blueprint is now used for the UI
  • Introduce linting and follow PEP8
  • Many fixed bugs and edge cases

Minor update with updated AppID.

  • The sidebar is now retractable
  • Graphs now uses symbolic GNOME icons in the toolbar wherever possible
  • The legend is now accomponied with a frame for all styles in order to improve readability
  • Some code clean-up has been performed
  • The new app-id for Graphs is now se.sjoerd.Graphs

Major update with a significant UI overhaul.

New in Graphs:

  • The data list has been completely redesigned, with a selection mode toggle to select/deselect data sets
  • The action grid on the left has been replaced by Expander Rows, sorting the actions by category
  • Checkboxes in settings have been replaced by switches
  • Old entry rows have been replaced by AdwEntryRows
  • The save data button has been moved to the header bar
  • There's no longer an error shown when a new config file is loaded (such as on initial setup)
  • There's now multiple options to choose from on how to handle data with the same name
  • The translation and multiplication transforms now also accept basic expressions (such as 1/2 instead of 0.5)

Bugfixes and other changes:

  • "Shift vertically" option now works properly on linear scale as well
  • Fixed a bug where the last graph to be deleted was still shown
  • Fixed a bug where the text in the dropdown menu of plot-settings didn't update when changing the name of a data set
  • Fourier transforms are now sorted so that the x-data is continiously increasing
  • Added new screenshots for Flathub
  • Some code clean-up has been performed

Minor update with some bugfixes, plus a new logo!

New in Graphs:

  • Graphs has a new icon that is more in-line with the GNOME guidelines!

Bugfixes:

  • Fixed a bug where the graph lines was drawn on top of the legend in certain cases
  • Fixed a bug where the legend wasn't shown for the picture-in-picture graph

Slightly bigger update than usual, with new features and some bug fixes.

New in Graphs:

  • Added the ability to get the derivative of the data
  • Added the ability to get the indefinite integral of the data
  • Added the ability to get the Fast Fourier Transform of the data
  • Added the ability to get the Inverse Fast Fourier Transform of the data
  • Added preference options for the default settings in the "Add equation" window
  • Added tooltips for all buttons on the main window

Other Changes:

  • Default graph limits have been improved, most prominently the y-axis scaling on a linear scale now shows a small margin in both directions if the y-data has a constant value
  • Changed the "reset view" button to use Graph's own canvas limits function, as the built-in version of the toolbar didn't always work properly
  • Gave the "Save Data" option a blue "Suggested action" colour
  • Update Screenshots on Flathub

Bugfixes:

  • Fixed a bug where the x-axis limits were not applied properly if the y-axis was on the right-hand side
  • Fixed a bug where equations would not function without having an X variable. (e.g. Y = 5 is now an accepted equation)
  • Fixed a bug where the initial values for data generated by equation were not added to the clipboard, making it possible to "undo" to the initial state

Minor update mostly focused on the color picker, plus minor quality of life stuff.

  • Changed the color picker to a pop-over window
  • Data set no longer deselects when changing the color
  • Removed the file extensions in the label used for each data set
  • When saving multiple files, files that already exists are saved with a (copy) suffix instead of overwriting the existing file
  • Minor code cleanup

Minor update:

  • Made all pop-up windows modal
  • Added the ability to change axes labels and the title of the plot by double clicking

Brings some minor quality of life features.

  • Ctrl+S now opens the Save Data dialog
  • Fixed a bug where custom settings for tick positions no longer worked properly

Probably last update of the year, most noteworthy is the ability to plot data on right and bottom axis independently. Operations on cutting data still work seamlessly, even when using different axes! Next few updates will probably mainly focus on polish and code clean-up.

  • Added the option to plot data on the right-hand axis
  • Added the option to plot data on the top axis
  • Added the ability to use different labels for right and top axis
  • Scaling of top-bottom and right-left is now handled independently
  • Added a series of toast pop-ups for common actions and errors
  • Made some linguistic changes

Hot-fix update, fixes some bugs with the new "plot settings" window

  • Fixed a bug where the axes labels and title would go back to default when plot settings was opened twice
  • Fixed a bug where the new font wouldn't properly display in plot settings menu
  • Fixed a bug where shortcut would stop working after opening plot settings menu
  • Added a shortcut to open plot settings menu (ctrl+shift+p)
  • Reordered Flathub screenshots

With the ability to edit axes properties without changing the default settings, all essential features are in and it's time to declare v1.2.0. Also added some QoL features. Still many ideas are yet to be implemented. See current changelog below:

  • The "Plot Settings" button now also contains a tab to edit the axes properties
  • The pop-up graph now follows the same scaling as the regular graph
  • Added a shortcut to select all data sets
  • Added a shortcut to deselect all data sets
  • Pressing delete now deletes all selected data sets
  • Changed the wording in "Transform Data" ui to be less confusing
  • Tweaked the save icon a bit
  • Updated Flathub description
  • Made new screenshots for Flathub
  • Changed the webpage to direct to the landing page of Graphs (under construction)

Minor update:

  • Added a "Plot settings" option to control the individual line settings
  • Added a pop-up button to show the graph in a separate window
  • Gave certain buttons a blue "Suggested action" colour
  • Fixed a bug where the advanced column choice didn't work for column indices higher than 1'
  • Some minor bug fixes

Minor update, next update will add an option to control individual line settings (e.g. thickness and style):

  • Cleaned up preferences, making it easier to find the correct setting
  • Linguistic chances
  • Minor UI tweaks in the pop up windows
  • Groundwork for the individual line settings in the code

Major update:

  • Improved the UI for the "Add equation" and "Transform data" windows
  • Added import settings
  • Added an advanced import setting
  • Changed the "general" tab in settings to be the default view
  • Changed screenshots for Flathub
  • Changed capitalization to be more in line with the GNOME HIG
  • Other minor linguistic changes

Minor update with some bug fixes, next update will focus on adding more options for file imports:

  • Fixed a bug where files with a special character in the name didn't save
  • Fixed a bug where minor ticks would show anyway when ticks were turned off
  • Configuration now resets when new options are detected (instead of crashing the application)
  • Added an option to allow for duplicate filenames when importing

Name change and more bugfixes

  • Changed the name of the application to be more in line with the GNOME HIG
  • Transformation window can be closed with the escape button
  • Fixed a bug where graph would not plot if any of the values where infinite (e.g. if equation Y = 1/X was plotted)
  • Step-size, X-start and X-stop in add-equation window now also handle numpy notation

Minor update with some bugfixes

  • Add equation window can now be closed with the escape button
  • File chooser dialog and preference dialogs are modal (stick to parent window)
  • Remove arrow from hamburger menu
  • Add a placeholder hint to Add equation window name
  • Changed saving behaviour to allow saving files without file system access for flatpak
  • Fixed a bug in the add equation window where powers were not working
  • Fixed a bug in add equation window where duplicate filenames were not detected properly

Initial release

keyboard pointing touch #99c1f1 #09356d Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/se.sjoerd.Graphs.desktop.in000066400000000000000000000005571511351636000265320ustar00rootroot00000000000000[Desktop Entry] Name=Graphs Exec=@PROJECT_NAME@ %U Icon=@APPLICATION_ID@ Terminal=false Type=Application Categories=Education;Science StartupNotify=true MimeType=application/graphs;text/plain;text/xy;text/dat;text/csv;text/xrdml;text/xry;text/txt Keywords=Plotting;Graph;Graphing;Science;Mathematics;Math;Equations;Data;Plot;Visualisation;Visualization;Fitting;xrdml;Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/se.sjoerd.Graphs.gschema.xml000066400000000000000000000162371511351636000266640ustar00rootroot00000000000000 "middle-x" "savgol" "linear" "trf" "1std" "a*x+b" 10 3 4 "X Value" "Linear" "Adwaita" "Y Value" "Linear" true "Best" "Y Value 2" "Linear" "" "X Value 2" "Linear" false false "X" "0.01" "0" "10" 100 "png" true 0 1 "whitespace" "" ". " 0 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/se.sjoerd.Graphs.mime.xml000066400000000000000000000005551511351636000262000ustar00rootroot00000000000000 Graphs Project File Graphs Graph Project File Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/style-hc.css000066400000000000000000000004271511351636000236510ustar00rootroot00000000000000@define-color card_border @borders; inline-stack-switcher { box-shadow: inset 0 0 0 1px @borders; } inline-stack-switcher button:checked { box-shadow: inset 0 0 0 1px @borders, 0 1px 3px 1px alpha(black, .07), 0 2px 6px 2px alpha(black, .03); } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/style.css000066400000000000000000000035471511351636000232670ustar00rootroot00000000000000@define-color card_border @card_shade_color; inline-stack-switcher { background: alpha(currentColor, .1); border-radius: 9px; padding: 3px; } inline-stack-switcher button { border-radius: 6px; padding: 2px 15px; transition: 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); transition-property: outline-color, outline-width, outline-offset, background, box-shadow; } inline-stack-switcher:not(.compact) button { min-width: 100px; } inline-stack-switcher button:checked { background: alpha(@card_bg_color, 2); color: @card_fg_color; box-shadow: 0 1px 3px 1px alpha(black, .07), 0 2px 6px 2px alpha(black, .03); } inline-stack-switcher separator { margin: 3px 1px; transition: opacity 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); } inline-stack-switcher separator.hidden { opacity: 0; } gridview#style-grid { background: @window_bg_color; } gridview#style-grid child { margin: 6px 6px 6px 6px; } gridview#style-grid child:selected { background: @window_bg_color; } gridview#style-grid child:hover { background: @window_bg_color; } gridview#style-grid child picture { border-radius: 12px; box-shadow: 0 0 0 1px @borders; } gridview#style-grid child:selected picture { box-shadow: 0 0 0 3px @theme_selected_bg_color; } gridview#style-grid child:focus { outline-style: none; } gridview#style-grid child:focus picture { outline: 0 solid transparent; outline-color: alpha(@theme_selected_bg_color, .5); outline-width: 2px; outline-offset: 2px; transition: outline-color 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94), outline-width 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94), outline-offset 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); } gridview#style-grid child image#indicator { opacity: 0; } gridview#style-grid child:selected image#indicator { color: @theme_selected_bg_color; opacity: 1; } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/000077500000000000000000000000001511351636000227275ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/adwaita-dark.mplstyle000066400000000000000000000042511511351636000270550ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Adwaita Dark # font font.family: sans-serif font.sans-serif: Adwaita Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 3 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: False axes.spines.left: False axes.spines.top: False axes.spines.right: False # ticks xtick.direction: in xtick.minor.visible: True xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 4 xtick.minor.size: 2 xtick.alignment: center ytick.direction: in ytick.minor.visible: True ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 4 ytick.minor.size: 2 ytick.alignment: center xtick.bottom: True ytick.left: True xtick.top: False ytick.right: False # grid axes.grid: True axes.grid.which: major axes.grid.axis: both grid.linewidth: 0.6 grid.alpha: 0.6 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: FFFFFF axes.labelcolor: FFFFFF xtick.labelcolor: FFFFFF ytick.labelcolor: FFFFFF xtick.color: C0BFBC ytick.color: C0BFBC axes.edgecolor: C0BFBC grid.color: 77767B axes.facecolor: 222226 figure.facecolor: 222226 figure.edgecolor: 222226 axes.prop_cycle: cycler('color', ['1A5FB4', '26A269', 'E5A50A', 'C64600', 'A51D2D', '613583', '63452C', '9A9996']) patch.facecolor: 1A5FB4 # other axes.linewidth: 0.8 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.linewidth: 1 image.cmap: Greys legend.numpoints: 1 legend.scatterpoints: 1Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/adwaita.mplstyle000066400000000000000000000042451511351636000261410ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Adwaita # font font.family: sans-serif font.sans-serif: Adwaita Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 3 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: False axes.spines.left: False axes.spines.top: False axes.spines.right: False # ticks xtick.direction: in xtick.minor.visible: True xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 4 xtick.minor.size: 2 xtick.alignment: center ytick.direction: in ytick.minor.visible: True ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 4 ytick.minor.size: 2 ytick.alignment: center xtick.bottom: True ytick.left: True xtick.top: False ytick.right: False # grid axes.grid: True axes.grid.which: major axes.grid.axis: both grid.linewidth: 0.6 grid.alpha: 0.6 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 000000 axes.labelcolor: 000000 xtick.labelcolor: 000000 ytick.labelcolor: 000000 xtick.color: 5E5C64 ytick.color: 5E5C64 axes.edgecolor: 5E5C64 grid.color: 9A9996 axes.facecolor: FAFAFB figure.facecolor: FAFAFB figure.edgecolor: FAFAFB axes.prop_cycle: cycler('color', ['1A5FB4', '26A269', 'E5A50A', 'C64600', 'A51D2D', '613583', '63452C', '9A9996']) patch.facecolor: 1A5FB4 # other axes.linewidth: 0.8 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.linewidth: .3 image.cmap: Greys legend.numpoints: 1 legend.scatterpoints: 1Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/bmh.mplstyle000066400000000000000000000043261511351636000252750ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # BMH # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 legend.fontsize: 12 figure.labelsize: 12 figure.titlesize: 16 axes.titlesize: 16 # lines lines.linewidth: 2 lines.linestyle: solid lines.marker: none lines.markersize: 6 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0.5 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: projecting lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: in xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 4 xtick.minor.size: 2 xtick.alignment : center ytick.direction: in ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 4 ytick.minor.size: 2 ytick.alignment: center xtick.bottom: True ytick.left: True xtick.top: True ytick.right: True # grid axes.grid: True axes.grid.which: major axes.grid.axis: both grid.linewidth: 0.5 grid.alpha: 1.0 grid.linestyle: -- # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: BCBCBC grid.color: B2B2B2 axes.facecolor: EEEEEE figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['348ABD', 'A60628', '7A68A6', '467821', 'D55E00', 'CC79A7', '56B4E9', '009E73', 'F0E442', '0072B2']) patch.facecolor: 348ABD # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.linewidth: 0.5 patch.edgecolor: eeeeee patch.antialiased: True text.hinting_factor: 8 mathtext.fontset: cmGraphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/classic.mplstyle000066400000000000000000000143611511351636000261500ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Classic # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 1.0 lines.linestyle: solid lines.marker: none lines.markersize: 6 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0.5 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: projecting lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: in xtick.minor.visible: False xtick.major.width: 0.5 xtick.minor.width: 0.5 xtick.major.size: 4 xtick.minor.size: 2 xtick.alignment: center ytick.direction: in ytick.minor.visible: False ytick.major.width: 0.5 ytick.minor.width: 0.5 ytick.major.size: 4 ytick.minor.size: 2 ytick.alignment: center xtick.bottom: True ytick.left: True xtick.top: True ytick.right: True # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 0.5 grid.alpha: 1.0 grid.linestyle: : # padding xtick.major.pad: 4 xtick.minor.pad: 4 ytick.major.pad: 4 ytick.minor.pad: 4 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 000000 axes.labelcolor: 000000 xtick.labelcolor: 000000 ytick.labelcolor: 000000 xtick.color: 000000 ytick.color: 000000 axes.edgecolor: 000000 grid.color: 000000 axes.facecolor: FFFFFF figure.facecolor: BFBFBF figure.edgecolor: BFBFBF axes.prop_cycle: cycler('color', ['0000FF', '008000', 'FF0000', '00BFBF', 'BF00BF', 'BFBF00', '000000']) patch.facecolor: 0000FF # other axes.linewidth: 1.0 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.linewidth: 1.0 patch.force_edgecolor: True patch.edgecolor: k patch.antialiased: True hatch.color: k hatch.linewidth: 1.0 hist.bins: 10 font.stretch: normal font.serif: DejaVu Serif, New Century Schoolbook, Century Schoolbook L, Utopia, ITC Bookman, Bookman, Nimbus Roman No9 L, Times New Roman, Times, Palatino, Charter, serif font.cursive: Apple Chancery, Textile, Zapf Chancery, Sand, Script MT, Felipa, cursive font.fantasy: Comic Sans MS, Chicago, Charcoal, ImpactWestern, Humor Sans, fantasy font.monospace: DejaVu Sans Mono, Andale Mono, Nimbus Mono L, Courier New, Courier, Fixed, Terminal, monospace text.usetex: False text.hinting_factor: 8 text.antialiased: True mathtext.cal: cursive mathtext.rm: serif mathtext.tt: monospace mathtext.it: serif:italic mathtext.bf: serif:bold mathtext.sf: sans\-serif mathtext.fontset: cm mathtext.fallback: cm axes.titley: 1.0 axes.formatter.limits: -7, 7 axes.formatter.use_locale: False axes.formatter.use_mathtext: False axes.formatter.useoffset: True axes.formatter.offset_threshold: 2 axes.unicode_minus: True axes.autolimit_mode: round_numbers axes.xmargin: 0 axes.ymargin: 0 polaraxes.grid: True axes3d.grid: True date.autoformatter.year: %Y date.autoformatter.month: %b %Y date.autoformatter.day: %b %d %Y date.autoformatter.hour: %H:%M:%S date.autoformatter.minute: %H:%M:%S.%f date.autoformatter.second: %H:%M:%S.%f date.autoformatter.microsecond: %H:%M:%S.%f date.converter: auto legend.numpoints: 2 legend.borderpad: 0.4 legend.markerscale: 1.0 legend.labelspacing: 0.5 legend.handlelength: 2. legend.handleheight: 0.7 legend.handletextpad: 0.8 legend.borderaxespad: 0.5 legend.columnspacing: 2. legend.shadow: False legend.framealpha: None legend.scatterpoints: 3 legend.facecolor: inherit legend.edgecolor: inherit figure.dpi: 80 figure.autolayout: False figure.frameon: True figure.subplot.left: 0.125 figure.subplot.right: 0.9 figure.subplot.bottom: 0.1 figure.subplot.top: 0.9 figure.subplot.wspace: 0.2 figure.subplot.hspace: 0.2 image.aspect: equal image.interpolation: bilinear image.cmap: jet image.lut: 256 image.origin: upper image.resample: False image.composite_image: True contour.negative_linestyle: dashed contour.corner_mask: True errorbar.capsize: 3 scatter.marker: o boxplot.bootstrap: None boxplot.boxprops.color: b boxplot.boxprops.linestyle: - boxplot.boxprops.linewidth: 1.0 boxplot.capprops.color: k boxplot.capprops.linestyle: - boxplot.capprops.linewidth: 1.0 boxplot.flierprops.color: b boxplot.flierprops.linestyle: none boxplot.flierprops.linewidth: 1.0 boxplot.flierprops.marker: + boxplot.flierprops.markeredgecolor: k boxplot.flierprops.markerfacecolor: auto boxplot.flierprops.markersize: 6.0 boxplot.meanline: False boxplot.meanprops.color: r boxplot.meanprops.linestyle: - boxplot.meanprops.linewidth: 1.0 boxplot.medianprops.color: r boxplot.meanprops.marker: s boxplot.meanprops.markerfacecolor: r boxplot.meanprops.markeredgecolor: k boxplot.meanprops.markersize: 6.0 boxplot.medianprops.linestyle: - boxplot.medianprops.linewidth: 1.0 boxplot.notch: False boxplot.patchartist: False boxplot.showbox: True boxplot.showcaps: True boxplot.showfliers: True boxplot.showmeans: False boxplot.vertical: True boxplot.whiskerprops.color: b boxplot.whiskerprops.linestyle: -- boxplot.whiskerprops.linewidth: 1.0 boxplot.whiskers: 1.5 agg.path.chunksize: 0 path.simplify: True path.simplify_threshold: 0.1111111111111111 path.snap: True path.sketch: None ps.papersize: letter ps.useafm: False ps.usedistiller: False ps.distiller.res: 6000 ps.fonttype: 3 pdf.compression: 6 pdf.fonttype: 3 pdf.inheritcolor: False pdf.use14corefonts: False pgf.texsystem: xelatex pgf.rcfonts: True pgf.preamble: svg.image_inline: True svg.fonttype: path keymap.fullscreen: f, ctrl+f keymap.home: h, r, home keymap.back: left, c, backspace keymap.forward: right, v keymap.pan: p keymap.zoom: o keymap.save: s, ctrl+s keymap.quit: ctrl+w, cmd+w keymap.grid: g keymap.yscale: l keymap.xscale: k, L animation.writer : ffmpeg animation.codec : mpeg4 animation.bitrate: -1 animation.frame_format: png animation.ffmpeg_path: ffmpeg animation.ffmpeg_args: animation.convert_path: convert animation.convert_args: animation.html: none _internal.classic_mode: TrueGraphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/dark-background.mplstyle000066400000000000000000000044611511351636000275650ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Dark Background # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 1 lines.linestyle: solid lines.marker: none lines.markersize: 6 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0.5 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: projecting lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 4 xtick.minor.size: 2 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 4 ytick.minor.size: 2 ytick.alignment: center xtick.bottom: True ytick.left: True xtick.top: True ytick.right: True # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 0.5 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: FFFFFF axes.labelcolor: FFFFFF xtick.labelcolor: FFFFFF ytick.labelcolor: FFFFFF xtick.color: FFFFFF ytick.color: FFFFFF axes.edgecolor: FFFFFF grid.color: FFFFFF axes.facecolor: 000000 figure.facecolor: 000000 figure.edgecolor: 000000 axes.prop_cycle: cycler('color', ['8DD3C7', 'FEFFB3', 'BFBBD9', 'FA8174', '81B1D2', 'FBD462', 'B3DE69', 'BC82BD', 'CCEBC4', 'FFED6F']) patch.facecolor: 8DD3C7 # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.edgecolor: white boxplot.boxprops.color: white boxplot.capprops.color: white boxplot.flierprops.color: white boxplot.flierprops.markeredgecolor: white boxplot.whiskerprops.color: whiteGraphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/fivethirtyeight.mplstyle000066400000000000000000000043241511351636000277430ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # FiveThirtyEight # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 14 axes.labelsize: 14 xtick.labelsize: 14 ytick.labelsize: 14 axes.titlesize: 14 legend.fontsize: 14 figure.titlesize: 14 figure.labelsize: 14 # lines lines.linewidth: 4 lines.linestyle: solid lines.marker: none lines.markersize: 6 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0.5 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: butt lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 0 xtick.minor.width: 0 xtick.major.size: 0 xtick.minor.size: 0 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 0 ytick.minor.width: 0 ytick.major.size: 0 ytick.minor.size: 0 ytick.alignment: center xtick.bottom: False ytick.left: False xtick.top: False ytick.right: False # grid axes.grid: True axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.0 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: F0F0F0 grid.color: CBCBCB axes.facecolor: F0F0F0 figure.facecolor: F0F0F0 figure.edgecolor: F0F0F0 axes.prop_cycle: cycler('color', ['008FD5', 'FC4F30', 'E5AE38', '6D904F', '8B8B8B', '810F7C']) patch.facecolor: 008FD5 # other axes.linewidth: 3 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.edgecolor: f0f0f0 patch.linewidth: 0.5 svg.fonttype: path figure.subplot.left: 0.08 figure.subplot.right: 0.95 figure.subplot.bottom: 0.07Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/ggplot.mplstyle000066400000000000000000000042211511351636000260150ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # ggplot # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 10 axes.labelsize: 10 xtick.labelsize: 10 ytick.labelsize: 10 axes.titlesize: 10 legend.fontsize: 10 figure.titlesize: 10 figure.labelsize: 10 # lines lines.linewidth: 1.0 lines.linestyle: solid lines.marker: none lines.markersize: 6 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0.5 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: projecting lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 4 xtick.minor.size: 2 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 4 ytick.minor.size: 2 ytick.alignment: center xtick.bottom: True ytick.left: True xtick.top: True ytick.right: True # grid axes.grid: True axes.grid.which: major axes.grid.axis: both grid.linewidth: 0.5 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 555555 axes.labelcolor: 555555 xtick.labelcolor: 555555 ytick.labelcolor: 555555 xtick.color: 555555 ytick.color: 555555 axes.edgecolor: FFFFFF grid.color: FFFFFF axes.facecolor: E5E5E5 figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['E24A33', '348ABD', '988ED5', '777777', 'FBC15E', '8EBA42', 'FFB5B8']) patch.facecolor: E24A33 # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.linewidth: 0.5 patch.edgecolor: EEEEEE patch.antialiased: TrueGraphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/grayscale.mplstyle000066400000000000000000000041321511351636000264740ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Grayscale # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 1.0 lines.linestyle: solid lines.marker: none lines.markersize: 6 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0.5 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: projecting lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 4 xtick.minor.size: 2 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 4 ytick.minor.size: 2 ytick.alignment: center xtick.bottom: True ytick.left: True xtick.top: True ytick.right: True # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 0.5 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 000000 axes.labelcolor: 000000 xtick.labelcolor: 000000 ytick.labelcolor: 000000 xtick.color: 000000 ytick.color: 000000 axes.edgecolor: 000000 grid.color: 000000 axes.facecolor: FFFFFF figure.facecolor: BFBFBF figure.edgecolor: BFBFBF axes.prop_cycle: cycler('color', ['000000', '666666', '999999', 'B3B3B3']) patch.facecolor: 000000 # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.edgecolor: black image.cmap: grayGraphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/matplotlib.mplstyle000066400000000000000000000043171511351636000266760ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Matplotlib # font font.family: sans-serif font.sans-serif: DejaVu Sans, Bitstream Vera Sans, Computer Modern Sans Serif, Lucida Grande, Verdana, Geneva, Lucid, Arial, Helvetica, Avant Garde, sans-serif font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 10 axes.labelsize: 10 xtick.labelsize: 10 ytick.labelsize: 10 axes.titlesize: 10 legend.fontsize: 10 figure.titlesize: 10 figure.labelsize: 10 # lines lines.linewidth: 1.5 lines.linestyle: solid lines.marker: none lines.markersize: 6.0 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 1.0 lines.antialiased: True lines.scale_dashes: True markers.fillstyle: full #axes axes.spines.bottom: True axes.spines.left: True axes.spines.right: True axes.spines.top: True #ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 0.8 xtick.minor.width: 0.6 xtick.major.size: 3.5 xtick.minor.size: 2.0 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 0.8 ytick.minor.width: 0.6 ytick.major.size: 3.5 ytick.minor.size: 2.0 ytick.alignment: center_baseline xtick.bottom: True ytick.left: True xtick.top: False ytick.right: False # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 0.8 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 3.5 xtick.minor.pad: 3.5 ytick.major.pad: 3.5 ytick.minor.pad: 3.5 axes.labelpad: 4.0 axes.titlepad: 6.0 # colors text.color: 000000 axes.labelcolor: 000000 xtick.labelcolor: 000000 ytick.labelcolor: 000000 xtick.color: 000000 ytick.color: 000000 axes.edgecolor: 000000 grid.color: b0b0b0 axes.facecolor: FFFFFF figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', '9467bd', '8c564b', 'e377c2', '7f7f7f', 'bcbd22', '17becf']) patch.facecolor: 1f77b4 # other axes.linewidth: 0.8 patch.antialiased: True patch.edgecolor: 000000 mathtext.default: regular axes.axisbelow: line legend.fancybox: True legend.frameon: True # custom patch.linewidth: 1.0 image.cmap: viridis legend.numpoints: 1 legend.scatterpoints: 1 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/seaborn-bright.mplstyle000066400000000000000000000041001511351636000274230ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Seaborn Bright # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 1.75 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 6 xtick.minor.size: 3 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 6 ytick.minor.size: 3 ytick.alignment: center xtick.bottom: False ytick.left: False xtick.top: False ytick.right: False # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.0 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: FFFFFF grid.color: FFFFFF axes.facecolor: EAEAF2 figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['003FFF', '03ED3A', 'E8000B', '8A2BE2', 'FFC400', '00D7FF']) patch.facecolor: 003FFF # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/seaborn-colorblind.mplstyle000066400000000000000000000041041511351636000302770ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Seaborn Colorblind # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 1.75 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 6 xtick.minor.size: 3 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 6 ytick.minor.size: 3 ytick.alignment: center xtick.bottom: False ytick.left: False xtick.top: False ytick.right: False # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.0 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: FFFFFF grid.color: FFFFFF axes.facecolor: EAEAF2 figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['0072B2', '009E73', 'D55E00', 'CC79A7', 'F0E442', '56B4E9']) patch.facecolor: 0072B2 # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/seaborn-dark-palette.mplstyle000066400000000000000000000041061511351636000305270ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Seaborn Dark-palette # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 1.75 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 6 xtick.minor.size: 3 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 6 ytick.minor.size: 3 ytick.alignment: center xtick.bottom: False ytick.left: False xtick.top: False ytick.right: False # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.0 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: FFFFFF grid.color: FFFFFF axes.facecolor: EAEAF2 figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['001C7F', '017517', '8C0900', '7600A1', 'B8860B', '006374']) patch.facecolor: 001C7F # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/seaborn-deep.mplstyle000066400000000000000000000040761511351636000270750ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Seaborn Deep # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 1.75 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 6 xtick.minor.size: 3 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 6 ytick.minor.size: 3 ytick.alignment: center xtick.bottom: False ytick.left: False xtick.top: False ytick.right: False # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.0 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: FFFFFF grid.color: FFFFFF axes.facecolor: EAEAF2 figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['4C72B0', '55A868', 'C44E52', '8172B2', 'CCB974', '64B5CD']) patch.facecolor: 4C72B0 # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/seaborn-muted.mplstyle000066400000000000000000000040771511351636000272770ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Seaborn Muted # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 1.75 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 6 xtick.minor.size: 3 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 6 ytick.minor.size: 3 ytick.alignment: center xtick.bottom: False ytick.left: False xtick.top: False ytick.right: False # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.0 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: FFFFFF grid.color: FFFFFF axes.facecolor: EAEAF2 figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['4878CF', '6ACC65', 'D65F5F', 'B47CC7', 'C4AD66', '77BEDB']) patch.facecolor: 4878CF # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/seaborn-paper.mplstyle000066400000000000000000000041501511351636000272600ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Seaborn Paper # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 10 axes.labelsize: 10 xtick.labelsize: 10 ytick.labelsize: 10 axes.titlesize: 10 legend.fontsize: 10 figure.titlesize: 10 figure.labelsize: 10 # lines lines.linewidth: 1.4 lines.linestyle: solid lines.marker: none lines.markersize: 5.6 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 6 xtick.minor.size: 3 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 6 ytick.minor.size: 3 ytick.alignment: center xtick.bottom: False ytick.left: False xtick.top: False ytick.right: False # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 0.8 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 5.6 xtick.minor.pad: 5.6 ytick.major.pad: 5.6 ytick.minor.pad: 5.6 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: FFFFFF grid.color: FFFFFF axes.facecolor: EAEAF2 figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['4C72B0', '55A868', 'C44E52', '8172B2', 'CCB974', '64B5CD']) patch.facecolor: 4C72B0 # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.linewidth: 0.24 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/seaborn-pastel.mplstyle000066400000000000000000000041001511351636000274340ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Seaborn Pastel # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 1.75 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 6 xtick.minor.size: 3 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 6 ytick.minor.size: 3 ytick.alignment: center xtick.bottom: False ytick.left: False xtick.top: False ytick.right: False # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.0 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: FFFFFF grid.color: FFFFFF axes.facecolor: EAEAF2 figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['92C6FF', '97F0AA', 'FF9F9A', 'D0BBFF', 'FFFEA3', 'B0E0E6']) patch.facecolor: 92C6FF # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/seaborn-poster.mplstyle000066400000000000000000000041561511351636000274730ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Seaborn Poster # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 16 axes.labelsize: 16 xtick.labelsize: 16 ytick.labelsize: 16 legend.fontsize: 16 figure.labelsize: 16 figure.titlesize: 19 axes.titlesize: 19 # lines lines.linewidth: 2.8 lines.linestyle: solid lines.marker: none lines.markersize: 11.2 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 6 xtick.minor.size: 3 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 6 ytick.minor.size: 3 ytick.alignment: center xtick.bottom: False ytick.left: False xtick.top: False ytick.right: False # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.6 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 11.2 xtick.minor.pad: 11.2 ytick.major.pad: 11.2 ytick.minor.pad: 11.2 axes.labelpad: 6 axes.titlepad: 18 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: FFFFFF grid.color: FFFFFF axes.facecolor: EAEAF2 figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['4C72B0', '55A868', 'C44E52', '8172B2', 'CCB974', '64B5CD']) patch.facecolor: 4C72B0 # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.linewidth: 0.48 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/seaborn-talk.mplstyle000066400000000000000000000041521511351636000271060ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Seaborn Talk # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 13 axes.labelsize: 13 xtick.labelsize: 13 ytick.labelsize: 13 legend.fontsize: 13 figure.labelsize: 13 figure.titlesize: 16 axes.titlesize: 16 # lines lines.linewidth: 2.275 lines.linestyle: solid lines.marker: none lines.markersize: 9.1 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 6 xtick.minor.size: 3 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 6 ytick.minor.size: 3 ytick.alignment: center xtick.bottom: False ytick.left: False xtick.top: False ytick.right: False # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.3 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 9.1 xtick.minor.pad: 9.1 ytick.major.pad: 9.1 ytick.minor.pad: 9.1 axes.labelpad: 10 axes.titlepad: 20 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: FFFFFF grid.color: FFFFFF axes.facecolor: EAEAF2 figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['4C72B0', '55A868', 'C44E52', '8172B2', 'CCB974', '64B5CD']) patch.facecolor: 4C72B0 # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.linewidth: 0.39 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/seaborn-ticks.mplstyle000066400000000000000000000042101511351636000272630ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Seaborn Ticks # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 1.75 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 6 xtick.minor.size: 3 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 6 ytick.minor.size: 3 ytick.alignment: center xtick.bottom: True ytick.left: True xtick.top: False ytick.right: False # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.0 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: 262626 grid.color: CCCCCC axes.facecolor: FFFFFF figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['4C72B0', '55A868', 'C44E52', '8172B2', 'CCB974', '64B5CD']) patch.facecolor: 4C72B0 # other axes.linewidth: 1.25 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom legend.numpoints: 1 legend.scatterpoints: 1 image.cmap: Greys Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/seaborn-white.mplstyle000066400000000000000000000042121511351636000272700ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Seaborn White # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 1.75 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 6 xtick.minor.size: 3 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 6 ytick.minor.size: 3 ytick.alignment: center xtick.bottom: False ytick.left: False xtick.top: False ytick.right: False # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.0 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: 262626 grid.color: CCCCCC axes.facecolor: FFFFFF figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['4C72B0', '55A868', 'C44E52', '8172B2', 'CCB974', '64B5CD']) patch.facecolor: 4C72B0 # other axes.linewidth: 1.25 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom legend.numpoints: 1 legend.scatterpoints: 1 image.cmap: Greys Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/seaborn-whitegrid.mplstyle000066400000000000000000000042121511351636000301360ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Seaborn Whitegrid # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 1.75 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 6 xtick.minor.size: 3 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 6 ytick.minor.size: 3 ytick.alignment: center xtick.bottom: False ytick.left: False xtick.top: False ytick.right: False # grid axes.grid: True axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.0 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: CCCCCC grid.color: CCCCCC axes.facecolor: FFFFFF figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['4C72B0', '55A868', 'C44E52', '8172B2', 'CCB974', '64B5CD']) patch.facecolor: 4C72B0 # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom legend.numpoints: 1 legend.scatterpoints: 1 image.cmap: Greys Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/seaborn.mplstyle000066400000000000000000000042241511351636000261550ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Seaborn # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 1.75 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 6 xtick.minor.size: 3 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 6 ytick.minor.size: 3 ytick.alignment: center xtick.bottom: False ytick.left: False xtick.top: False ytick.right: False # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.0 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: FFFFFF grid.color: FFFFFF axes.facecolor: EAEAF2 figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['4C72B0', '55A868', 'C44E52', '8172B2', 'CCB974', '64B5CD']) patch.facecolor: 4C72B0 # other axes.linewidth: 0 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom image.cmap: Greys legend.numpoints: 1 legend.scatterpoints: 1 patch.linewidth: .3Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/solarized-light.mplstyle000066400000000000000000000041551511351636000276300ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Solarized Light # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 legend.fontsize: 12 figure.labelsize: 12 axes.titlesize: 16 figure.titlesize: 16 # lines lines.linewidth: 2 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: butt lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 4 xtick.minor.size: 2 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 4 ytick.minor.size: 2 ytick.alignment: center xtick.bottom: True ytick.left: True xtick.top: True ytick.right: True # grid axes.grid: True axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.0 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 657B83 axes.labelcolor: 657B83 xtick.labelcolor: 657B83 ytick.labelcolor: 657B83 xtick.color: 657B83 ytick.color: 657B83 axes.edgecolor: EEE8D5 grid.color: FDF6E3 axes.facecolor: EEE8D5 figure.facecolor: FDF6E3 figure.edgecolor: FDF6E3 axes.prop_cycle: cycler('color', ['268BD2', '2AA198', '859900', 'B58900', 'CB4B16', 'DC322F', 'D33682', '6C71C4']) patch.facecolor: 268BD2 # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.antialiased: TrueGraphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/tableau-colorblind10.mplstyle000066400000000000000000000041511511351636000304260ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Tableau Colorblind10 # font font.family: sans-serif font.sans-serif: DejaVu Sans font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 1.75 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: out xtick.minor.visible: False xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 4 xtick.minor.size: 2 xtick.alignment: center ytick.direction: out ytick.minor.visible: False ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 4 ytick.minor.size: 2 ytick.alignment: center xtick.bottom: True ytick.left: True xtick.top: True ytick.right: True # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 1.0 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 262626 axes.labelcolor: 262626 xtick.labelcolor: 262626 ytick.labelcolor: 262626 xtick.color: 262626 ytick.color: 262626 axes.edgecolor: FFFFFF grid.color: FFFFFF axes.facecolor: EAEAF2 figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['006BA4', 'FF800E', 'ABABAB', '595959', '5F9ED1', 'C85200', '898989', 'A2C8EC', 'FFBC79', 'CFCFCF']) patch.facecolor: 006BA4 # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: TrueGraphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/thesis.mplstyle000066400000000000000000000042331511351636000260230ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Thesis # font font.family: sans-serif font.sans-serif: Cantarell font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 3 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: True axes.spines.left: True axes.spines.top: True axes.spines.right: True # ticks xtick.direction: in xtick.minor.visible: True xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 8 xtick.minor.size: 4 xtick.alignment: center ytick.direction: in ytick.minor.visible: True ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 8 ytick.minor.size: 4 ytick.alignment: center xtick.bottom: True ytick.left: True xtick.top: True ytick.right: True # grid axes.grid: False axes.grid.which: major axes.grid.axis: both grid.linewidth: 0.6 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 000000 axes.labelcolor: 000000 xtick.labelcolor: 000000 ytick.labelcolor: 000000 xtick.color: 000000 ytick.color: 000000 axes.edgecolor: 000000 grid.color: 646464 axes.facecolor: FFFFFF figure.facecolor: FFFFFF figure.edgecolor: FFFFFF axes.prop_cycle: cycler('color', ['BF1926', '1970D7', '33A02C', 'FF7F00', 'A6CEE3', 'B2DF8A', 'FB9A99', 'FDBF6F', 'CAB2D6']) patch.facecolor: E24A33 # other axes.linewidth: 1 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.linewidth: 0.5 patch.edgecolor: EEEEEE patch.antialiased: True Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/yaru-dark.mplstyle000066400000000000000000000042431511351636000264240ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Yaru Dark # font font.family: sans-serif font.sans-serif: Cantarell font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 3 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: False axes.spines.left: False axes.spines.top: False axes.spines.right: False # ticks xtick.direction: in xtick.minor.visible: True xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 4 xtick.minor.size: 2 xtick.alignment: center ytick.direction: in ytick.minor.visible: True ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 4 ytick.minor.size: 2 ytick.alignment: center xtick.bottom: True ytick.left: True xtick.top: False ytick.right: False # grid axes.grid: True axes.grid.which: major axes.grid.axis: both grid.linewidth: 0.6 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: FFFFFF axes.labelcolor: FFFFFF xtick.labelcolor: FFFFFF ytick.labelcolor: FFFFFF xtick.color: C0BFBC ytick.color: C0BFBC axes.edgecolor: C0BFBC grid.color: 77767B axes.facecolor: 2C2C2C figure.facecolor: 2C2C2C figure.edgecolor: 2C2C2C axes.prop_cycle: cycler('color', ['335280', '0E8420', 'F99B11', 'E95420', 'A91224', '2C001E', '72441D', '878787']) patch.facecolor: 1A5FB4 # other axes.linewidth: 0.8 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.linewidth: 1 image.cmap: Greys legend.numpoints: 1 legend.scatterpoints: 1Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/styles/yaru.mplstyle000066400000000000000000000042371511351636000255100ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later # Yaru # font font.family: sans-serif font.sans-serif: Cantarell font.style: normal font.weight: 400 axes.titleweight: 400 axes.labelweight: 400 figure.titleweight: 400 figure.labelweight: 400 font.variant: normal font.size: 12 axes.labelsize: 12 xtick.labelsize: 12 ytick.labelsize: 12 axes.titlesize: 12 legend.fontsize: 12 figure.titlesize: 12 figure.labelsize: 12 # lines lines.linewidth: 3 lines.linestyle: solid lines.marker: none lines.markersize: 7 lines.markerfacecolor: auto lines.markeredgecolor: auto lines.markeredgewidth: 0 lines.dash_joinstyle: round lines.dash_capstyle: butt lines.solid_joinstyle: round lines.solid_capstyle: round lines.antialiased: True lines.dashed_pattern: 6, 6 lines.dashdot_pattern: 3, 5, 1, 5 lines.dotted_pattern: 1, 3 lines.scale_dashes: False markers.fillstyle: full # axes axes.spines.bottom: False axes.spines.left: False axes.spines.top: False axes.spines.right: False # ticks xtick.direction: in xtick.minor.visible: True xtick.major.width: 1 xtick.minor.width: 0.5 xtick.major.size: 4 xtick.minor.size: 2 xtick.alignment: center ytick.direction: in ytick.minor.visible: True ytick.major.width: 1 ytick.minor.width: 0.5 ytick.major.size: 4 ytick.minor.size: 2 ytick.alignment: center xtick.bottom: True ytick.left: True xtick.top: False ytick.right: False # grid axes.grid: True axes.grid.which: major axes.grid.axis: both grid.linewidth: 0.6 grid.alpha: 1.0 grid.linestyle: - # padding xtick.major.pad: 7 xtick.minor.pad: 7 ytick.major.pad: 7 ytick.minor.pad: 7 axes.labelpad: 5 axes.titlepad: 15 # colors text.color: 000000 axes.labelcolor: 000000 xtick.labelcolor: 000000 ytick.labelcolor: 000000 xtick.color: 5E5C64 ytick.color: 5E5C64 axes.edgecolor: 5E5C64 grid.color: 9A9996 axes.facecolor: FAFAFA figure.facecolor: FAFAFA figure.edgecolor: FAFAFA axes.prop_cycle: cycler('color', ['335280', '0E8420', 'F99B11', 'E95420', 'A91224', '2C001E', '72441D', '878787']) patch.facecolor: 1A5FB4 # other axes.linewidth: 0.8 mathtext.default: regular axes.axisbelow: True legend.fancybox: True legend.frameon: True # custom patch.linewidth: .3 image.cmap: Greys legend.numpoints: 1 legend.scatterpoints: 1Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/000077500000000000000000000000001511351636000220215ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/add-equation.blp000066400000000000000000000043331511351636000250760ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsAddEquationDialog : Adw.Dialog { content-width: 450; title: _("Add Equation"); default-widget: confirm_button; focus-widget: confirm_button; child: Adw.ToolbarView { [top] Adw.HeaderBar { show-end-title-buttons: false; [start] Button { label: _("Cancel"); action-name: "window.close"; } [end] Button confirm_button { label: _("Add"); styles ["suggested-action"] clicked => $on_accept(); } } content: Adw.ToastOverlay toast_overlay { child: Adw.Clamp { margin-start: 12; margin-end: 12; margin-top: 12; margin-bottom: 12; Box { spacing: 20; orientation: vertical; Adw.PreferencesGroup { Adw.EntryRow equation { max-width-chars: 25; title: _("Y ="); activates-default: true; styles ["preferencesgroup"] } Label { margin-top: 6; halign: start; hexpand: true; label: _("Enter a mathematical expression to generate data from"); styles ["dim-label"] } } Adw.PreferencesGroup { Adw.EntryRow item_name { max-width-chars: 25; title: _("Name (optional)"); activates-default: true; } } Adw.PreferencesGroup { Box { spacing: 12; orientation: horizontal; homogeneous: true; Adw.PreferencesGroup { Adw.EntryRow x_start { title: _("X Start"); activates-default: true; } } Adw.PreferencesGroup { Adw.EntryRow x_stop { title: _("X Stop"); activates-default: true; } } Adw.PreferencesGroup { Adw.EntryRow step_size { title: _("Step Size"); activates-default: true; } } } } } }; }; }; }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/add-style.blp000066400000000000000000000015661511351636000244160ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsAddStyleDialog : Adw.Dialog { content-width: 450; title: _("Add New Style"); focus-widget: confirm_button; child: Adw.ToolbarView { [top] Adw.HeaderBar { show-end-title-buttons: false; [start] Button { label: _("Cancel"); action-name: "window.close"; } [end] Button confirm_button { label: _("Add"); styles ["suggested-action"] clicked => $on_accept(); } } content: Adw.Clamp { margin-start: 12; margin-end: 12; margin-top: 12; margin-bottom: 12; Adw.PreferencesGroup { Adw.ComboRow style_templates { title: _("Template"); notify::selected => $on_template_changed(); } Adw.EntryRow new_style_name { title: _("Name"); } } }; }; }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/curve-fitting.blp000066400000000000000000000117461511351636000253170ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsCurveFittingDialog : Adw.Dialog { content-width: 1000; content-height: 600; width-request: 400; height-request: 400; focus-widget: confirm_button; Adw.Breakpoint { condition ("max-width: 725sp") setters { split_view.collapsed: true; } } child: Adw.ToolbarView { top-bar-style: flat; content: Adw.OverlaySplitView split_view { show-sidebar: bind show_sidebar_button.active; sidebar: Adw.ToolbarView { [top] Adw.HeaderBar { styles ["flat"] title-widget: Adw.WindowTitle { title: _("Curve Fitting"); }; [end] MenuButton { icon-name: "view-more-symbolic"; tooltip-text: _("Open Curve Fitting Menu"); primary: true; menu-model: menu; } } ScrolledWindow { vexpand: true; width-request: 280; hscrollbar-policy: never; Box { vexpand: true; orientation: vertical; margin-start: 12; margin-end: 12; margin-bottom: 12; margin-top: 12; Adw.PreferencesGroup { Adw.ComboRow equation { title: _("Equation"); model: StringList{ strings [ C_("regression-type", "Linear"), C_("regression-type", "Quadratic"), C_("regression-type", "Exponential"), C_("regression-type", "Power Law"), C_("regression-type", "Logarithmic"), C_("regression-type", "Sigmoid Logistic"), C_("regression-type", "Gaussian"), C_("regression-type", "Custom"), ] }; } Adw.EntryRow custom_equation { visible: false; title: _("Y ="); } Box fitting_params_box { margin-top: 12; margin-bottom: 12; spacing: 12; orientation: vertical; } ScrolledWindow { vexpand: false; hexpand: false; hscrollbar-policy: automatic; vscrollbar-policy: never; TextView text_view { halign: fill; top-margin: 12; left-margin: 12; bottom-margin: 12; editable: false; styles ["card"] } } Button confirm_button { margin-start: 12; margin-end: 12; margin-bottom: 12; margin-top: 12; label: _("Add Fit to Data"); styles ["pill", "suggested-action"] clicked => $emit_add_fit_request(); } } } } }; content: Box { orientation: vertical; Adw.HeaderBar { styles ["flat"] show-title: false; ToggleButton show_sidebar_button { icon-name: "sidebar-show-symbolic"; tooltip-text: _("Toggle Sidebar"); active: bind split_view.show-sidebar; visible: bind split_view.collapsed; } } Adw.ToastOverlay toast_overlay { focusable: true; height-request: 150; width-request: 300; hexpand: true; child: Adw.StatusPage { icon-name: "dialog-error-symbolic"; title: _("Canvas Failed to Load"); }; } }; }; }; } menu menu { section { label: _("Optimization Method"); item { // Translators: see https://help.gnome.org/graphs/curve_fitting.html#algorithms label: C_("optimization", "Levenberg-Marquardt"); action: "win.optimization"; target: "lm"; } item { // Translators: see https://help.gnome.org/graphs/curve_fitting.html#algorithms label: C_("optimization", "Trust Region Reflective"); action: "win.optimization"; target: "trf"; } item { // Translators: see https://help.gnome.org/graphs/curve_fitting.html#algorithms label: C_("optimization", "Dogbox"); action: "win.optimization"; target: "dogbox"; } } section { label: _("Confidence Bounds"); item { label: C_("confidence", "None"); action: "win.confidence"; target: "none"; } item { /* xgettext: no-c-format */ label: C_("confidence", "1σ: 68% Confidence"); action: "win.confidence"; target: "1std"; } item { /* xgettext: no-c-format */ label: C_("confidence", "2σ: 95% Confidence"); action: "win.confidence"; target: "2std"; } item { /* xgettext: no-c-format */ label: C_("confidence", "3σ: 99.7% Confidence"); action: "win.confidence"; target: "3std"; } } }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/dialogs.blp000066400000000000000000000015351511351636000241460ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; Adw.AlertDialog save_changes { heading: _("Save Changes?"); body: _("Current project contains unsaved changes, changes that are not saved will be permanently lost."); responses [ cancel_close: _("Cancel"), discard_close: _("Discard") destructive, save_close: _("Save") suggested, ] close-response: "cancel_close"; default-response: "discard"; } Adw.AlertDialog delete_style_dialog { heading: _("Delete Style?"); responses [ cancel_delete_style: _("Cancel"), delete_style: _("Delete") destructive, ] close-response: "cancel_delete_style"; default-response: "delete"; } Adw.AlertDialog reset_to_defaults { heading: _("Reset to Defaults?"); responses [ cancel_reset: _("Cancel"), reset: _("Reset") destructive, ] close-response: "cancel_reset"; default-response: "reset"; }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/edit-item.blp000066400000000000000000000062221511351636000244030ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsEditItemDialog : Adw.PreferencesDialog { notify::item => $on_item_change(); closed => $on_close(); Adw.PreferencesPage { Adw.PreferencesGroup { Adw.ComboRow item_selector { title: _("Selected Item"); subtitle: _("Choose which item to edit"); notify::selected => $on_select(); } } Adw.PreferencesGroup { title: _("Data"); Adw.EntryRow name { title: _("Name"); max-width-chars: 25; } Adw.ComboRow xposition { title: _("X-Axis Position"); model: StringList { strings [_("Bottom"), _("Top")] }; } Adw.ComboRow yposition { title: _("Y-Axis Position"); model: StringList { strings [_("Left"), _("Right")] }; } } Adw.PreferencesGroup item_group { visible: false; title: _("Line Properties"); Adw.ComboRow linestyle { title: _("Linestyle"); model: StringList { strings [ C_("linestyle", "None"), C_("linestyle", "Solid"), C_("linestyle", "Dotted"), C_("linestyle", "Dashed"), C_("linestyle", "Dashdot"), ] }; notify::selected => $on_linestyle(); } Adw.ActionRow { title: _("Linewidth"); visible: bind linewidth.sensitive; Scale linewidth { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 0; upper: 10; }; sensitive: false; } } Adw.ComboRow markerstyle { title: _("Markers"); model: StringList { strings [ C_("markerstyle", "None"), C_("markerstyle", "Point"), C_("markerstyle", "Pixel"), C_("markerstyle", "Circle"), C_("markerstyle", "Triangle Down"), C_("markerstyle", "Triangle Up"), C_("markerstyle", "Triangle Left"), C_("markerstyle", "Triangle Right"), C_("markerstyle", "Octagon"), C_("markerstyle", "Square"), C_("markerstyle", "Pentagon"), C_("markerstyle", "Star"), C_("markerstyle", "Hexagon 1"), C_("markerstyle", "Hexagon 2"), C_("markerstyle", "Plus"), C_("markerstyle", "X"), // Translators: Diamond as in Cards Symbol C_("markerstyle", "Diamond"), // Translators: Diamond as in Cards Symbol C_("markerstyle", "Thin Diamond"), C_("markerstyle", "Vertical Line"), C_("markerstyle", "Horizontal Line"), C_("markerstyle", "Filled Plus"), C_("markerstyle", "Filled X"), ] }; notify::selected => $on_markers(); } Adw.ActionRow { title: _("Marker Size"); visible: bind markersize.sensitive; Scale markersize { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 0; upper: 10; }; sensitive: false; } } } } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/export-figure.blp000066400000000000000000000031131511351636000253160ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsExportFigureDialog : Adw.Dialog { content-width: 450; title: _("Export Figure"); focus-widget: confirm_button; child: Adw.ToolbarView { [top] Adw.HeaderBar { show-end-title-buttons: false; [start] Button { label: _("Cancel"); action-name: "window.close"; } [end] Button confirm_button { label: _("Export"); styles ["suggested-action"] clicked => $on_accept(); } } content: Adw.Clamp { margin-start: 12; margin-end: 12; margin-top: 12; margin-bottom: 12; Adw.PreferencesGroup { focus-on-click: false; Adw.ComboRow file_format { title: _("File Format"); model: StringList { strings [ C_("file-format", "Encapsulated Postscript"), C_("file-format", "Joint Photographic Experts Group"), C_("file-format", "Portable Document Format"), C_("file-format", "Portable Network Graphics"), C_("file-format", "Postscript"), C_("file-format", "Scalable Vector Graphics"), C_("file-format", "WebP Image Format"), ] }; notify::selected => $on_file_format(); } Adw.SpinRow dpi { title: _("Resolution (dpi)"); adjustment: Adjustment { step-increment: 1; upper: 999; }; } Adw.SwitchRow transparent { title: _("Transparent Background"); } } }; }; } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/figure-settings-dialog.blp000066400000000000000000000020251511351636000270730ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsFigureSettingsDialog : Adw.Dialog { content-width: 640; content-height: 576; width-request: 400; height-request: 400; styles ["preferences"] Adw.Breakpoint { condition ("max-width: 600sp") setters { grid_view.max-columns: 2; grid_view.min-columns: 2; } } child: Adw.ToastOverlay toast_overlay { Adw.NavigationView navigation_view { popped => $on_pop(); } }; } Adw.NavigationPage style_overview { title: _("Style"); Adw.ToolbarView { [top] Adw.HeaderBar { [end] Button { icon-name: "list-add-symbolic"; tooltip-text: _("Add New Style"); clicked => $add_style(); } } content: ScrolledWindow { hscrollbar-policy: never; Adw.Clamp { margin-bottom: 6; margin-top: 6; margin-start: 6; margin-end: 6; GridView grid_view { name: "style-grid"; max-columns: 3; min-columns: 3; } } }; } }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/figure-settings-page.blp000066400000000000000000000154651511351636000265640ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; Adw.NavigationPage settings_page { title: _("Figure Settings"); Adw.ToolbarView { [top] Adw.HeaderBar {} content: ScrolledWindow { hscrollbar-policy: never; propagate-natural-height: true; Adw.Clamp { margin-bottom: 12; margin-top: 12; margin-start: 12; margin-end: 12; Box { orientation: vertical; spacing: 16; Adw.PreferencesGroup { title: _("Labels"); Adw.EntryRow title { title: _("Title"); } Adw.EntryRow bottom_label { visible: false; title: _("Bottom X Axis Label"); } Adw.EntryRow top_label { visible: false; title: _("Top X Axis Label"); } Adw.EntryRow left_label { visible: false; title: _("Left Y Axis Label"); } Adw.EntryRow right_label { visible: false; title: _("Right Y Axis Label"); } } Adw.PreferencesGroup { title: _("Axis Limits"); Box { orientation: vertical; spacing: 12; Box bottom_limits { visible: false; orientation: horizontal; homogeneous: true; spacing: 6; Adw.PreferencesGroup { Adw.EntryRow min_bottom { title: _("Bottom X Axis Minimum"); } } Adw.PreferencesGroup { Adw.EntryRow max_bottom { title: _("Bottom X Axis Maximum"); } } } Box top_limits { visible: false; spacing: 6; homogeneous: true; orientation: horizontal; Adw.PreferencesGroup { Adw.EntryRow min_top { title: _("Top X Axis Minimum"); } } Adw.PreferencesGroup { Adw.EntryRow max_top { title: _("Top X Axis Maximum"); } } } Box left_limits { visible: false; orientation: horizontal; homogeneous: true; spacing: 6; Adw.PreferencesGroup { Adw.EntryRow min_left { title: _("Left Y Axis Minimum"); } } Adw.PreferencesGroup { Adw.EntryRow max_left { title: _("Left Y Axis Maximum"); } } } Box right_limits { visible: false; homogeneous: true; orientation: horizontal; spacing: 6; Adw.PreferencesGroup { Adw.EntryRow min_right { title: _("Right Y Axis Minimum"); } } Adw.PreferencesGroup { Adw.EntryRow max_right { title: _("Right Y Axis Maximum"); } } } } } Adw.PreferencesGroup { title: _("Scaling"); Adw.ComboRow bottom_scale { visible: false; title: _("Bottom X Axis Scale"); model: StringList{ strings [ C_("scale", "Linear"), C_("scale", "Logarithmic"), C_("scale", "Radians"), C_("scale", "Square Root"), C_("scale", "Inverse"), ] }; } Adw.ComboRow top_scale { visible: false; title: _("Top X Axis Scale"); model: StringList{ strings [ C_("scale", "Linear"), C_("scale", "Logarithmic"), C_("scale", "Radians"), C_("scale", "Square Root"), C_("scale", "Inverse"), ] }; } Adw.ComboRow left_scale { visible: false; title: _("Left Y Axis Scale"); model: StringList{ strings [ C_("scale", "Linear"), C_("scale", "Logarithmic"), C_("scale", "Radians"), C_("scale", "Square Root"), C_("scale", "Inverse"), ] }; } Adw.ComboRow right_scale { visible: false; title: _("Right Y Axis Scale"); model: StringList{ strings [ C_("scale", "Linear"), C_("scale", "Logarithmic"), C_("scale", "Radians"), C_("scale", "Square Root"), C_("scale", "Inverse"), ] }; } } Adw.PreferencesGroup { title: _("Appearance"); Adw.ExpanderRow legend { title: _("Legend"); show-enable-switch: true; Adw.ComboRow legend_position { title: _("Legend Position"); model: StringList{ strings [ C_("legend-position", "Auto"), C_("legend-position", "Upper Right"), C_("legend-position", "Upper Left"), C_("legend-position", "Lower Left"), C_("legend-position", "Lower Right"), C_("legend-position", "Center Left"), C_("legend-position", "Center Right"), C_("legend-position", "Lower Center"), C_("legend-position", "Upper Center"), C_("legend-position", "Center"), ] }; } } Adw.SwitchRow hide_unselected { title: _("Hide Unselected Items"); } Adw.ActionRow style_row { title: _("Style"); hexpand: true; activatable: true; [suffix] Label style_name {} [suffix] Image { icon-name: "go-next-symbolic"; } } } Adw.PreferencesGroup { margin-top: 12; hexpand: false; halign: center; margin-bottom: 12; Button set_as_default { label: _("Set as Default"); styles ["pill"] } } } } }; } }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/fitting-parameters.blp000066400000000000000000000021111511351636000263200ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsFittingParameterBox : Box { orientation: vertical; Grid { column-homogeneous: true; margin-bottom: 3; margin-top: 3; column-spacing: 4; row-spacing: 4; Label label { justify: left; use-markup: true; margin-bottom: 4; hexpand: false; halign: start; layout { column: 0; row: 0; column-span: 2; } } Adw.PreferencesGroup { Adw.EntryRow initial { title: _("Initial Guess"); text: "1"; } layout { column: 0; row: 1; column-span: 2; } } Adw.PreferencesGroup { visible: bind lower_bound.visible; Adw.EntryRow lower_bound { title: _("Minimum"); text: "-inf"; } layout { column: 0; row: 2; } } Adw.PreferencesGroup { visible: bind upper_bound.visible; Adw.EntryRow upper_bound { title: _("Maximum"); text: "inf"; } layout { column: 1; row: 2; } } } }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/import-columns.blp000066400000000000000000000027511511351636000255150ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; Adw.PreferencesGroup columns_group { Adw.ComboRow columns_delimiter { title: _("Delimiter"); subtitle: _("Character sequence used to split values"); //notify::selected => $on_delimiter_change(); model: StringList{ strings [ C_("delimiter", "Whitespace"), C_("delimiter", "Tab"), C_("delimiter", "Colon (:)"), C_("delimiter", "Semicolon (;)"), C_("delimiter", "Decimal comma (,)"), C_("delimiter", "Decimal point (.)"), C_("delimiter", "Custom"), ] }; } Adw.EntryRow columns_custom_delimiter { visible: false; max-width-chars: 10; title: _("Custom Delimiter"); } Adw.ComboRow columns_separator { title: _("Decimal Separator"); model: StringList{ strings [ C_("separator", "Decimal comma (,)"), C_("separator", "Decimal point (.)"), ] }; } Adw.SpinRow columns_column_x { title: _("Column X"); subtitle: _("X-data column index"); adjustment: Adjustment { step-increment: 1; upper: 100; value: 0; }; } Adw.SpinRow columns_column_y { title: _("Column Y"); selectable: false; subtitle: _("Y-data column index"); adjustment: Adjustment { step-increment: 1; upper: 100; }; } Adw.SpinRow columns_skip_rows { title: _("Skip Rows"); subtitle: _("Ignored row indices"); adjustment: Adjustment { step-increment: 1; upper: 9999; }; } }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/import.blp000066400000000000000000000016321511351636000240340ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsImportDialog : Adw.Dialog { content-width: 450; title: _("Modify Import Parameters"); focus-widget: confirm_button; child: Adw.ToolbarView { [top] Adw.HeaderBar { show-end-title-buttons: false; [start] Button { label: _("Cancel"); action-name: "window.close"; } [end] Box { orientation: horizontal; spacing: 10; Button { icon-name: "history-undo-symbolic"; clicked => $on_reset(); } Button confirm_button { label: _("Import"); styles ["suggested-action"] clicked => $on_accept(); } } } content: Adw.Clamp { margin-start: 12; margin-end: 12; margin-top: 12; margin-bottom: 12; Box mode_box { orientation: vertical; spacing: 10; } }; }; }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/item-box.blp000066400000000000000000000025051511351636000242460ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsItemBox : Box { margin-start: 6; margin-end: 6; margin-bottom: 9; margin-top: 9; CheckButton check_button { halign: start; hexpand: false; styles ["selection-mode"] notify::active => $on_toggle(); } Label label { margin-start: 6; margin-end: 6; halign: start; hexpand: true; ellipsize: end; } Button color_button { tooltip-text: _("Pick Color"); styles ["flat"] clicked => $choose_color(); Image { hexpand: false; icon-name: "color-picker-symbolic"; pixel-size: 20; } } MenuButton { icon-name: "view-more-symbolic"; popover: popover_menu; styles ["flat"] } } PopoverMenu popover_menu { name: "popover_menu"; has-arrow: false; menu-model: menu_app; } menu menu_app { section { label: _("Move Item "); display-hint: "inline-buttons"; item { verb-icon: "down-symbolic"; action: "item_box.move_down"; } item { verb-icon: "up-symbolic"; action: "item_box.move_up"; } } section { item { label: _("Curve Fitting"); action: "item_box.curve_fitting"; } item { label: _("Edit Item"); action: "item_box.edit"; } item { label: _("Remove Item"); action: "item_box.delete"; } } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/shortcuts.blp000066400000000000000000000100761511351636000245620ustar00rootroot00000000000000using Gtk 4.0; ShortcutsWindow help_overlay { modal: true; ShortcutsSection { section-name: "shortcuts"; max-height: 10; ShortcutsGroup { title: C_("shortcut window", "General"); ShortcutsShortcut { title: C_("shortcut window", "Show Shortcuts"); action-name: "win.show-help-overlay"; } ShortcutsShortcut { title: C_("shortcut window", "Open Help"); action-name: "app.help"; } ShortcutsShortcut { title: C_("shortcut window", "Open Figure Settings"); accelerator: "comma"; } ShortcutsShortcut { title: C_("shortcut window", "Close Application"); accelerator: "q"; } ShortcutsShortcut { title: C_("shortcut window", "Close Application Window"); accelerator: "w"; } } ShortcutsGroup { title: C_("shortcut window", "Mode Switching"); ShortcutsShortcut { title: C_("shortcut window", "Pan Mode"); accelerator: "1"; } ShortcutsShortcut { title: C_("shortcut window", "Zoom Mode"); accelerator: "2"; } ShortcutsShortcut { title: C_("shortcut window", "Select Mode"); accelerator: "3"; } } ShortcutsGroup { title: C_("shortcut window", "New Data"); ShortcutsShortcut { title: C_("shortcut window", "Add Data from Equation"); accelerator: "N"; } ShortcutsShortcut { title: C_("shortcut window", "Add Data from File"); accelerator: "N"; } ShortcutsShortcut { title: C_("shortcut window", "New Project"); accelerator: "N"; } ShortcutsShortcut { title: C_("shortcut window", "Open Project"); accelerator: "O"; } ShortcutsShortcut { title: C_("shortcut window", "Delete Selected Data"); accelerator: "Delete"; } } ShortcutsGroup { title: C_("shortcut window", "Save and Export"); ShortcutsShortcut { title: C_("shortcut window", "Export Data"); accelerator: "E"; } ShortcutsShortcut { title: C_("shortcut window", "Export Figure"); accelerator: "E"; } ShortcutsShortcut { title: C_("shortcut window", "Save Project"); accelerator: "S"; } ShortcutsShortcut { title: C_("shortcut window", "Save Project As"); accelerator: "S"; } } ShortcutsGroup { title: C_("shortcut window", "View"); ShortcutsShortcut { title: C_("shortcut window", "Previous View"); accelerator: "Z"; } ShortcutsShortcut { title: C_("shortcut window", "Next View"); accelerator: "Z"; } ShortcutsShortcut { title: C_("shortcut window", "Optimize limits"); accelerator: "0"; } ShortcutsShortcut { title: C_("shortcut window", "Zoom in"); accelerator: "plus"; } ShortcutsShortcut { title: C_("shortcut window", "Zoom out"); accelerator: "minus"; } ShortcutsShortcut { title: C_("shortcut window", "Select None"); accelerator: "A"; } ShortcutsShortcut { title: C_("shortcut window", "Toggle Sidebar"); accelerator: "F9"; } } ShortcutsGroup { title: C_("shortcut window", "Actions"); ShortcutsShortcut { title: C_("shortcut window", "Select All"); accelerator: "A"; } ShortcutsShortcut { title: C_("shortcut window", "Select None"); accelerator: "A"; } ShortcutsShortcut { title: C_("shortcut window", "Undo Action"); accelerator: "Z"; } ShortcutsShortcut { title: C_("shortcut window", "Redo Action"); accelerator: "Z"; } } } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/smoothen-settings.blp000066400000000000000000000027741511351636000262240ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsSmoothenDialog : Adw.Dialog { content-width: 640; content-height: 576; title: _("Smoothen Settings"); focus-widget: reset_button; child: Adw.ToolbarView { [top] Adw.HeaderBar { [start] Button reset_button { icon-name: "history-undo-symbolic"; clicked => $on_reset(); } } content: Adw.Clamp { margin-start: 12; margin-end: 12; margin-top: 12; margin-bottom: 12; Box { orientation: vertical; spacing: 10; Adw.PreferencesGroup { title: _("Savitzky–Golay Filter"); Adw.SpinRow savgol_window { title: _("Span percentage"); subtitle: _("What percentage of the data span to use for the filter window"); adjustment: Adjustment { step-increment: 1; upper: 99; }; } Adw.SpinRow savgol_polynomial { title: _("Polynomial degree"); adjustment: Adjustment { step-increment: 1; upper: 20; }; } } Adw.PreferencesGroup { title: _("Moving Average"); Adw.SpinRow moving_average_box { title: _("Box points"); subtitle: _("Amount of points to use when calculating a moving average"); adjustment: Adjustment { step-increment: 1; upper: 9999; }; } } } }; }; }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/style-color-box.blp000066400000000000000000000012231511351636000255600ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsStyleColorBox : Box { margin-start: 6; margin-end: 6; margin-bottom: 9; margin-top: 9; styles ["boxed-list"] Label label { margin-end: 6; halign: start; hexpand: true; } Button color_button { tooltip-text: _("Edit"); clicked => $on_color_choose(); styles ["flat"] Image { hexpand: false; icon-name: "color-picker-symbolic"; pixel-size: 20; } } Button { tooltip-text: _("Remove"); clicked => $on_delete(); styles ["flat"] Image { hexpand: false; icon-name: "edit-delete-symbolic"; pixel-size: 20; } } }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/style-editor.blp000066400000000000000000000346121511351636000251520ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsStyleEditor : Adw.NavigationPage { title: _("Error - No Style selected"); Adw.ToolbarView { [top] Adw.HeaderBar { styles ["flat"] [end] Button delete_button { tooltip-text: _("Delete"); styles ["flat"] Image { hexpand: false; icon-name: "user-trash-symbolic"; } clicked => $on_delete(); } } content: ScrolledWindow { hscrollbar-policy: never; vexpand: true; Adw.Clamp { margin-bottom: 12; margin-top: 12; margin-start: 12; margin-end: 12; Box { orientation: vertical; hexpand: true; spacing: 20; Adw.PreferencesGroup { Adw.EntryRow style_name { title: _("Style Name"); } Adw.ActionRow { title: _("Font"); activatable-widget: font_chooser; FontDialogButton font_chooser { valign: center; dialog: FontDialog {}; use-font: true; } } Adw.ActionRow { title: _("Title Size"); subtitle: _("Title size in relation to text size"); Scale titlesize { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 1; upper: 4; }; } } Adw.ActionRow { title: _("Label Size"); subtitle: _("Label size in relation to text size"); Scale labelsize { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 1; upper: 4; }; } } } Adw.PreferencesGroup { title: _("Lines"); Adw.ComboRow linestyle { title: _("Linestyle"); model: StringList { strings [ C_("linestyle", "None"), C_("linestyle", "Solid"), C_("linestyle", "Dotted"), C_("linestyle", "Dashed"), C_("linestyle", "Dashdot"), ] }; notify::selected => $on_linestyle(); } Adw.ActionRow { title: _("Linewidth"); visible: bind linewidth.sensitive; Scale linewidth { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 0; upper: 10; }; sensitive: false; } } Adw.ComboRow markers { title: _("Markers"); model: StringList { strings [ C_("markerstyle", "None"), C_("markerstyle", "Point"), C_("markerstyle", "Pixel"), C_("markerstyle", "Circle"), C_("markerstyle", "Triangle Down"), C_("markerstyle", "Triangle Up"), C_("markerstyle", "Triangle Left"), C_("markerstyle", "Triangle Right"), C_("markerstyle", "Octagon"), C_("markerstyle", "Square"), C_("markerstyle", "Pentagon"), C_("markerstyle", "Star"), C_("markerstyle", "Hexagon 1"), C_("markerstyle", "Hexagon 2"), C_("markerstyle", "Plus"), C_("markerstyle", "X"), // Translators: Diamond as in Cards Symbol C_("markerstyle", "Diamond"), // Translators: Diamond as in Cards Symbol C_("markerstyle", "Thin Diamond"), C_("markerstyle", "Vertical Line"), C_("markerstyle", "Horizontal Line"), C_("markerstyle", "Filled Plus"), C_("markerstyle", "Filled X"), ] }; notify::selected => $on_markers(); } Adw.ActionRow { title: _("Marker Size"); visible: bind markersize.sensitive; Scale markersize { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 0; upper: 10; }; sensitive: false; } } } Adw.PreferencesGroup { title: _("Axes"); Adw.ActionRow { title: _("Axis Width"); Scale axis_width { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 0; upper: 4; }; } } Adw.SwitchRow draw_frame { title: _("Draw Frame"); } } Adw.PreferencesGroup { title: _("Ticks"); Adw.ComboRow tick_direction { title: _("Tick Directions"); model: StringList { strings [_("Inwards"), _("Outwards")] }; } Adw.SwitchRow minor_ticks { title: _("Minor Ticks"); } Adw.ActionRow { title: _("Major Tick Width"); Scale major_tick_width { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 0; upper: 4; }; } } Adw.ActionRow { title: _("Minor Tick Width"); visible: bind minor_ticks.active; Scale minor_tick_width { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 0; upper: 4; }; } } Adw.ActionRow { title: _("Major Tick Length"); Scale major_tick_length { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 0; upper: 20; }; } } Adw.ActionRow { title: _("Minor Tick Length"); visible: bind minor_ticks.active; Scale minor_tick_length { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 0; upper: 20; }; } } Adw.SwitchRow tick_bottom { title: _("Ticks on Bottom Axis"); } Adw.SwitchRow tick_left { title: _("Ticks on Left Axis"); } Adw.SwitchRow tick_right { title: _("Ticks on Right Axis"); } Adw.SwitchRow tick_top { title: _("Ticks on Top Axis"); } } Adw.PreferencesGroup { title: _("Grid"); Adw.SwitchRow show_grid { title: _("Show Grid"); } Adw.ActionRow { title: _("Grid Width"); visible: bind show_grid.active; Scale grid_linewidth { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 0; upper: 4; }; } } Adw.ActionRow { title: _("Grid Opacity"); visible: bind show_grid.active; Scale grid_opacity { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 0; upper: 1; }; } } } Adw.PreferencesGroup { title: _("Padding"); description: _("Padding for different parts of the figure"); Adw.ActionRow { title: _("Value Padding"); subtitle: _("Padding between axes and values"); Scale value_padding { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 0; upper: 40; }; } } Adw.ActionRow { title: _("Label Padding"); subtitle: _("Padding between axes and labels"); Scale label_padding { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 0; upper: 40; }; } } Adw.ActionRow { title: _("Title Padding"); subtitle: _("Padding between axes and the title"); Scale title_padding { draw-value: true; width-request: 200; adjustment: Adjustment { lower: 0; upper: 40; }; } } } Adw.PreferencesGroup { title: _("Colors"); header-suffix: Box poor_contrast_warning { styles ["warning", "flat"] spacing: 6; tooltip-text: _("Label text might be hard to read"); Image { icon-name: "dialog-warning-symbolic"; } Label { label: _("Poor Contrast"); } }; Grid { row-spacing: 20; column-spacing: 20; Adw.PreferencesGroup { layout { column: 0; row: 0; } Adw.ActionRow { title: _("Text Color"); activatable-widget: text_color; hexpand: true; Button text_color { tooltip-text: _("Change Color"); valign: center; styles ["flat"] Image { icon-name: "color-picker-symbolic"; pixel-size: 20; } } } } Adw.PreferencesGroup { layout { column: 1; row: 0; } Adw.ActionRow { title: _("Tick Color"); activatable-widget: tick_color; hexpand: true; Button tick_color { tooltip-text: _("Change Color"); valign: center; styles ["flat"] Image { icon-name: "color-picker-symbolic"; pixel-size: 20; } } } } Adw.PreferencesGroup { layout { column: 0; row: 1; } Adw.ActionRow { title: _("Axis Color"); activatable-widget: axis_color; hexpand: true; Button axis_color { tooltip-text: _("Change Color"); valign: center; styles ["flat"] Image { icon-name: "color-picker-symbolic"; pixel-size: 20; } } } } Adw.PreferencesGroup { layout { column: 1; row: 1; } Adw.ActionRow { title: _("Grid Color"); activatable-widget: grid_color; hexpand: true; Button grid_color { tooltip-text: _("Change Color"); valign: center; styles ["flat"] Image { icon-name: "color-picker-symbolic"; pixel-size: 20; } } } } Adw.PreferencesGroup { layout { column: 0; row: 2; } Adw.ActionRow { title: _("Background Color"); activatable-widget: background_color; hexpand: true; Button background_color { tooltip-text: _("Change Color"); valign: center; styles ["flat"] Image { icon-name: "color-picker-symbolic"; pixel-size: 20; } } } } Adw.PreferencesGroup { layout { column: 1; row: 2; } Adw.ActionRow { title: _("Outline Color"); activatable-widget: outline_color; hexpand: true; Button outline_color { tooltip-text: _("Change Color"); valign: center; styles ["flat"] Image { icon-name: "color-picker-symbolic"; pixel-size: 20; } } } } } } Adw.PreferencesGroup { title: _("Line Colors"); header-suffix: Button { clicked => $add_color(); Adw.ButtonContent { halign: center; icon-name: "list-add-symbolic"; label: _("Add Color"); } styles ["flat"] }; ListBox line_colors_box { vexpand: false; valign: start; hexpand: true; selection-mode: none; styles ["boxed-list"] } } } } }; } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/style-preview.blp000066400000000000000000000015661511351636000253470ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsStylePreview : Box { AspectFrame { child: Box { orientation: vertical; Overlay { height-request: 96; width-request: 160; [overlay] Image { halign: end; valign: end; margin-end: 6; margin-bottom: 6; icon-name: "check-round-outline-whole-symbolic"; name: "indicator"; } [overlay] Button edit_button { halign: end; valign: start; margin-end: 6; margin-top: 6; visible: false; tooltip-text: _("Edit"); styles ["flat"] Image { hexpand: false; icon-name: "document-edit-symbolic"; } } child: Picture picture {}; } Label label { margin-top: 8; } }; } }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/transform.blp000066400000000000000000000030411511351636000245310ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsTransformDialog : Adw.Dialog { content-width: 450; title: _("Transform Data"); default-widget: confirm_button; focus-widget: confirm_button; child: Adw.ToolbarView { [top] Adw.HeaderBar { show-end-title-buttons: false; [start] Button { label: _("Cancel"); action-name: "window.close"; } [end] Button confirm_button { label: _("Transform"); styles ["suggested-action"] clicked => $on_accept(); } [end] Button help_button { Adw.ButtonContent { halign: center; icon-name: "info-symbolic"; Popover help_popover { position: bottom; Label help_info { label: _("Additional variables:\nx_min, y_min\nx_max, y_max \n\nTrigonometric functions use radians\nby default, append d to the function\nto use degrees, e.g. sind(x) or cosd(x)."); } } } halign: end; tooltip-text: _("More info"); styles ["flat"] } } content: Adw.Clamp { margin-start: 12; margin-end: 12; margin-top: 12; margin-bottom: 12; Adw.PreferencesGroup { Adw.EntryRow transform_x { title: _("X ="); activates-default: true; } Adw.EntryRow transform_y { title: _("Y ="); activates-default: true; } Adw.SwitchRow discard { title: _("Discard Unselected Data"); } } }; }; }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/ui/window.blp000066400000000000000000000510061511351636000240310ustar00rootroot00000000000000using Gtk 4.0; using Adw 1; template $GraphsWindow: Adw.ApplicationWindow { default-width: 1200; default-height: 700; width-request: 400; height-request: 420; title: _("Graphs"); ShortcutController { Shortcut { trigger: "q"; action: "action(app.quit)"; } Shortcut { trigger: "w"; action: "action(app.quit)"; } Shortcut { trigger: "comma"; action: "action(app.figure_settings)"; } Shortcut { trigger: "N"; action: "action(app.add_data)"; } Shortcut { trigger: "N"; action: "action(app.add_equation)"; } Shortcut { trigger: "A"; action: "action(app.select_all)"; } Shortcut { trigger: "A"; action: "action(app.select_none)"; } Shortcut { trigger: "Z"; action: "action(app.undo)"; } Shortcut { trigger: "Z"; action: "action(app.redo)"; } Shortcut { trigger: "KP_0"; action: "action(app.optimize_limits)"; } Shortcut { trigger: "0"; action: "action(app.optimize_limits)"; } Shortcut { trigger: "Z"; action: "action(app.view_back)"; } Shortcut { trigger: "Z"; action: "action(app.view_forward)"; } Shortcut { trigger: "E"; action: "action(app.export_data)"; } Shortcut { trigger: "E"; action: "action(app.export_figure)"; } Shortcut { trigger: "period"; action: "action(app.styles)"; } Shortcut { trigger: "S"; action: "action(app.save_project)"; } Shortcut { trigger: "O"; action: "action(app.open_project)"; } Shortcut { trigger: "Delete"; action: "action(app.delete_selected)"; } Shortcut { trigger: "plus"; action: "action(app.zoom_in)"; } Shortcut { trigger: "KP_Add"; action: "action(app.zoom_in)"; } Shortcut { trigger: "equal"; action: "action(app.zoom_in)"; } Shortcut { trigger: "ZoomIn"; action: "action(app.zoom_in)"; } Shortcut { trigger: "minus"; action: "action(app.zoom_out)"; } Shortcut { trigger: "KP_Subtract"; action: "action(app.zoom_out)"; } Shortcut { trigger: "ZoomOut"; action: "action(app.zoom_out)"; } Shortcut { trigger: "S"; action: "action(app.save_project_as)"; } Shortcut { trigger: "N"; action: "action(app.new_project)"; } Shortcut { trigger: "F9"; action: "action(app.toggle_sidebar)"; } Shortcut { trigger: "1"; action: "action(app.mode_pan)"; } Shortcut { trigger: "2"; action: "action(app.mode_zoom)"; } Shortcut { trigger: "3"; action: "action(app.mode_select)"; } } // If both width and height are limited Adw.Breakpoint { condition ("max-width: 725sp and max-height: 640sp") setters { scrollwindow_itemlist.height-request: 60; scrollwindow_itemlist.vexpand: true; split_view.collapsed: true; } } // If only height is limited Adw.Breakpoint { condition ("min-width: 725sp and max-height: 640sp") setters { scrollwindow_itemlist.height-request: 60; scrollwindow_itemlist.vexpand: true; } } // If only width is limited Adw.Breakpoint { condition ("max-width: 725sp and min-height: 640sp") setters { scrollwindow_itemlist.height-request: 250; scrollwindow_itemlist.vexpand: false; split_view.collapsed: true; } } content: Adw.OverlaySplitView split_view { show-sidebar: bind show_sidebar_button.active; notify => $on_sidebar_toggle(); sidebar: Adw.ToolbarView { width-request: 350; [top] Adw.HeaderBar sidebar_headerbar { styles ["flat"] [start] MenuButton add_button { icon-name: "list-add-symbolic"; tooltip-text: _("Add New Data"); always-show-arrow: true; menu-model: add_data_menu; } [end] MenuButton { icon-name: "open-menu-symbolic"; menu-model: primary_menu; tooltip-text: _("Open Application Menu"); primary: true; } } Box { orientation: vertical; ScrolledWindow scrollwindow_itemlist { height-request: 250; hscrollbar-policy: never; Viewport { Box { ListBox item_list { margin-bottom: 12; margin-top: 6; margin-start: 12; margin-end: 12; vexpand: false; valign: start; hexpand: true; visible: false; selection-mode: none; styles ["boxed-list"] } Adw.StatusPage { icon-name: "list-compact"; hexpand: true; width-request: 240; halign: center; visible: bind item_list.visible inverted; title: _("No Data"); description: _("Add data from a file or manually as an equation"); styles ["compact"] } } } } Separator { orientation: horizontal; } Box { spacing: 10; margin-top: 12; margin-bottom: 12; margin-start: 12; margin-end: 12; ToggleButton pan_button { icon-name: "move-tool-symbolic"; action-name: "app.mode_pan"; hexpand: true; tooltip-text: _("Pan Mode. Click and drag to pan"); } ToggleButton zoom_button { icon-name: "loupe-symbolic"; action-name: "app.mode_zoom"; hexpand: true; tooltip-text: _("Zoom Mode. Select an area on the figure to zoom in"); } ToggleButton select_button { icon-name: "edit-select-all-symbolic"; action-name: "app.mode_select"; hexpand: true; tooltip-text: _("Select Mode. Click and drag to make a selection of data"); } } Separator { orientation: horizontal; } Box { orientation: vertical; hexpand: false; spacing: 12; margin-start: 6; valign: start; margin-end: 6; margin-top: 6; margin-bottom: 6; Box stack_switcher_box { margin-start: 6; margin-end: 6; margin-top: 6; margin-bottom: 6; } Stack stack { valign: start; margin-start: 6; margin-end: 6; margin-bottom: 6; transition-type: slide_left_right; StackPage{ title: _("Adjust"); child: Adw.PreferencesGroup { Grid { column-spacing: 10; row-spacing: 10; margin-bottom:12; Button shift_button { hexpand: true; layout { column: 0; row: 0; } Adw.ButtonContent { halign: center; icon-name: "shift-symbolic"; label: _("Shift"); } tooltip-text: _("Shift all data vertically with respect to each other"); clicked => $perform_operation(); } Button normalize_button { hexpand: true; sensitive: bind shift_button.sensitive; width-request: 130; layout { column: 1; row: 0; } Adw.ButtonContent { halign: center; icon-name: "normalize-symbolic"; label: _("Normalize"); } tooltip-text: _("Normalize data"); clicked => $perform_operation(); } Adw.SplitButton smoothen_button { hexpand: false; can-shrink: true; sensitive: bind shift_button.sensitive; layout { column: 0; row: 1; } Adw.ButtonContent { halign: center; can-shrink: true; icon-name: "smoothen-symbolic"; label: _("Smoothen"); } tooltip-text: _("Smoothen data"); menu-model: smoothen_menu; clicked => $perform_operation(); } Adw.SplitButton center_button { hexpand: true; sensitive: bind shift_button.sensitive; layout { column: 1; row: 1; } Adw.ButtonContent { halign: center; icon-name: "center-symbolic"; label: _("Center"); } tooltip-text: _("Center data"); menu-model: center_menu; clicked => $perform_operation(); } Button combine_button { hexpand: true; sensitive: bind shift_button.sensitive; layout { column: 0; row: 2; } Adw.ButtonContent { halign: center; icon-name: "edit-paste-symbolic"; label: _("Combine"); } tooltip-text: _("Combine all selected data"); clicked => $perform_operation(); } Button cut_button { hexpand: true; sensitive: bind select_button.active; layout { column: 1; row: 2; } Adw.ButtonContent { halign: center; icon-name: "edit-cut-symbolic"; label: _("Cut"); } tooltip-text: _("Cut selected data"); clicked => $perform_operation(); } } }; } StackPage{ title: _("Transform"); child: Adw.PreferencesGroup { Grid { column-spacing: 12; row-spacing: 12; margin-bottom:12; Button derivative_button { hexpand: true; sensitive: bind shift_button.sensitive; layout { column: 0; row: 0; } Adw.ButtonContent { halign: center; icon-name: "derivative2-symbolic"; label: _("Derivative"); } tooltip-text: _("Get the derivative of the data"); clicked => $perform_operation(); } Button integral_button { hexpand: true; sensitive: bind shift_button.sensitive; layout { column: 1; row: 0; } Adw.ButtonContent { halign: center; icon-name: "integral-symbolic"; label: _("Integral"); } tooltip-text: _("Get the indefinite integral of the data"); clicked => $perform_operation(); } Button fft_button { hexpand: true; sensitive: bind shift_button.sensitive; layout { column: 0; row: 1; } Adw.ButtonContent { halign: center; icon-name: "fast-fourier-transform-symbolic"; label: _("FFT"); } tooltip-text: _("Get the Fast Fourier Transform of the data"); clicked => $perform_operation(); } Button inverse_fft_button { hexpand: true; sensitive: bind shift_button.sensitive; layout { column: 1; row: 1; } Adw.ButtonContent { halign: center; icon-name: "inverse-fast-fourier-transform-symbolic"; label: _("Inverse FFT"); } tooltip-text: _("Get the Inverse Fast Fourier Transform of the data"); clicked => $perform_operation(); } Button custom_transformation_button { hexpand: true; sensitive: bind shift_button.sensitive; layout { column: 0; row: 2; column-span: 2; } Adw.ButtonContent { halign: center; icon-name: "transform-symbolic"; label: _("Custom Transformation"); } tooltip-text: _("Perform custom transformations on the data"); clicked => $perform_operation(); } } }; } StackPage{ title: _("Modify"); child: Adw.PreferencesGroup { Grid { column-spacing: 10; row-spacing: 10; margin-bottom:12; Entry translate_x_entry { max-width-chars: 6; hexpand: true; text: "10"; layout { column: 0; row: 0; } } Button translate_x_button { valign: center; clicked => $perform_operation(); width-request: 120; Adw.ButtonContent { icon-name: "horizontal-arrows-symbolic"; label: _("Translate X"); } layout { column: 1; row: 0; } } Entry translate_y_entry { max-width-chars: 6; hexpand: true; text: "10"; layout { column: 0; row: 1; } } Button translate_y_button { valign: center; clicked => $perform_operation(); width-request: 120; Adw.ButtonContent { icon-name: "vertical-arrows-symbolic"; label: _("Translate Y"); } layout { column: 1; row: 1; } } Entry multiply_x_entry { max-width-chars: 6; hexpand: true; text: "10"; layout { column: 0; row: 2; } } Button multiply_x_button { valign: center; clicked => $perform_operation(); width-request: 120; Adw.ButtonContent { icon-name: "horizontal-arrows-symbolic"; label: _("Multiply X"); } layout { column: 1; row: 2; } } Entry multiply_y_entry { max-width-chars: 6; hexpand: true; text: "10"; layout { column: 0; row: 3; } } Button multiply_y_button { valign: center; clicked => $perform_operation(); width-request: 120; Adw.ButtonContent { icon-name: "vertical-arrows-symbolic"; label: _("Multiply Y"); } layout { column: 1; row: 3; } } } }; } } } } }; content: Box { orientation: vertical; Adw.HeaderBar content_headerbar { styles ["flat"] title-widget: Adw.WindowTitle content_title {}; ToggleButton show_sidebar_button { icon-name: "sidebar-show-symbolic"; tooltip-text: _("Toggle Sidebar"); active: bind split_view.show-sidebar; visible: bind split_view.collapsed; } Button undo_button { action-name: "app.undo"; icon-name: "edit-undo-symbolic"; tooltip-text: _("Undo"); } Button redo_button { action-name: "app.redo"; icon-name: "edit-redo-symbolic"; tooltip-text: _("Redo"); } [end] MenuButton view_menu_button { icon-name: "view-reveal-symbolic"; tooltip-text: _("View Menu"); always-show-arrow: true; menu-model: view_menu; } [end] Button view_forward_button { action-name: "app.view_forward"; icon-name: "go-next-symbolic"; tooltip-text: _("Next View"); } [end] Button view_back_button { action-name: "app.view_back"; icon-name: "go-previous-symbolic"; tooltip-text: _("Previous View"); } } Adw.ToastOverlay toast_overlay { focusable: true; height-request: 200; width-request: 200; child: Adw.StatusPage { icon-name: "dialog-error-symbolic"; title: _("Canvas Failed to Load"); }; } }; }; } menu primary_menu { section{ item (_("New Project"), "app.new_project") } section{ item (_("Save Project…"), "app.save_project") item (_("Save Project as…"), "app.save_project_as") item (_("Open Project…"), "app.open_project") } section { item (_("Export Data…"), "app.export_data") item (_("Export Figure…"), "app.export_figure") } section { item (_("Figure Settings"), "app.figure_settings") } section { item (_("Keyboard Shortcuts"), "win.show-help-overlay") item (_("Help"), "app.help") item (_("About Graphs"), "app.about") } } menu add_data_menu { section { item (_("Add Data from File…"), "app.add_data") item (_("Add Equation…"), "app.add_equation") } } menu view_menu { } menu center_menu { item { label: _("At Maximum Y Value"); action: "app.center"; target: "max-y"; } item { label: _("At Middle X Value"); action: "app.center"; target: "middle-x"; } } menu smoothen_menu { section { item { label: _("Savitzky–Golay Filter"); action: "app.smoothen"; target: "savgol"; } item { label: _("Moving Average"); action: "app.smoothen"; target: "moving-average"; } } section { item { label: _("Advanced Settings"); action: "app.smoothen_settings"; } } }Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/data/whats_new000066400000000000000000000126221511351636000233310ustar00rootroot00000000000000

Version 1.7 marks our biggest release yet. With a major UI overhaul and a revamped style editor. Other major highlights include a new curve fitting functionality, the ability to open projects directly, and support for gestures in the canvas.

New in Graphs:

  • The style editor has been revamped completely and now shows previews for each style
  • Curve fitting functionality has been added to Graphs
  • Graphs can now open data as well as Graphs project files directly from the file manager
  • When closing the application while unsaved changes are present, a dialog is now shown allowing you to save the data
  • The active project and its directory are now shown in the headerbar, including an indicator for unsaved changes
  • Graphs now includes forwards/backwards buttons to quickly navigate to the next and previous view
  • The smoothening action can now be configured with a choice of two different filters, a rolling average and a LOESS Savitzky-Gotsky filter
  • A new Yaru style has been added, which is set as the default system style for the Snap package when Yaru is used in the system, offering a more consistent experience
  • The default matplotlib style has been added to Graphs
  • The title font size can now be changed with respect to the overall font size
  • New scaling options are available for the axes, allowing for radian, square root and inverse scaling
  • Transparent colours for the curves are now supported
  • Superscript characters are now supported when entering equations
  • Support has been added for cotangents, secants and cosecants for equations and transformations
  • Headers are now written to exported files if present in the originally imported data
  • Style sheets now allow you to choose whether a frame should be drawn around the axes
  • The size of the title and labels can now be set seperately in the style editor
  • Importing column data is now more robust, supporting expressions as data points
  • Pinch and ctrl+scroll to zoom gestures are now supported on the canvas
  • The canvas can now be panned using two fingers on a touch screen, as well as with the middle mouse button
  • Toasts now show an "Open Location" button when saving data, bringing you to the saved file location
  • Entries for numerical values now get a red CSS when input value is invalid
  • There is now a warning, when editing a style with poor contrast between labels and background colors
  • Added some new goniometric functions such as the hyperbolic sinoids and their inverses
  • Graphs now fully supports touch screen devices

Changed behaviour:

  • Several linguistic changes were made, to get a clearer and more consistent description
  • The syntax for equations has been simplified
  • Delimiters can now be specified from a dropdown menu instead of having to rely on regex
  • The used axes limits are now saved when saving/loading a project
  • Settings related to specific axes are now only displayed when the axis is in use
  • The preferences have been redesigned and simplified, leaving only a single dialog for the figure settings
  • The drag and drop animation when moving items has been improved
  • The headerbar now follows the color of the used stylesheet, giving a more unified look
  • The shortcuts have been modified to follow the rest of the GNOME ecosystem
  • The logic for placing the legend has been changed, so that it now properly moves away when it intersects with a curve
  • The behaviour of the "Shift" action has been revamped to be more consistent when only part of the data span is selected
  • Help is now shipped with Graphs

Bugfixes and changes under the hood:

  • Graphs has been migrated to GNOME 46, and uses the new Libadwaita 1.5 widgets
  • Number inputs are now handled safely without calling the Python eval function
  • The clipboard implementation has been rewritten from scratch
  • The scale buttons in the view menu are now properly changed when the scale has been changed from the figure settings
  • Automatic scaling is now handled properly even when the dataset contains infinite values
  • Graphs now uses unit tests, reducing the risk of regression bugs
  • Part of the code-base has been migrated to Vala
  • Fixed a bug where "Skip rows" did not work properly with single-column data
  • Fixed a bug where rows would change width when selected in case they are adjecent to other entry rows.
  • Curve fitting now supports duplicate variables, and has gotten some bug fixes with regards to equation naming
  • Graphs translations are now hosted on Weblate
  • More smaller changes and fixes throughout the code-base
Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/000077500000000000000000000000001511351636000217575ustar00rootroot00000000000000Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/about.vala000066400000000000000000000027741511351636000237500ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later using Adw; using Gtk; namespace Graphs { /** * Show Graphs's about window. * * Displays the about window, fetching the changelog from whats_new * * @param application Appplication */ public void show_about_dialog (Application application) { var file = File.new_for_uri ("resource:///se/sjoerd/Graphs/whats_new"); string release_notes; try { release_notes = (string) file.load_bytes ().get_data (); } catch { release_notes = ""; } var dialog = new Adw.AboutDialog () { application_name = _("Graphs"), application_icon = application.application_id, website = Config.HOMEPAGE_URL, developer_name = Config.AUTHOR, issue_url = Config.ISSUE_URL, version = application.version, developers = { "Sjoerd Stendahl ", "Christoph Kohnen " }, designers = { "Sjoerd Stendahl ", "Christoph Kohnen ", "Tobias Bernard " }, copyright = "© 2022 – 2024", license_type = License.GPL_3_0, translator_credits = _("translator-credits"), release_notes = release_notes }; dialog.present (application.window); } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/actions.py000066400000000000000000000170331511351636000237750ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Main actions.""" import logging import sys from gettext import gettext as _ from gi.repository import Adw, Gio, Graphs from graphs import file_io, project, ui, utilities from graphs.figure_settings import FigureSettingsDialog from graphs.item import DataItem import numexpr import numpy def on_action_invoked(application: Graphs.Application, name: str) -> None: """Handle action invokation.""" getattr(sys.modules[__name__], name + "_action")(application) def quit_action(application: Graphs.Application) -> None: """Quit the application.""" application.close_application() def about_action(application: Graphs.Application) -> None: """Display about dialog.""" Graphs.show_about_dialog(application) def figure_settings_action(application: Graphs.Application) -> None: """Open the figure settings.""" FigureSettingsDialog(application) def add_data_action(application: Graphs.Application) -> None: """Import data.""" ui.add_data_dialog(application) def add_equation_action(application: Graphs.Application) -> None: """Add data from an equation.""" def on_accept(_dialog, name): settings = application.get_settings_child("add-equation") try: x_start = utilities.string_to_float(settings.get_string("x-start")) x_stop = utilities.string_to_float(settings.get_string("x-stop")) step_size = utilities.string_to_float( settings.get_string("step-size"), ) datapoints = int(abs(x_start - x_stop) / step_size) + 1 xdata = numpy.ndarray.tolist( numpy.linspace(x_start, x_stop, datapoints), ) equation = utilities.preprocess(settings.get_string("equation")) ydata = numpy.ndarray.tolist( numexpr.evaluate(equation + " + x*0", local_dict={"x": xdata}), ) if name == "": name = f"Y = {settings.get_string('equation')}" style_manager = application.get_figure_style_manager() application.get_data().add_items( [ DataItem.new( style_manager.get_selected_style_params(), xdata, ydata, name=name, ), ], style_manager, ) return "" except ValueError as error: return str(error) except (NameError, SyntaxError, TypeError, KeyError) as exception: message = _("{error} - Unable to add data from equation") msg = message.format(error=exception.__class__.__name__) logging.exception(msg) return msg def on_entry_change(entry, _param): ui.validate_entry(entry) dialog = Graphs.AddEquationDialog.new(application) for s in ("x_start", "x_stop", "step_size"): entry = dialog.get_property(s) entry.connect("notify::text", on_entry_change) on_entry_change(entry, None) dialog.connect("accept", on_accept) def select_all_action(application: Graphs.Application) -> None: """Select all Items.""" data = application.get_data() for item in data: item.set_selected(True) data.add_history_state() def select_none_action(application: Graphs.Application) -> None: """Deselect all items.""" data = application.get_data() for item in data: item.set_selected(False) data.add_history_state() def undo_action(application: Graphs.Application) -> None: """Undo last action.""" application.get_data().undo() def redo_action(application: Graphs.Application) -> None: """Redo last action.""" application.get_data().redo() def optimize_limits_action(application: Graphs.Application) -> None: """Optimize figure limits.""" application.get_data().optimize_limits() def view_back_action(application: Graphs.Application) -> None: """Restore last view.""" data = application.get_data() if data.props.can_view_back: data.view_back() def view_forward_action(application: Graphs.Application) -> None: """Restore current view.""" data = application.get_data() if data.props.can_view_forward: data.view_forward() def export_data_action(application: Graphs.Application) -> None: """Export Data.""" ui.export_data_dialog(application) def export_figure_action(application: Graphs.Application) -> None: """Export Figure.""" def on_accept(_dialog, file): window = application.get_window() settings = application.get_settings_child("export-figure") with file_io.open_wrapped(file, "wb") as wrapper: window.get_canvas().figure.savefig( wrapper, format=settings.get_string("file-format"), dpi=settings.get_int("dpi"), transparent=settings.get_boolean("transparent"), ) window.add_toast_string_with_file(_("Exported Figure"), file) dialog = Graphs.ExportFigureDialog.new(application) dialog.connect("accept", on_accept) def new_project_action(application: Graphs.Application) -> None: """Clear the current project and reset Graphs to the initial state.""" data = application.get_data() if data.get_unsaved(): def on_response(_dialog, response): application.save_handler = data.connect( "saved", application.on_project_saved, "reset_project", ) if response == "discard_close": data.reset() if response == "save_close": project.save_project(application) dialog = Graphs.tools_build_dialog("save_changes") dialog.connect("response", on_response) dialog.present(application.get_window()) return data.reset() def save_project_action(application: Graphs.Application) -> None: """Save the current project.""" project.save_project(application) def save_project_as_action(application: Graphs.Application) -> None: """Save the current project and ask for save location.""" project.save_project(application, require_dialog=True) def smoothen_settings_action(application: Graphs.Application) -> None: """Open settings for the smoothen action.""" Graphs.SmoothenDialog.new(application) def zoom_in_action(application: Graphs.Application) -> None: """Zoom into the figure.""" canvas = application.get_window().get_canvas() canvas.zoom(1.15, respect_mouse=False) def zoom_out_action(application: Graphs.Application) -> None: """Zoom out of the figure.""" canvas = application.get_window().get_canvas() canvas.zoom(1 / 1.15, respect_mouse=False) def open_project_action(application: Graphs.Application) -> None: """Open a project.""" project.open_project(application) def delete_selected_action(application: Graphs.Application) -> None: """Delete selected items.""" items = [item for item in application.get_data() if item.get_selected()] names = ", ".join(item.get_name() for item in items) application.get_data().delete_items(items) toast = Adw.Toast.new(_("Deleted {name}").format(name=names)) toast.set_button_label(_("Undo")) toast.set_action_name("app.undo") application.get_window().add_toast(toast) def help_action(application: Graphs.Application) -> None: """Open Help page for Graphs.""" Gio.AppInfo.launch_default_for_uri( "help:graphs", application.get_window().get_display().get_app_launch_context(), ) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/actions.vala000066400000000000000000000071211511351636000242650ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later using Adw; using Gee; namespace Graphs { private const string[] X_DIRECTIONS = {"top", "bottom"}; private const string[] Y_DIRECTIONS = {"left", "right"}; /** * Setup actions. * * @param application Application */ public void setup_actions (Application application) { foreach (string name in ACTION_NAMES) { var action = new SimpleAction (name, null); action.activate.connect (() => { application.action_invoked.emit (name); }); application.add_action (action); } application.set_accels_for_action ("app.help", {"F1"}); var toggle_sidebar_action = new SimpleAction.stateful ( "toggle_sidebar", null, new Variant.boolean (true) ); toggle_sidebar_action.activate.connect (() => { OverlaySplitView split_view = application.window.split_view; split_view.collapsed = !split_view.collapsed; }); application.add_action (toggle_sidebar_action); var modes = new ArrayList.wrap ({"pan", "zoom", "select"}); foreach (string mode in modes) { var action = new SimpleAction (@"mode_$mode", null); action.activate.connect (() => { application.window.canvas.mode = modes.index_of (mode); }); application.add_action (action); } Settings settings = application.get_settings_child ("figure"); FigureSettings figure_settings = application.data.figure_settings; foreach (string dir in DIRECTION_NAMES) { string val = @"$dir-scale"; var action = new SimpleAction.stateful ( @"change-$val", new VariantType ("s"), new Variant.string (settings.get_enum (val).to_string ()) ); action.activate.connect ((a, target) => { string[] directions = {dir}; bool[] visible_axes = application.data.get_used_positions (); // Also set opposite axis if opposite axis not in use if (dir in X_DIRECTIONS && visible_axes[0] ^ visible_axes[1]) { directions = X_DIRECTIONS; } if (dir in Y_DIRECTIONS && visible_axes[2] ^ visible_axes[3]) { directions = Y_DIRECTIONS; } foreach (string target_dir in directions) { figure_settings.set ( val, int.parse (target.get_string ()) ); } application.data.add_history_state_request.emit (); }); figure_settings.notify[val].connect (() => { int scale; figure_settings.get (val, out scale); action.set_state (new Variant.string (scale.to_string ())); }); application.add_action (action); } string[] settings_actions = {"center", "smoothen"}; Settings actions_settings = application.settings.get_child ("actions"); foreach (string settings_action in settings_actions) { application.add_action (actions_settings.create_action (settings_action)); } var operation_action = new SimpleAction ( "app.perform_operation", new VariantType ("s") ); operation_action.activate.connect ((a, target) => { application.operation_invoked.emit (target.get_string ()); }); application.add_action (operation_action); } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/add_equation.vala000066400000000000000000000025241511351636000252640ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later using Adw; using Gtk; namespace Graphs { /** * Add Equation dialog. */ [GtkTemplate (ui = "/se/sjoerd/Graphs/ui/add-equation.ui")] public class AddEquationDialog : Adw.Dialog { [GtkChild] public unowned Adw.EntryRow equation { get; } [GtkChild] public unowned Adw.EntryRow x_start { get; } [GtkChild] public unowned Adw.EntryRow x_stop { get; } [GtkChild] public unowned Adw.EntryRow step_size { get; } [GtkChild] public unowned Adw.EntryRow item_name { get; } [GtkChild] private unowned Adw.ToastOverlay toast_overlay { get; } private Application application; public signal string accept (string name); public AddEquationDialog (Application application) { Object (); this.application = application; Tools.bind_settings_to_widgets ( application.get_settings_child ("add-equation"), this ); present (application.window); } [GtkCallback] private void on_accept () { string error = this.accept.emit (this.item_name.get_text ()); if (error == "") close (); else this.toast_overlay.add_toast (new Adw.Toast (error)); } } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/application.py000066400000000000000000000143551511351636000246440ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Main application.""" import logging from gettext import gettext as _ from gi.repository import Adw, Gio, Graphs, Gtk from graphs import ( actions, file_import, migrate, operations, project, styles, ui, ) from graphs.data import Data from matplotlib import font_manager class PythonApplication(Graphs.Application): """The main application singleton class.""" __gtype_name__ = "GraphsPythonApplication" def __init__(self, application_id, **kwargs): settings = Gio.Settings(application_id) migrate.migrate_config(settings) data = Data(settings.get_child("figure")) super().__init__( application_id=application_id, settings=settings, flags=Gio.ApplicationFlags.HANDLES_OPEN, data=data, **kwargs, ) font_list = font_manager.findSystemFonts(fontpaths=None, fontext="ttf") for font in font_list: try: font_manager.fontManager.addfont(font) except RuntimeError: logging.warning(_("Could not load {font}").format(font=font)) Graphs.setup_actions(self) self.connect("action_invoked", actions.on_action_invoked) self.connect("operation_invoked", operations.perform_operation) data.connect( "notify::items", ui.on_items_change, self, ) def on_project_saved(self, _application, handler=None, *args) -> None: """Change unsaved state.""" self.disconnect(self.save_handler) if handler == "close": self.quit() data = self.get_data() if handler == "open_project": data.props.project_file = args[0] data.load() def do_open(self, files: list, nfiles: int, _hint: str) -> None: """Open Graphs with a File as argument.""" self.do_activate() data = self.get_data() if nfiles == 1 and files[0].get_uri().endswith(".graphs"): project_file = files[0] if data.get_unsaved(): def on_response(_dialog, response): if response == "discard_close": project.load(data, project_file) if response == "save_close": self.save_handler = data.connect( "saved", self.on_project_saved, "open_project", project_file, ) project.save_project(self) dialog = Graphs.tools_build_dialog("save_changes") dialog.set_transient_for(self.get_window()) dialog.connect("response", on_response) dialog.present() else: project.load(data, project_file) else: file_import.import_from_files(self, files) def close_application(self, *_args) -> None: """ Intercept when closing the application. Will ask the user to confirm and save/discard open data if any unsaved changes are present. """ if self.get_data().get_unsaved(): def on_response(_dialog, response): if response == "discard_close": self.quit() if response == "save_close": self.save_handler = self.get_data().connect( "saved", self.on_project_saved, "close", ) project.save_project(self) dialog = Graphs.tools_build_dialog("save_changes") dialog.connect("response", on_response) dialog.present(self.get_window()) return True self.quit() def do_activate(self) -> None: """ Activate the application. We raise the application"s main window, creating it if necessary. """ window = self.props.active_window if not window: window = Graphs.Window.new(self) data = self.get_data() binding_table = [ ("can_undo", window.get_undo_button(), "sensitive"), ("can_redo", window.get_redo_button(), "sensitive"), ("can_view_back", window.get_view_back_button(), "sensitive"), ( "can_view_forward", window.get_view_forward_button(), "sensitive", ), ("project_name", window.get_content_title(), "title"), ("project_path", window.get_content_title(), "subtitle"), ] for prop1, obj, prop2 in binding_table: data.bind_property(prop1, obj, prop2, 2) actions = ( "multiply_x", "multiply_y", "translate_x", "translate_y", ) for action in actions: entry = window.get_property(action + "_entry") button = window.get_property(action + "_button") entry.connect( "notify::text", self.set_entry_css, entry, button, ) data.connect( "notify::items-selected", self.set_entry_css, entry, button, ) self.set_entry_css(None, None, entry, button) self.set_window(window) window.connect("close-request", self.close_application) self.set_figure_style_manager(styles.StyleManager(self)) data.connect_after( "notify::items", lambda _data, _param: window.update_view_menu(), ) window.update_view_menu() window.present() def set_entry_css( self, _object, _param, entry: Adw.EntryRow, button: Gtk.Button, ) -> None: """Validate text field input.""" button.set_sensitive( ui.validate_entry(entry)[1] and self.get_data().props.items_selected, ) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/application.vala000066400000000000000000000023721511351636000251330ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later using Adw; namespace Graphs { /** * Graphs application */ public class Application : Adw.Application { public Window window { get; set; } public Settings settings { get; construct set; } public Data data { get; construct set; } public StyleManager figure_style_manager { get; set; } public bool debug { get; construct set; default = false; } public signal void action_invoked (string name); public signal void operation_invoked (string name); construct { Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR); Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8"); Intl.textdomain (Config.GETTEXT_PACKAGE); this.version = Config.VERSION; } /** * Retrieve a child of the applications settings. * * @param path a slash-separated path */ public Settings get_settings_child (string path) { Settings settings = this.settings; foreach (string child_name in path.split ("/")) { settings = settings.get_child (child_name); } return settings; } } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/artist.py000066400000000000000000000170651511351636000236500ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """ Wrapper classes for mpl artists. Provides GObject based wrappers for mpl artists. """ from gi.repository import GObject, Graphs from graphs import misc from matplotlib import artist, pyplot from matplotlib.figure import Figure def new_for_item(canvas: Graphs.Canvas, item: Graphs.Item): """ Create a new artist for an item. Creates bindings between item and artist properties so changes are handled automatically. """ match item.__gtype_name__: case "GraphsDataItem": cls = DataItemArtistWrapper case "GraphsTextItem": cls = TextItemArtistWrapper case "GraphsFillItem": cls = FillItemArtistWrapper case _: pass artist_wrapper = cls( canvas.axes[item.get_yposition() * 2 + item.get_xposition()], item, ) for prop in dir(artist_wrapper.props): if not (prop == "label" and artist_wrapper.legend): item.bind_property(prop, artist_wrapper, prop, 0) artist_wrapper.connect("notify", lambda _x, _y: canvas.update_legend()) return artist_wrapper class ItemArtistWrapper(GObject.Object): """Wrapper for base Item.""" __gtype_name__ = "GraphsItemArtistWrapper" legend = False def get_artist(self) -> artist: """Get underlying mpl artist.""" return self._artist @GObject.Property(type=str, default="") def name(self) -> str: """Get name/label property.""" return self._artist.get_label() @name.setter def name(self, name: str) -> None: """Set name/label property.""" self._artist.set_label(Graphs.tools_shorten_label(name, 40)) @GObject.Property(type=str, default="000000") def color(self) -> str: """Get color property.""" return self._artist.get_color() @color.setter def color(self, color: str) -> None: """Set color property.""" self._artist.set_color(color) @GObject.Property(type=float, default=1) def alpha(self) -> float: """Get alpha property.""" return self._artist.get_alpha() @alpha.setter def alpha(self, alpha: float) -> None: """Set alpha property.""" self._artist.set_alpha(alpha) class DataItemArtistWrapper(ItemArtistWrapper): """Wrapper for DataItem.""" __gtype_name__ = "GraphsDataItemArtistWrapper" selected = GObject.Property(type=bool, default=True) linewidth = GObject.Property(type=float, default=3) markersize = GObject.Property(type=float, default=7) legend = True @GObject.Property def xdata(self) -> list: """Get xdata property.""" return self._artist.get_xdata() @xdata.setter def xdata(self, xdata: list) -> None: """Set xdata property.""" self._artist.set_xdata(xdata) @GObject.Property def ydata(self) -> list: """Get ydata property.""" return self._artist.get_ydata() @ydata.setter def ydata(self, ydata: list) -> None: """Set ydata property.""" self._artist.set_ydata(ydata) @GObject.Property(type=int, default=1) def linestyle(self) -> int: """Get linestyle property.""" return misc.LINESTYLES.index(self._artist.get_linestyle()) @linestyle.setter def linestyle(self, linestyle: int) -> None: """Set linestyle property.""" self._artist.set_linestyle(misc.LINESTYLES[linestyle]) @GObject.Property(type=int, default=1) def markerstyle(self) -> int: """Get markerstyle property.""" return misc.MARKERSTYLES.index(self._artist.get_marker()) @markerstyle.setter def markerstyle(self, markerstyle: int) -> None: """Set markerstyle property.""" self._artist.set_marker(misc.MARKERSTYLES[markerstyle]) def _set_properties(self, _x, _y) -> None: linewidth, markersize = self.props.linewidth, self.props.markersize if not self.props.selected: linewidth *= 0.35 markersize *= 0.35 self._artist.set_linewidth(linewidth) self._artist.set_markersize(markersize) def __init__(self, axis: pyplot.axis, item: Graphs.Item): super().__init__() self._artist = axis.plot( item.props.xdata, item.props.ydata, label=Graphs.tools_shorten_label(item.get_name(), 40), color=item.get_color(), alpha=item.get_alpha(), linestyle=misc.LINESTYLES[item.props.linestyle], marker=misc.MARKERSTYLES[item.props.markerstyle], )[0] for prop in ("selected", "linewidth", "markersize"): self.set_property(prop, item.get_property(prop)) self.connect(f"notify::{prop}", self._set_properties) self._set_properties(None, None) class TextItemArtistWrapper(ItemArtistWrapper): """Wrapper for TextItem.""" __gtype_name__ = "GraphsTextItemArtistWrapper" @GObject.Property(type=float, default=12) def size(self) -> float: """Get size property.""" return self._artist.get_fontsize() @size.setter def size(self, size: float) -> None: """Set size property.""" self._artist.set_fontsize(size) @GObject.Property(type=int, default=0, minimum=0, maximum=360) def rotation(self) -> int: """Get rotation property.""" return self._artist.get_rotation() @rotation.setter def rotation(self, rotation: int) -> None: """Set rotation property.""" self._artist.set_rotation(rotation) @GObject.Property(type=str, default="") def text(self) -> str: """Get text property.""" return self._artist.get_text() @text.setter def text(self, text: str) -> None: """Set text property.""" self._artist.set_text(text) @GObject.Property(type=float, default=0) def xanchor(self) -> float: """Get xanchor property.""" return self._artist.get_position()[0] @xanchor.setter def xanchor(self, xanchor: float) -> None: """Set xanchor property.""" self._artist.set_position((xanchor, self.props.yanchor)) @GObject.Property(type=float, default=0) def yanchor(self) -> float: """Get yanchor property.""" return self._artist.get_position()[1] @yanchor.setter def yanchor(self, yanchor: float) -> None: """Set yanchor property.""" self._artist.set_position((self.props.xanchor, yanchor)) def __init__(self, axis: pyplot.axis, item: Graphs.Item): super().__init__() self._artist = axis.text( item.props.xanchor, item.props.yanchor, item.props.text, label=Graphs.tools_shorten_label(item.get_name(), 40), color=item.get_color(), alpha=item.get_alpha(), clip_on=True, fontsize=item.props.size, rotation=item.props.rotation, ) class FillItemArtistWrapper(ItemArtistWrapper): """Wrapper for FillItem.""" __gtype_name__ = "GraphsFillItemArtistWrapper" @GObject.Property(type=object, flags=2) def data(self) -> None: """Write-only property, ignored.""" @data.setter def data(self, data) -> None: dummy = Figure().add_subplot().fill_between(*data) self._artist.set_paths([dummy.get_paths()[0].vertices]) def __init__(self, axis: pyplot.axis, item: Graphs.Item): super().__init__() self._artist = axis.fill_between( *item.props.data, label=Graphs.tools_shorten_label(item.get_name(), 40), color=item.get_color(), alpha=item.get_alpha(), ) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/canvas.py000066400000000000000000000750311511351636000236120ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """ Custom canvas implementation. Acts as an interface between matplotlib and GObject and contains a custom implementation of a `SpanSelector` as well as a dummy toolbar used for interactive navigation in conjunction with graphs-specific structures. Classes: Canvas """ import math from gi.repository import Adw, GObject, Gdk, Graphs, Gtk from graphs import artist, misc, scales, utilities from matplotlib import backend_tools as tools, pyplot from matplotlib.backend_bases import ( FigureCanvasBase, MouseEvent, NavigationToolbar2, ) from matplotlib.backends.backend_gtk4cairo import FigureCanvas from matplotlib.widgets import SpanSelector _SCROLL_SCALE = 1.06 class Canvas(Graphs.Canvas, FigureCanvas): """ Custom Canvas. Implements properties analouge to `FigureSettings`. Automatically connects to `FigureSettings` and `Data.items` during init. Properties: mode: int items: list (write-only) title: str bottom_label: str left_label: str top_label: str right_label: str bottom_scale: int (0: linear, 1: logarithmic) left_scale: int (0: linear, 1: logarithmic) top_scale: int (0: linear, 1: logarithmic) right_scale: int (0: linear, 1: logarithmic) legend: bool legend_position: int use_custom_style: bool custom_style: str min_bottom: float max_bottom: float min_left: float max_lef: float min_top: float max_top: float min_right: float max_right: float min_selected: float (fraction) max_selected: float (fraction) Signals: edit_request view_changed Functions: update_legend """ __gtype_name__ = "GraphsPythonCanvas" items = GObject.Property(type=object) def __init__( self, style_params: dict, interactive: bool = True, ): """ Create the canvas. Create figure, axes and define rubberband_colors based on the current style context. Bind `items` to `data.items` and all figure settings attributes to their respective values. """ self._style_params = style_params pyplot.rcParams.update(self._style_params) # apply style_params Graphs.Canvas.__init__( self, can_focus=True, focusable=True, hexpand=True, vexpand=True, items=[], ) self._idle_draw_id = 0 self.set_draw_func(self._draw_func) self.connect("resize", self.resize_event) self.connect("notify::scale-factor", self._update_device_pixel_ratio) FigureCanvasBase.__init__(self) self.figure.set_tight_layout(True) self._axis = self.figure.add_subplot(111) self._top_left_axis = self._axis.twiny() self._right_axis = self._axis.twinx() self._top_right_axis = self._top_left_axis.twinx() self.axes = [ self._axis, self._top_left_axis, self._right_axis, self._top_right_axis, ] self._legend_axis = self._axis self._legend = True self._legend_position = misc.LEGEND_POSITIONS[0] self._handles = [] self._rubberband_rect = None # Handle stuff only used if the canvas is interactive if interactive: self._setup_interactive() self.connect("notify::hide-unselected", self._redraw) self.connect("notify::items", self._redraw) def _setup_interactive(self): self._ctrl_held, self._shift_held = False, False self._xfrac, self._yfrac = None, None self.mpl_connect("pick_event", self._on_pick) self.mpl_connect("motion_notify_event", self._set_mouse_fraction) # Reference is created by the toolbar itself _DummyToolbar(self) click = Gtk.GestureClick() click.set_button(0) # All buttons. click.connect("pressed", self.button_press_event) click.connect("update", self.handle_touch_update) click.connect("released", self.button_release_event) self.add_controller(click) key = Gtk.EventControllerKey() key.connect("key-pressed", self.key_press_event) key.connect("key-released", self.key_release_event) self.add_controller(key) motion = Gtk.EventControllerMotion() motion.connect("motion", self.motion_notify_event) motion.connect("enter", self.enter_notify_event) motion.connect("leave", self.leave_notify_event) self.add_controller(motion) scroll = Gtk.EventControllerScroll.new( Gtk.EventControllerScrollFlags.BOTH_AXES, ) scroll.connect("scroll", self.scroll_event) scroll.connect("scroll-end", self.toolbar.push_current) self.add_controller(scroll) zoom = Gtk.GestureZoom.new() zoom.connect("scale-changed", self.zoom_event) zoom.connect("end", self.end_zoom_event) self.add_controller(zoom) def rgba_to_tuple(rgba): return (rgba.red, rgba.green, rgba.blue, rgba.alpha) style_manager = Adw.StyleManager() rgba = style_manager.get_accent_color_rgba() self.rubberband_edge_color = rgba_to_tuple(rgba) rgba.alpha = 0.2 self.rubberband_fill_color = rgba_to_tuple(rgba) self.highlight = _Highlight(self) for item in ("min", "max"): self.connect( f"notify::{item}-selected", lambda _a, _b: self.highlight.load(self), ) def handle_touch_update(self, controller: Gtk.GestureClick, _data) -> None: """ Handle an update event for GtkGestureClick motion. This is needed for touch screen devices to handle gestures properly. """ if not controller.get_point()[0]: # If touch event coords = controller.get_bounding_box_center() x, y = coords.x, coords.y MouseEvent( "motion_notify_event", self, *self._mpl_coords((x, y)), )._process() def key_press_event( self, controller: Gtk.EventControllerKey, keyval: int, keycode: int, state: Gdk.ModifierType, ) -> None: """Handle key press event.""" if keyval == 65507 or keyval == 65508: # Control_L or Control_R self._ctrl_held = True elif keyval == 65505 or keyval == 65506: # Left or right Shift self._shift_held = True else: # Prevent keys from being true with key combos self._ctrl_held = False super().key_press_event(controller, keyval, keycode, state) def key_release_event( self, controller: Gtk.EventControllerKey, keyval: int, keycode: int, state: Gdk.ModifierType, ) -> None: """Handle key release event.""" self._ctrl_held = False self._shift_held = False super().key_release_event(controller, keyval, keycode, state) def scroll_event( self, controller: Gtk.EventControllerScroll, dx: float, dy: float, ) -> None: """Handle scroll event.""" if self._ctrl_held: self.zoom(1 / _SCROLL_SCALE if dy > 0 else _SCROLL_SCALE) else: if self._shift_held: dx, dy = dy, dx if controller.get_unit() == Gdk.ScrollUnit.WHEEL: dx *= 10 dy *= 10 for ax in self.axes: xmin, xmax, ymin, ymax = \ self._calculate_pan_values(ax, dx, dy) ax.set_xlim(xmin, xmax) ax.set_ylim(ymin, ymax) self.queue_draw() super().scroll_event(controller, dx, dy) def zoom_event( self, controller: Gtk.GestureZoom, scale: float, ) -> None: """Handle zoom event.""" coords = controller.get_bounding_box_center() x, y = coords.x, coords.y event = MouseEvent( "motion_notify_event", self, *self._mpl_coords((x, y)), ) self._set_mouse_fraction(event) scale = 1 + 0.01 * (scale - 1) if scale > 5 or scale < 0.2: # Don't scale if ridiculous values are registered return self.zoom(scale) def end_zoom_event(self, controller: Gtk.GestureZoom, _sequence) -> None: """ End the zoom event. Pushes the canvas to the stack, and emits a `release` signal cancel out registered touches from touchscreen devices. """ coords = controller.get_bounding_box_center() x, y = coords.x, coords.y MouseEvent( "button_release_event", self, *self._mpl_coords((x, y)), 1, )._process() self.toolbar.push_current() def enter_notify_event( self, controller: Gtk.EventControllerMotion, x: float, y: float, ) -> None: """Process pointer entry.""" self.grab_focus() super().enter_notify_event(controller, x, y) def _set_mouse_fraction(self, event) -> None: """Set the mouse coordinate in terms of fraction of the canvas.""" if event.inaxes is not None: xlim = self._top_right_axis.get_xlim() ylim = self._top_right_axis.get_ylim() self._xfrac = utilities.get_fraction_at_value( event.xdata, xlim[0], xlim[1], self.top_scale, ) self._yfrac = utilities.get_fraction_at_value( event.ydata, ylim[0], ylim[1], self.right_scale, ) else: self._xfrac, self._yfrac = None, None def zoom(self, scaling: float = 1.15, respect_mouse: bool = True) -> None: """ Zoom with given scaling. Update all axes' limits in respect to the current mouse position. """ if not respect_mouse: self._xfrac, self._yfrac = 0.5, 0.5 if self._xfrac is None or self._yfrac is None: return for ax in self.axes: ax.set_xlim( self._calculate_zoomed_values( self._xfrac, scales.to_int(ax.get_xscale()), ax.get_xlim(), scaling, ), ) ax.set_ylim( self._calculate_zoomed_values( self._yfrac, scales.to_int(ax.get_yscale()), ax.get_ylim(), scaling, ), ) self.queue_draw() @staticmethod def _calculate_pan_values( ax: pyplot.axis, x_panspeed: float, y_panspeed: float, ) -> None: """ Calculate values required for panning. Calculates the coordinates of the canvas after a panning gesture has been emitted. """ xmin, xmax = ax.get_xlim() ymin, ymax = ax.get_ylim() x_scale = scales.to_int(ax.get_xscale()) y_scale = scales.to_int(ax.get_yscale()) pan_scale = 0.002 xvalue1 = utilities.get_value_at_fraction( x_panspeed * pan_scale, xmin, xmax, x_scale, ) xvalue2 = utilities.get_value_at_fraction( 1 + x_panspeed * pan_scale, xmin, xmax, x_scale, ) yvalue1 = utilities.get_value_at_fraction( -y_panspeed * pan_scale, ymin, ymax, y_scale, ) yvalue2 = utilities.get_value_at_fraction( 1 - y_panspeed * pan_scale, ymin, ymax, y_scale, ) if x_scale == 4: xvalue1, xvalue2 = xvalue2, xvalue1 if y_scale == 4: yvalue1, yvalue2 = yvalue2, yvalue1 return xvalue1, xvalue2, yvalue1, yvalue2 @staticmethod def _calculate_zoomed_values( fraction: float, scale: float, limit: float, zoom_factor: float, ) -> tuple[float, float]: """ Calculate zoomed values. Calculates the coordinates of the canvas after a zoom gesture has been ezoomed. """ min_, max_ = limit[0], limit[1] value1 = utilities.get_value_at_fraction( fraction - fraction / zoom_factor, min_, max_, scale, ) value2 = utilities.get_value_at_fraction( fraction + (1 - fraction) / zoom_factor, min_, max_, scale, ) if scale == 4: value1, value2 = value2, value1 return value1, value2 def _redraw(self, *_args) -> None: # bottom, top, left, right used_axes = [False, False, False, False] visible_axes = [False, False, False, False] if self.props.hide_unselected: drawable_items = [] for item in self.props.items: if hasattr(item, "handler"): item.disconnect(item.handler) item.handler = item.connect("notify::selected", self._redraw) if item.get_selected(): drawable_items.append(item) else: drawable_items = self.props.items for item in drawable_items: xposition = item.get_xposition() yposition = item.get_yposition() visible_axes[xposition] = True visible_axes[2 + yposition] = True used_axes[xposition + 2 * yposition] = True axes_directions = ( ("bottom", "left"), # axis ("top", "left"), # top_left_axis ("bottom", "right"), # right_axis ("top", "right"), # top_right_axis ) if not any(visible_axes): visible_axes = (True, False, True, False) # Left and bottom used_axes = (True, False, False, False) # self.axis visible self._legend_axis = self._axis params = self._style_params draw_frame = params["axes.spines.bottom"] ticks = "both" if params["xtick.minor.visible"] else "major" for directions, axis, used \ in zip(axes_directions, self.axes, used_axes): axis.get_xaxis().set_visible(False) axis.get_yaxis().set_visible(False) # Set tick where requested, as long as that axis is not occupied # and visible if ( params[f"xtick.{directions[0]}"] or params[f"ytick.{directions[1]}"] ): axis.tick_params( which=ticks, **{ direction: ( draw_frame and not visible_axes[i] or direction in directions ) and params[f"{'x' if i < 2 else 'y'}tick.{direction}"] for i, direction in enumerate(misc.DIRECTIONS) }, ) for handle in axis.lines + axis.texts: handle.remove() axis_legend = axis.get_legend() if axis_legend is not None: axis_legend.remove() for direction in misc.DIRECTIONS: axis.spines[direction].set_visible( direction in directions and used or draw_frame, ) if used: self._legend_axis = axis self._axis.get_xaxis().set_visible(visible_axes[0]) self._top_left_axis.get_xaxis().set_visible(visible_axes[1]) self._axis.get_yaxis().set_visible(visible_axes[2]) self._right_axis.get_yaxis().set_visible(visible_axes[3]) self._handles = [ artist.new_for_item(self, item) for item in reversed(drawable_items) ] self.update_legend() def _on_pick(self, event) -> None: """Emit edit-request signal for picked label/title.""" self.emit("edit_request", event.artist.id) # Overwritten function - do not change name def _post_draw(self, _widget, context) -> None: """Allow custom rendering extensions.""" if self._rubberband_rect is not None: self._draw_rubberband(context) def _draw_rubberband(self, ctx) -> None: """ Implement custom rubberband. Draw a rubberband matching libadwaitas style, where `_rubberband_rect` is set. """ radius = 6 x0, y0, width, height = ( dim / self.device_pixel_ratio for dim in self._rubberband_rect ) x1 = x0 + width y1 = y0 + height if x1 < x0: x0, x1 = x1, x0 if y1 < y0: y0, y1 = y1, y0 degrees = math.pi / 180 ctx.new_sub_path() ctx.arc(x1 - radius, y0 + radius, radius, -90 * degrees, 0 * degrees) ctx.arc(x1 - radius, y1 - radius, radius, 0 * degrees, 90 * degrees) ctx.arc(x0 + radius, y1 - radius, radius, 90 * degrees, 180 * degrees) ctx.arc(x0 + radius, y0 + radius, radius, 180 * degrees, 270 * degrees) ctx.close_path() ctx.set_line_width(1) ctx.set_source_rgba(*self.rubberband_fill_color) ctx.fill_preserve() ctx.set_source_rgba(*self.rubberband_edge_color) ctx.stroke() def update_legend(self) -> None: """Update the legend or hide if not used.""" if self._legend and self._handles: handles = [ handle.get_artist() for handle in self._handles if handle.legend ] if handles: self._legend_axis.legend( handles=handles, loc=self._legend_position, frameon=True, reverse=True, ) self.queue_draw() return legend = self._legend_axis.get_legend() if legend is not None: legend.remove() self.queue_draw() @GObject.Property(type=bool, default=True) def legend(self) -> bool: """Whether or not, the legend is visible.""" return self._legend @legend.setter def legend(self, legend: bool) -> None: self._legend = legend self.update_legend() @GObject.Property(type=int, default=0) def legend_position(self) -> int: """Legend Position (see `misc.LEGEND_POSITIONS`).""" return misc.LEGEND_POSITIONS.index(self._legend_position) @legend_position.setter def legend_position(self, legend_position: int) -> None: self._legend_position = misc.LEGEND_POSITIONS[legend_position] self.update_legend() @GObject.Property(type=str) def title(self) -> str: """Figure title.""" return self._axis.get_title() @title.setter def title(self, title: str) -> None: self._axis.set_title(title, picker=True).id = "title" self.queue_draw() @GObject.Property(type=str) def bottom_label(self) -> str: """Label of the bottom axis.""" return self._axis.get_xlabel() @bottom_label.setter def bottom_label(self, label: str) -> None: self._axis.set_xlabel(label, picker=True).id = "bottom_label" self.queue_draw() @GObject.Property(type=str) def left_label(self) -> str: """Label of the left axis.""" return self._axis.get_ylabel() @left_label.setter def left_label(self, label: str) -> None: self._axis.set_ylabel(label, picker=True).id = "left_label" self.queue_draw() @GObject.Property(type=str) def top_label(self) -> str: """Label of the top axis.""" return self._top_left_axis.get_xlabel() @top_label.setter def top_label(self, label: str) -> None: self._top_left_axis.set_xlabel(label, picker=True).id = "top_label" self.queue_draw() @GObject.Property(type=str) def right_label(self) -> str: """Label of the right axis.""" return self._right_axis.get_ylabel() @right_label.setter def right_label(self, label: str) -> None: self._right_axis.set_ylabel(label, picker=True).id = "right_label" self.queue_draw() @GObject.Property(type=int) def bottom_scale(self) -> int: """Scale of the bottom axis.""" return scales.to_int(self._axis.get_xscale()) @bottom_scale.setter def bottom_scale(self, scale: int) -> None: scale = scales.to_string(scale) for axis in (self._axis, self._right_axis): axis.set_xscale(scale) axis.set_xlim(None, None) self.queue_draw() @GObject.Property(type=int) def left_scale(self) -> int: """Scale of the left axis.""" return scales.to_int(self._axis.get_yscale()) @left_scale.setter def left_scale(self, scale: int) -> None: scale = scales.to_string(scale) for axis in (self._axis, self._top_left_axis): axis.set_yscale(scale) axis.set_ylim(None, None) self.queue_draw() @GObject.Property(type=int) def top_scale(self) -> int: """Scale of the top axis.""" return scales.to_int(self._top_left_axis.get_xscale()) @top_scale.setter def top_scale(self, scale: int) -> None: scale = scales.to_string(scale) for axis in (self._top_right_axis, self._top_left_axis): axis.set_xscale(scale) axis.set_xlim(None, None) self.queue_draw() @GObject.Property(type=int) def right_scale(self) -> int: """Scale of the right axis.""" return scales.to_int(self._right_axis.get_yscale()) @right_scale.setter def right_scale(self, scale: int) -> None: scale = scales.to_string(scale) for axis in (self._top_right_axis, self._right_axis): axis.set_yscale(scale) axis.set_ylim(None, None) self.queue_draw() @GObject.Property(type=float) def min_bottom(self) -> float: """Lower limit for the bottom axis.""" return self._axis.get_xlim()[0] @min_bottom.setter def min_bottom(self, value: float) -> None: for axis in (self._axis, self._right_axis): axis.set_xlim(value, None) self.queue_draw() @GObject.Property(type=float) def max_bottom(self) -> float: """Upper limit for the bottom axis.""" return self._axis.get_xlim()[1] @max_bottom.setter def max_bottom(self, value: float) -> None: for axis in (self._axis, self._right_axis): axis.set_xlim(None, value) self.queue_draw() @GObject.Property(type=float) def min_left(self) -> float: """Lower limit for the left axis.""" return self._axis.get_ylim()[0] @min_left.setter def min_left(self, value: float) -> None: for axis in (self._axis, self._top_left_axis): axis.set_ylim(value, None) self.queue_draw() @GObject.Property(type=float) def max_left(self) -> float: """Upper limit for the left axis.""" return self._axis.get_ylim()[1] @max_left.setter def max_left(self, value: float) -> None: for axis in (self._axis, self._top_left_axis): axis.set_ylim(None, value) self.queue_draw() @GObject.Property(type=float) def min_top(self) -> float: """Lower limit for the top axis.""" return self._top_left_axis.get_xlim()[0] @min_top.setter def min_top(self, value: float) -> None: for axis in (self._top_left_axis, self._top_right_axis): axis.set_xlim(value, None) self.highlight.load(self) self.queue_draw() @GObject.Property(type=float) def max_top(self) -> float: """Upper limit for the top axis.""" return self._top_left_axis.get_xlim()[1] @max_top.setter def max_top(self, value: float) -> None: for axis in (self._top_left_axis, self._top_right_axis): axis.set_xlim(None, value) self.highlight.load(self) self.queue_draw() @GObject.Property(type=float) def min_right(self) -> float: """Lower limit for the right axis.""" return self._right_axis.get_ylim()[0] @min_right.setter def min_right(self, value: float) -> None: for axis in (self._right_axis, self._top_right_axis): axis.set_ylim(value, None) self.queue_draw() @GObject.Property(type=float) def max_right(self) -> float: """Upper limit for the right axis.""" return self._right_axis.get_ylim()[1] @max_right.setter def max_right(self, value: float) -> None: for axis in (self._right_axis, self._top_right_axis): axis.set_ylim(None, value) self.queue_draw() @GObject.Property(type=bool, default=False) def highlight_enabled(self) -> bool: """Whether or not the highlight is enabled.""" return hasattr(self, "highlight") and self.highlight.get_active() @highlight_enabled.setter def highlight_enabled(self, enabled: bool) -> None: if hasattr(self, "highlight"): self.highlight.set_active(enabled) self.highlight.set_visible(enabled) self.queue_draw() class _DummyToolbar(NavigationToolbar2): """Custom Toolbar implementation.""" # Overwritten function - do not change name def _zoom_pan_handler(self, event) -> None: mode = self.canvas.props.mode if event.button == 2: event.button = 1 mode = 0 elif event.button != 1: return if mode == 0: if event.name == "button_press_event": self.press_pan(event) elif event.name == "button_release_event": self.release_pan(event) elif mode == 1: if event.name == "button_press_event": self.press_zoom(event) elif event.name == "button_release_event": self.release_zoom(event) # Overwritten function - do not change name def _update_cursor(self, event) -> None: mode = self.canvas.props.mode if event.inaxes and event.inaxes.get_navigate(): if mode == 1 and self._last_cursor != tools.Cursors.SELECT_REGION: self.canvas.set_cursor(tools.Cursors.SELECT_REGION) self._last_cursor = tools.Cursors.SELECT_REGION elif mode == 0 and self._last_cursor != tools.Cursors.MOVE: self.canvas.set_cursor(tools.Cursors.MOVE) self._last_cursor = tools.Cursors.MOVE elif self._last_cursor != tools.Cursors.POINTER: self.canvas.set_cursor(tools.Cursors.POINTER) self._last_cursor = tools.Cursors.POINTER # Overwritten function - do not change name def drag_pan(self, event): """Handle dragging in pan/zoom mode.""" for ax in self._pan_info.axes: # Using the recorded button at the press is safer than the current # button, as multiple buttons can get pressed during motion. # Use custom drag_pan that maxes sure limits are set in right order # even on inverted scale self.ax_drag_pan( ax, self._pan_info.button, event.key, event.x, event.y, ) self.canvas.draw_idle() @staticmethod def ax_drag_pan(self, button, key: str, x: float, y: float) -> None: """ Handle mouse events during a pan operation. Notes ----- This is intended to be overridden by new projection types. """ points = self._get_pan_points(button, key, x, y) if points is not None: # Max and min needs to be defined at correct position for this to # work with inverted scaling ylim = points[:, 1] xlim = points[:, 0] self.set_xlim(min(xlim), max(xlim)) self.set_ylim(min(ylim), max(ylim)) # Overwritten function - do not change name def draw_rubberband(self, _event, x0, y0, x1, y1) -> None: self.canvas._rubberband_rect = [ int(val) for val in (x0, self.canvas.figure.bbox.height - y0, x1 - x0, y0 - y1) ] self.canvas.queue_draw() # Overwritten function - do not change name def remove_rubberband(self) -> None: self.canvas._rubberband_rect = None self.canvas.queue_draw() # Overwritten function - do not change name def push_current(self, *_args) -> None: """Use custom functionality for the view clipboard.""" self.canvas.highlight.load(self.canvas) for direction in ("bottom", "left", "top", "right"): self.canvas.notify(f"min-{direction}") self.canvas.notify(f"max-{direction}") self.canvas.emit("view_changed") # Overwritten function - do not change name def save_figure(self) -> None: pass class _Highlight(SpanSelector): def __init__(self, canvas: Canvas): super().__init__( canvas.axes[3], lambda _x, _y: self.apply(canvas), "horizontal", useblit=True, props={ "facecolor": canvas.rubberband_fill_color, "edgecolor": canvas.rubberband_edge_color, "linewidth": 1, }, handle_props={"linewidth": 0}, interactive=True, drag_from_anywhere=True, ) self.load(canvas) def load(self, canvas: Canvas) -> None: xmin, xmax = canvas.axes[1].get_xlim() scale = canvas.props.top_scale self.extents = ( utilities.get_value_at_fraction( canvas.get_min_selected(), xmin, xmax, scale, ), utilities.get_value_at_fraction( canvas.get_max_selected(), xmin, xmax, scale, ), ) def apply(self, canvas: Canvas) -> None: xmin, xmax = canvas.axes[1].get_xlim() extents = self.extents extents = max(xmin, extents[0]), min(xmax, extents[1]) self.extents = extents for prefix, value in zip(["min_", "max_"], extents): canvas.set_property( prefix + "selected", utilities.get_fraction_at_value( value, xmin, xmax, canvas.props.top_scale, ), ) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/canvas.vala000066400000000000000000000010061511351636000240740ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later using Gtk; namespace Graphs { /** * Custom Canvas implementation. */ public class Canvas : DrawingArea { public bool hide_unselected { get; set; default = false; } public int mode { get; set; default = 0; } public double min_selected { get; set; default = 0; } public double max_selected { get; set; default = 0; } public signal void edit_request (string id); public signal void view_changed (); } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/config.vapi000066400000000000000000000005511511351636000241060ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later [CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "config.h")] namespace Config { public const string GETTEXT_PACKAGE; public const string LOCALEDIR; public const string VERSION; public const string AUTHOR; public const string HOMEPAGE_URL; public const string ISSUE_URL; } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/curve_fitting.py000066400000000000000000000357411511351636000252130ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Curve fitting module.""" import re from gettext import gettext as _ from gi.repository import Adw, Graphs, Gtk from graphs import utilities from graphs.canvas import Canvas from graphs.item import DataItem, FillItem import numpy from scipy.optimize import _minpack, curve_fit class CurveFittingDialog(Graphs.CurveFittingDialog): """Class for displaying the Curve Fitting dialog.""" __gtype_name__ = "GraphsPythonCurveFittingDialog" def __init__(self, application: Graphs.Application, item: Graphs.Item): """Initialize the curve fitting dialog.""" super().__init__(application=application) Adw.StyleManager.get_default().connect("notify", self.reload_canvas) self.connect("equation_change", self.on_equation_change) self.connect("fit_curve_request", self.fit_curve) self.connect("add_fit_request", self.add_fit) self.fitting_parameters = FittingParameterContainer() style = \ application.get_figure_style_manager().get_system_style_params() self.param = [] self.sigma = [] self.r2 = 0 # Generate items for the canvas self.data_curve = DataItem.new( style, xdata=item.xdata, ydata=item.ydata, name=item.get_name(), color="#1A5FB4", ) self.data_curve.linestyle = 0 self.data_curve.markerstyle = 1 self.data_curve.markersize = 13 self.fitted_curve = DataItem.new(style, color="#A51D2D") self.fill = FillItem.new( style, ( self.fitted_curve.xdata, self.fitted_curve.ydata, self.fitted_curve.ydata, ), color="#1A5FB4", alpha=0.15, ) self.reload_canvas() self.setup() self.present(application.get_window()) def reload_canvas(self, *_args) -> None: """Reinitialise the currently used canvas.""" application = self.props.application figure_settings = application.get_data().get_figure_settings() style = \ application.get_figure_style_manager().get_system_style_params() canvas = Canvas(style, interactive=False) canvas.props.items = [self.fitted_curve, self.data_curve, self.fill] axis = canvas.axes[0] axis.yscale = "linear" axis.xscale = "linear" axis.xlabel = "X Value" canvas.left_label = figure_settings.get_property("left_label") canvas.bottom_label = figure_settings.get_property("bottom_label") self.set_canvas(canvas) @staticmethod def on_equation_change(self, equation: str) -> None: """ Logic to execute whenever the equation is changed. Set the free variables and corresponding entry rows when the equation has been changed. """ processed_equation = utilities.preprocess(equation) self.set_equation_string(processed_equation) free_variables = utilities.get_free_variables(processed_equation) if len(free_variables) == 0: self.set_results(error="equation") return self.fitting_parameters.update(free_variables) fit = self.fit_curve() if not fit: return False box = self.get_fitting_params_box() while box.get_last_child() is not None: box.remove(box.get_last_child()) for param in self.fitting_parameters: parameter_box = Graphs.FittingParameterBox.new(param) parameter_box.set_bounds_visible( self.get_settings().get_string("optimization") != "lm", ) for prop in ("initial", "upper_bound", "lower_bound"): entry = parameter_box.get_property(prop) entry.connect("notify::text", self.on_entry_change) box.append(parameter_box) return bool(fit) def on_entry_change(self, entry, _param) -> None: """ Triggered whenever an entry changes. Update the parameters of the curve and perform a new subsequent fit. """ error = False def _is_float(value): """ Check if a value can be converted to a float. If value cannot be parsed as a float, the CSS class "error" to is added to the entry. """ try: float(value) return True except ValueError: entry.add_css_class("error") return False entries = entry.get_ancestor(Graphs.FittingParameterBox) # Set the parameters for the row corresponding to the entry that # was edited for row, params in \ zip(self.get_fitting_params_box(), self.fitting_parameters): if row == entries: initial = entries.get_initial().get_text() lower_bound = entries.get_lower_bound().get_text() upper_bound = entries.get_upper_bound().get_text() entries.get_initial().remove_css_class("error") entries.get_lower_bound().remove_css_class("error") entries.get_upper_bound().remove_css_class("error") for bound in [initial, lower_bound, upper_bound]: if not _is_float(bound): self.set_results(error="value") return initial = float(initial) lower_bound = float(lower_bound) upper_bound = float(upper_bound) if (initial < lower_bound or initial > upper_bound): entries.get_initial().add_css_class("error") self.set_results(error="bounds") error = True if not lower_bound < upper_bound: entries.get_lower_bound().add_css_class("error") entries.get_upper_bound().add_css_class("error") error = True self.set_results(error="bounds") params.set_initial(initial) params.set_lower_bound(str(lower_bound)) params.set_upper_bound(str(upper_bound)) if not error: self.fit_curve() def set_results(self, error="") -> None: """Set the results dialog based on the fit.""" initial_string = _("Results:") + "\n" buffer_string = initial_string if error == "value": buffer_string += _( "Please enter valid fitting \nparameters to start the fit", ) elif error == "equation": buffer_string += _( "Please enter a valid equation \nto start the fit", ) elif error == "bounds": buffer_string += _( "Please enter valid fitting bounds \nto start the fit", ) else: free_variables = utilities.get_free_variables( self.get_equation_string(), ) for index, arg in enumerate(free_variables): parameter = utilities.sig_fig_round(self.param[index], 3) sigma = utilities.sig_fig_round(self.sigma[index], 3) buffer_string += f"{arg}: {parameter}" if self.get_settings().get_enum("confidence") != 0: buffer_string += f" (± {sigma})" buffer_string += "\n" buffer_string += "\n" + _("Sum of R²: {R2}").format(R2=self.r2) self.get_text_view().get_buffer().set_text(buffer_string) bold_tag = Gtk.TextTag(weight=700) self.get_text_view().get_buffer().get_tag_table().add(bold_tag) start_iter = self.get_text_view().get_buffer().get_start_iter() end_iter = self.get_text_view().get_buffer().get_start_iter() # Highlight first word while not end_iter.ends_word() and not end_iter.ends_sentence(): end_iter.forward_char() self.get_text_view().get_buffer().apply_tag( bold_tag, start_iter, end_iter, ) def fit_curve(self, *_args) -> bool: """ Fit the data to the equation in the entry. Returns a boolean indicating whether the fit was succesfull or not. """ def _get_equation_name(equation_name, values): """Obtain the equation name with the fitted parameter values.""" free_variables = utilities.get_free_variables( self.get_equation_string(), ) var_to_val = dict(zip(free_variables, values)) for var, val in var_to_val.items(): if var.lower() == "e": pattern = r"(? None: """ Obtain the confidence interval from the fit. Get the confidence interval in terms of the standard deviation resulting from the covariance in the plot. The bounds are navively calculated by using the extremes on each parameter for both sides of the bounds. """ # Get standard deviation self.get_canvas().axes[0].relim() # Reset limits self.sigma = numpy.sqrt(numpy.diagonal(self.param_cov)) self.sigma *= self.get_settings().get_enum("confidence") try: fitted_y = [ function(x, *self.param) for x in self.data_curve.xdata ] except (OverflowError, ZeroDivisionError): return ss_res = numpy.sum( (numpy.asarray(self.data_curve.ydata) - numpy.asarray(fitted_y))**2, ) ss_sum = numpy.sum((self.data_curve.ydata - numpy.mean(fitted_y))**2) self.r2 = utilities.sig_fig_round(1 - (ss_res / ss_sum), 3) # Get confidence band upper_bound = function( self.fitted_curve.xdata, *(self.param + self.sigma), ) lower_bound = function( self.fitted_curve.xdata, *(self.param - self.sigma), ) # Filter non-finite values from the bounds lower_bound = lower_bound[numpy.isfinite(lower_bound)] upper_bound = upper_bound[numpy.isfinite(upper_bound)] # Cancel if there's no valid values left after filtering non-finite if len(upper_bound) == 0 or len(lower_bound) == 0: return span = max(self.fitted_curve.ydata) - min(self.fitted_curve.ydata) middle = \ (max(self.fitted_curve.ydata) - min(self.fitted_curve.ydata)) / 2 # Don't try to draw complicated and resource-hogging bounds when # far out of range, instead set them to be single-values far away if max(upper_bound) > middle + 1e5 * span: upper_bound = [middle + 1e5 * span] if min(lower_bound) < middle - 1e5 * span: lower_bound = [middle - 1e5 * span] self.fill.props.data = ( self.fitted_curve.props.xdata, lower_bound, upper_bound, ) @staticmethod def add_fit(self) -> None: """Add fitted data to the items in the main application.""" application = self.props.application style_manager = application.get_figure_style_manager() application.get_data().add_items( [ DataItem.new( style_manager.get_selected_style_params(), name=self.fitted_curve.get_name(), xdata=list(self.fitted_curve.xdata), ydata=list(self.fitted_curve.ydata), ), ], style_manager, ) self.close() class FittingParameterContainer(): """ Container class for the Fitting Parameters. Each item in the container represents one fitting parameter. """ def __init__(self): self._items = {} def __iter__(self): """Iterate over items.""" return iter(self._items.values()) def get(self, arg): """Retrieve an item.""" return self._items[arg] def update(self, parameters: list) -> None: """ Remove unused fitting parameters. Removes the used parameters `used_list` from the container, this ensures that the fitting parameters are kept up-to-date with the actual equation. """ names = [item.get_name() for item in self] for var in parameters: if var not in names: self._items[var] = Graphs.FittingParameter( name=var, initial=1, lower_bound="-inf", upper_bound="inf", ) # First create list with items to remove # to avoid dict changing size during iteration remove_list = [] for item in self._items.values(): if item.get_name() not in parameters: remove_list.append(item.get_name()) for item_name in remove_list: del self._items[item_name] self._items = {key: self._items[key] for key in parameters} def get_p0(self) -> list: """Get the initial values of the fitting.""" return [float(item_.get_initial()) for item_ in self] def get_bounds(self) -> tuple: """Get the bounds of the fitting parameters.""" lower_bounds = [float(item_.get_lower_bound()) for item_ in self] upper_bounds = [float(item_.get_upper_bound()) for item_ in self] return (lower_bounds, upper_bounds) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/curve_fitting.vala000066400000000000000000000125311511351636000254760ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later using Adw; using Gtk; namespace Graphs { public class FittingParameter : Object { public string name { get; construct set; } public double initial { get; construct set; } public string lower_bound { get; construct set; } public string upper_bound { get; construct set; } } private const string[] EQUATIONS = { "a*x+b", // linear "a*x²+b*x+c", // quadratic "a*exp(b*x)", // exponential "a*x^b", // power "a*log(x)+b", // log "L/(1+exp(-k*(x-b)))", // sigmoid "a*exp(-(x-mu)²/(2*s²))" // gaussian }; /** * Curve fitting dialog. */ [GtkTemplate (ui = "/se/sjoerd/Graphs/ui/curve-fitting.ui")] public class CurveFittingDialog : Adw.Dialog { [GtkChild] private unowned Adw.ComboRow equation { get; } [GtkChild] protected unowned Adw.EntryRow custom_equation { get; } [GtkChild] protected unowned Box fitting_params_box { get; } [GtkChild] protected unowned TextView text_view { get; } [GtkChild] private unowned Adw.ToastOverlay toast_overlay { get; } public Application application { get; construct set; } protected GLib.Settings settings { get; private set; } protected string equation_string { get; protected set; } protected Canvas canvas { get { return (Canvas) this.toast_overlay.get_child (); } set { this.toast_overlay.set_child (value); } } protected signal bool equation_change (string equation); protected signal void fit_curve_request (); protected signal void add_fit_request (); protected void setup () { this.settings = this.application.get_settings_child ("curve-fitting"); var action_map = new SimpleActionGroup (); Action confidence_action = this.settings.create_action ("confidence"); Action optimization_action = this.settings.create_action ("optimization"); confidence_action.notify.connect (emit_fit_curve_request); optimization_action.notify.connect (() => { emit_fit_curve_request (); bool visible = this.settings.get_string ("optimization") != "lm"; var entry = (FittingParameterBox) this.fitting_params_box.get_first_child (); while (entry != null) { entry.set_bounds_visible (visible); entry = (FittingParameterBox) entry.get_next_sibling (); } }); action_map.add_action (confidence_action); action_map.add_action (optimization_action); this.insert_action_group ("win", action_map); this.equation.set_selected (this.settings.get_enum ("equation")); this.equation.notify["selected"].connect (set_equation); this.custom_equation.notify["text"].connect (() => { bool success = this.equation_change.emit (this.custom_equation.get_text ()); if (success) { this.custom_equation.remove_css_class ("error"); if (this.equation.get_selected () == 7) { this.settings.set_string ("custom-equation", this.custom_equation.get_text ()); } } else this.custom_equation.add_css_class ("error"); }); set_equation (); } private void emit_fit_curve_request () { this.fit_curve_request.emit (); } private void set_equation () { int selected = (int) this.equation.get_selected (); if (this.settings.get_enum ("equation") != selected ) { this.settings.set_enum ("equation", selected); } string equation; if (selected != 7) { equation = EQUATIONS[selected]; this.equation.set_subtitle (@"Y=$equation"); this.custom_equation.set_visible (false); } else { equation = this.settings.get_string ("custom-equation"); this.equation.set_subtitle (""); this.settings.set_string ("custom-equation", equation); this.custom_equation.set_visible (true); } this.custom_equation.set_text (equation); } [GtkCallback] private void emit_add_fit_request () { this.add_fit_request.emit (); } } [GtkTemplate (ui = "/se/sjoerd/Graphs/ui/fitting-parameters.ui")] public class FittingParameterBox : Box { [GtkChild] private unowned Label label { get; } [GtkChild] public unowned Adw.EntryRow initial { get; } [GtkChild] public unowned Adw.EntryRow upper_bound { get; } [GtkChild] public unowned Adw.EntryRow lower_bound { get; } public FittingParameterBox (FittingParameter param) { string msg = _("Fitting Parameters for %s").printf (param.name); this.label.set_markup (@" $msg: "); this.initial.set_text (param.initial.to_string ()); } public void set_bounds_visible (bool visible) { this.upper_bound.set_visible (visible); this.lower_bound.set_visible (visible); } } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/data.py000066400000000000000000000564051511351636000232540ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Data management module.""" import copy import math import os from gettext import gettext as _ from urllib.parse import unquote, urlparse from gi.repository import GObject, Gio, Graphs from graphs import item, misc import numpy _FIGURE_SETTINGS_HISTORY_IGNORELIST = misc.LIMITS + [ "min_selected", "max_selected", ] class Data(Graphs.Data): """Class for managing data.""" __gtype_name__ = "GraphsPythonData" def __init__(self, settings: Gio.Settings): figure_settings = Graphs.FigureSettings.new(settings) super().__init__( settings=settings, figure_settings=figure_settings, ) self._initialize() figure_settings.connect("notify", self._on_figure_settings_change) self.connect("notify::unsaved", self._on_unsaved_change) self._update_used_positions() self.connect( "add_history_state_request", lambda _s: self.add_history_state(), ) def reset(self): """Reset data.""" # Reset figure settings default_figure_settings = Graphs.FigureSettings.new( self.props.settings, ) figure_settings = self.get_figure_settings() for prop in dir(default_figure_settings.props): new_value = default_figure_settings.get_property(prop) figure_settings.set_property(prop, new_value) # Reset items if not self.empty: self.delete_items([item for item in self]) self._initialize() self.props.file = None self.props.unsaved = False def _initialize(self): """Initialize the data class and set default values.""" figure_settings = self.get_figure_settings() limits = figure_settings.get_limits() self.props.can_undo = False self.props.can_redo = False self.props.can_view_back = False self.props.can_view_forward = False self._history_states = [([], limits)] self._history_pos = -1 self._view_history_states = [limits] self._view_history_pos = -1 self._items = {} self._set_data_copy() @GObject.Property(type=bool, default=True, flags=1 | 1073741824) def empty(self) -> bool: """Whether or not the class is empty.""" return not self._items def _on_unsaved_change(self, _a, _b) -> None: if not self.props.unsaved: self.emit("saved") self.notify("project-name") self.notify("project-path") @GObject.Property(type=str, default="", flags=1) def project_name(self) -> str: """Get project_name property.""" if self.props.file is None: title = _("Untitled Project") else: title = Graphs.tools_get_filename(self.props.file) if self.props.unsaved: title = "• " + title return title @GObject.Property(type=str, default="", flags=1) def project_path(self) -> str: """Retrieve the path of the associated file.""" if self.props.file is None: return _("Draft") uri_parse = urlparse(self.props.file.get_uri()) filepath = os.path.dirname( os.path.join(uri_parse.netloc, unquote(uri_parse.path)), ) if filepath.startswith("/var"): # Fix for rpm-ostree distros, where home is placed in /var/home filepath = filepath.replace("/var", "", 1) return filepath.replace(os.path.expanduser("~"), "~") @GObject.Property(type=bool, default=False, flags=1) def items_selected(self) -> bool: """Whether or not at least one item is selected.""" return any(item_.get_selected() for item_ in self) @GObject.Property(type=object, flags=3 | 1073741824) # explicit notify def items(self) -> misc.ItemList: """All managed items.""" return self.get_items() @items.setter def items(self, items: misc.ItemList) -> None: """Set items property.""" self.set_items(items) def get_items(self) -> misc.ItemList: """Get all managed items.""" return list(self._items.values()) def set_items(self, items: misc.ItemList) -> None: """Set all managed items.""" self._items = {} for item_ in items: self._add_item(item_) self._update_used_positions() self.notify("items") self.notify("empty") def _add_item(self, item_: Graphs.Item) -> None: """Append items to self.""" self._connect_to_item(item_) self._items[item_.get_uuid()] = item_ def _delete_item(self, uuid: str) -> None: """Pop and delete item.""" item_ = self._items[uuid] self._items.pop(uuid) del item_ def index(self, item_: Graphs.Item) -> int: """Get the index of an item.""" return list(self._items.keys()).index(item_.get_uuid()) def get_names(self) -> list[str]: """All items' names.""" return [item_.get_name() for item_ in self] def get_n_items(self) -> int: """Amount of managed items.""" return len(self._items) def get_at_pos(self, position: int) -> Graphs.Item: """Get item at position.""" return self.get_items()[position] def get_for_uuid(self, uuid: str) -> Graphs.Item: """Get item for key.""" return self._items[uuid] def __len__(self) -> int: """Magic alias for `get_n_items()`.""" return self.get_n_items() def __iter__(self): """Iterate over items.""" return iter(self._items.values()) def __getitem__(self, getter: str | int): """Magic alias for retrieving items.""" if isinstance(getter, str): return self.get_for_uuid(getter) return self.get_at_pos(getter) def change_position(self, index1: int, index2: int) -> None: """Change item position of index2 to that of index1.""" items = self.get_items() # Check if target key is lower in the order, if so we can put the old # key below the target key. Otherwise put it above. if index1 < index2: items[index1:index2 + 1] = [items[index2]] + items[index1:index2] else: items[index2:index1 + 1] = \ items[index2 + 1:index1 + 1] + [items[index2]] self.set_items(items) self._current_batch.append((3, (index2, index1))) def add_items( self, items: misc.ItemList, style_manager: Graphs.StyleManager, ) -> None: """ Add items to be managed. Respects settings in regards to handling duplicate names. New Items with a x- or y-label change the figures current labels if they are still the default. If they are already modified and do not match the items label, they get moved to another axis. """ figure_settings = self.get_figure_settings() selected_style = style_manager.get_selected_style_params() color_cycle = selected_style["axes.prop_cycle"].by_key()["color"] used_colors = [] def _append_used_color(color): used_colors.append(color) if len(set(used_colors)) == len(color_cycle): for color in color_cycle: used_colors.remove(color) def _is_default(prop): return figure_settings.get_property(prop) == \ self.props.settings.get_string(prop) for item_ in self: color = item_.get_color() if color in color_cycle: _append_used_color(color) used_names = set(self.get_names()) for new_item in items: item_name = new_item.get_name() if item_name in used_names: new_item.set_name( Graphs.tools_get_duplicate_string( item_name, list(used_names), ), ) used_names.add(new_item.get_name()) xlabel = new_item.get_xlabel() if xlabel: original_position = new_item.get_xposition() if original_position == 0: if _is_default("bottom-label") or self.props.empty: figure_settings.set_bottom_label(xlabel) elif xlabel != figure_settings.get_bottom_label(): new_item.set_xposition(1) if new_item.get_xposition() == 1: if _is_default("top-label"): figure_settings.set_top_label(xlabel) elif xlabel != figure_settings.get_top_label(): new_item.set_xposition(original_position) ylabel = new_item.get_ylabel() if ylabel: original_position = new_item.get_yposition() if original_position == 0: if _is_default("left-label") or self.props.empty: figure_settings.set_left_label(ylabel) elif ylabel != figure_settings.get_left_label(): new_item.set_yposition(1) if new_item.get_yposition() == 1: if _is_default("right-label"): figure_settings.set_right_label(ylabel) elif ylabel != figure_settings.get_right_label(): new_item.set_yposition(original_position) if new_item.get_color() == "": for color in color_cycle: if color not in used_colors: _append_used_color(color) new_item.set_color(color) break self._add_item(new_item) change = (1, copy.deepcopy(new_item.to_dict())) self._current_batch.append(change) self.optimize_limits() self.add_history_state() self._update_used_positions() self.notify("items") self.notify("empty") self.notify("items_selected") def delete_items(self, items: misc.ItemList): """Delete specified items.""" settings = self.get_figure_settings() for item_ in items: self._current_batch.append( (2, (self.index(item_), item_.to_dict())), ) x_position = item_.get_xposition() y_position = item_.get_yposition() + 2 xlabel = item_.get_xlabel() ylabel = item_.get_ylabel() self._delete_item(item_.get_uuid()) used = self.get_used_positions() for position in [x_position, y_position]: direction = misc.DIRECTIONS[position] item_label = xlabel if position < 2 else ylabel axis_label = getattr(settings, f"get_{direction}_label")() if not used[position] and item_label == axis_label: set_label = getattr(settings, f"set_{direction}_label") set_label( self.props.settings.get_string(f"{direction}-label"), ) self._update_used_positions() self.notify("items") self.notify("empty") self.add_history_state() self.notify("items_selected") def _connect_to_item(self, item_: Graphs.Item): item_.connect("notify::selected", self._on_item_select) item_.connect("notify", self._on_item_change) for prop in ("xposition", "yposition"): item_.connect(f"notify::{prop}", self._on_item_position_change) def _on_item_position_change(self, _item, _ignored) -> None: self.optimize_limits() self._update_used_positions() self.notify("items") def _on_item_select(self, _x, _y) -> None: self.notify("items_selected") def _on_item_change(self, item_, param) -> None: self._current_batch.append(( 0, ( item_.get_uuid(), param.name, copy.deepcopy(self._data_copy[item_.get_uuid()][param.name]), copy.deepcopy(item_.get_property(param.name)), ), )) def _update_used_positions(self) -> None: # bottom, top, left, right figure_settings = self.get_figure_settings() used_positions = [False, False, False, False] for item_ in self.items: if ( figure_settings.get_hide_unselected() and not item_.get_selected() ): continue used_positions[item_.get_xposition()] = True used_positions[item_.get_yposition() + 2] = True if not any(used_positions): self.set_used_positions(True, False, True, False) return self.set_used_positions(*used_positions) def _on_figure_settings_change(self, figure_settings, param) -> None: if param.name in _FIGURE_SETTINGS_HISTORY_IGNORELIST: return self._current_batch.append(( 4, ( param.name, copy.deepcopy(self._figure_settings_copy[param.name]), copy.deepcopy(figure_settings.get_property(param.name)), ), )) def _set_data_copy(self) -> None: """Set a deep copy for the data.""" self._current_batch: list = [] self._data_copy = copy.deepcopy({ item_.get_uuid(): item_.to_dict() for item_ in self }) self._figure_settings_copy = copy.deepcopy({ prop.replace("_", "-"): self.props.figure_settings.get_property(prop) for prop in dir(self.props.figure_settings.props) }) def add_history_state(self, old_limits: misc.Limits = None) -> None: """Add a state to the clipboard.""" if not self._current_batch: return if self._history_pos != -1: self._history_states = self._history_states[:self._history_pos + 1] self._history_pos = -1 self._history_states.append( (self._current_batch, self.get_figure_settings().get_limits()), ) if old_limits is not None: old_state = self._history_states[-2][1] for index in range(8): old_state[index] = old_limits[index] self.props.can_redo = False self.props.can_undo = True # Keep history states length limited to 100 spots if len(self._history_states) > 101: self._history_states = self._history_states[1:] self._set_data_copy() self.props.unsaved = True def undo(self) -> None: """Undo the latest change that was added to the clipboard.""" if not self.props.can_undo: return batch = self._history_states[self._history_pos][0] self._history_pos -= 1 items_changed = False for change_type, change in reversed(batch): if change_type == 0: self[change[0]].set_property(change[1], change[2]) elif change_type == 1: self._delete_item(change["uuid"]) items_changed = True elif change_type == 2: item_ = item.new_from_dict(copy.deepcopy(change[1])) self._add_item(item_) self.change_position(change[0], len(self) - 1) items_changed = True elif change_type == 3: self.change_position(change[0], change[1]) items_changed = True elif change_type == 4: self.props.figure_settings.set_property( change[0], change[1], ) if items_changed: self._update_used_positions() self.notify("items") self.notify("empty") self.notify("items_selected") self.get_figure_settings().set_limits( self._history_states[self._history_pos][1], ) self.props.can_redo = True self.props.can_undo = \ abs(self._history_pos) < len(self._history_states) self._set_data_copy() self.add_view_history_state() def redo(self) -> None: """Redo the latest change that was added to the clipboard.""" if not self.props.can_redo: return self._history_pos += 1 state = self._history_states[self._history_pos] items_changed = False for change_type, change in state[0]: if change_type == 0: self[change[0]].set_property(change[1], change[3]) elif change_type == 1: self._add_item(item.new_from_dict(copy.deepcopy(change))) items_changed = True elif change_type == 2: self._delete_item(change[1]["uuid"]) items_changed = True elif change_type == 3: self.change_position(change[1], change[0]) items_changed = True elif change_type == 4: self.props.figure_settings.set_property( change[0], change[2], ) if items_changed: self._update_used_positions() self.notify("items") self.notify("empty") self.notify("items_selected") self.get_figure_settings().set_limits(state[1]) self.props.can_redo = self._history_pos < -1 self.props.can_undo = True self._set_data_copy() self.add_view_history_state() def add_view_history_state(self) -> None: """Add the view to the view history.""" limits = self.get_figure_settings().get_limits() if all( math.isclose(old, new) for old, new in zip(self._view_history_states[-1], limits) ): return # If a couple of redo's were performed previously, it deletes the # clipboard data that is located after the current clipboard # position and disables the redo button if self._view_history_pos != -1: self._view_history_states = \ self._view_history_states[:self._view_history_pos + 1] self._view_history_pos = -1 self._view_history_states.append(limits) self.props.can_view_back = True self.props.can_view_forward = False self.props.unsaved = True def view_back(self) -> None: """Move the view to the previous value in the view history.""" if not self.props.can_view_back: return self._view_history_pos -= 1 self.get_figure_settings().set_limits( self._view_history_states[self._view_history_pos], ) self.props.can_view_forward = True self.props.can_view_back = \ abs(self._view_history_pos) < len(self._view_history_states) def view_forward(self) -> None: """Move the view to the next value in the view history.""" if not self.props.can_view_forward: return self._view_history_pos += 1 self.get_figure_settings().set_limits( self._view_history_states[self._view_history_pos], ) self.props.can_view_back = True self.props.can_view_forward = self._view_history_pos < -1 def optimize_limits(self) -> None: """Optimize the limits of the canvas to the data class.""" figure_settings = self.get_figure_settings() axes = [[ direction, False, [], [], figure_settings.get_property(f"{direction}_scale"), ] for direction in ("bottom", "left", "top", "right")] for item_ in self: if not isinstance(item_, item.DataItem) or ( not item_.get_selected() and figure_settings.get_hide_unselected() ): continue for index in \ item_.get_xposition() * 2, 1 + item_.get_yposition() * 2: axes[index][1] = True xydata = numpy.asarray( item_.ydata if index % 2 else item_.xdata, ) try: xydata = xydata[numpy.isfinite(xydata)] except TypeError: return nonzero_data = numpy.array([ value for value in xydata if value != 0 ]) axes[index][2].append( nonzero_data.min() if axes[index][4] in (1, 4) and len(nonzero_data) > 0 else xydata.min(), ) axes[index][3].append(xydata.max()) for count, (direction, used, min_all, max_all, scale) in \ enumerate(axes): if not used: continue min_all = min(min_all) max_all = max(max_all) if scale != 1: # For non-logarithmic scales span = max_all - min_all # 0.05 padding on y-axis, 0.015 padding on x-axis padding_factor = 0.05 if count % 2 else 0.015 max_all += padding_factor * span # For inverse scale, calculate padding using a factor min_all = ( min_all - padding_factor * span if scale != 4 else min_all * 0.99 ) else: # Use different scaling type for logarithmic scale # Use padding factor of 2 for y-axis, 1.025 for x-axis padding_factor = 2 if count % 2 else 1.025 min_all *= 1 / padding_factor max_all *= padding_factor figure_settings.set_property(f"min_{direction}", min_all) figure_settings.set_property(f"max_{direction}", max_all) self.add_view_history_state() def get_project_dict(self, version: str) -> dict: """Convert data to dict.""" figure_settings = self.get_figure_settings() return { "version": version, "data": [item_.to_dict() for item_ in self], "figure-settings": { key: figure_settings.get_property(key) for key in dir(figure_settings.props) }, "history-states": self._history_states, "history-position": self._history_pos, "view-history-states": self._view_history_states, "view-history-position": self._view_history_pos, } def load_from_project_dict(self, project_dict: dict) -> None: """Load data from dict.""" # Load data figure_settings = self.get_figure_settings() for key, value in project_dict["figure-settings"].items(): if figure_settings.get_property(key) != value: figure_settings.set_property(key, value) self.set_items(item.new_from_dict(d) for d in project_dict["data"]) self.notify("items_selected") # Set clipboard self._set_data_copy() self._history_states = project_dict["history-states"] self._history_pos = project_dict["history-position"] self._view_history_states = project_dict["view-history-states"] self._view_history_pos = project_dict["view-history-position"] self.unsaved = False # Set clipboard/view buttons self.props.can_undo = \ abs(self._history_pos) < len(self._history_states) self.props.can_redo = self._history_pos < -1 self.props.can_view_back = \ abs(self._view_history_pos) < len(self._view_history_states) self.props.can_view_forward = self._view_history_pos < -1 Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/data.vala000066400000000000000000000017751511351636000235470ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later namespace Graphs { /** * Data class */ public class Data : Object { protected Settings settings { get; set; } public FigureSettings figure_settings { get; construct set; } public bool can_undo { get; protected set; default = false; } public bool can_redo { get; protected set; default = false; } public bool can_view_back { get; protected set; default = false; } public bool can_view_forward { get; protected set; default = false; } public File file { get; set; } public bool unsaved { get; set; default = false; } private bool[] _used_positions; public signal void add_history_state_request (); public bool[] get_used_positions () { return this._used_positions; } public void set_used_positions (bool a, bool b, bool c, bool d) { this._used_positions = { a, b, c, d }; } public signal void saved (); } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/edit_item.py000066400000000000000000000067411511351636000243040ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Module for Editing an Item.""" from gi.repository import Adw, GObject, Graphs, Gtk from graphs.item import DataItem _IGNORELIST = [ "alpha", "color", "item_type", "uuid", "selected", "xdata", "xlabel", "ydata", "ylabel", ] @Gtk.Template(resource_path="/se/sjoerd/Graphs/ui/edit-item.ui") class EditItemDialog(Adw.PreferencesDialog): """Class for editing an Item.""" __gtype_name__ = "GraphsEditItemDialog" item_selector = Gtk.Template.Child() name = Gtk.Template.Child() xposition = Gtk.Template.Child() yposition = Gtk.Template.Child() item_group = Gtk.Template.Child() linestyle = Gtk.Template.Child() linewidth = Gtk.Template.Child() markerstyle = Gtk.Template.Child() markersize = Gtk.Template.Child() data = GObject.Property(type=Graphs.Data) item = GObject.Property(type=Graphs.Item) model = GObject.Property(type=Gtk.StringList) bindings = GObject.Property(type=object) def __init__(self, application, item): data = application.get_data() super().__init__( data=data, item=item, bindings=[], model=Gtk.StringList.new(data.get_names()), ) self.item_selector.set_model(self.props.model) self.item_selector.set_selected(data.get_items().index(item)) self.present(application.get_window()) @Gtk.Template.Callback() def on_select(self, _action, _target) -> None: """Handle Item selection.""" item = self.props.data[self.item_selector.get_selected()] if item != self.item: self.props.model.splice( self.props.data.get_items().index(self.item), 1, [self.props.item.get_name()], ) self.props.item = item @Gtk.Template.Callback() def on_item_change(self, _a, _b) -> None: """Handle Item change.""" item = self.props.item self.set_title(item.get_name()) for binding in self.props.bindings: binding.unbind() bindings = [] for key in dir(item.props): if key in _IGNORELIST: continue widget = getattr(self, key) if isinstance(widget, Adw.EntryRow): bindings.append( item.bind_property(key, widget, "text", 1 | 2), ) elif isinstance(widget, Adw.ComboRow): bindings.append( item.bind_property(key, widget, "selected", 1 | 2), ) elif isinstance(widget, Gtk.Scale): bindings.append( item.bind_property( key, widget.get_adjustment(), "value", 1 | 2, ), ) self.props.bindings = bindings self.item_group.set_visible(isinstance(item, DataItem)) @Gtk.Template.Callback() def on_close(self, _a) -> None: """Handle dialog closing.""" self.props.data.add_history_state() @Gtk.Template.Callback() def on_linestyle(self, comborow, _b) -> None: """Handle linestyle selection.""" self.linewidth.set_sensitive(comborow.get_selected() != 0) @Gtk.Template.Callback() def on_markers(self, comborow, _b) -> None: """Handle marker selection.""" self.markersize.set_sensitive(comborow.get_selected() != 0) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/export_figure.vala000066400000000000000000000042741511351636000255150ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later using Adw; using Gtk; namespace Graphs { private const uint[] FORMATS_WITH_DPI = {1, 3, 6}; /** * Export figure dialog */ [GtkTemplate (ui = "/se/sjoerd/Graphs/ui/export-figure.ui")] public class ExportFigureDialog : Adw.Dialog { [GtkChild] public unowned Adw.SpinRow dpi { get; } [GtkChild] public unowned Adw.SwitchRow transparent { get; } [GtkChild] public unowned Adw.ComboRow file_format { get; } private Application application; public signal void accept (File file); public ExportFigureDialog (Application application) { Object (); this.application = application; Tools.bind_settings_to_widgets ( application.get_settings_child ("export-figure"), this ); present (application.window); } [GtkCallback] private void on_file_format () { this.dpi.set_visible ( this.file_format.get_selected () in FORMATS_WITH_DPI ); } [GtkCallback] private void on_accept () { string filename = C_("filename", "Exported Figure"); GLib.Settings settings = application.get_settings_child ("export-figure"); string suffix = settings.get_string ("file-format"); var dialog = new FileDialog (); dialog.set_initial_name (@"$filename.$suffix"); dialog.set_accept_label (_("Export")); GLib.ListStore filter_store = new GLib.ListStore (typeof (FileFilter)); var filter = new FileFilter (); StringObject selected = (StringObject) this.file_format.get_selected_item (); filter.name = selected.get_string (); filter.add_suffix (suffix); filter_store.append (filter); dialog.set_filters (filter_store); dialog.save.begin (this.application.window, null, (d, r) => { try { File file = dialog.save.end (r); this.accept.emit (file); close (); } catch {} }); } } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/figure_settings.py000066400000000000000000000050121511351636000255300ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Figure Settings Dialog.""" from gettext import gettext as _ from gi.repository import Graphs from graphs import misc, styles, ui class FigureSettingsDialog(Graphs.FigureSettingsDialog): """Figure Settings Dialog.""" __gtype_name__ = "GraphsPythonFigureSettingsDialog" def __init__( self, application: Graphs.Application, highlighted: str = None, ): """Initialize the Figure Settings window and set the widget entries.""" super().__init__(application=application) self.props.style_editor = styles.StyleEditor(self) self.setup(highlighted) self.connect("copy-request", self.copy_style) self.connect("closed", self.on_close) self.connect("entry-change", self.on_entry_change) self.connect("set-as-default", self.on_set_as_default) @staticmethod def on_entry_change(self, entry, prop) -> None: """Bind the entry upon change.""" is_valid, value = ui.validate_entry(entry) if is_valid: self.props.figure_settings.set_property(prop, value) @staticmethod def copy_style(self, template, name) -> None: """Open the new style window.""" style_manager = self.props.application.get_figure_style_manager() style_manager.copy_style(template, name) def on_close(self, *_args) -> None: """ Handle closing. Closes the figure settings, saves the current style and adds the new state to the clipboard """ self.emit("save-style-request") data = self.props.application.get_data() data.add_view_history_state() data.add_history_state() @staticmethod def on_set_as_default(self) -> None: """Set the current figure settings as the new default.""" figure_settings = self.props.figure_settings settings = self.props.application.get_settings_child("figure") ignorelist = ["min_selected", "max_selected"] + misc.LIMITS for prop in dir(figure_settings.props): if prop in ignorelist: continue value = figure_settings.get_property(prop) prop = prop.replace("_", "-") if isinstance(value, str): settings.set_string(prop, value) elif isinstance(value, bool): settings.set_boolean(prop, value) elif isinstance(value, int): settings.set_enum(prop, value) self.add_toast_string(_("Defaults Updated")) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/figure_settings.vala000066400000000000000000000250261511351636000260320ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later using Adw; using Gtk; namespace Graphs { /** * Figure settings */ public class FigureSettings : Object { public string title { get; set; default = ""; } public string bottom_label { get; set; default = ""; } public string left_label { get; set; default = ""; } public string top_label { get; set; default = ""; } public string right_label { get; set; default = ""; } public int bottom_scale { get; set; default = 0; } public int left_scale { get; set; default = 0; } public int top_scale { get; set; default = 0; } public int right_scale { get; set; default = 0; } public bool legend { get; set; default = true; } public int legend_position { get; set; default = 0; } public bool use_custom_style { get; set; default = false; } public string custom_style { get; set; default = "Adwaita"; } public bool hide_unselected { get; set; default = false; } public double min_bottom { get; set; default = 0; } public double max_bottom { get; set; default = 1; } public double min_left { get; set; default = 0; } public double max_left { get; set; default = 10; } public double min_top { get; set; default = 0; } public double max_top { get; set; default = 1; } public double min_right { get; set; default = 0; } public double max_right { get; set; default = 10; } public double min_selected { get; set; default = 0; } public double max_selected { get; set; default = 0; } public FigureSettings (GLib.Settings settings) { Object ( bottom_scale: settings.get_enum ("bottom-scale"), left_scale: settings.get_enum ("left-scale"), right_scale: settings.get_enum ("right-scale"), top_scale: settings.get_enum ("top-scale"), title: settings.get_string ("title"), bottom_label: settings.get_string ("bottom-label"), left_label: settings.get_string ("left-label"), top_label: settings.get_string ("top-label"), right_label: settings.get_string ("right-label"), legend: settings.get_boolean ("legend"), use_custom_style: settings.get_boolean ("use-custom-style"), legend_position: settings.get_enum ("legend-position"), custom_style: settings.get_string ("custom-style") ); } public double[] get_limits () { double[] limits = {}; foreach (string limit_name in LIMIT_NAMES) { double limit; get (limit_name, out limit); limits += limit; } return limits; } public void set_limits (double[] limits) requires (limits.length == 8) { for (int i = 0; i < LIMIT_NAMES.length; i++) { set (LIMIT_NAMES[i], limits[i]); } } public void set_selection_range (double minimum, double maximum) requires (0 <= minimum <= 1) requires (0 <= maximum <= 1) requires (minimum <= maximum) { this.min_selected = minimum; this.max_selected = maximum; } } private const string PAGE_RESOURCE = "/se/sjoerd/Graphs/ui/figure-settings-page.ui"; /** * Figure settings dialog */ [GtkTemplate (ui = "/se/sjoerd/Graphs/ui/figure-settings-dialog.ui")] public class FigureSettingsDialog : Adw.Dialog { [GtkChild] public unowned Adw.NavigationView navigation_view { get; } [GtkChild] private unowned GridView grid_view { get; } [GtkChild] private unowned Adw.NavigationPage style_overview { get; } [GtkChild] private unowned Adw.ToastOverlay toast_overlay { get; } public Application application { get; construct set; } protected Adw.NavigationPage settings_page { get; private set; } protected Adw.NavigationPage style_editor { get; protected set; } protected FigureSettings figure_settings { get { return this.application.data.figure_settings; } } protected signal void entry_change (Adw.EntryRow entry, string prop); protected signal void set_as_default (); protected signal void copy_request (string template, string name); public signal void load_style_request (Style style); public signal void save_style_request (); protected void setup (string? highlighted) { FigureSettings figure_settings = this.figure_settings; GLib.Settings settings = this.application.get_settings_child ("figure"); var builder = new Builder.from_resource (PAGE_RESOURCE); this.settings_page = (Adw.NavigationPage) builder.get_object ("settings_page"); this.navigation_view.push (this.settings_page); foreach (string key in settings.settings_schema.list_keys ()) { if (key[-5:] == "style") continue; key = key.replace ("-", "_"); Object object = builder.get_object (key); if (object is Adw.EntryRow) { figure_settings.bind_property (key, object, "text", 1 | 2); } else if (object is Adw.ComboRow) { figure_settings.bind_property (key, object, "selected", 1 | 2); } else if (object is Adw.ExpanderRow) { figure_settings.bind_property (key, object, "enable-expansion", 1 | 2); } else if (object is Adw.SwitchRow) { figure_settings.bind_property (key, object, "active", 1 | 2); } else assert_not_reached (); } bool[] visible_axes = this.application.data.get_used_positions (); bool both_x = visible_axes[0] && visible_axes[1]; bool both_y = visible_axes[2] && visible_axes[3]; string[] min_max = {"min", "max"}; for (int i = 0; i < 4; i++) { string direction = DIRECTION_NAMES[i]; bool visible = visible_axes[i]; bool x = i < 2; if (visible) { foreach (string s in min_max) { string key = s + "_" + direction; var entry = (Adw.EntryRow) builder.get_object (key); double val; figure_settings.get (key, out val); entry.set_text (val.to_string ()); entry.notify["text"].connect (() => { this.entry_change.emit (entry, key); }); if (s == "min") { if (x && !both_x) entry.set_title (_("X Axis Minimum")); else if (!x && !both_y) entry.set_title (_("Y Axis Minimum")); } else { if (x && !both_x) entry.set_title (_("X Axis Maximum")); else if (!x && !both_y) entry.set_title (_("Y Axis Maximum")); } } var scale = (Adw.ComboRow) builder.get_object (direction + "_scale"); var label = (Adw.EntryRow) builder.get_object (direction + "_label"); var limits = (Box) builder.get_object (direction + "_limits"); scale.set_visible (true); label.set_visible (true); limits.set_visible (true); if (x && !both_x) { scale.set_title (_("X Axis Scale")); label.set_title (_("X Axis Label")); } else if (!x && !both_y) { scale.set_title (_("Y Axis Scale")); label.set_title (_("X Axis Label")); } } } var style_name = (Label) builder.get_object ("style_name"); StyleManager style_manager = this.application.figure_style_manager; style_manager.bind_property ( "selected_stylename", style_name, "label", 2 ); var factory = new SignalListItemFactory (); factory.setup.connect (on_factory_setup); factory.bind.connect (on_factory_bind); this.grid_view.set_factory (factory); this.grid_view.set_model (style_manager.selection_model); var style_row = (Adw.ActionRow) builder.get_object ("style_row"); style_row.activated.connect (() => { this.navigation_view.push (this.style_overview); }); var button = (Button) builder.get_object ("set_as_default"); button.clicked.connect (() => { this.set_as_default.emit (); }); present (this.application.window); if (highlighted != null) { var widget = (Widget) builder.get_object (highlighted); widget.grab_focus (); } } protected void add_toast_string (string msg) { this.toast_overlay.add_toast (new Adw.Toast (msg)); } [GtkCallback] private void on_pop (Adw.NavigationPage page) { if (page == this.style_editor) this.save_style_request.emit (); } [GtkCallback] private void add_style () { StyleManager style_manager = this.application.figure_style_manager; var dialog = new AddStyleDialog (style_manager, this); dialog.accept.connect ((d, template, name) => { this.copy_request.emit (template, name); }); } private void on_factory_setup (Object object) { ListItem item = (ListItem) object; item.set_child (new StylePreview ()); } private void on_factory_bind (Object object) { ListItem item = (ListItem) object; StylePreview preview = (StylePreview) item.get_child (); Style style = (Style) item.get_item (); preview.style = style; if (style.mutable && !preview.edit_button.get_visible ()) { preview.edit_button.set_visible (true); preview.edit_button.clicked.connect (() => { this.load_style_request.emit (style); this.navigation_view.push (this.style_editor); }); } } } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/file_import.py000066400000000000000000000054241511351636000246470ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """ Module for importing data from files. Functions: import_from_files """ from pathlib import Path from gi.repository import Gio, Graphs from graphs import parse_file from graphs.misc import ParseError _IMPORT_MODES = { # name: suffix "project": ".graphs", "xrdml": ".xrdml", "xry": ".xry", "columns": None, } def import_from_files( application: Graphs.Application, files: list[Gio.File], ) -> None: """ Import from a list of files. Automatically guesses, which mode to use. If configurable settings are present at /se/sjoerd/Graphs/import-params, a Window will be shown, giving the option to configure them. """ import_dict = {mode: [] for mode in _IMPORT_MODES.keys()} for file in files: import_dict[_guess_import_mode(file)].append(file) modes = [] for mode, files in import_dict.items(): if files: modes.append(mode) configurable_modes = [] settings = application.get_settings_child("import-params") for mode in settings.list_children(): if mode in modes: configurable_modes.append(mode) if configurable_modes: def on_accept(_dialog): _import_from_files( application, settings, configurable_modes, import_dict, ) dialog = Graphs.ImportDialog.new(application, configurable_modes) dialog.connect("accept", on_accept) else: _import_from_files( application, settings, configurable_modes, import_dict, ) def _import_from_files( application: Graphs.Application, settings: Gio.Settings, configurable_modes: list[str], import_dict: dict, ): items = [] style = application.get_figure_style_manager().get_selected_style_params() for mode, files in import_dict.items(): callback = getattr(parse_file, "import_from_" + mode) params = settings.get_child(mode) if mode in configurable_modes \ else None for file in files: try: items.extend(callback(params, style, file)) except ParseError as error: application.get_window().add_toast_string(error.message) continue application.get_data().add_items( items, application.get_figure_style_manager(), ) def _guess_import_mode(file: Gio.File) -> str: try: filename = Graphs.tools_get_filename(file) file_suffix = Path(filename).suffixes[-1] except IndexError: file_suffix = None for mode, suffix in _IMPORT_MODES.items(): if suffix is not None and file_suffix == suffix: return mode return "columns" Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/file_import.vala000066400000000000000000000074751511351636000251520ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later using Adw; using Gtk; namespace Graphs { /** * Import dialog */ [GtkTemplate (ui = "/se/sjoerd/Graphs/ui/import.ui")] public class ImportDialog : Adw.Dialog { [GtkChild] private unowned Box mode_box { get; } private GLib.Settings settings; private string[] modes; public signal void accept (); public ImportDialog (Application application, string[] modes) { this.settings = application.get_settings_child ("import-params"); this.modes = modes; var builder = new Builder (); foreach (string mode in modes) { try { builder.add_from_resource (@"/se/sjoerd/Graphs/ui/import-$mode.ui"); } catch { assert_not_reached (); } var group = (PreferencesGroup) builder.get_object (mode + "_group"); GLib.Settings mode_settings = this.settings.get_child (mode); foreach (string key in mode_settings.settings_schema.list_keys ()) { Object object = builder.get_object (mode + "_" + key.replace ("-", "_")); bind_setting (mode_settings, object, key); } // mode specific setups: switch (mode) { case "columns": { var delimiter = (Adw.ComboRow) builder.get_object ("columns_delimiter"); var custom_delimiter = (Adw.EntryRow) builder.get_object ("columns_custom_delimiter"); if (delimiter.get_selected () == 6) { custom_delimiter.set_visible (true); } delimiter.notify["selected"].connect (() => { custom_delimiter.set_visible (delimiter.get_selected () == 6); }); break; } default: { break; } } this.mode_box.append (group); } present (application.window); } [GtkCallback] private void on_accept () { this.accept.emit (); close (); } [GtkCallback] private void on_reset () { var dialog = (Adw.AlertDialog) Tools.build_dialog ("reset_to_defaults"); dialog.set_body (_("Are you sure you want to reset the import settings?")); dialog.response.connect ((d, response) => { if (response == "reset") { foreach (string mode in this.modes) { Tools.reset_settings (this.settings.get_child (mode)); } } }); dialog.present (this); } private static void bind_setting (GLib.Settings settings, Object object, string key) { if (object is Adw.EntryRow) { settings.bind (key, object, "text", 0); } else if (object is Adw.ComboRow) { var comborow = (Adw.ComboRow) object; comborow.set_selected (settings.get_enum (key)); comborow.notify["selected"].connect (() => { if (settings.get_enum (key) != comborow.get_selected ()) { settings.set_enum ( key, (int) comborow.get_selected () ); } }); settings.changed[key].connect (() => { comborow.set_selected (settings.get_enum (key)); }); } else if (object is Adw.SpinRow) { settings.bind (key, object, "value", 0); } } } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/file_io.py000066400000000000000000000113271511351636000237430ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Module for file operations.""" import io import json from xml.dom import minidom from gi.repository import GLib, Gio from graphs import item class FileLikeWrapper(io.BufferedIOBase): """FileLike Wrapper for Gio.Files.""" def __init__(self, read_stream=None, write_stream=None): self._read_stream, self._write_stream = read_stream, write_stream @classmethod def new_for_io_stream(cls, io_stream: Gio.IOStream): """Create a wrapper for an IOStream.""" return cls( read_stream=io_stream.get_input_stream(), write_stream=io_stream.get_output_stream(), ) @property def closed(self) -> bool: """Whether or not the stream is closed.""" return self._read_stream is None and self._write_stream is None def close(self) -> None: """Close the stream.""" if self._read_stream is not None: self._read_stream.close() self._read_stream = None if self._write_stream is not None: self._write_stream.close() self._write_stream = None def writable(self) -> bool: """Whether or not the stream can be written to.""" return self._write_stream is not None def write(self, b) -> int: """Write to the stream.""" if self._write_stream is None: raise OSError() elif b is None or b == b"": return 0 return self._write_stream.write_bytes(GLib.Bytes(b)) def readable(self) -> bool: """Whether or not the stream can be read from.""" return self._read_stream is not None def read(self, size=-1): """Read from the stream.""" if self._read_stream is None: raise OSError() elif size == 0: return b"" elif size > 0: return self._read_stream.read_bytes(size, None).get_data() buffer = io.BytesIO() while True: chunk = self._read_stream.read_bytes(4096, None) if chunk.get_size() == 0: break buffer.write(chunk.get_data()) return buffer.getvalue() read1 = read def create_write_stream(file: Gio.File) -> Gio.OutputStream: """Create a write stream for a given file.""" if file.query_exists(None): file.delete(None) return file.create(0, None) def open_wrapped(file: Gio.File, mode: str = "rt", encoding: str = "utf-8"): """Open a file in a FileLike wrapper.""" read = "r" in mode append = "a" in mode replace = "w" in mode def _io_stream(): return FileLikeWrapper.new_for_io_stream(file.open_readwrite(None)) if "x" in mode: if file.query_exists(): return OSError() stream = create_write_stream(file) stream.close() if read and append: obj = _io_stream() elif read and replace: stream = create_write_stream(file) stream.close() obj = _io_stream() elif read: obj = FileLikeWrapper(read_stream=file.read(None)) elif replace: obj = FileLikeWrapper(write_stream=create_write_stream(file)) elif append: obj = FileLikeWrapper(write_stream=file.append(None)) if "b" not in mode: obj = io.TextIOWrapper(obj, encoding=encoding) return obj def iter_data_stream(stream: Gio.DataInputStream): """ Iterate over a data stream. Note: This can be removed in the next release of pygobject. """ line = stream.read_line_utf8(None)[0] while line is not None: yield line line = stream.read_line_utf8(None)[0] def save_item(file: Gio.File, item_: item.DataItem) -> None: """Save an Item to a txt file.""" delimiter = "\t" fmt = delimiter.join(["%.12e"] * 2) xlabel, ylabel = item_.get_xlabel(), item_.get_ylabel() stream = Gio.DataOutputStream.new(create_write_stream(file)) if xlabel != "" and ylabel != "": stream.stream(xlabel + delimiter + ylabel + "\n") for values in zip(item_.xdata, item_.ydata): stream.put_string(fmt % values + "\n") stream.close() def parse_json(file: Gio.File) -> dict: """Parse a json file to a python dict.""" with open_wrapped(file, "rb") as wrapper: return json.load(wrapper) def write_json(file: Gio.File, json_object: dict, pretty_print=True) -> None: """Write a python dict to a python file.""" with open_wrapped(file, "wt") as wrapper: json.dump( json_object, wrapper, indent=4 if pretty_print else None, sort_keys=True, ) def parse_xml(file: Gio.File) -> dict: """Parse a xml file to a python dict.""" with open_wrapped(file, "rb") as wrapper: return minidom.parse(wrapper) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/graphs.in000077500000000000000000000044051511351636000236010ustar00rootroot00000000000000#!@PYTHON@ # Graphs # Plot and manipulate data # # @HOMEPAGE_URL@ # @VCS_URL@ # # Copyright @COPYRIGHT@ @AUTHOR@ # # 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 3 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, see . # # SPDX-License-Identifier: GPL-3.0-or-later """Main graphs module.""" import gettext import locale import logging import os import signal import sys if __name__ == "__main__": import gi gi.require_version("Adw", "1") gi.require_version("Gtk", "4.0") gi.require_version("Graphs", "1") graph_path_dir = "@PKGDATADIR@" if os.environ.get("GRAPHS_DEVEL_PATH"): graph_path_dir = os.environ.get("GRAPHS_DEVEL_PATH") sys.path.insert(1, graph_path_dir) signal.signal(signal.SIGINT, signal.SIG_DFL) localedir = "@LOCALEDIR@" if os.environ.get('GRAPHS_OVERRIDE_LOCALEDIR'): localedir = os.environ.get('GRAPHS_OVERRIDE_LOCALEDIR') locale.bindtextdomain("@GETTEXT_PACKAGE@", localedir) locale.textdomain("@GETTEXT_PACKAGE@") gettext.bindtextdomain("@GETTEXT_PACKAGE@", localedir) gettext.textdomain("@GETTEXT_PACKAGE@") from gi.repository import Gio gresource_location = os.path.join("@PKGDATADIR@", "@APPLICATION_ID@.gresource") if os.environ.get("GRAPHS_OVERRIDE_RESOURCES"): gresource_location = os.environ.get('GRAPHS_OVERRIDE_RESOURCES') resource = Gio.Resource.load(gresource_location) resource._register() debug = @DEBUG@ loglevel = logging.DEBUG if debug else logging.INFO logging.basicConfig(format="%(levelname)s: %(message)s", level=loglevel) logging.getLogger("matplotlib.font_manager").disabled = True from graphs.application import PythonApplication sys.exit(PythonApplication("@APPLICATION_ID@", debug=debug).run(sys.argv)) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/inline-stack-switcher.vala000066400000000000000000000170651511351636000270440ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later namespace Graphs { public class InlineStackSwitcher : Gtk.Widget { private const int TIMEOUT_EXPAND = 500; private Gtk.Stack _stack; public Gtk.Stack stack { get { return _stack; } set { if (stack == value) return; if (stack != null) disconnect_stack (); _stack = value; if (stack != null) connect_stack (); } } private Gtk.SelectionModel pages; private HashTable buttons; static construct { set_css_name ("inline-stack-switcher"); set_accessible_role (TAB_LIST); set_layout_manager_type (typeof (Gtk.BoxLayout)); } construct { buttons = new HashTable (direct_hash, direct_equal); } protected override void dispose () { if (stack != null) disconnect_stack (); base.dispose (); } private void connect_stack () { pages = stack.pages; pages.items_changed.connect (items_changed_cb); pages.selection_changed.connect (selection_changed_cb); populate_switcher (); } private void disconnect_stack () { clear_switcher (); pages.items_changed.disconnect (items_changed_cb); pages.selection_changed.disconnect (selection_changed_cb); pages = null; } private void populate_switcher () { for (int i = 0; i < pages.get_n_items (); i++) add_child (i); } private void clear_switcher () { buttons.for_each ((page, button) => { var separator = button.get_data ("next-separator"); button.unparent (); separator.unparent (); page.notify.disconnect (page_updated_cb); }); buttons.remove_all (); } private void items_changed_cb () { clear_switcher (); populate_switcher (); } private void selection_changed_cb (uint position, uint n_items) { for (uint i = position; i < position + n_items; i++) { var page = pages.get_item (i) as Gtk.StackPage; var button = buttons[page]; if (button != null) { bool selected = pages.is_selected (i); button.active = selected; button.update_state (Gtk.AccessibleState.SELECTED, selected, -1); } } } private void add_child (int index) { var button = new Gtk.ToggleButton () { accessible_role = TAB, hexpand = true, vexpand = true, focus_on_click = false, }; button.can_shrink = true; button.add_css_class ("flat"); var controller = new Gtk.DropControllerMotion (); controller.enter.connect (() => { if (button.active) return; uint switch_timer = Timeout.add (TIMEOUT_EXPAND, () => { button.steal_data ("switch-timer"); button.active = true; return Source.REMOVE; }); button.set_data_full ("switch-timer", (void *) switch_timer, data => { if (data != null) Source.remove ((uint) data); }); }); controller.leave.connect (() => { uint switch_timer = button.steal_data ("switch-timer"); if (switch_timer > 0) Source.remove (switch_timer); }); button.add_controller (controller); var page = pages.get_item (index) as Gtk.StackPage; update_button (page, button); button.set_parent (this); bool selected = pages.is_selected (index); button.active = selected; button.update_state (Gtk.AccessibleState.SELECTED, selected, -1); button.update_relation (Gtk.AccessibleRelation.CONTROLS, page, null, -1); button.notify["active"].connect (() => { if (button.active) pages.select_item (index, true); else button.active = pages.is_selected (index); }); page.notify.connect (page_updated_cb); buttons[page] = button; var separator = new Gtk.Separator (VERTICAL); separator.set_parent (this); button.set_data ("separator", separator); button.state_flags_changed.connect (() => { var prev_separator = button.get_prev_sibling (); var next_separator = button.get_next_sibling (); if (prev_separator != null) update_separator (prev_separator); if (next_separator != null) update_separator (next_separator); }); var prev_separator = button.get_prev_sibling (); var next_separator = button.get_next_sibling (); if (prev_separator != null) update_separator (prev_separator); if (next_separator != null) update_separator (next_separator); } private void page_updated_cb (Object object, ParamSpec pspec) { var page = object as Gtk.StackPage; var button = buttons[page]; update_button (page, button); } private void update_button (Gtk.StackPage page, Gtk.Button button) { if (page.icon_name != null) { button.icon_name = page.icon_name; button.tooltip_text = page.title; } else if (page.title != null) { button.label = page.title; button.use_underline = page.use_underline; button.tooltip_text = null; } button.update_property (Gtk.AccessibleProperty.LABEL, page.title, -1); button.visible = page.visible && (page.title != null || page.icon_name != null); if (page.needs_attention) button.add_css_class ("needs-attention"); else button.remove_css_class ("needs-attention"); } private bool should_hide_separators (Gtk.Widget? widget) { if (widget == null) return true; var flags = widget.get_state_flags (); if ((flags & (Gtk.StateFlags.PRELIGHT | Gtk.StateFlags.ACTIVE | Gtk.StateFlags.CHECKED)) != 0) return true; if ((flags & Gtk.StateFlags.FOCUSED) != 0 && (flags & Gtk.StateFlags.FOCUS_VISIBLE) != 0) return true; return false; } private void update_separator (Gtk.Widget separator) { var prev_button = separator.get_prev_sibling (); var next_button = separator.get_next_sibling (); separator.visible = prev_button != null && next_button != null; if (should_hide_separators (prev_button) || should_hide_separators (next_button)) separator.add_css_class ("hidden"); else separator.remove_css_class ("hidden"); } } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/item.py000066400000000000000000000074471511351636000233030ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Module for data Items.""" from gi.repository import GObject, Graphs from graphs import misc def new_from_dict(dictionary: dict): """Instanciate item from dict.""" match dictionary["type"]: case "GraphsDataItem": cls = DataItem case "GrapsTextItem": cls = TextItem case "GrapsFillItem": cls = FillItem case _: pass dictionary.pop("type") return cls(**dictionary) class _ItemMixin(): def reset(self, old_style, new_style): """Reset all properties.""" for prop, (key, function) in self._style_properties.items(): old_value = old_style[key] new_value = new_style[key] if function is not None: old_value = function(old_value) new_value = function(new_value) if self.get_property(prop) == old_value: self.set_property(prop, new_value) def _extract_params(self, style): return { prop: style[key] if function is None else function(style[key]) for prop, (key, function) in self._style_properties.items() } def to_dict(self): """Convert item to dict.""" dictionary = {key: self.get_property(key) for key in dir(self.props)} dictionary["type"] = self.__gtype_name__ return dictionary class DataItem(Graphs.Item, _ItemMixin): """DataItem.""" __gtype_name__ = "GraphsDataItem" xdata = GObject.Property(type=object) ydata = GObject.Property(type=object) linestyle = GObject.Property(type=int, default=1) linewidth = GObject.Property(type=float, default=3) markerstyle = GObject.Property(type=int, default=0) markersize = GObject.Property(type=float, default=7) _style_properties = { "linestyle": ("lines.linestyle", misc.LINESTYLES.index), "linewidth": ("lines.linewidth", None), "markerstyle": ("lines.marker", misc.MARKERSTYLES.index), "markersize": ("lines.markersize", None), } @classmethod def new(cls, style, xdata=None, ydata=None, **kwargs): """Create new DataItem.""" return cls( xdata=xdata, ydata=ydata, **cls._extract_params(cls, style), **kwargs, ) def __init__(self, **kwargs): super().__init__(**kwargs) for prop in ("xdata", "ydata"): if self.get_property(prop) is None: self.set_property(prop, []) class TextItem(Graphs.Item, _ItemMixin): """TextItem.""" __gtype_name__ = "GraphsTextItem" xanchor = GObject.Property(type=float, default=0) yanchor = GObject.Property(type=float, default=0) text = GObject.Property(type=str, default="") size = GObject.Property(type=float, default=12) rotation = GObject.Property(type=int, default=0, minimum=0, maximum=360) _style_properties = { "size": ("font.size", None), "color": ("text.color", None), } @classmethod def new(cls, style, xanchor=0, yanchor=0, text="", **kwargs): """Create new textItem.""" return cls( xanchor=xanchor, yanchor=yanchor, text=text, **cls._extract_params(cls, style), **kwargs, ) class FillItem(Graphs.Item, _ItemMixin): """FillItem.""" __gtype_name__ = "GraphsFillItem" data = GObject.Property(type=object) @classmethod def new(cls, _params, data, **kwargs): """Create new FillItem.""" return cls(data=data, **kwargs) def __init__(self, **kwargs): super().__init__(**kwargs) if self.props.data is None: self.props.data = (None, None, None) def reset(self): """Not yet implemented.""" raise NotImplementedError Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/item.vala000066400000000000000000000017461511351636000235720ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later using Gdk; namespace Graphs { /** * Base item class */ public class Item : Object { public string name { get; set; default = ""; } public string color { get; set; default = ""; } public float alpha { get; set; default = 1; } public bool selected { get; set; default = true; } public string xlabel { get; set; default = ""; } public string ylabel { get; set; default = ""; } public int xposition { get; set; default = 0; } public int yposition { get; set; default = 0; } public string uuid { get; set; default = Uuid.string_random (); } public Gdk.RGBA get_rgba () { Gdk.RGBA rgba = Tools.hex_to_rgba (this.color); rgba.alpha = this.alpha; return rgba; } public void set_rgba (Gdk.RGBA rgba) { this.color = Tools.rgba_to_hex (rgba); this.alpha = rgba.alpha; } } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/item_box.py000066400000000000000000000051001511351636000241330ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """UI representation of an Item.""" from gettext import gettext as _ from gi.repository import Adw, Gio, Graphs from graphs.curve_fitting import CurveFittingDialog from graphs.edit_item import EditItemDialog class ItemBox(Graphs.ItemBox): """UI representation of an Item.""" __gtype_name__ = "GraphsPythonItemBox" def __init__(self, application, item, index): super().__init__( application=application, item=item, index=index, ) self.setup() self.props.drop_target.connect("drop", self.on_dnd_drop) action_list = ["edit", "delete", "curve_fitting"] if self.props.index > 0: action_list += ["move_up"] if self.props.index + 1 < len(self.props.application.get_data()): action_list += ["move_down"] action_group = Gio.SimpleActionGroup() # Set the action group for the widget for name in action_list: action = Gio.SimpleAction.new(name, None) action.connect("activate", getattr(self, f"{name}")) action_group.add_action(action) self.insert_action_group("item_box", action_group) def _change_position(self, source_index: int) -> None: data = self.props.application.get_data() data.change_position(self.props.index, source_index) data.add_history_state() data.add_view_history_state() def on_dnd_drop(self, _drop_target, value: int, _x, _y) -> None: """Handle dropped data.""" self._change_position(value) def curve_fitting(self, _action, _shortcut) -> None: """Open Curve Fitting dialog.""" CurveFittingDialog(self.props.application, self.props.item) def move_up(self, _action, _shortcut) -> None: """Move item up in hirarchy.""" self._change_position(self.props.index - 1) def move_down(self, _action, _shortcut) -> None: """Move item down in hirarchy.""" self._change_position(self.props.index + 1) def delete(self, _action, _shortcut) -> None: """Delete Item.""" name = self.props.item.get_name() self.props.application.get_data().delete_items([self.props.item]) toast = Adw.Toast.new(_("Deleted {name}").format(name=name)) toast.set_button_label(_("Undo")) toast.set_action_name("app.undo") self.props.application.get_window().add_toast(toast) def edit(self, _action, _shortcut) -> None: """Show Edit Item Dialog.""" EditItemDialog(self.props.application, self.props.item) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/item_box.vala000066400000000000000000000061661511351636000244430ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later using Adw; using Gtk; using Gdk; namespace Graphs { /** * Item Box widget */ [GtkTemplate (ui = "/se/sjoerd/Graphs/ui/item-box.ui")] public class ItemBox : Box { [GtkChild] private unowned Label label { get; } [GtkChild] public unowned CheckButton check_button { get; } [GtkChild] private unowned Button color_button { get; } public Application application { get; construct set; } public Item item { get; construct set; } public int index { get; construct set; } public GestureClick click_gesture { get; private set; } public DragSource drag_source { get; private set; } public DropTarget drop_target { get; private set; } private CssProvider provider; public void setup () { this.provider = new CssProvider (); this.color_button.get_style_context ().add_provider ( this.provider, STYLE_PROVIDER_PRIORITY_APPLICATION ); this.item.bind_property ("name", this.label, "label", 2); this.item.bind_property ("selected", this.check_button, "active", 2); this.item.notify["color"].connect (on_color_change); on_color_change (); this.click_gesture = new GestureClick (); this.click_gesture.released.connect (() => { this.check_button.set_active (!this.check_button.get_active ()); }); this.drag_source = new DragSource (); this.drag_source.set_actions (DragAction.COPY); this.drag_source.prepare.connect ((s, x, y) => { Widget widget = this.get_parent (); widget.add_css_class ("card"); var paintable = new WidgetPaintable (widget); this.drag_source.set_icon (paintable, (int) x, (int) y); return new ContentProvider.for_value (this.index); }); this.drop_target = new DropTarget (typeof (int), DragAction.COPY); } private void on_color_change () { string c = this.item.color; string o = this.item.alpha.to_string (); this.provider.load_from_string (@"button { color: $c; opacity: $o; }"); } [GtkCallback] private void on_toggle () { bool new_value = this.check_button.get_active (); if (this.item.selected != new_value) { this.item.selected = new_value; this.application.data.add_history_state_request.emit (); } } [GtkCallback] private void choose_color () { var dialog = new ColorDialog (); dialog.choose_rgba.begin ( this.application.window, this.item.get_rgba (), null, (d, result) => { try { this.item.set_rgba (dialog.choose_rgba.end (result)); this.application.data.add_history_state_request.emit (); } catch {} } ); } } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/meson.build000066400000000000000000000037121511351636000241240ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later configure_file( input: 'graphs.in', output: meson.project_name(), configuration: conf, install: true, install_mode: 'rwxr-xr-x', install_dir: bindir ) python.install_sources( files( 'actions.py', 'application.py', 'artist.py', 'canvas.py', 'curve_fitting.py', 'data.py', 'edit_item.py', 'figure_settings.py', 'file_import.py', 'file_io.py', 'item.py', 'item_box.py', 'migrate.py', 'misc.py', 'operations.py', 'parse_file.py', 'project.py', 'scales.py', 'style_io.py', 'styles.py', 'ui.py', 'utilities.py', ), subdir: meson.project_name() ) configure_file (output: 'config.h', configuration: vala_conf) config_dep = valac.find_library ('config', dirs: meson.project_source_root () / 'graphs') graphs_lib = shared_library( 'graphs', files( 'about.vala', 'actions.vala', 'add_equation.vala', 'application.vala', 'canvas.vala', 'curve_fitting.vala', 'data.vala', 'export_figure.vala', 'figure_settings.vala', 'file_import.vala', 'item.vala', 'inline-stack-switcher.vala', 'item_box.vala', 'misc.vala', 'smoothen_settings.vala', 'styles.vala', 'transform.vala', 'utilities.vala', 'window.vala', ), gresource_hack, gresource_bundle, vala_gir: 'Graphs-1.gir', dependencies: dependencies + config_dep, include_directories: include_directories('.'), link_args: ['-lm',], install: true, install_dir: [true], ) custom_target('graphs typelib', command: [find_program('g-ir-compiler'), '--shared-library', 'libgraphs.so', '--output', '@OUTPUT@', meson.current_build_dir() / 'Graphs-1.gir'], output: 'Graphs-1.typelib', depends: graphs_lib, install: true, install_dir: get_option('libdir') / 'girepository-1.0') devenv.set('GI_TYPELIB_PATH', meson.current_build_dir()) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/migrate.py000066400000000000000000000271321511351636000237660ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Module for migrating old data to new structures.""" import contextlib import pickle import sys from pathlib import Path from gi.repository import GLib, Gio, Graphs from graphs import file_io, misc, style_io _CONFIG_MIGRATION_TABLE = { # old-key: (category, key, old-default) "action_center_data": ("actions", "center", "Center at middle coordinate"), "addequation_equation": ("add-equation", "equation", "X"), "addequation_step_size": ("add-equation", "step-size", "0.01"), "addequation_x_start": ("add-equation", "x-start", "0"), "addequation_x_stop": ("add-equation", "x-stop", "10"), "export_figure_dpi": ("export-figure", "dpi", 100), "export_figure_transparent": ("export-figure", "transparent", True), "hide_unselected": ("figure", "hide-unselected", False), "plot_custom_style": ("figure", "custom-style", "adwaita"), "plot_legend": ("figure", "legend", True), "plot_right_label": ("figure", "right-label", "Y Value 2"), "plot_right_scale": ("figure", "right-scale", "linear"), "plot_title": ("figure", "title", ""), "plot_top_label": ("figure", "top-label", "X Value 2"), "plot_top_scale": ("figure", "top-scale", "linear"), "plot_use_custom_style": ("figure", "use-custom-style", False), "plot_x_label": ("figure", "bottom-label", "X Value"), "plot_x_scale": ("figure", "bottom-scale", "linear"), "plot_y_label": ("figure", "left-label", "Y Value"), "plot_y_scale": ("figure", "left-scale", "linear"), } _CENTER_ACTION_MIGRATION_TABLE = { "Center at middle coordinate": "middle-x", "Center at middle X value": "max-y", } def migrate_config(settings: Gio.Settings) -> None: """Migrate old file-based user config to dconf.""" main_dir = Gio.File.new_for_path(GLib.get_user_config_dir()) old_config_dir = main_dir.get_child_for_display_name("Graphs") if not old_config_dir.query_exists(None): return new_config_dir = Graphs.tools_get_config_directory() config_file = old_config_dir.get_child_for_display_name("config.json") import_file = old_config_dir.get_child_for_display_name("import.json") old_styles_dir = old_config_dir.get_child_for_display_name("styles") if config_file.query_exists(None): _migrate_config(settings, config_file) if import_file.query_exists(None): _migrate_import_params(settings, import_file) if old_styles_dir.query_exists(None): _migrate_styles(old_styles_dir, new_config_dir) old_config_dir.delete(None) def _migrate_config(settings_, config_file): config = file_io.parse_json(config_file) for old_key, (category, key, old_default) \ in _CONFIG_MIGRATION_TABLE.items(): with contextlib.suppress(KeyError, ValueError): value = config[old_key] if old_key == "action_center_data": value = _CENTER_ACTION_MIGRATION_TABLE[value] elif "scale" in key: value = value.capitalize() if old_default != value: settings_.get_child(category)[key] = value config_file.delete(None) def _migrate_import_params(settings_, import_file): params = file_io.parse_json(import_file)["columns"] settings = settings_.get_child("import-params").get_child("columns") settings.set_string("separator", params["separator"] + " ") old_delimiter = params["delimiter"] if old_delimiter in misc.DELIMITERS.values(): inverted_dict = {value: key for key, value in misc.DELIMITERS.items()} settings.set_string("delimiter", inverted_dict[old_delimiter]) else: settings.set_string("delimiter", "custom") settings.set_string("custom-delimiter", old_delimiter) for key in ("column_x", "column_y", "skip_rows"): settings.set_int(key.replace("_", "-"), params[key]) import_file.delete(None) SYSTEM_STYLES = [ "adwaita", "adwaita-dark", "bmh", "classic", "dark-background", "fivethirtyeight", "ggplot", "grayscale", "seaborn", "seaborn-bright", "seaborn-colorblind", "seaborn-dark", "seaborn-darkgrid", "seaborn-dark-pallete", "seaborn-deep", "seaborn-muted", "seaborn-notebook", "seaborn-paper", "seaborn-pastel", "seaborn-poster", "seaborn-talk", "seaborn-ticks", "seaborn-white", "seaborn-whitegrid", "solarized-light", "tableu-colorblind10", "thesis", "yaru", "yaru-dark", ] def _migrate_styles(old_styles_dir, new_config_dir): new_styles_dir = new_config_dir.get_child_for_display_name("styles") if not new_styles_dir.query_exists(None): new_styles_dir.make_directory_with_parents() enumerator = old_styles_dir.enumerate_children("default::*", 0, None) adwaita = style_io.parse( Gio.File.new_for_uri( "resource:///se/sjoerd/Graphs/styles/adwaita.mplstyle", ), )[0] for file in map(enumerator.get_child, enumerator): stylename = Path(Graphs.tools_get_filename(file)).stem if stylename not in SYSTEM_STYLES: params = style_io.parse(file)[0] for key, value in adwaita.items(): if key not in params: params[key] = value style_io.write( new_styles_dir.get_child_for_display_name( f"{stylename.lower().replace(' ', '-')}.mplstyle", ), stylename, params, ) file.delete(None) enumerator.close(None) old_styles_dir.delete(None) ITEM_MIGRATION_TABLE = { "plot_x_position": "xposition", "plot_y_position": "yposition", "x_anchor": "xanchor", "y_anchor": "yanchor", "key": "uuid", } ITEM_VALUE_MIGRATION_TABLE = { "linestyle": ["none", "solid", "dotted", "dashed", "dashdot"], "markerstyle": [ "none", ".", ",", "o", "v", "^", "<", ">", "8", "s", "p", "*", "h", "H", "+", "x", "D", "d", "|", "_", "P", "X", ], "xposition": ["bottom", "top"], "yposition": ["left", "right"], } PLOT_SETTINGS_MIGRATION_TABLE = { "xlabel": "bottom_label", "ylabel": "left_label", "xscale": "bottom_scale", "yscale": "left_scale", "use_custom_plot_style": "use_custom_style", "custom_plot_style": "custom_style", "mix_right": "min_right", } LEGEND_POSITIONS = [ "best", "upper right", "upper left", "lower left", "lower right", "center left", "center right", "lower center", "upper center", "center", ] class PlotSettings: """Old PlotSettings standin.""" def migrate(self) -> dict: """Migrate class to dict.""" dictionary = {} for key, value in self.__dict__.items(): with contextlib.suppress(KeyError): key = PLOT_SETTINGS_MIGRATION_TABLE[key] if "scale" in key: value = 0 if value == "linear" else 1 elif key == "legend_position": value = LEGEND_POSITIONS.index(value) dictionary[key] = value return dictionary class ItemBase: """Old ItemBase standin.""" def migrate(self) -> dict: """Migrate class to dict.""" dictionary = {"type": self.item_type} for key, value in self.__dict__.items(): with contextlib.suppress(KeyError): key = ITEM_MIGRATION_TABLE[key] if key in ITEM_VALUE_MIGRATION_TABLE: try: value = ITEM_VALUE_MIGRATION_TABLE[key].index(value) except IndexError: value = 0 dictionary[key] = value return dictionary class Item(ItemBase): """Old Item standin.""" item_type = "GraphsDataItem" class TextItem(ItemBase): """Old TextItem standin.""" item_type = "GraphsTextItem" _DEFAULT_VIEW = [0, 1, 0, 10, 0, 1, 0, 10] def migrate_project(file: Gio.File) -> dict: """Migrate pickle-based project.""" sys.modules["graphs.misc"] = sys.modules[__name__] sys.modules["graphs.item"] = sys.modules[__name__] with file_io.open_wrapped(file, "rb") as wrapper: project = pickle.load(wrapper) figure_settings = project["plot_settings"].migrate() current_limits = [figure_settings[key] for key in misc.LIMITS] history_pos = project["clipboard_pos"] history_states = _migrate_clipboard( project["datadict_clipboard"], history_pos, current_limits, ) return { "version": project["version"], "data": [item.migrate() for item in project["data"].values()], "figure-settings": figure_settings, "history-states": history_states, "history-position": history_pos, "view-history-states": [_DEFAULT_VIEW.copy(), current_limits], "view-history-position": -1, } def _migrate_clipboard(clipboard, clipboard_pos, current_limits): if not clipboard: return [] new_clipboard = [] if len(clipboard) > 100: clipboard = clipboard[len(clipboard) - 100:] states = [{ item.key: item.migrate() for item in state.values() } for state in clipboard] new_clipboard.append(([], _DEFAULT_VIEW.copy())) initial_items = states[1].values() new_clipboard.append(( [(1, item) for item in initial_items], _get_limits(initial_items), )) if len(states) > 2: for count in range(len(states) - 2): batch = [] previous_state = states[count + 1] current_state = states[count + 2] if len(current_state) < len(previous_state): for key, item in previous_state.copy().items(): if key not in current_state: batch.append((2, previous_state.index(item), item)) previous_state.pop(item) for count_2, (key, item) in enumerate(current_state.items()): if key in previous_state: previous_index = list(previous_state.keys()).index(key) if previous_index != count_2: batch.append((3, (previous_index, count_2))) else: for key_2, value in item.items(): previous_value = previous_state[key][key_2] if value != previous_value: batch.append( (0, (key, key_2, previous_value, value)), ) else: batch.append((1, item)) if clipboard_pos == count - len(states) + 1: limits = _get_limits(current_state.values()) else: limits = current_limits new_clipboard.append((batch, limits)) return new_clipboard def _get_limits(items): limits = [None] * 8 for item in items: if item["type"] != "Item": continue for count, x_or_y in enumerate(["x", "y"]): index = item[f"{x_or_y}position"] * 2 + 4 * count data = item[f"{x_or_y}data"] try: limits[index] = min(limits[index], min(data)) except TypeError: limits[index] = min(data) try: limits[index + 1] = max(limits[index + 1], max(data)) except TypeError: limits[index + 1] = max(data) for count in range(8): default_view_copy = _DEFAULT_VIEW.copy() if limits[count] is None: limits[count] = default_view_copy[count] return limits Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/misc.py000066400000000000000000000031721511351636000232670ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Miscallaneous constants.""" from gettext import pgettext as C_ from gi.repository import Graphs # Type hints ItemList = list[Graphs.Item] Limits = tuple[float, float, float, float] class ParseError(Exception): """Custom Error for parsing files.""" def __init__(self, message): self.message = message super().__init__(self.message) LEGEND_POSITIONS = [ "best", "upper right", "upper left", "lower left", "lower right", "center left", "center right", "lower center", "upper center", "center", ] DELIMITERS = { "whitespace": "\\s+", "tabs": "\\t+", "colon": ":", "semicolon": ";", "comma": ",", "period": ".", "custom": "custom", } LINESTYLES = ["none", "solid", "dotted", "dashed", "dashdot"] MARKERSTYLES = [ "none", ".", ",", "o", "v", "^", "<", ">", "8", "s", "p", "*", "h", "H", "+", "x", "D", "d", "|", "_", "P", "X", ] LIMITS = [ "min_bottom", "max_bottom", "min_top", "max_top", "min_left", "max_left", "min_right", "max_right", ] DIRECTIONS = ["bottom", "top", "left", "right"] FUNCTIONS = { "sin", "cos", "tan", "sinh", "cosh", "tanh", "arcsin", "arccos", "arctan", "arcsinh", "arccosh", "arctanh", "cot", "sec", "csc", "arccot", "arcsec", "arccsc", "sqrt", "exp", "abs", "log", "log10", } GRAPHS_PROJECT_FILE_FILTER_TEMPLATE = \ (C_("file-filter", "Graphs Project File"), ["graphs"]) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/misc.vala000066400000000000000000000015671511351636000235700ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later namespace Graphs { public const string[] LIMIT_NAMES = { "min-bottom", "max-bottom", "min-top", "max-top", "min-left", "max-left", "min-right", "max-right", }; public const string[] DIRECTION_NAMES = { "bottom", "top", "left", "right" }; public const string[] ACTION_NAMES = { "quit", "help", "about", "figure_settings", "add_data", "add_equation", "select_all", "select_none", "undo", "redo", "optimize_limits", "view_back", "view_forward", "export_data", "export_figure", "new_project", "save_project", "save_project_as", "smoothen_settings", "open_project", "delete_selected", "zoom_in", "zoom_out" }; } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/operations.py000066400000000000000000000400021511351636000245100ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Module for data transformations.""" import logging import sys from gettext import gettext as _ from gi.repository import Graphs from graphs import misc, utilities from graphs.item import DataItem import numexpr import numpy import scipy def get_data(application: Graphs.Application, item: DataItem): """ Retrieve item from datadict with start and stop index. If interaction_mode is set to "SELECT" """ new_xdata = item.props.xdata new_ydata = item.props.ydata canvas = application.get_window().get_canvas() if canvas.get_mode() == 2: figure_settings = application.get_data().get_figure_settings() if item.get_xposition() == 0: xmin = figure_settings.get_min_bottom() xmax = figure_settings.get_max_bottom() scale = figure_settings.get_bottom_scale() else: xmin = figure_settings.get_min_top() xmax = figure_settings.get_max_top() scale = figure_settings.get_top_scale() startx = utilities.get_value_at_fraction( figure_settings.get_min_selected(), xmin, xmax, scale, ) stopx = utilities.get_value_at_fraction( figure_settings.get_max_selected(), xmin, xmax, scale, ) # If startx and stopx are not out of range, that is, # if the item data is within the highlight new_min = min(new_xdata) a = startx < new_min and stopx < new_min if not (a or (startx > max(new_xdata))): new_xdata, new_ydata = filter_data( new_xdata, new_ydata, ">=", startx, ) new_xdata, new_ydata = filter_data( new_xdata, new_ydata, "<=", stopx, ) else: new_xdata = None new_ydata = None return new_xdata, new_ydata def filter_data( xdata: list, ydata: list, condition: str, value: float, ) -> list: """Filter coordinates based on the given condition.""" xdata = numpy.array(xdata) ydata = numpy.array(ydata) conditions = { "<=": numpy.less_equal, ">=": numpy.greater_equal, "==": numpy.equal, } mask = conditions[condition](xdata, value) xdata_filtered = xdata[mask] ydata_filtered = ydata[mask] return list(xdata_filtered), list(ydata_filtered) def create_data_mask(xdata1: list, ydata1: list, xdata2: list, ydata2: list): """ Create a mask for matching pairs of coordinates. Returns: - Boolean mask indicating where pairs of coordinates match. """ xdata1, ydata1, xdata2, ydata2 = \ map(numpy.array, [xdata1, ydata1, xdata2, ydata2]) return numpy.any((xdata1[:, None] == xdata2) & (ydata1[:, None] == ydata2), axis=1) def sort_data(xdata: list, ydata: list) -> (list, list): """Sort data.""" return map( list, zip(*sorted( zip(xdata, ydata), key=lambda x_values: x_values[0], )), ) def perform_operation(application: Graphs.Application, name: str) -> None: """Perform an operation.""" this = sys.modules[__name__] window = application.get_window() if name in ("combine", ): return getattr(this, name)(application) elif name == "custom_transformation": def on_accept(_dialog, input_x, input_y, discard): try: _apply(application, transform, input_x, input_y, discard) except (RuntimeError, KeyError) as exception: toast = _( "{name}: Unable to do transformation, \ make sure the syntax is correct", ).format(name=exception.__class__.__name__) window.add_toast_string(toast) logging.exception(_("Unable to do transformation")) dialog = Graphs.TransformDialog.new(application) dialog.connect("accept", on_accept) return elif name == "cut" and window.get_canvas().get_mode() != 2: return args = [] actions_settings = application.get_settings_child("actions") if name in ("center", "smoothen"): args = [actions_settings.get_enum(name)] if name == "smoothen": params = {} settings = actions_settings.get_child("smoothen") for setting in settings: params[setting] = int(settings.get_int(setting)) args += [params] elif name == "shift": figure_settings = application.get_data().get_figure_settings() right_range = ( figure_settings.get_max_right() - figure_settings.get_min_right() ) left_range = ( figure_settings.get_max_left() - figure_settings.get_min_left() ) args += [ figure_settings.get_left_scale(), figure_settings.get_right_scale(), application.get_data().get_items(), [left_range, right_range], ] elif "translate" in name or "multiply" in name: try: args += [ utilities.string_to_float( window.get_property(name + "_entry").get_text(), ), ] except ValueError as error: window.add_toast_string(str(error)) return _apply(application, getattr(this, name), *args) def _apply(application, callback, *args): data = application.get_data() figure_settings = data.get_figure_settings() data_selected = False old_limits = figure_settings.get_limits() for item in data: if not (item.get_selected() and isinstance(item, DataItem)): continue xdata, ydata = get_data(application, item) if xdata is not None and len(xdata) != 0: data_selected = True new_xdata, new_ydata, sort, discard = callback( item, xdata, ydata, *args, ) new_xdata, new_ydata = list(new_xdata), list(new_ydata) canvas = application.get_window().get_canvas() if discard and canvas.get_mode() == 2: logging.debug("Discard is true") application.get_window().add_toast_string( _( "Data that was outside of the highlighted area has" " been discarded", ), ) item.props.xdata = new_xdata item.props.ydata = new_ydata else: logging.debug("Discard is false") mask = create_data_mask( item.props.xdata, item.props.ydata, xdata, ydata, ) if new_xdata == []: # If cut action was performed remove_list = \ [index for index, masked in enumerate(mask) if masked] for index in sorted(remove_list, reverse=True): item.props.xdata.pop(index) item.props.ydata.pop(index) else: i = 0 for index, masked in enumerate(mask): # Change coordinates that were within span if masked: item.props.xdata[index] = new_xdata[i] item.props.ydata[index] = new_ydata[i] i += 1 if sort: logging.debug("Sorting data") item.xdata, item.ydata = sort_data(item.xdata, item.ydata) item.notify("xdata") item.notify("ydata") figure_settings.set_selection_range(0, 0) if not data_selected: application.get_window().add_toast_string( _("No data found within the highlighted area"), ) return data.optimize_limits() data.add_history_state(old_limits) _return = (list[float], list[float], bool, bool) def translate_x(_item, xdata: list, ydata: list, offset: float) -> _return: """ Translate all selected data on the x-axis. Amount to be shifted is equal to the value in the translate_x entry widget Will show a toast if a ValueError is raised, typically when a user entered an invalid number (e.g. comma instead of point separators) """ return [value + offset for value in xdata], ydata, True, False def translate_y(_item, xdata: list, ydata: list, offset: float) -> _return: """ Translate all selected data on the y-axis. Amount to be shifted is equal to the value in the translate_y entry widget Will show a toast if a ValueError is raised, typically when a user entered an invalid number (e.g. comma instead of point separators) """ return xdata, [value + offset for value in ydata], False, False def multiply_x(_item, xdata: list, ydata: list, multiplier: float) -> _return: """ Multiply all selected data on the x-axis. Amount to be shifted is equal to the value in the multiply_x entry widget Will show a toast if a ValueError is raised, typically when a user entered an invalid number (e.g. comma instead of point separators) """ return [value * multiplier for value in xdata], ydata, True, False def multiply_y(_item, xdata: list, ydata: list, multiplier: float) -> _return: """ Multiply all selected data on the y-axis. Amount to be shifted is equal to the value in the multiply_y entry widget Will show a toast if a ValueError is raised, typically when a user entered an invalid number (e.g. comma instead of point separators) """ return xdata, [value * multiplier for value in ydata], False, False def normalize(_item, xdata: list, ydata: list) -> _return: """Normalize all selected data.""" return xdata, [value / max(ydata) for value in ydata], False, False def smoothen( _item, xdata: list, ydata: list, smooth_type: int, params: dict, ) -> None: """Smoothen y-data.""" if smooth_type == 0: minimum = params["savgol-polynomial"] + 1 window_percentage = params["savgol-window"] / 100 window = max(minimum, int(len(xdata) * window_percentage)) new_ydata = scipy.signal.savgol_filter( ydata, window, params["savgol-polynomial"], ) elif smooth_type == 1: box_points = params["moving-average-box"] box = numpy.ones(box_points) / box_points new_ydata = numpy.convolve(ydata, box, mode="same") return xdata, new_ydata, False, False def center(_item, xdata: list, ydata: list, center_maximum: int) -> _return: """ Center all selected data. Depending on the key, will center either on the middle coordinate, or on the maximum value of the data """ if center_maximum == 0: # Center at maximum Y middle_index = ydata.index(max(ydata)) middle_value = xdata[middle_index] elif center_maximum == 1: # Center at middle middle_value = (min(xdata) + max(xdata)) / 2 new_xdata = [coordinate - middle_value for coordinate in xdata] return new_xdata, ydata, True, False def shift( item, xdata: list, ydata: list, left_scale: int, right_scale: int, items: misc.ItemList, ranges: tuple[float, float], ) -> _return: """ Shifts data vertically with respect to each other. By default it scales linear data by 1.2 times the total span of the ydata, and log data 10 to the power of the yspan. """ data_list = [ item for item in items if item.get_selected() and isinstance(item, DataItem) ] y_range = ranges[1] if item.get_yposition() else ranges[0] shift_value_log = 0 shift_value_linear = 0 for index, item_ in enumerate(data_list): # Compare first element with itself, not "previous" item index = 1 if index == 0 and len(data_list) > 1 else index previous_item = data_list[index - 1] # Only use selected span when obtaining values to determine shift value new_xdata, new_ydata = xdata, ydata if ( min(xdata) >= min(previous_item.xdata) and max(xdata) <= max(previous_item.xdata) ): new_xdata, new_ydata = filter_data( previous_item.xdata, previous_item.ydata, ">=", min(xdata)) new_xdata, new_ydata = filter_data( new_xdata, new_ydata, "<=", max(xdata)) ymin = min(x for x in new_ydata if x != 0) ymax = max(x for x in new_ydata if x != 0) scale = right_scale if item.get_yposition() else left_scale if scale == 1: # Use log values for log scaling shift_value_log += \ numpy.log10(abs(ymax / ymin)) + 0.1 * numpy.log10(y_range) else: shift_value_linear += (ymax - ymin) + 0.1 * y_range if item.get_uuid() == item_.get_uuid(): if scale == 1: # Log scaling new_ydata = [value * 10**shift_value_log for value in ydata] else: new_ydata = [value + shift_value_linear for value in ydata] return xdata, new_ydata, False, False return xdata, ydata, False, False def cut(_item, _xdata, _ydata) -> _return: """Cut selected data over the span that is selected.""" return [], [], False, False def derivative(_item, xdata: list, ydata: list) -> _return: """Calculate derivative of all selected data.""" x_values = numpy.array(xdata) y_values = numpy.array(ydata) dy_dx = numpy.gradient(y_values, x_values) return xdata, dy_dx.tolist(), False, True def integral(_item, xdata: list, ydata: list) -> _return: """Calculate indefinite integral of all selected data.""" x_values = numpy.array(xdata) y_values = numpy.array(ydata) indefinite_integral = scipy.integrate.cumulative_trapezoid( y_values, x_values, initial=0, ).tolist() return xdata, indefinite_integral, False, True def fft(_item, xdata: list, ydata: list) -> _return: """Perform Fourier transformation on all selected data.""" x_values = numpy.array(xdata) y_values = numpy.array(ydata) y_fourier = numpy.fft.fft(y_values) x_fourier = numpy.fft.fftfreq(len(x_values), x_values[1] - x_values[0]) y_fourier = [value.real for value in y_fourier] return x_fourier, y_fourier, False, True def inverse_fft(_item, xdata: list, ydata: list) -> _return: """Perform Inverse Fourier transformation on all selected data.""" x_values = numpy.array(xdata) y_values = numpy.array(ydata) y_fourier = numpy.fft.ifft(y_values) x_fourier = numpy.fft.fftfreq(len(x_values), x_values[1] - x_values[0]) y_fourier = [value.real for value in y_fourier] return x_fourier, y_fourier, False, True def transform( _item, xdata: list, ydata: list, input_x: str, input_y: str, discard: bool = False, ) -> _return: """Perform custom transformation.""" local_dict = { "x": xdata, "y": ydata, "x_min": min(xdata), "x_max": max(xdata), "y_min": min(ydata), "y_max": max(ydata), } # Add array of zeros to return values, such that output remains a list # of the correct size, even when a float is given as input. return ( numexpr.evaluate(utilities.preprocess(input_x) + "+ 0*x", local_dict), numexpr.evaluate(utilities.preprocess(input_y) + "+ 0*y", local_dict), True, discard, ) def combine(application: Graphs.Application) -> None: """Combine the selected data into a new data set.""" new_xdata, new_ydata = [], [] for item in application.get_data(): if not (item.get_selected() and isinstance(item, DataItem)): continue xdata, ydata = get_data(application, item)[:2] new_xdata.extend(xdata) new_ydata.extend(ydata) # Create the item itself new_xdata, new_ydata = sort_data(new_xdata, new_ydata) style = application.get_figure_style_manager().get_selected_style_params() application.get_data().add_items( [DataItem.new(style, new_xdata, new_ydata, name=_("Combined Data"))], application.get_figure_style_manager(), ) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/parse_file.py000066400000000000000000000146031511351636000244460ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Module for parsing files to usable data.""" import re from gettext import gettext as _ from gi.repository import Gio, Graphs from graphs import file_io, item, misc, project, utilities from graphs.misc import ParseError import numpy def import_from_project(_params, _style, file: Gio.File) -> misc.ItemList: """Import data from project file.""" project_dict = project.read_project_file(file) return list(map(item.new_from_dict, project_dict["data"])) def import_from_xrdml(_params, style, file: Gio.File) -> misc.ItemList: """Import data from xrdml file.""" content = file_io.parse_xml(file) intensities = content.getElementsByTagName("intensities") counting_time = content.getElementsByTagName("commonCountingTime") counting_time = float(counting_time[0].firstChild.data) ydata = intensities[0].firstChild.data.split() ydata = [int(value) / counting_time for value in ydata] scan_type = content.getElementsByTagName("scan") scan_axis = scan_type[0].attributes["scanAxis"].value if scan_axis.startswith("2Theta"): scan_axis = "2Theta" if scan_axis.startswith("Omega"): scan_axis = "Omega" data_points = content.getElementsByTagName("positions") for position in data_points: axis = position.attributes["axis"] if axis.value == scan_axis: unit = position.attributes["unit"].value start_pos = position.getElementsByTagName("startPosition") end_pos = position.getElementsByTagName("endPosition") start_pos = float(start_pos[0].firstChild.data) end_pos = float(end_pos[0].firstChild.data) xdata = numpy.linspace(start_pos, end_pos, len(ydata)) xdata = numpy.ndarray.tolist(xdata) return [ item.DataItem.new( style, xdata, ydata, name=Graphs.tools_get_filename(file), xlabel=f"{scan_axis} ({unit})", ylabel=_("Intensity (cps)"), ), ] def import_from_xry(_params, style, file: Gio.File) -> misc.ItemList: """Import data from .xry files used by Leybold X-ray apparatus.""" with file_io.open_wrapped(file, "rt", encoding="ISO-8859-1") as wrapper: def skip(lines: int): for _count in range(lines): next(wrapper) if wrapper.readline().strip() != "XR01": raise ParseError(_("Invalid .xry format")) skip(3) b_params = wrapper.readline().strip().split() x_step = float(b_params[3]) x_value = float(b_params[0]) skip(12) info = wrapper.readline().strip().split() item_count = int(info[0]) name = Graphs.tools_get_filename(file) items = [ item.DataItem.new( style, name=f"{name} - {i + 1}" if item_count > 1 else name, xlabel=_("β (°)"), ylabel=_("R (1/s)"), ) for i in range(item_count) ] for _count in range(int(info[1])): for value, item_ in zip(wrapper.readline().strip().split(), items): if value != "NaN": item_.xdata.append(x_value) item_.ydata.append(float(value)) x_value += x_step skip(9 + item_count) for _count in range(int(wrapper.readline().strip())): values = wrapper.readline().strip().split() text = " ".join(values[7:]) items.append( item.TextItem.new( style, float(values[5]), float(values[6]), text, name=text, ), ) return items _PH = "dVldZaXqENhuPLPw" def import_from_columns(params, style, file: Gio.File) -> misc.ItemList: """Import data from columns file.""" item_ = item.DataItem.new(style, name=Graphs.tools_get_filename(file)) column_x = params.get_int("column-x") column_y = params.get_int("column-y") separator = params.get_string("separator").replace(" ", "") skip_rows = params.get_int("skip-rows") delimiter = misc.DELIMITERS[params.get_string("delimiter")] if delimiter == "custom": delimiter = params.get_string("custom-delimiter") stream = Gio.DataInputStream.new(file.read(None)) start_values = False for index, line in \ enumerate(file_io.iter_data_stream(stream), -skip_rows): if index < 0: continue values = re.split(delimiter, line) if separator == ",": values = ( string.replace(",", _PH).replace(".", ", ").replace(_PH, ".") for string in values ) try: if len(values) == 1: float_value = utilities.string_to_float(values[0]) if float_value is not None: item_.ydata.append(float_value) item_.xdata.append(index) start_values = True else: try: x_value = utilities.string_to_float(values[column_x]) y_value = utilities.string_to_float(values[column_y]) if x_value is None or y_value is None: raise ValueError else: item_.xdata.append(x_value) item_.ydata.append(y_value) start_values = True except IndexError as error: raise ParseError( _("Import failed, column index out of range"), ) from error # If not all values in the line are floats, start looking for # headers instead except ValueError: # Don't try to add headers when started adding values if not start_values: try: headers = re.split(delimiter, line) if len(values) == 1: item_.set_ylabel(headers[column_x]) else: item_.set_xlabel(headers[column_x]) item_.set_ylabel(headers[column_y]) # If no label could be found at the index, skip. except IndexError: pass stream.close() if not item_.xdata: raise ParseError(_("Unable to import from file")) return [item_] Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/project.py000066400000000000000000000060411511351636000240000ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Module for saving and loading projects.""" import contextlib from gettext import gettext as _ from gi.repository import GLib, Gio, Graphs, Gtk from graphs import file_io, migrate, misc, utilities def read_project_file(file: Gio.File) -> dict: """Read a project dict from file and account for migration.""" try: project_dict = file_io.parse_json(file) except UnicodeDecodeError: project_dict = migrate.migrate_project(file) return project_dict def save_project_dict(file: Gio.File, project_dict: dict) -> None: """Save a project dict to a file.""" file_io.write_json(file, project_dict) _GRAPHS_PROJECT_FILE_ONLY_FILE_FILTER = utilities.create_file_filters( (misc.GRAPHS_PROJECT_FILE_FILTER_TEMPLATE, ), ) def _save(application: Graphs.Application) -> None: data = application.get_data() project_file = data.props.file project_dict = data.get_project_dict(application.get_version()) save_project_dict(project_file, project_dict) data.set_unsaved(False) data.emit("saved") application.get_window().add_toast_string_with_file( _("Saved Project"), project_file, ) def save_project( application: Graphs.Application, require_dialog: bool = False, ) -> None: """Save the current data to disk.""" data = application.get_data() if data.props.file is not None and not require_dialog: _save(application) return def on_response(dialog, response): with contextlib.suppress(GLib.GError): data.props.file = dialog.save_finish(response) _save(application) dialog = Gtk.FileDialog() dialog.set_filters(_GRAPHS_PROJECT_FILE_ONLY_FILE_FILTER) dialog.set_initial_name(_("Project") + ".graphs") dialog.save(application.get_window(), None, on_response) def open_project(application: Graphs.Application) -> None: """Open a project.""" def pick_file(): def on_pick_response(dialog, response): with contextlib.suppress(GLib.GError): load(application.get_data(), dialog.open_finish(response)) dialog = Gtk.FileDialog() dialog.set_filters(_GRAPHS_PROJECT_FILE_ONLY_FILE_FILTER) dialog.open(application.get_window(), None, on_pick_response) if not application.get_data().get_unsaved(): pick_file() return def on_save_response(_dialog, response): if response == "discard_close": pick_file() if response == "save_close": # Retrieving open dialog first means that save dialog will be # on top. Thus user will be presented with save dialog first. pick_file() save_project(application) dialog = Graphs.tools_build_dialog("save_changes") dialog.connect("response", on_save_response) dialog.present(application.get_window()) def load(data: Graphs.Data, file: Gio.File) -> None: """Load a project from file into data.""" data.props.file = file data.load_from_project_dict(read_project_file(file)) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/scales.py000066400000000000000000000203041511351636000236020ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """ Scales Module. Contains helper functions to convert between string and int as well as custom Scale classes. Functions: to_string to_int """ from matplotlib import scale, ticker, transforms import numpy _SCALES = ["linear", "log", "radians", "squareroot", "inverse"] def to_string(scale: int) -> str: """Convert an int to a string.""" return _SCALES[scale] def to_int(scale: str) -> int: """Convert a string to an int.""" return _SCALES.index(scale) class RadiansScale(scale.LinearScale): """Radians Scale.""" name = "radians" def set_default_locators_and_formatters(self, axis): """Handle locators and formatters.""" super().set_default_locators_and_formatters(axis) axis.set_major_formatter( ticker.FuncFormatter(lambda x, _pos=None: f"{x / numpy.pi:.3g}π"), ) axis.set_major_locator(RadianLocator()) class SquareRootScale(scale.ScaleBase): """Class for generating custom square root scale.""" name = "squareroot" def set_default_locators_and_formatters(self, axis): """Handle locators and formatters.""" axis.set_major_locator(CustomScaleLocator()) axis.set_minor_locator(CustomScaleLocator(is_minor=True)) axis.set_major_formatter(ticker.ScalarFormatter()) axis.set_minor_formatter(ticker.NullFormatter()) def limit_range_for_scale(self, vmin, vmax, _minpos): """Limit scale range.""" return max(0, vmin), vmax class SquareRootTransform(transforms.Transform): """The transform to convert from linear to square root scale.""" input_dims = 1 # Amount of input params in transform output_dims = 1 # Amount of output params in transform is_separable = True # Seperable in X and Y dimension def transform_non_affine(self, a): """Transform data.""" # Don't spam about invalid divide by zero errors with numpy.errstate(divide="ignore", invalid="ignore"): return numpy.array(a)**0.5 def inverted(self): """Get the inverse transform.""" return SquareRootScale.InvertedSquareRootTransform() class InvertedSquareRootTransform(transforms.Transform): """Inverse transform to convert from square root to linear scale.""" input_dims = 1 # Amount of input params in transform output_dims = 1 # Amount of output params in transform is_separable = True # Seperable in X and Y dimension def transform_non_affine(self, a): """Transform data.""" # Don't spam about invalid divide by zero errors with numpy.errstate(divide="ignore", invalid="ignore"): return numpy.array(a)**2 def inverted(self): """Get the inverse transform.""" return SquareRootScale.SquareRootTransform() def get_transform(self): """Get the transform.""" return self.SquareRootTransform() class InverseScale(scale.ScaleBase): """Inverse scale.""" name = "inverse" def set_default_locators_and_formatters(self, axis): """Handle locators and formatters.""" axis.set_major_locator(CustomScaleLocator()) axis.set_minor_locator(CustomScaleLocator(is_minor=True)) axis.set_major_formatter(ticker.ScalarFormatter()) axis.set_minor_formatter(ticker.NullFormatter()) def limit_range_for_scale(self, vmin, vmax, minpos): """Limit scale range.""" if not numpy.isfinite(minpos): minpos = 1e-300 return ( minpos if vmin <= 0 else vmin, minpos if vmax <= 0 else vmax, ) def get_transform(self): """Get the transform.""" return InverseScale.InverseTransform() class InverseTransform(transforms.Transform): """The transform to invert the scaling on the axis.""" input_dims = 1 output_dims = 1 is_separable = True has_inverse = True def inverted(self): """Get the inverse transform.""" return InverseScale.InverseTransform() def transform_non_affine(self, a): """Transform data.""" # Don't spam about invalid divide by zero errors with numpy.errstate(divide="ignore", invalid="ignore"): return 1 / numpy.array(a) class CustomScaleLocator(ticker.MaxNLocator): """Dynamically find tick positions on custom scales.""" def __init__(self, is_minor=False): self.is_minor = is_minor @property def numticks(self): """Get number of ticks.""" if self.axis is not None: numticks = max(1, self.axis.get_tick_space() - 4) # Amount of ticks is set between 3 and 9 self._numticks = numpy.clip(numticks, 3, 9) else: self._numticks = 9 if self.is_minor: # Amount of minor ticks is equal to amount of major ticks # times (N+1) minus N. Where N is the amount of minor ticks # in between the major ticks. self._numticks = len(self.axis.get_majorticklocs()) * 4 - 3 return self._numticks @numticks.setter def numticks(self, numticks): """Set number of ticks.""" self._numticks = numticks def tick_values(self, vmin, vmax): """Get tick values.""" vmin, vmax = transforms.nonsingular(vmin, vmax, expander=0.05) vmin, vmax = min(vmin, vmax), max(vmin, vmax) # Swap values if needed lin_tick_pos = numpy.linspace(vmin, vmax, self.numticks) lin_tick_pos = lin_tick_pos[lin_tick_pos != 0] # Remove zeroes if self.axis.get_scale() == "squareroot": tick_pos = lin_tick_pos**2 elif self.axis.get_scale() == "inverse": tick_pos = 1 / lin_tick_pos else: raise ValueError("Wrong locator for the axis type") tick_pos = tick_pos * ((vmax - vmin) / (max(tick_pos) - min(tick_pos))) tick_pos *= 2 if self.axis.get_scale() == "squareroot" else 1 return tick_pos class RadianLocator(ticker.MultipleLocator): """ Dynamically place tick positions on radian scale. Places ticks at a distance of pi if there's between 4 and 8 ticks Otherwise it places ticks at a distance of 2pi if reasonable, or with a multiple of 5 pi such that a number between 3 and 8 ticks are placed At smaller values, the distances between the ticks are a power of 2, multiplied by pi. e.g. (1/2)pi, (1/4)pi, (1/8)pi etc.. """ def __init__(self): super().__init__(base=self.base) def tick_values(self, vmin, vmax): """Get tick values.""" if vmax < vmin: vmin, vmax = vmax, vmin self._edge = ticker._Edge_integer(self.base, 0) step = self._edge.step vmin -= self._offset vmax -= self._offset vmin = self._edge.ge(vmin) * step n = (vmax - vmin + 0.001 * step) // step locs = vmin - step + numpy.arange(n + 3) * step + self._offset return self.raise_if_exceeds(locs) @property def base(self): """Get base.""" if self.axis is None: return numpy.pi vmin, vmax = self.axis.get_view_interval() distance = numpy.pi # Amount of ticks if we use a multiple of pi num_ticks = (vmax - vmin) / distance # Desired amount of ticks, should be between 3 and 8 numticks_goal = max(1, self.axis.get_tick_space() - 4) numticks_goal = numpy.clip(numticks_goal, 3, 7) ratio = (num_ticks / numticks_goal) if num_ticks > 8: if ratio < 2: # Use a distance of 2pi if reasonable return distance * 2 # Make sure ratio is never rounded to 0: ratio = 5 if round(ratio / 5) == 0 else ratio return distance * round(ratio / 5) * 5 elif num_ticks < 4: ratio = (num_ticks / numticks_goal) exponent = int(numpy.log2(abs(ratio))) result = 2**exponent # Return distance as a power of 2 return distance * result return distance scale.register_scale(RadiansScale) scale.register_scale(SquareRootScale) scale.register_scale(InverseScale) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/smoothen_settings.vala000066400000000000000000000020641511351636000264020ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later using Adw; using Gtk; namespace Graphs { /** * Smoothen settings dialog */ [GtkTemplate (ui = "/se/sjoerd/Graphs/ui/smoothen-settings.ui")] public class SmoothenDialog : Adw.Dialog { [GtkChild] public unowned Adw.SpinRow savgol_window { get; } [GtkChild] public unowned Adw.SpinRow savgol_polynomial { get; } [GtkChild] public unowned Adw.SpinRow moving_average_box { get; } private Application application { get; set; } public SmoothenDialog (Application application) { Object (); this.application = application; Tools.bind_settings_to_widgets ( application.get_settings_child ("actions/smoothen"), this ); present (application.window); } [GtkCallback] private void on_reset () { GLib.Settings settings = this.application.get_settings_child ("actions/smoothen"); Tools.reset_settings (settings); } } } Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/style_io.py000066400000000000000000000124261511351636000241650ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Module for parsing and writing styles.""" import logging import typing from gettext import gettext as _ from gi.repository import Gio from matplotlib import RcParams, cbook, rc_context from matplotlib.figure import Figure from matplotlib.font_manager import font_scalings, weight_dict from matplotlib.style.core import STYLE_BLACKLIST import numpy STYLE_IGNORELIST = [ "savefig.dpi", "savefig.facecolor", "savefig.edgecolor", "savefig.format", "savefix.bbox", "savefig.pad_inches", "savefig.transparent", "savefig.orientation", ] FONT_SIZE_KEYS = [ "font.size", "axes.labelsize", "xtick.labelsize", "ytick.labelsize", "legend.fontsize", "figure.labelsize", "figure.titlesize", "axes.titlesize", ] def parse(file: Gio.File) -> (RcParams, str): """ Parse a style to RcParams. This is an improved version of matplotlibs '_rc_params_in_file()' function. It is also modified to work with GFile instead of the python builtin functions. """ style = RcParams() filename = file.get_basename() try: stream = Gio.DataInputStream.new(file.read(None)) line_number = 0 while True: line = stream.read_line_utf8(None)[0] if line is None: break line_number += 1 if line_number == 2: name = line[2:] line = cbook._strip_comment(line) if not line: continue try: key, value = line.split(":", 1) except ValueError: msg = _("Missing colon in file {file}, line {line}") logging.warning(msg.format(file=filename, line=line_number)) continue key = key.strip() value = value.strip() if value.startswith('"') and value.endswith('"'): value = value[1:-1] # strip double quotes if key in STYLE_BLACKLIST: msg = _("Non-style related parameter {param} in file {file}") logging.warning(msg.format(param=key, file=filename)) elif key in STYLE_IGNORELIST: msg = _("Ignoring parameter {param} in file {file}") logging.warning(msg.format(param=key, file=filename)) elif key in style: msg = _("Duplicate key in file {file}, on line {line}") logging.warning(msg.format(file=filename, line=line_number)) else: if key in FONT_SIZE_KEYS \ and not value.replace(".", "", 1).isdigit(): try: value = font_scalings[value] except KeyError: continue elif key == "font.weight" and not value.isdigit(): try: value = weight_dict[value] except KeyError: continue try: style[key] = value except (KeyError, ValueError): msg = _("Bad value in file {file} on line {line}") logging.exception( msg.format(file=filename, line=line_number), ) except UnicodeDecodeError: logging.exception( _("Could not parse {filename}").format(filename=filename), ) finally: stream.close() return style, name WRITE_IGNORELIST = STYLE_IGNORELIST + [ "lines.dashdot_pattern", "lines.dashed_pattern", "lines.dotted_pattern", "lines.dash_capstyle", "lines.dash_joinstyle", "lines.solid_capstyle", "lines.solid_joinstyle", ] def write(file: Gio.File, name: str, style: RcParams) -> None: """Write a style to a file.""" if file.query_exists(None): file.delete(None) stream = Gio.DataOutputStream.new(file.create(0, None)) stream.put_string("# Generated via Graphs\n") stream.put_string(f"# {name}\n") for key, value in style.items(): if key not in STYLE_BLACKLIST and key not in WRITE_IGNORELIST: value = str(value).replace("#", "") if key != "axes.prop_cycle": value = value.replace("[", "").replace("]", "") value = value.replace("'", "").replace("'", "") value = value.replace('"', "").replace('"', "") stream.put_string(f"{key}: {value}\n") stream.close() _PREVIEW_XDATA = numpy.linspace(0, 10, 30) _PREVIEW_YDATA1 = numpy.sin(_PREVIEW_XDATA) _PREVIEW_YDATA2 = numpy.cos(_PREVIEW_XDATA) def create_preview( file: typing.IO, params: RcParams, file_format: str = "svg", ) -> None: """Create preview of params and write it to file.""" with rc_context(params): # set render size in inch figure = Figure(figsize=(5, 3)) axis = figure.add_subplot() axis.spines.bottom.set_visible(True) axis.spines.left.set_visible(True) if not params["axes.spines.top"]: axis.tick_params(which="both", top=False, right=False) axis.plot(_PREVIEW_XDATA, _PREVIEW_YDATA1) axis.plot(_PREVIEW_XDATA, _PREVIEW_YDATA2) axis.set_xlabel(_("X Label")) axis.set_xlabel(_("Y Label")) figure.savefig(file, format=file_format) Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/styles.py000066400000000000000000000672641511351636000236730ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later """Module for style utilities.""" import contextlib import io import os from gettext import gettext as _ from pathlib import Path from cycler import cycler from gi.repository import ( Adw, GLib, GObject, Gdk, Gio, Graphs, Gtk, Pango, ) import graphs from graphs import item, style_io from matplotlib import RcParams, rcParams, rcParamsDefault def _generate_filename(name: str) -> str: name = name.replace("(", "").replace(")", "") return f"{name.lower().replace(' ', '-')}.mplstyle" def _is_style_bright(params: RcParams): return Graphs.tools_get_luminance_from_hex(params["axes.facecolor"]) < 0.4 def _generate_preview(style: RcParams) -> Gdk.Texture: buffer = io.BytesIO() style_io.create_preview(buffer, style) return Gdk.Texture.new_from_bytes(GLib.Bytes.new(buffer.getvalue())) class StyleManager(Graphs.StyleManager): """ Main Style Manager. Keeps track of all files in the style dir and represents them in the `selection_model` property. """ __gtype_name__ = "GraphsPythonStyleManager" def __init__(self, application: Graphs.Application): # Check for Ubuntu gtk_theme = Gtk.Settings.get_default().get_property("gtk-theme-name") self._system_style_name = "Yaru" \ if "SNAP" in os.environ \ and gtk_theme.lower().startswith("yaru") \ else "Adwaita" super().__init__(application=application) self._selected_style_params = None self._stylenames = self.load_system_styles( self._system_style_name.lower(), ) self._update_system_style() enumerator = self.props.style_dir.enumerate_children( "default::*", 0, None, ) for file in map(enumerator.get_child, enumerator): if file.query_file_type(0, None) != 1: continue if Path(Graphs.tools_get_filename(file)).suffix != ".mplstyle": continue self._add_user_style(file) enumerator.close(None) self._style_monitor = self.props.style_dir.monitor_directory(0, None) self._style_monitor.connect("changed", self._on_file_change) application.get_style_manager().connect( "notify", self._on_system_style_change, ) notifiers = ("custom_style", "use_custom_style") for prop in notifiers: self.connect( "notify::" + prop.replace("_", "-"), getattr(self, "_on_" + prop), ) self.setup_bindings(application.get_data().get_figure_settings()) self._on_style_change() def _on_system_style_change(self, _a, _b): if not self.props.use_custom_style: self._on_style_change() def _add_user_style( self, file: Gio.File, style_params: RcParams = None, name: str = None, ) -> None: if style_params is None: tmp_style_params, name = style_io.parse(file) style_params = self._complete_style(tmp_style_params) if name in self._stylenames: new_name = Graphs.tools_get_duplicate_string( name, self._stylenames, ) file.delete(None) file = self.props.style_dir.get_child_for_display_name( _generate_filename(new_name), ) style_io.write(style_params, new_name, file) self._stylenames.append(name) self.props.selection_model.get_model().insert_sorted( Graphs.Style( name=name, file=file, mutable=True, preview=_generate_preview(style_params), light=_is_style_bright(style_params), ), Graphs.style_cmp, ) if not self.props.use_custom_style: return old_style = self.props.custom_style if old_style not in self._stylenames: self.props.custom_style = name for index, style in enumerate(self.props.selection_model): if index > 0 and style.get_name() == self.props.custom_style: self.props.selection_model.set_selected(index) break def get_selected_style_params(self) -> RcParams: """Get the selected style properties.""" return self._selected_style_params def get_system_style_params(self) -> RcParams: """Get the system style properties.""" return self._system_style_params def _on_file_change( self, _monitor, file: Gio.File, _other_file, event_type: int, ) -> None: if Path(file.peek_path()).stem.startswith("."): return possible_visual_impact = False stylename = None style_model = self.props.selection_model.get_model() if event_type == 2: for index, style in enumerate(style_model): file2 = style.get_file() if file2 is not None and file.equal(file2): stylename = style.get_name() self._stylenames.remove(stylename) style_model.remove(index) break if stylename is None: return possible_visual_impact = True else: tmp_style_params, stylename = style_io.parse(file) style_params = self._complete_style(tmp_style_params) if event_type == 1: for obj in style_model: if obj.get_name() == stylename: obj.set_preview(_generate_preview(style_params)) obj.set_light(_is_style_bright(style_params)) break possible_visual_impact = False elif event_type == 3: self._add_user_style(file, style_params, stylename) if possible_visual_impact \ and self.props.use_custom_style \ and self.props.custom_style == stylename \ and event_type != 2: self._on_style_change() def _on_style_select(self, style_model, _pos, _n_items): """Set the style upon selection.""" selected_item = style_model.get_selected_item() # Don't trigger unneccesary reloads if selected_item.get_file() is None: # System style if self.props.use_custom_style: self.props.use_custom_style = False else: stylename = selected_item.get_name() if stylename != self.props.custom_style: self.props.custom_style = stylename if not self.props.use_custom_style: self.props.use_custom_style = True @staticmethod def _on_use_custom_style(self, _a) -> None: """Handle `use_custom_style` property change.""" if self.props.use_custom_style: self._on_custom_style(self, None) else: self.props.selection_model.set_selected(0) self._on_style_change(True) @staticmethod def _on_custom_style(self, _a) -> None: """Handle `custom_style` property change.""" if self.props.use_custom_style: for index, style in enumerate(self.props.selection_model): if index > 0 and style.get_name() == self.props.custom_style: self.props.selection_model.set_selected(index) break self._on_style_change(True) def _on_style_change(self, override: bool = False) -> None: rcParams.update(rcParamsDefault) self.props.selected_stylename = self.get_selected_style().get_name() old_style = self._selected_style_params self._update_system_style() self._update_selected_style() data = self.props.application.get_data() if old_style is not None and override: old_colors = old_style["axes.prop_cycle"].by_key()["color"] color_cycle = self._selected_style_params["axes.prop_cycle" ].by_key()["color"] for item_ in data: item_.reset(old_style, self._selected_style_params) count = 0 for item_ in data: if isinstance(item_, item.DataItem) \ and item_.get_color() in old_colors: if count > len(color_cycle): count = 0 item_.set_color(color_cycle[count]) count += 1 window = self.props.application.get_window() canvas = graphs.canvas.Canvas(self._selected_style_params) figure_settings = data.get_figure_settings() for prop in dir(figure_settings.props): if prop not in ("use_custom_style", "custom_style"): figure_settings.bind_property(prop, canvas, prop, 1 | 2) data.bind_property("items", canvas, "items", 2) from graphs.figure_settings import FigureSettingsDialog def on_edit_request(_canvas, label_id): FigureSettingsDialog(self.props.application, label_id) def on_view_changed(_canvas): data.add_view_history_state() canvas.connect("edit_request", on_edit_request) canvas.connect("view_changed", on_view_changed) # Set headerbar color and contrast bg_color = self._selected_style_params["figure.facecolor"] color = self._selected_style_params["text.color"] css_provider = window.get_headerbar_provider() css_provider.load_from_string( f"headerbar {{ background-color: {bg_color}; color: {color}; }}", ) window.set_canvas(canvas) window.get_cut_button().bind_property( "sensitive", canvas, "highlight_enabled", 2, ) def _update_system_style(self) -> None: system_style = self._system_style_name if Adw.StyleManager.get_default().get_dark(): system_style += " Dark" filename = _generate_filename(system_style) self._system_style_params = style_io.parse( Gio.File.new_for_uri( "resource:///se/sjoerd/Graphs/styles/" + filename, ), )[0] def _update_selected_style(self) -> None: self._selected_style_params = None if self.props.use_custom_style: stylename = self.props.custom_style for style in self.props.selection_model.get_model(): if stylename == style.get_name(): try: style_params = style_io.parse(style.get_file())[0] if style.get_mutable(): style_params = self._complete_style(style_params) self._selected_style_params = style_params return except (ValueError, SyntaxError, AttributeError): self._reset_selected_style( _( f"Could not parse {stylename}, loading " "system preferred style", ).format(stylename=stylename), ) break if self._selected_style_params is None: self._reset_selected_style( _( f"Plot style {stylename} does not exist " "loading system preferred", ).format(stylename=stylename), ) self._selected_style_params = self._system_style_params def _reset_selected_style(self, message: str) -> None: self.props.use_custom_style = False self.props.custom_style = self._system_style_name self.props.application.get_window().add_toast_string(message) def copy_style(self, template: str, new_name: str) -> None: """Copy a style.""" new_name = Graphs.tools_get_duplicate_string( new_name, self._stylenames, ) destination = self.props.style_dir.get_child_for_display_name( _generate_filename(new_name), ) for style in self.props.selection_model.get_model(): if template == style.get_name(): style_params = style_io.parse(style.get_file())[0] source = self._complete_style(style_params) \ if style.get_mutable() else style_params break style_io.write(destination, new_name, source) def _complete_style(self, params: RcParams) -> RcParams: for key, value in self._system_style_params.items(): if key not in params: params[key] = value return params def delete_style(self, file: Gio.File) -> None: """Delete a style.""" style_model = self.props.selection_model.get_model() for index, style in enumerate(style_model): if style is not None: file2 = style.get_file() if file2 is not None and file.equal(file2): stylename = style.get_name() style_model.remove(index) self._stylenames.remove(stylename) self.props.use_custom_style = False self.props.custom_style = self._system_style_name STYLE_DICT = { "linestyle": ["lines.linestyle"], "linewidth": ["lines.linewidth"], "markers": ["lines.marker"], "markersize": ["lines.markersize"], "draw_frame": [ "axes.spines.bottom", "axes.spines.left", "axes.spines.top", "axes.spines.right", ], "tick_direction": ["xtick.direction", "ytick.direction"], "minor_ticks": ["xtick.minor.visible", "ytick.minor.visible"], "major_tick_width": ["xtick.major.width", "ytick.major.width"], "minor_tick_width": ["xtick.minor.width", "ytick.minor.width"], "major_tick_length": ["xtick.major.size", "ytick.major.size"], "minor_tick_length": ["xtick.minor.size", "ytick.minor.size"], "tick_bottom": ["xtick.bottom"], "tick_left": ["ytick.left"], "tick_top": ["xtick.top"], "tick_right": ["ytick.right"], "show_grid": ["axes.grid"], "grid_linewidth": ["grid.linewidth"], "value_padding": [ "xtick.major.pad", "xtick.minor.pad", "ytick.major.pad", "ytick.minor.pad", ], "label_padding": ["axes.labelpad"], "title_padding": ["axes.titlepad"], "axis_width": ["axes.linewidth"], "text_color": [ "text.color", "axes.labelcolor", "xtick.labelcolor", "ytick.labelcolor", ], "tick_color": ["xtick.color", "ytick.color"], "axis_color": ["axes.edgecolor"], "grid_color": ["grid.color"], "grid_opacity": ["grid.alpha"], "background_color": ["axes.facecolor"], "outline_color": ["figure.facecolor", "figure.edgecolor"], } VALUE_DICT = { "linestyle": ["none", "solid", "dotted", "dashed", "dashdot"], "markers": [ "none", ".", ",", "o", "v", "^", "<", ">", "8", "s", "p", "*", "h", "H", "+", "x", "D", "d", "|", "_", "P", "X", ], "tick_direction": ["in", "out"], } FONT_STYLE_DICT = { 0: "normal", 1: "oblique", 2: "italic", } FONT_VARIANT_DICT = { 0: "normal", 1: "small-caps", } def _title_format_function(_scale, value: float) -> str: """Format a float value as percentage string.""" return str(value / 2 * 100).split(".")[0] + "%" @Gtk.Template(resource_path="/se/sjoerd/Graphs/ui/style-editor.ui") class StyleEditor(Adw.NavigationPage): """Style editor widget.""" __gtype_name__ = "GraphsStyleEditor" style_name = Gtk.Template.Child() font_chooser = Gtk.Template.Child() titlesize = Gtk.Template.Child() labelsize = Gtk.Template.Child() linestyle = Gtk.Template.Child() linewidth = Gtk.Template.Child() markers = Gtk.Template.Child() markersize = Gtk.Template.Child() draw_frame = Gtk.Template.Child() tick_direction = Gtk.Template.Child() minor_ticks = Gtk.Template.Child() major_tick_width = Gtk.Template.Child() minor_tick_width = Gtk.Template.Child() major_tick_length = Gtk.Template.Child() minor_tick_length = Gtk.Template.Child() tick_bottom = Gtk.Template.Child() tick_left = Gtk.Template.Child() tick_top = Gtk.Template.Child() tick_right = Gtk.Template.Child() show_grid = Gtk.Template.Child() grid_linewidth = Gtk.Template.Child() grid_opacity = Gtk.Template.Child() value_padding = Gtk.Template.Child() label_padding = Gtk.Template.Child() title_padding = Gtk.Template.Child() axis_width = Gtk.Template.Child() text_color = Gtk.Template.Child() tick_color = Gtk.Template.Child() axis_color = Gtk.Template.Child() grid_color = Gtk.Template.Child() background_color = Gtk.Template.Child() outline_color = Gtk.Template.Child() line_colors_box = Gtk.Template.Child() poor_contrast_warning = Gtk.Template.Child() def __init__(self, parent): super().__init__() self.style = None self.parent = parent self._style_manager = \ parent.get_application().get_figure_style_manager() self.titlesize.set_format_value_func(_title_format_function) self.labelsize.set_format_value_func(_title_format_function) # color buttons self.color_buttons = [ self.text_color, self.tick_color, self.axis_color, self.grid_color, self.background_color, self.outline_color, ] for button in self.color_buttons: button.connect("clicked", self.on_color_change) button.provider = Gtk.CssProvider() button.get_style_context().add_provider( button.provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, ) parent.connect("load-style-request", self._load_style) parent.connect("save-style-request", self._save_style) def _load_style(self, _parent, style): """Load a style.""" if not style.get_mutable(): return self.style = style self.style_params = self._style_manager._complete_style( style_io.parse(self.style.get_file())[0], ) stylename = self.style.get_name() self.set_title(stylename) self.style_name.set_text(stylename) for key, value in STYLE_DICT.items(): value = self.style_params[value[0]] with contextlib.suppress(KeyError): value = VALUE_DICT[key].index(value) widget = getattr(self, key.replace("-", "_")) if isinstance(widget, Adw.EntryRow): widget.set_text(str(value)) elif isinstance(widget, Adw.ComboRow): widget.set_selected(int(value)) elif isinstance(widget, Gtk.Scale): widget.set_value(value) elif isinstance(widget, Gtk.Button): widget.color = Graphs.tools_hex_to_rgba(value) elif isinstance(widget, Adw.SwitchRow): widget.set_active(bool(value)) else: raise ValueError # font font_description = Pango.FontDescription.new() font_size = self.style_params["font.size"] font_description.set_size(font_size * Pango.SCALE) self.titlesize.set_value( round(self.style_params["figure.titlesize"] * 2 / font_size, 1), ) self.labelsize.set_value( round(self.style_params["axes.labelsize"] * 2 / font_size, 1), ) font_description.set_family(self.style_params["font.sans-serif"][0]) font_description.set_weight(self.style_params["font.weight"]) inverted_style_dict = {b: a for a, b in FONT_STYLE_DICT.items()} font_description.set_style( inverted_style_dict[self.style_params["font.style"]], ) inverted_variant_dict = {b: a for a, b in FONT_VARIANT_DICT.items()} font_description.set_variant( inverted_variant_dict[self.style_params["font.variant"]], ) self.font_chooser.set_font_desc(font_description) for button in self.color_buttons: hex_color = Graphs.tools_rgba_to_hex(button.color) button.provider.load_from_string( f"button {{ color: {hex_color}; }}", ) self._check_contrast() # line colors self.line_colors = \ self.style_params["axes.prop_cycle"].by_key()["color"] self._reload_line_colors() def _save_style(self, _parent): """Save the style.""" if self.style is None: return for key in STYLE_DICT.keys(): widget = getattr(self, key.replace("-", "_")) if isinstance(widget, Adw.EntryRow): value = str(widget.get_text()) elif isinstance(widget, Adw.ComboRow): value = widget.get_selected() elif isinstance(widget, Gtk.Scale): value = widget.get_value() elif isinstance(widget, Gtk.Button): value = Graphs.tools_rgba_to_hex(widget.color) elif isinstance(widget, Adw.SwitchRow): value = bool(widget.get_active()) else: raise ValueError with contextlib.suppress(KeyError): value = VALUE_DICT[key][value] for item_ in STYLE_DICT[key]: self.style_params[item_] = value # font font_description = self.font_chooser.get_font_desc() self.style_params["font.sans-serif"] = [font_description.get_family()] font_size = font_description.get_size() / Pango.SCALE for key in ( "font.size", "xtick.labelsize", "ytick.labelsize", "legend.fontsize", "figure.labelsize", ): self.style_params[key] = font_size titlesize = round(self.titlesize.get_value() / 2 * font_size, 1) labelsize = round(self.labelsize.get_value() / 2 * font_size, 1) self.style_params["figure.titlesize"] = titlesize self.style_params["axes.titlesize"] = titlesize self.style_params["axes.labelsize"] = labelsize font_weight = font_description.get_weight() for key in ( "font.weight", "axes.titleweight", "axes.labelweight", "figure.titleweight", "figure.labelweight", ): self.style_params[key] = font_weight self.style_params["font.style"] = \ FONT_STYLE_DICT[font_description.get_style()] self.style_params["font.variant"] = \ FONT_VARIANT_DICT[font_description.get_variant()] # line colors self.style_params["axes.prop_cycle"] = cycler(color=self.line_colors) self.style_params["patch.facecolor"] = self.line_colors[0] # name & save new_name = self.style_name.get_text() if self.style.get_name() != new_name: new_name = Graphs.tools_get_duplicate_string( new_name, self._style_manager.list_stylenames(), ) style_io.write(self.style.get_file(), new_name, self.style_params) self._style_manager._on_style_change(True) self.style = None def _reload_line_colors(self): list_box = self.line_colors_box while list_box.get_last_child() is not None: list_box.remove(list_box.get_last_child()) if self.line_colors: for index in range(len(self.line_colors)): list_box.append(_StyleColorBox(self, index)) else: self.line_colors.append("#000000") list_box.append(_StyleColorBox(self, 0)) def on_color_change(self, button): """Handle color change.""" def on_accept(dialog, result): with contextlib.suppress(GLib.GError): color = dialog.choose_rgba_finish(result) if color is not None: button.color = color self._check_contrast() hex_color = Graphs.tools_rgba_to_hex(button.color) button.provider.load_from_string( f"button {{ color: {hex_color}; }}", ) dialog = Gtk.ColorDialog() dialog.set_with_alpha(False) dialog.choose_rgba( self.parent.get_application().get_window(), button.color, None, on_accept, ) def _check_contrast(self): contrast = Graphs.tools_get_contrast( self.outline_color.color, self.text_color.color, ) self.poor_contrast_warning.set_visible(contrast < 4.5) @Gtk.Template.Callback() def on_linestyle(self, comborow, _b): """Handle linestyle selection.""" self.linewidth.set_sensitive(comborow.get_selected() != 0) @Gtk.Template.Callback() def on_markers(self, comborow, _b): """Handle marker selection.""" self.markersize.set_sensitive(comborow.get_selected() != 0) @Gtk.Template.Callback() def add_color(self, _button): """Add a color.""" self.line_colors.append("000000") self._reload_line_colors() @Gtk.Template.Callback() def on_delete(self, _button): """Handle style deletion.""" def on_response(_dialog, response): if response == "cancel_delete_style": return if response == "delete_style": file = self.style.get_file() self._style_manager.delete_style(file) file.trash(None) self.style = None self.parent.get_navigation_view().pop() dialog = Graphs.tools_build_dialog("delete_style_dialog") msg = _("Are you sure you want to delete {stylename}?") dialog.set_body(msg.format(stylename=self.style.get_name())) dialog.connect("response", on_response) dialog.present(self.parent) @Gtk.Template(resource_path="/se/sjoerd/Graphs/ui/style-color-box.ui") class _StyleColorBox(Gtk.Box): __gtype_name__ = "GraphsStyleColorBox" label = Gtk.Template.Child() color_button = Gtk.Template.Child() parent = GObject.Property(type=StyleEditor) index = GObject.Property(type=int, default=0) def __init__(self, parent, index): super().__init__(parent=parent, index=index) self.label.set_label(_("Color {number}").format(number=index + 1)) self.provider = Gtk.CssProvider() self.color_button.get_style_context().add_provider( self.provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, ) self._reload_color() def _reload_color(self): color = self.props.parent.line_colors[self.props.index] self.provider.load_from_string(f"button {{ color: {color}; }}") @Gtk.Template.Callback() def on_color_choose(self, _button): def on_accept(dialog, result): with contextlib.suppress(GLib.GError): color = dialog.choose_rgba_finish(result) if color is not None: self.props.parent.line_colors[self.props.index] = \ Graphs.tools_rgba_to_hex(color) self._reload_color() dialog = Gtk.ColorDialog() dialog.set_with_alpha(False) dialog.choose_rgba( self.props.parent.parent, Graphs.tools_hex_to_rgba( self.props.parent.line_colors[self.props.index], ), None, on_accept, ) @Gtk.Template.Callback() def on_delete(self, _button): self.props.parent.line_colors.pop(self.props.index) self.props.parent._reload_line_colors() Graphs-v1.8.6-ab61976766ce7935774ea19327dcd572d8dc065e/graphs/styles.vala000066400000000000000000000204331511351636000241510ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later using Gdk; using Gtk; namespace Graphs { public int style_cmp (Style a, Style b) { if (a.file == null) return -1; else if (b.file == null) return 1; return strcmp (a.name.down (), b.name.down ()); } /** * Style manager */ public class StyleManager : Object { public Application application { get; construct set; } public bool use_custom_style { get; set; default = false; } public string custom_style { get; set; default = "Adwaita"; } public SingleSelection selection_model { get; set; } public File style_dir { get; construct set; } public string selected_stylename { get; protected set; } private GLib.ListStore style_model; construct { this.style_model = new GLib.ListStore (typeof (Style)); this.selection_model = new SingleSelection (this.style_model); try { File config_dir = Tools.get_config_directory (); this.style_dir = config_dir.get_child_for_display_name ("styles"); if (!this.style_dir.query_exists ()) { this.style_dir.make_directory_with_parents (); } } catch { assert_not_reached (); } } protected string[] load_system_styles (string system_style) { string[] stylenames = {}; try { var directory = File.new_for_uri ("resource:///se/sjoerd/Graphs/styles"); FileEnumerator enumerator = directory.enumerate_children ( "standard::*", FileQueryInfoFlags.NONE ); FileInfo info = null; while ((info = enumerator.next_file ()) != null) { File file = enumerator.get_child (info); var stream = new DataInputStream (file.read ()); stream.read_line_utf8 (); size_t size; string name = stream.read_line_utf8 (out size)[2: (long)size]; stylenames += name; string preview_name = info.get_name ().replace (".mplstyle", ".png"); var preview = Gdk.Texture.from_resource ( @"/se/sjoerd/Graphs/$preview_name" ); CompareDataFunc