pax_global_header00006660000000000000000000000064144342221310014506gustar00rootroot0000000000000052 comment=ed1d42dd5d9c689803e172d36a39d6db76aadeb9 altair-5.0.1/000077500000000000000000000000001443422213100127655ustar00rootroot00000000000000altair-5.0.1/.gitattributes000066400000000000000000000000141443422213100156530ustar00rootroot00000000000000* text=auto altair-5.0.1/.github/000077500000000000000000000000001443422213100143255ustar00rootroot00000000000000altair-5.0.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001443422213100165105ustar00rootroot00000000000000altair-5.0.1/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000013671443422213100211270ustar00rootroot00000000000000--- name: 'Bug report' about: 'Create a bug report to help us improve' labels: 'bug' --- Type here... --- Please follow these steps to make it more efficient to solve your issue: - [ ] Since Altair is a Python wrapper around the Vega-Lite visualization grammar, [most bugs should be reported directly to Vega-Lite](https://github.com/vega/vega-lite/issues). You can click the Action Button of your Altair chart and "Open in Vega Editor" to create a reproducible Vega-Lite example and see if you get the same error in the Vega Editor. - [ ] Search for duplicate issues. - [ ] Use the latest version of Altair. - [ ] Describe how to reproduce the bug and include the full code and data to reproduce it, ideally using a sample data set from `vega_datasets`. altair-5.0.1/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000007211443422213100205000ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Question url: https://stackoverflow.com/tags/altair about: Please ask questions such as "How do I do X?" or "Why does this not work?" on Stack Overflow using the `altair` tag. - name: Discussion url: https://github.com/altair-viz/altair/discussions about: If you want to discuss a topic or ask a question that is not a good fit for Stack Overflow, please open a new disscussion here on GitHub. altair-5.0.1/.github/ISSUE_TEMPLATE/feature-request.md000066400000000000000000000011731443422213100221550ustar00rootroot00000000000000--- name: 'Feature request' about: 'Suggest a new idea or feature' labels: 'enhancement' --- Type here... --- Please follow these steps to make it more efficient to respond to your feature request. - [ ] Since Altair is a Python wrapper around the Vega-Lite visualization grammar, [most feature requests should be reported directly to Vega-Lite](https://github.com/vega/vega-lite/issues). You can click the Action Button of your Altair chart and "Open in Vega Editor" to create a reproducible Vega-Lite example. - [ ] Search for duplicate issues. - [ ] Describe the feature's goal, motivating use cases, and its expected behavior. altair-5.0.1/.github/workflows/000077500000000000000000000000001443422213100163625ustar00rootroot00000000000000altair-5.0.1/.github/workflows/build.yml000066400000000000000000000061031443422213100202040ustar00rootroot00000000000000name: build on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] jsonschema-version: ["3.0", "4.17"] name: py ${{ matrix.python-version }} js ${{ matrix.jsonschema-version }} steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} # - name: Set Up Chromedriver # run: | # sudo apt-get update # sudo apt-get --only-upgrade install google-chrome-stable # sudo apt-get -yqq install chromium-chromedriver - name: Install dependencies run: | python -m pip install --upgrade pip pip install jsonschema==${{ matrix.jsonschema-version }} pip install .[dev] # pip install "selenium<4.3.0" # pip install altair_saver - name: Test that schema generation has no effect run: | python tools/generate_schema_wrapper.py # This gets the paths of all files which were either deleted, modified # or are not yet tracked by Git files=`git ls-files --deleted --modified --others --exclude-standard` # Depending on the shell it can happen that 'files' contains empty # lines which are filtered out in the for loop below files_cleaned=() for i in "${files[@]}"; do # Skip empty items if [ -z "$i" ]; then continue fi # Add the rest of the elements to a new array files_cleaned+=("${i}") done if [ ${#files_cleaned[@]} -gt 0 ]; then echo "The code generation modified the following files:" echo $files exit 1 fi - name: Test with pytest run: | pytest --doctest-modules tests # - name: Selected tests without vl-convert-python # run: | # pip uninstall vl-convert-python --yes # pytest -m save_engine --doctest-modules tests # - name: Selected tests without vl-convert-python and altair_saver # run: | # pip uninstall altair_saver --yes # pytest -m save_engine --doctest-modules tests - name: Selected tests with vl-convert-python and without altair_saver run: | # pip install vl-convert-python pytest -m save_engine --doctest-modules tests - name: Validate Vega-Lite schema run: | # We install all 'format' dependencies of jsonschema as check-jsonschema # only does the 'format' checks which are installed. # We can always use the latest jsonschema version here. # uri-reference check is disabled as the URIs in the Vega-Lite schema do # not conform RFC 3986. pip install 'jsonschema[format]' check-jsonschema --upgrade check-jsonschema --check-metaschema altair/vegalite/v5/schema/vega-lite-schema.json --disable-formats uri-reference altair-5.0.1/.github/workflows/docbuild.yml000066400000000000000000000007771443422213100207050ustar00rootroot00000000000000name: docbuild on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python 3.10 uses: actions/setup-python@v4 with: python-version: "3.10" - name: Install dependencies run: | python -m pip install --upgrade pip pip install hatch - name: Run doc:build-html run: | hatch run doc:build-html - name: Run doc:doctest run: | hatch run doc:doctest altair-5.0.1/.github/workflows/lint.yml000066400000000000000000000013131443422213100200510ustar00rootroot00000000000000name: lint on: [push, pull_request] jobs: build: runs-on: ubuntu-latest name: black-ruff-mypy steps: - uses: actions/checkout@v3 - name: Set up Python 3.10 uses: actions/setup-python@v4 with: python-version: "3.10" # Installing all dependencies and not just the linters as mypy needs them for type checking - name: Install dependencies run: | python -m pip install --upgrade pip pip install .[dev] - name: Check formatting with black run: | black --diff --color . black --check . - name: Lint with ruff run: | ruff check . - name: Lint with mypy run: | mypy altair tests altair-5.0.1/.gitignore000066400000000000000000000015451443422213100147620ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ # emacs backups *~ \#*\# .ipynb_checkpoints .idea/* tools/_build Untitled*.ipynb .mypy* .pytest_cache *.DS_Store # VSCode .vscode # hatch, doc generation data.jsonaltair-5.0.1/CONTRIBUTING.md000066400000000000000000000205401443422213100152170ustar00rootroot00000000000000# Feedback and Contribution We welcome any input, feedback, bug reports, and contributions via [Altair's GitHub Repository](http://github.com/altair-viz/altair/). In particular, we welcome companion efforts from other visualization libraries to render the Vega-Lite specifications output by Altair. We see this portion of the effort as much bigger than Altair itself: the Vega and Vega-Lite specifications are perhaps the best existing candidates for a principled *lingua franca* of data visualization. We are also seeking contributions of additional Jupyter notebook-based examples in our separate GitHub repository: https://github.com/altair-viz/altair_notebooks. ## How To Contribute Code to Vega-Altair ### Setting Up Your Environment Fork the Altair repository on GitHub and then clone the fork to you local machine. For more details on forking see the [GitHub Documentation](https://help.github.com/en/articles/fork-a-repo). ```cmd git clone https://github.com/YOUR-USERNAME/altair.git ``` To keep your fork up to date with changes in this repo, you can [use the fetch upstream button on GitHub](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork). Now you can install the latest version of Altair locally using `pip`. The `-e` flag indicates that your local changes will be reflected every time you open a new Python interpreter (instead of having to reinstall the package each time). ```cmd cd altair/ python -m pip install -e .[dev] ``` '[dev]' indicates that pip should also install the development requirements which you can find in `pyproject.toml` (`[project.optional-dependencies]/dev`) ### Creating a Branch Once your local environment is up-to-date, you can create a new git branch which will contain your contribution (always create a new branch instead of making changes to the master branch): ```cmd git switch -c ``` With this branch checked-out, make the desired changes to the package. ### Testing your Changes Before suggesting your contributing your changing to the main Altair repository, it is recommended that you run the Altair test suite, which includes a number of tests to validate the correctness of your code: ```cmd hatch run test ``` This also runs the [`black`](https://black.readthedocs.io/) code formatter, [`ruff`](https://ruff.rs/) linter and [`mypy`](https://mypy-lang.org/) as type checker. Study the output of any failed tests and try to fix the issues before proceeding to the next section. ### Creating a Pull Request When you are happy with your changes, you can commit them to your branch by running ```cmd git add git commit -m "Some descriptive message about your change" git push origin ``` You will then need to submit a pull request (PR) on GitHub asking to merge your example branch into the main Altair repository. For details on creating a PR see GitHub documentation [Creating a pull request](https://help.github.com/en/articles/creating-a-pull-request). You can add more details about your example in the PR such as motivation for the example or why you thought it would be a good addition. You will get feed back in the PR discussion if anything needs to be changed. To make changes continue to push commits made in your local example branch to origin and they will be automatically shown in the PR. Hopefully your PR will be answered in a timely manner and your contribution will help others in the future. ## How To Contribute Documentation to Vega-Altair Altair documentation is written in [reStructuredText](http://docutils.sourceforge.net/rst.html) and compiled into html pages using [Sphinx](http://www.sphinx-doc.org/en/master/). Contributing to the documentation requires some extra dependencies and we have some conventions and plugins that are used to help navigate the docs and generate great Altair visualizations. Note that the [Altair website](https://altair-viz.github.io/) is only updated when a new version is released so your contribution might not show up for a while. ### Adding Examples We are always interested in new examples contributed from the community. These could be everything from simple one-panel scatter and line plots, to more complicated layered or stacked plots, to more advanced interactive features. Before submitting a new example check the [Altair Example Gallery](https://altair-viz.github.io/gallery/index.html) to make sure that your idea has not already been implemented. Once you have an example you would like to add there are a few guide lines to follow. Every example should: - be saved as a stand alone script in the `altair/examples/` directory. - have a descriptive docstring, which will eventually be extracted for the documentation website. - contain a category tag. - define a chart variable with the main chart object (This will be used both in the unit tests to confirm that the example executes properly, and also eventually used to display the visualization on the documentation website). - not make any external calls to download data within the script (i.e. don't use urllib). You can define your data directly within the example file, generate your data using pandas and numpy, or you can use data available in the `vega_datasets` package. The easiest way to get started would be to adapt examples from the [Vega-Lite example gallery](https://vega.github.io/vega-lite/examples/) which are missing in the Altair gallery. Or you can feel free to be creative and build your own visualizations. Often it is convenient to draft an example outside of the main repository, such as [Google Colab](https://colab.research.google.com/), to avoid difficulties when working with git. Once you have an example you would like to add, follow the same contribution procedure outlined above. Some additional notes: - all examples should be in their own file in the `altair/examples` directory, and the format and style of new contributions should generally match that of existing examples. - The file docstring will be rendered into HTML via [reStructuredText](http://docutils.sourceforge.net/rst.html), so use that format for any hyperlinks or text styling. In particular, be sure you include a title in the docstring underlined with `---`, and be sure that the size of the underline exactly matches the size of the title text. - If your example fits into a chart type but involves significant configuration it should be in the `Case Studies` category. - For consistency all data used for a visualization should be assigned to the variable `source`. Then `source` is passed to the `alt.Chart` object. See other examples for guidance. - Example code should not require downloading external datasets. We suggest using the `vega_datasets` package if possible. If you are using the `vega_datasets` package there are multiple ways to refer to a data source. If the dataset you would like to use is included in local installation (`vega_datasets.local_data.list_datasets()`) then the data can be referenced directly, such as `source = data.iris()`. If the data is not included then it should be referenced by URL, such as `source = data.movies.url`. This is to ensure that Altair's automated test suite does not depend on availability of external HTTP resources. - If VlConvert does not support PNG export of the chart (e.g. in the case of emoji), then add the name of the example to the `SVG_EXAMPLES` set in `tests/examples_arguments_syntax/__init__.py` and `tests/examples_methods_syntax/__init__.py` ### Building the Documentation Locally The process to build the documentation locally consists of three steps: 1. Clean any previously generated files to ensure a clean build. 2. Generate the documentation in HTML format. 3. View the generated documentation using a local Python testing server. The specific commands for each step depend on your operating system. Make sure you execute the following commands from the root dir of altair and have [`hatch`](https://hatch.pypa.io/) installed in your local environment. - For MacOS and Linux, run the following commands in your terminal: ```cmd hatch run doc:clean-all hatch run doc:build-html hatch run doc:serve ``` - For Windows, use these commands instead: ```cmd hatch run doc:clean-all-win hatch run doc:build-html-win hatch run doc:serve ``` To view the documentation, open your browser and go to `http://localhost:8000`. To stop the server, use `^C` (control+c) in the terminal.altair-5.0.1/LICENSE000066400000000000000000000027311443422213100137750ustar00rootroot00000000000000Copyright (c) 2015-2023, Vega-Altair Developers All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of vega-altair nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. altair-5.0.1/NOTES_FOR_MAINTAINERS.md000066400000000000000000000040721443422213100165420ustar00rootroot00000000000000# Notes for Maintainers of Altair ## Auto-generating the Python code The core Python API for Altair can be found in the following locations: - ``altair/vegalite/v5/schema/`` All the files within these directories are created automatically by running the following script from the root of the repository: ```bash $ python tools/generate_schema_wrapper.py ``` This script does a couple things: - downloads the appropriate schema files from the specified vega-lite release versions & copies the JSON file to the appropriate ``schema`` directory - generates basic low-level schemapi wrappers from the definitions within the schema: this is put in the ``schema/core.py`` file - generates a second layer of higher level wrappers for some vega-lite functionality; this is put in ``schema/channels.py`` and ``schema/mixins.py`` The script output is designed to be deterministic; if the vega-lite version is not changed, then running the script should overwrite the schema wrappers with identical copies. ## Updating the Vega-Lite version The vega & vega-lite versions for the Python code can be updated by manually changing the ``SCHEMA_VERSION`` definition within ``tools/generate_schema_wrapper.py``, and then re-running the script. This will update all of the automatically-generated files in the ``schema`` directory for each version, but please note that it will *not* update other pieces (for example, the core of the Altair API, including methods and doc strings within ``altair/vegalite/v5/api.py``. These additional methods have fairly good test coverage, so running the test suite should identify any inconsistencies: ``` $ make test ``` Generally, minor version updates (e.g. Vega-Lite 2.3->2.4) have been relatively painless, maybe requiring the addition of a few chart methods or modification of some docstrings. Major version updates (e.g. Vega-Lite 1.X->2.X) have required substantial rewrites, because the internal structure of the schema changed appreciably. ## Releasing the Package To cut a new release of Altair, follow the steps outlined in [RELEASING.md](RELEASING.md).altair-5.0.1/README.md000066400000000000000000000142441443422213100142510ustar00rootroot00000000000000# Vega-Altair [![github actions](https://github.com/altair-viz/altair/workflows/build/badge.svg)](https://github.com/altair-viz/altair/actions?query=workflow%3Abuild) [![code style black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![JOSS Paper](https://joss.theoj.org/papers/10.21105/joss.01057/status.svg)](https://joss.theoj.org/papers/10.21105/joss.01057) [![PyPI - Downloads](https://img.shields.io/pypi/dm/altair)](https://pypi.org/project/altair) **Vega-Altair** is a declarative statistical visualization library for Python. With Vega-Altair, you can spend more time understanding your data and its meaning. Vega-Altair's API is simple, friendly and consistent and built on top of the powerful [Vega-Lite](https://github.com/vega/vega-lite) JSON specification. This elegant simplicity produces beautiful and effective visualizations with a minimal amount of code. *Vega-Altair was originally developed by [Jake Vanderplas](https://github.com/jakevdp) and [Brian Granger](https://github.com/ellisonbg) in close collaboration with the [UW Interactive Data Lab](https://idl.cs.washington.edu/).* *The Vega-Altair open source project is not affiliated with Altair Engineering, Inc.* ## Documentation See [Vega-Altair's Documentation Site](https://altair-viz.github.io) as well as the [Tutorial Notebooks](https://github.com/altair-viz/altair_notebooks). You can run the notebooks directly in your browser by clicking on one of the following badges: [![Binder](https://beta.mybinder.org/badge.svg)](https://beta.mybinder.org/v2/gh/altair-viz/altair_notebooks/master) [![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/altair-viz/altair_notebooks/blob/master/notebooks/Index.ipynb) ## Example Here is an example using Vega-Altair to quickly visualize and display a dataset with the native Vega-Lite renderer in the JupyterLab: ```python import altair as alt # load a simple dataset as a pandas DataFrame from vega_datasets import data cars = data.cars() alt.Chart(cars).mark_point().encode( x='Horsepower', y='Miles_per_Gallon', color='Origin', ) ``` ![Vega-Altair Visualization](https://raw.githubusercontent.com/altair-viz/altair/master/images/cars.png) One of the unique features of Vega-Altair, inherited from Vega-Lite, is a declarative grammar of not just visualization, but _interaction_. With a few modifications to the example above we can create a linked histogram that is filtered based on a selection of the scatter plot. ```python import altair as alt from vega_datasets import data source = data.cars() brush = alt.selection_interval() points = alt.Chart(source).mark_point().encode( x='Horsepower', y='Miles_per_Gallon', color=alt.condition(brush, 'Origin', alt.value('lightgray')) ).add_params( brush ) bars = alt.Chart(source).mark_bar().encode( y='Origin', color='Origin', x='count(Origin)' ).transform_filter( brush ) points & bars ``` ![Vega-Altair Visualization Gif](https://raw.githubusercontent.com/altair-viz/altair/master/images/cars_scatter_bar.gif) ## Features * Carefully-designed, declarative Python API. * Auto-generated internal Python API that guarantees visualizations are type-checked and in full conformance with the [Vega-Lite](https://github.com/vega/vega-lite) specification. * Display visualizations in JupyterLab, Jupyter Notebook, Visual Studio Code, on GitHub and [nbviewer](https://nbviewer.jupyter.org/), and many more. * Export visualizations to various formats such as PNG/SVG images, stand-alone HTML pages and the [Online Vega-Lite Editor](https://vega.github.io/editor/#/). * Serialize visualizations as JSON files. ## Installation Vega-Altair can be installed with: ```bash pip install altair ``` If you are using the conda package manager, the equivalent is: ```bash conda install altair -c conda-forge ``` For full installation instructions, please see [the documentation](https://altair-viz.github.io/getting_started/installation.html). ## Getting Help If you have a question that is not addressed in the documentation, you can post it on [StackOverflow](https://stackoverflow.com/questions/tagged/altair) using the `altair` tag. For bugs and feature requests, please open a [Github Issue](https://github.com/altair-viz/altair/issues). ## Development You can find the instructions on how to install the package for development in [the documentation](https://altair-viz.github.io/getting_started/installation.html). To run the tests and linters, use ``` hatch run test ``` For information on how to contribute your developments back to the Vega-Altair repository, see [`CONTRIBUTING.md`](https://github.com/altair-viz/altair/blob/master/CONTRIBUTING.md) ## Citing Vega-Altair [![JOSS Paper](https://joss.theoj.org/papers/10.21105/joss.01057/status.svg)](https://joss.theoj.org/papers/10.21105/joss.01057) If you use Vega-Altair in academic work, please consider citing https://joss.theoj.org/papers/10.21105/joss.01057 as ```bib @article{VanderPlas2018, doi = {10.21105/joss.01057}, url = {https://doi.org/10.21105/joss.01057}, year = {2018}, publisher = {The Open Journal}, volume = {3}, number = {32}, pages = {1057}, author = {Jacob VanderPlas and Brian Granger and Jeffrey Heer and Dominik Moritz and Kanit Wongsuphasawat and Arvind Satyanarayan and Eitan Lees and Ilia Timofeev and Ben Welsh and Scott Sievert}, title = {Altair: Interactive Statistical Visualizations for Python}, journal = {Journal of Open Source Software} } ``` Please additionally consider citing the [Vega-Lite](https://vega.github.io/vega-lite/) project, which Vega-Altair is based on: https://dl.acm.org/doi/10.1109/TVCG.2016.2599030 ```bib @article{Satyanarayan2017, author={Satyanarayan, Arvind and Moritz, Dominik and Wongsuphasawat, Kanit and Heer, Jeffrey}, title={Vega-Lite: A Grammar of Interactive Graphics}, journal={IEEE transactions on visualization and computer graphics}, year={2017}, volume={23}, number={1}, pages={341-350}, publisher={IEEE} } ``` altair-5.0.1/RELEASING.md000066400000000000000000000047761443422213100146360ustar00rootroot000000000000001. Create a new virtual environment following the instructions in `CONTRIBUTING.md`. Make sure to also install all dependencies for the documentation including `altair_saver` and uninstall `vl-convert-python` (this is not needed for normal contributions to the repo, see `CONTRIBUTING.md` for details). 2. Make certain your branch is in sync with head: git pull upstream master 3. Do a clean doc build: hatch run doc:clean-all hatch run doc:build-html hatch run doc:serve Navigate to http://localhost:8000 and ensure it looks OK (particularly do a visual scan of the gallery thumbnails). 4. Make sure changes.rst is up to date for the release: compare against PRs merged since the last release & update top heading with release date. 5. Update version to, e.g. 5.0.0: - in ``altair/__init__.py`` - in ``doc/conf.py`` (two places) 6. Double-check that all vega-lite/vega/vega-embed versions are up-to-date: - URLs in ``doc/conf.py`` - versions in ``altair/vegalite/v5/display.py`` 7. Commit change and push to master: git add . -u git commit -m "MAINT: bump version to 5.0.0" git push upstream master 8. Tag the release: git tag -a v5.0.0 -m "version 5.0.0 release" git push upstream v5.0.0 9. Build source & wheel distributions: hatch clean # clean old builds & distributions hatch build # create a source distribution and universal wheel 10. publish to PyPI (Requires correct PyPI owner permissions): hatch publish 11. build and publish docs (Requires write-access to altair-viz/altair-viz.github.io): hatch run doc:publish-clean-build 12. update version to, e.g. 5.1.0dev: - in ``altair/__init__.py`` - in ``doc/conf.py`` (two places) 13. add a new changelog entry for the unreleased version: Version 5.1.0 (unreleased) -------------------------- Enhancements ~~~~~~~~~~~~ Bug Fixes ~~~~~~~~~ Backward-Incompatible Changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 14. Commit change and push to master: git add . -u git commit -m "MAINT: bump version to 5.1.0dev" git push upstream master 15. Double-check that a conda-forge pull request is generated from the updated pip package by the conda-forge bot (may take up to ~an hour): https://github.com/conda-forge/altair-feedstock/pulls 16. Copy changes.rst section into release notes within https://github.com/altair-viz/altair/releases/, and publish the release. altair-5.0.1/altair/000077500000000000000000000000001443422213100142415ustar00rootroot00000000000000altair-5.0.1/altair/__init__.py000066400000000000000000000337741443422213100163700ustar00rootroot00000000000000# ruff: noqa __version__ = "5.0.1" from typing import Any # Necessary as mypy would see expr as the module alt.expr although due to how # the imports are set up it is expr in the alt.expr module expr: Any # The content of __all__ is automatically written by # tools/update_init_file.py. Do not modify directly. __all__ = [ "Aggregate", "AggregateOp", "AggregateTransform", "AggregatedFieldDef", "Align", "AllSortString", "Angle", "AngleDatum", "AngleValue", "AnyMark", "AnyMarkConfig", "AreaConfig", "ArgmaxDef", "ArgminDef", "AutoSizeParams", "AutosizeType", "Axis", "AxisConfig", "AxisOrient", "AxisResolveMap", "BBox", "BarConfig", "BaseTitleNoValueRefs", "Baseline", "Bin", "BinExtent", "BinParams", "BinTransform", "BindCheckbox", "BindDirect", "BindInput", "BindRadioSelect", "BindRange", "Binding", "Blend", "BoxPlot", "BoxPlotConfig", "BoxPlotDef", "BrushConfig", "CalculateTransform", "Categorical", "Chart", "Color", "ColorDatum", "ColorDef", "ColorName", "ColorScheme", "ColorValue", "Column", "CompositeMark", "CompositeMarkDef", "CompositionConfig", "ConcatChart", "ConcatSpecGenericSpec", "ConditionalAxisColor", "ConditionalAxisLabelAlign", "ConditionalAxisLabelBaseline", "ConditionalAxisLabelFontStyle", "ConditionalAxisLabelFontWeight", "ConditionalAxisNumber", "ConditionalAxisNumberArray", "ConditionalAxisPropertyAlignnull", "ConditionalAxisPropertyColornull", "ConditionalAxisPropertyFontStylenull", "ConditionalAxisPropertyFontWeightnull", "ConditionalAxisPropertyTextBaselinenull", "ConditionalAxisPropertynumberArraynull", "ConditionalAxisPropertynumbernull", "ConditionalAxisPropertystringnull", "ConditionalAxisString", "ConditionalMarkPropFieldOrDatumDef", "ConditionalMarkPropFieldOrDatumDefTypeForShape", "ConditionalParameterMarkPropFieldOrDatumDef", "ConditionalParameterMarkPropFieldOrDatumDefTypeForShape", "ConditionalParameterStringFieldDef", "ConditionalParameterValueDefGradientstringnullExprRef", "ConditionalParameterValueDefTextExprRef", "ConditionalParameterValueDefnumber", "ConditionalParameterValueDefnumberArrayExprRef", "ConditionalParameterValueDefnumberExprRef", "ConditionalParameterValueDefstringExprRef", "ConditionalParameterValueDefstringnullExprRef", "ConditionalPredicateMarkPropFieldOrDatumDef", "ConditionalPredicateMarkPropFieldOrDatumDefTypeForShape", "ConditionalPredicateStringFieldDef", "ConditionalPredicateValueDefAlignnullExprRef", "ConditionalPredicateValueDefColornullExprRef", "ConditionalPredicateValueDefFontStylenullExprRef", "ConditionalPredicateValueDefFontWeightnullExprRef", "ConditionalPredicateValueDefGradientstringnullExprRef", "ConditionalPredicateValueDefTextBaselinenullExprRef", "ConditionalPredicateValueDefTextExprRef", "ConditionalPredicateValueDefnumber", "ConditionalPredicateValueDefnumberArrayExprRef", "ConditionalPredicateValueDefnumberArraynullExprRef", "ConditionalPredicateValueDefnumberExprRef", "ConditionalPredicateValueDefnumbernullExprRef", "ConditionalPredicateValueDefstringExprRef", "ConditionalPredicateValueDefstringnullExprRef", "ConditionalStringFieldDef", "ConditionalValueDefGradientstringnullExprRef", "ConditionalValueDefTextExprRef", "ConditionalValueDefnumber", "ConditionalValueDefnumberArrayExprRef", "ConditionalValueDefnumberExprRef", "ConditionalValueDefstringExprRef", "ConditionalValueDefstringnullExprRef", "Config", "CsvDataFormat", "Cursor", "Cyclical", "Data", "DataFormat", "DataSource", "Datasets", "DateTime", "DatumChannelMixin", "DatumDef", "Day", "DensityTransform", "DerivedStream", "Description", "DescriptionValue", "Detail", "Dict", "DictInlineDataset", "DictSelectionInit", "DictSelectionInitInterval", "Diverging", "DomainUnionWith", "DsvDataFormat", "Element", "Encoding", "EncodingSortField", "ErrorBand", "ErrorBandConfig", "ErrorBandDef", "ErrorBar", "ErrorBarConfig", "ErrorBarDef", "ErrorBarExtent", "EventStream", "EventType", "Expr", "ExprRef", "Facet", "FacetChart", "FacetEncodingFieldDef", "FacetFieldDef", "FacetMapping", "FacetSpec", "FacetedEncoding", "FacetedUnitSpec", "Feature", "FeatureCollection", "FeatureGeometryGeoJsonProperties", "Field", "FieldChannelMixin", "FieldDefWithoutScale", "FieldEqualPredicate", "FieldGTEPredicate", "FieldGTPredicate", "FieldLTEPredicate", "FieldLTPredicate", "FieldName", "FieldOneOfPredicate", "FieldOrDatumDefWithConditionDatumDefGradientstringnull", "FieldOrDatumDefWithConditionDatumDefnumber", "FieldOrDatumDefWithConditionDatumDefnumberArray", "FieldOrDatumDefWithConditionDatumDefstringnull", "FieldOrDatumDefWithConditionMarkPropFieldDefGradientstringnull", "FieldOrDatumDefWithConditionMarkPropFieldDefTypeForShapestringnull", "FieldOrDatumDefWithConditionMarkPropFieldDefnumber", "FieldOrDatumDefWithConditionMarkPropFieldDefnumberArray", "FieldOrDatumDefWithConditionStringDatumDefText", "FieldOrDatumDefWithConditionStringFieldDefText", "FieldOrDatumDefWithConditionStringFieldDefstring", "FieldRange", "FieldRangePredicate", "FieldValidPredicate", "Fill", "FillDatum", "FillOpacity", "FillOpacityDatum", "FillOpacityValue", "FillValue", "FilterTransform", "Fit", "FlattenTransform", "FoldTransform", "FontStyle", "FontWeight", "Generator", "GenericUnitSpecEncodingAnyMark", "GeoJsonFeature", "GeoJsonFeatureCollection", "GeoJsonProperties", "Geometry", "GeometryCollection", "Gradient", "GradientStop", "GraticuleGenerator", "GraticuleParams", "HConcatChart", "HConcatSpecGenericSpec", "Header", "HeaderConfig", "HexColor", "Href", "HrefValue", "Impute", "ImputeMethod", "ImputeParams", "ImputeSequence", "ImputeTransform", "InlineData", "InlineDataset", "Interpolate", "IntervalSelectionConfig", "IntervalSelectionConfigWithoutType", "JoinAggregateFieldDef", "JoinAggregateTransform", "JsonDataFormat", "Key", "LabelOverlap", "LatLongDef", "LatLongFieldDef", "Latitude", "Latitude2", "Latitude2Datum", "Latitude2Value", "LatitudeDatum", "LayerChart", "LayerRepeatMapping", "LayerRepeatSpec", "LayerSpec", "LayoutAlign", "Legend", "LegendBinding", "LegendConfig", "LegendOrient", "LegendResolveMap", "LegendStreamBinding", "LineConfig", "LineString", "LinearGradient", "LocalMultiTimeUnit", "LocalSingleTimeUnit", "Locale", "LoessTransform", "LogicalAndPredicate", "LogicalNotPredicate", "LogicalOrPredicate", "Longitude", "Longitude2", "Longitude2Datum", "Longitude2Value", "LongitudeDatum", "LookupData", "LookupSelection", "LookupTransform", "Mark", "MarkConfig", "MarkDef", "MarkPropDefGradientstringnull", "MarkPropDefnumber", "MarkPropDefnumberArray", "MarkPropDefstringnullTypeForShape", "MarkType", "MaxRowsError", "MergedStream", "Month", "MultiLineString", "MultiPoint", "MultiPolygon", "MultiTimeUnit", "NamedData", "NonArgAggregateOp", "NonLayerRepeatSpec", "NonNormalizedSpec", "NumberLocale", "NumericArrayMarkPropDef", "NumericMarkPropDef", "OffsetDef", "Opacity", "OpacityDatum", "OpacityValue", "Order", "OrderFieldDef", "OrderValue", "OrderValueDef", "Orient", "Orientation", "OverlayMarkDef", "Padding", "Parameter", "ParameterExpression", "ParameterExtent", "ParameterName", "ParameterPredicate", "Parse", "ParseValue", "PivotTransform", "Point", "PointSelectionConfig", "PointSelectionConfigWithoutType", "PolarDef", "Polygon", "Position", "Position2Def", "PositionDatumDef", "PositionDatumDefBase", "PositionDef", "PositionFieldDef", "PositionFieldDefBase", "PositionValueDef", "Predicate", "PredicateComposition", "PrimitiveValue", "Projection", "ProjectionConfig", "ProjectionType", "QuantileTransform", "RadialGradient", "Radius", "Radius2", "Radius2Datum", "Radius2Value", "RadiusDatum", "RadiusValue", "RangeConfig", "RangeEnum", "RangeRaw", "RangeRawArray", "RangeScheme", "RectConfig", "RegressionTransform", "RelativeBandSize", "RepeatChart", "RepeatMapping", "RepeatRef", "RepeatSpec", "Resolve", "ResolveMode", "Root", "Row", "RowColLayoutAlign", "RowColboolean", "RowColnumber", "RowColumnEncodingFieldDef", "SCHEMA_URL", "SCHEMA_VERSION", "SampleTransform", "Scale", "ScaleBinParams", "ScaleBins", "ScaleConfig", "ScaleDatumDef", "ScaleFieldDef", "ScaleInterpolateEnum", "ScaleInterpolateParams", "ScaleResolveMap", "ScaleType", "SchemaBase", "SchemeParams", "SecondaryFieldDef", "SelectionConfig", "SelectionExpression", "SelectionInit", "SelectionInitInterval", "SelectionInitIntervalMapping", "SelectionInitMapping", "SelectionParameter", "SelectionPredicateComposition", "SelectionResolution", "SelectionType", "SequenceGenerator", "SequenceParams", "SequentialMultiHue", "SequentialSingleHue", "Shape", "ShapeDatum", "ShapeDef", "ShapeValue", "SharedEncoding", "SingleDefUnitChannel", "SingleTimeUnit", "Size", "SizeDatum", "SizeValue", "Sort", "SortArray", "SortByChannel", "SortByChannelDesc", "SortByEncoding", "SortField", "SortOrder", "Spec", "SphereGenerator", "StackOffset", "StackTransform", "StandardType", "Step", "StepFor", "Stream", "StringFieldDef", "StringFieldDefWithCondition", "StringValueDefWithCondition", "Stroke", "StrokeCap", "StrokeDash", "StrokeDashDatum", "StrokeDashValue", "StrokeDatum", "StrokeJoin", "StrokeOpacity", "StrokeOpacityDatum", "StrokeOpacityValue", "StrokeValue", "StrokeWidth", "StrokeWidthDatum", "StrokeWidthValue", "StyleConfigIndex", "SymbolShape", "TOPLEVEL_ONLY_KEYS", "Text", "TextBaseline", "TextDatum", "TextDef", "TextDirection", "TextValue", "Theta", "Theta2", "Theta2Datum", "Theta2Value", "ThetaDatum", "ThetaValue", "TickConfig", "TickCount", "TimeInterval", "TimeIntervalStep", "TimeLocale", "TimeUnit", "TimeUnitParams", "TimeUnitTransform", "Title", "TitleAnchor", "TitleConfig", "TitleFrame", "TitleOrient", "TitleParams", "Tooltip", "TooltipContent", "TooltipValue", "TopLevelConcatSpec", "TopLevelFacetSpec", "TopLevelHConcatSpec", "TopLevelLayerSpec", "TopLevelMixin", "TopLevelParameter", "TopLevelRepeatSpec", "TopLevelSelectionParameter", "TopLevelSpec", "TopLevelUnitSpec", "TopLevelVConcatSpec", "TopoDataFormat", "Transform", "Type", "TypeForShape", "TypedFieldDef", "URI", "Undefined", "UnitSpec", "UnitSpecWithFrame", "Url", "UrlData", "UrlValue", "UtcMultiTimeUnit", "UtcSingleTimeUnit", "VConcatChart", "VConcatSpecGenericSpec", "VEGAEMBED_VERSION", "VEGALITE_VERSION", "VEGA_VERSION", "ValueChannelMixin", "ValueDefWithConditionMarkPropFieldOrDatumDefGradientstringnull", "ValueDefWithConditionMarkPropFieldOrDatumDefTypeForShapestringnull", "ValueDefWithConditionMarkPropFieldOrDatumDefnumber", "ValueDefWithConditionMarkPropFieldOrDatumDefnumberArray", "ValueDefWithConditionMarkPropFieldOrDatumDefstringnull", "ValueDefWithConditionStringFieldDefText", "ValueDefnumber", "ValueDefnumberwidthheightExprRef", "VariableParameter", "Vector10string", "Vector12string", "Vector2DateTime", "Vector2Vector2number", "Vector2boolean", "Vector2number", "Vector2string", "Vector3number", "Vector7string", "VegaLite", "VegaLiteSchema", "ViewBackground", "ViewConfig", "WindowEventType", "WindowFieldDef", "WindowOnlyOp", "WindowTransform", "X", "X2", "X2Datum", "X2Value", "XDatum", "XError", "XError2", "XError2Value", "XErrorValue", "XOffset", "XOffsetDatum", "XOffsetValue", "XValue", "Y", "Y2", "Y2Datum", "Y2Value", "YDatum", "YError", "YError2", "YError2Value", "YErrorValue", "YOffset", "YOffsetDatum", "YOffsetValue", "YValue", "api", "binding", "binding_checkbox", "binding_radio", "binding_range", "binding_select", "channels", "check_fields_and_encodings", "concat", "condition", "core", "curry", "data", "data_transformers", "datum", "default_data_transformer", "display", "expr", "graticule", "hconcat", "layer", "limit_rows", "load_ipython_extension", "load_schema", "mixins", "overload", "param", "parse_shorthand", "pipe", "renderers", "repeat", "sample", "schema", "selection_interval", "selection_point", "sequence", "sphere", "theme", "themes", "to_csv", "to_json", "to_values", "topo_feature", "utils", "v5", "value", "vconcat", "vegalite", "with_property_setters", ] def __dir__(): return __all__ from .vegalite import * def load_ipython_extension(ipython): from ._magics import vegalite ipython.register_magic_function(vegalite, "cell") altair-5.0.1/altair/_magics.py000066400000000000000000000057021443422213100162210ustar00rootroot00000000000000""" Magic functions for rendering vega-lite specifications """ __all__ = ["vegalite"] import json import warnings import IPython from IPython.core import magic_arguments import pandas as pd from toolz import curried from altair.vegalite import v5 as vegalite_v5 try: import yaml YAML_AVAILABLE = True except ImportError: YAML_AVAILABLE = False RENDERERS = { "vega-lite": { "5": vegalite_v5.VegaLite, }, } TRANSFORMERS = { "vega-lite": { "5": vegalite_v5.data_transformers, }, } def _prepare_data(data, data_transformers): """Convert input data to data for use within schema""" if data is None or isinstance(data, dict): return data elif isinstance(data, pd.DataFrame): return curried.pipe(data, data_transformers.get()) elif isinstance(data, str): return {"url": data} else: warnings.warn("data of type {} not recognized".format(type(data)), stacklevel=1) return data def _get_variable(name): """Get a variable from the notebook namespace.""" ip = IPython.get_ipython() if ip is None: raise ValueError( "Magic command must be run within an IPython " "environemnt, in which get_ipython() is defined." ) if name not in ip.user_ns: raise NameError( "argument '{}' does not match the " "name of any defined variable".format(name) ) return ip.user_ns[name] @magic_arguments.magic_arguments() @magic_arguments.argument( "data", nargs="?", help="local variablename of a pandas DataFrame to be used as the dataset", ) @magic_arguments.argument("-v", "--version", dest="version", default="v5") @magic_arguments.argument("-j", "--json", dest="json", action="store_true") def vegalite(line, cell): """Cell magic for displaying vega-lite visualizations in CoLab. %%vegalite [dataframe] [--json] [--version='v5'] Visualize the contents of the cell using Vega-Lite, optionally specifying a pandas DataFrame object to be used as the dataset. if --json is passed, then input is parsed as json rather than yaml. """ args = magic_arguments.parse_argstring(vegalite, line) existing_versions = {"v5": "5"} version = existing_versions[args.version] assert version in RENDERERS["vega-lite"] VegaLite = RENDERERS["vega-lite"][version] data_transformers = TRANSFORMERS["vega-lite"][version] if args.json: spec = json.loads(cell) elif not YAML_AVAILABLE: try: spec = json.loads(cell) except json.JSONDecodeError as err: raise ValueError( "%%vegalite: spec is not valid JSON. " "Install pyyaml to parse spec as yaml" ) from err else: spec = yaml.load(cell, Loader=yaml.SafeLoader) if args.data is not None: data = _get_variable(args.data) spec["data"] = _prepare_data(data, data_transformers) return VegaLite(spec) altair-5.0.1/altair/expr/000077500000000000000000000000001443422213100152175ustar00rootroot00000000000000altair-5.0.1/altair/expr/__init__.py000066400000000000000000000007001443422213100173250ustar00rootroot00000000000000"""Tools for creating transform & filter expressions with a python syntax""" # ruff: noqa from typing import Any from .core import datum, Expression from .funcs import * from .consts import * from ..vegalite.v5.schema.core import ExprRef as _ExprRef class _ExprType: def __init__(self, expr): vars(self).update(expr) def __call__(self, expr, **kwargs): return _ExprRef(expr, **kwargs) expr: Any = _ExprType(globals()) altair-5.0.1/altair/expr/consts.py000066400000000000000000000016241443422213100171050ustar00rootroot00000000000000from typing import Dict from .core import ConstExpression CONST_LISTING = { "NaN": "not a number (same as JavaScript literal NaN)", "LN10": "the natural log of 10 (alias to Math.LN10)", "E": "the transcendental number e (alias to Math.E)", "LOG10E": "the base 10 logarithm e (alias to Math.LOG10E)", "LOG2E": "the base 2 logarithm of e (alias to Math.LOG2E)", "SQRT1_2": "the square root of 0.5 (alias to Math.SQRT1_2)", "LN2": "the natural log of 2 (alias to Math.LN2)", "SQRT2": "the square root of 2 (alias to Math.SQRT1_2)", "PI": "the transcendental number pi (alias to Math.PI)", } NAME_MAP: Dict[str, str] = {} def _populate_namespace(): globals_ = globals() for name, doc in CONST_LISTING.items(): py_name = NAME_MAP.get(name, name) globals_[py_name] = ConstExpression(name, doc) yield py_name __all__ = list(_populate_namespace()) altair-5.0.1/altair/expr/core.py000066400000000000000000000153001443422213100165200ustar00rootroot00000000000000from ..utils import SchemaBase class DatumType: """An object to assist in building Vega-Lite Expressions""" def __repr__(self): return "datum" def __getattr__(self, attr): if attr.startswith("__") and attr.endswith("__"): raise AttributeError(attr) return GetAttrExpression("datum", attr) def __getitem__(self, attr): return GetItemExpression("datum", attr) def __call__(self, datum, **kwargs): """Specify a datum for use in an encoding""" return dict(datum=datum, **kwargs) datum = DatumType() def _js_repr(val): """Return a javascript-safe string representation of val""" if val is True: return "true" elif val is False: return "false" elif val is None: return "null" elif isinstance(val, OperatorMixin): return val._to_expr() else: return repr(val) # Designed to work with Expression and VariableParameter class OperatorMixin: def _to_expr(self): return repr(self) def _from_expr(self, expr): return expr def __add__(self, other): comp_value = BinaryExpression("+", self, other) return self._from_expr(comp_value) def __radd__(self, other): comp_value = BinaryExpression("+", other, self) return self._from_expr(comp_value) def __sub__(self, other): comp_value = BinaryExpression("-", self, other) return self._from_expr(comp_value) def __rsub__(self, other): comp_value = BinaryExpression("-", other, self) return self._from_expr(comp_value) def __mul__(self, other): comp_value = BinaryExpression("*", self, other) return self._from_expr(comp_value) def __rmul__(self, other): comp_value = BinaryExpression("*", other, self) return self._from_expr(comp_value) def __truediv__(self, other): comp_value = BinaryExpression("/", self, other) return self._from_expr(comp_value) def __rtruediv__(self, other): comp_value = BinaryExpression("/", other, self) return self._from_expr(comp_value) __div__ = __truediv__ __rdiv__ = __rtruediv__ def __mod__(self, other): comp_value = BinaryExpression("%", self, other) return self._from_expr(comp_value) def __rmod__(self, other): comp_value = BinaryExpression("%", other, self) return self._from_expr(comp_value) def __pow__(self, other): # "**" Javascript operator is not supported in all browsers comp_value = FunctionExpression("pow", (self, other)) return self._from_expr(comp_value) def __rpow__(self, other): # "**" Javascript operator is not supported in all browsers comp_value = FunctionExpression("pow", (other, self)) return self._from_expr(comp_value) def __neg__(self): comp_value = UnaryExpression("-", self) return self._from_expr(comp_value) def __pos__(self): comp_value = UnaryExpression("+", self) return self._from_expr(comp_value) # comparison operators def __eq__(self, other): comp_value = BinaryExpression("===", self, other) return self._from_expr(comp_value) def __ne__(self, other): comp_value = BinaryExpression("!==", self, other) return self._from_expr(comp_value) def __gt__(self, other): comp_value = BinaryExpression(">", self, other) return self._from_expr(comp_value) def __lt__(self, other): comp_value = BinaryExpression("<", self, other) return self._from_expr(comp_value) def __ge__(self, other): comp_value = BinaryExpression(">=", self, other) return self._from_expr(comp_value) def __le__(self, other): comp_value = BinaryExpression("<=", self, other) return self._from_expr(comp_value) def __abs__(self): comp_value = FunctionExpression("abs", (self,)) return self._from_expr(comp_value) # logical operators def __and__(self, other): comp_value = BinaryExpression("&&", self, other) return self._from_expr(comp_value) def __rand__(self, other): comp_value = BinaryExpression("&&", other, self) return self._from_expr(comp_value) def __or__(self, other): comp_value = BinaryExpression("||", self, other) return self._from_expr(comp_value) def __ror__(self, other): comp_value = BinaryExpression("||", other, self) return self._from_expr(comp_value) def __invert__(self): comp_value = UnaryExpression("!", self) return self._from_expr(comp_value) class Expression(OperatorMixin, SchemaBase): """Expression Base object for enabling build-up of Javascript expressions using a Python syntax. Calling ``repr(obj)`` will return a Javascript representation of the object and the operations it encodes. """ _schema = {"type": "string"} def to_dict(self, *args, **kwargs): return repr(self) def __setattr__(self, attr, val): # We don't need the setattr magic defined in SchemaBase return object.__setattr__(self, attr, val) # item access def __getitem__(self, val): return GetItemExpression(self, val) class UnaryExpression(Expression): def __init__(self, op, val): super(UnaryExpression, self).__init__(op=op, val=val) def __repr__(self): return "({op}{val})".format(op=self.op, val=_js_repr(self.val)) class BinaryExpression(Expression): def __init__(self, op, lhs, rhs): super(BinaryExpression, self).__init__(op=op, lhs=lhs, rhs=rhs) def __repr__(self): return "({lhs} {op} {rhs})".format( op=self.op, lhs=_js_repr(self.lhs), rhs=_js_repr(self.rhs) ) class FunctionExpression(Expression): def __init__(self, name, args): super(FunctionExpression, self).__init__(name=name, args=args) def __repr__(self): args = ",".join(_js_repr(arg) for arg in self.args) return "{name}({args})".format(name=self.name, args=args) class ConstExpression(Expression): def __init__(self, name, doc): self.__doc__ = """{}: {}""".format(name, doc) super(ConstExpression, self).__init__(name=name, doc=doc) def __repr__(self): return str(self.name) class GetAttrExpression(Expression): def __init__(self, group, name): super(GetAttrExpression, self).__init__(group=group, name=name) def __repr__(self): return "{}.{}".format(self.group, self.name) class GetItemExpression(Expression): def __init__(self, group, name): super(GetItemExpression, self).__init__(group=group, name=name) def __repr__(self): return "{}[{!r}]".format(self.group, self.name) altair-5.0.1/altair/expr/funcs.py000066400000000000000000001031621443422213100167120ustar00rootroot00000000000000from .core import FunctionExpression FUNCTION_LISTING = { "isArray": r"Returns true if _value_ is an array, false otherwise.", "isBoolean": r"Returns true if _value_ is a boolean (`true` or `false`), false otherwise.", "isDate": r"Returns true if _value_ is a Date object, false otherwise. This method will return false for timestamp numbers or date-formatted strings; it recognizes Date objects only.", "isDefined": r"Returns true if _value_ is a defined value, false if _value_ equals `undefined`. This method will return true for `null` and `NaN` values.", "isNumber": r"Returns true if _value_ is a number, false otherwise. `NaN` and `Infinity` are considered numbers.", "isObject": r"Returns true if _value_ is an object (including arrays and Dates), false otherwise.", "isRegExp": r"Returns true if _value_ is a RegExp (regular expression) object, false otherwise.", "isString": r"Returns true if _value_ is a string, false otherwise.", "isValid": r"Returns true if _value_ is not `null`, `undefined`, or `NaN`, false otherwise.", "toBoolean": r"Coerces the input _value_ to a string. Null values and empty strings are mapped to `null`.", "toDate": r"Coerces the input _value_ to a Date instance. Null values and empty strings are mapped to `null`. If an optional _parser_ function is provided, it is used to perform date parsing, otherwise `Date.parse` is used. Be aware that `Date.parse` has different implementations across browsers!", "toNumber": r"Coerces the input _value_ to a number. Null values and empty strings are mapped to `null`.", "toString": r"Coerces the input _value_ to a string. Null values and empty strings are mapped to `null`.", "if": r"If _test_ is truthy, returns _thenValue_. Otherwise, returns _elseValue_. The _if_ function is equivalent to the ternary operator `a ? b : c`.", "isNaN": r"Returns true if _value_ is not a number. Same as JavaScript's `isNaN`.", "isFinite": r"Returns true if _value_ is a finite number. Same as JavaScript's `isFinite`.", "abs": r"Returns the absolute value of _value_. Same as JavaScript's `Math.abs`.", "acos": r"Trigonometric arccosine. Same as JavaScript's `Math.acos`.", "asin": r"Trigonometric arcsine. Same as JavaScript's `Math.asin`.", "atan": r"Trigonometric arctangent. Same as JavaScript's `Math.atan`.", "atan2": r"Returns the arctangent of _dy / dx_. Same as JavaScript's `Math.atan2`.", "ceil": r"Rounds _value_ to the nearest integer of equal or greater value. Same as JavaScript's `Math.ceil`.", "clamp": r"Restricts _value_ to be between the specified _min_ and _max_.", "cos": r"Trigonometric cosine. Same as JavaScript's `Math.cos`.", "exp": r"Returns the value of _e_ raised to the provided _exponent_. Same as JavaScript's `Math.exp`.", "floor": r"Rounds _value_ to the nearest integer of equal or lower value. Same as JavaScript's `Math.floor`.", "hypot": r"Returns the square root of the sum of squares of its arguments. Same as JavaScript's `Math.hypot`.", "log": r"Returns the natural logarithm of _value_. Same as JavaScript's `Math.log`.", "max": r"Returns the maximum argument value. Same as JavaScript's `Math.max`.", "min": r"Returns the minimum argument value. Same as JavaScript's `Math.min`.", "pow": r"Returns _value_ raised to the given _exponent_. Same as JavaScript's `Math.pow`.", "random": r"Returns a pseudo-random number in the range [0,1). Same as JavaScript's `Math.random`.", "round": r"Rounds _value_ to the nearest integer. Same as JavaScript's `Math.round`.", "sin": r"Trigonometric sine. Same as JavaScript's `Math.sin`.", "sqrt": r"Square root function. Same as JavaScript's `Math.sqrt`.", "tan": r"Trigonometric tangent. Same as JavaScript's `Math.tan`.", "sampleNormal": r"Returns a sample from a univariate [normal (Gaussian) probability distribution](https://en.wikipedia.org/wiki/Normal_distribution) with specified _mean_ and standard deviation _stdev_. If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.", "cumulativeNormal": r"Returns the value of the [cumulative distribution function](https://en.wikipedia.org/wiki/Cumulative_distribution_function) at the given input domain _value_ for a normal distribution with specified _mean_ and standard deviation _stdev_. If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.", "densityNormal": r"Returns the value of the [probability density function](https://en.wikipedia.org/wiki/Probability_density_function) at the given input domain _value_, for a normal distribution with specified _mean_ and standard deviation _stdev_. If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.", "quantileNormal": r"Returns the quantile value (the inverse of the [cumulative distribution function](https://en.wikipedia.org/wiki/Cumulative_distribution_function)) for the given input _probability_, for a normal distribution with specified _mean_ and standard deviation _stdev_. If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.", "sampleLogNormal": r"Returns a sample from a univariate [log-normal probability distribution](https://en.wikipedia.org/wiki/Log-normal_distribution) with specified log _mean_ and log standard deviation _stdev_. If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.", "cumulativeLogNormal": r"Returns the value of the [cumulative distribution function](https://en.wikipedia.org/wiki/Cumulative_distribution_function) at the given input domain _value_ for a log-normal distribution with specified log _mean_ and log standard deviation _stdev_. If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.", "densityLogNormal": r"Returns the value of the [probability density function](https://en.wikipedia.org/wiki/Probability_density_function) at the given input domain _value_, for a log-normal distribution with specified log _mean_ and log standard deviation _stdev_. If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.", "quantileLogNormal": r"Returns the quantile value (the inverse of the [cumulative distribution function](https://en.wikipedia.org/wiki/Cumulative_distribution_function)) for the given input _probability_, for a log-normal distribution with specified log _mean_ and log standard deviation _stdev_. If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.", "sampleUniform": r"Returns a sample from a univariate [continuous uniform probability distribution](https://en.wikipedia.org/wiki/Uniform_distribution_(continuous)) over the interval [_min_, _max_). If unspecified, _min_ defaults to `0` and _max_ defaults to `1`. If only one argument is provided, it is interpreted as the _max_ value.", "cumulativeUniform": r"Returns the value of the [cumulative distribution function](https://en.wikipedia.org/wiki/Cumulative_distribution_function) at the given input domain _value_ for a uniform distribution over the interval [_min_, _max_). If unspecified, _min_ defaults to `0` and _max_ defaults to `1`. If only one argument is provided, it is interpreted as the _max_ value.", "densityUniform": r"Returns the value of the [probability density function](https://en.wikipedia.org/wiki/Probability_density_function) at the given input domain _value_, for a uniform distribution over the interval [_min_, _max_). If unspecified, _min_ defaults to `0` and _max_ defaults to `1`. If only one argument is provided, it is interpreted as the _max_ value.", "quantileUniform": r"Returns the quantile value (the inverse of the [cumulative distribution function](https://en.wikipedia.org/wiki/Cumulative_distribution_function)) for the given input _probability_, for a uniform distribution over the interval [_min_, _max_). If unspecified, _min_ defaults to `0` and _max_ defaults to `1`. If only one argument is provided, it is interpreted as the _max_ value.", "now": r"Returns the timestamp for the current time.", "datetime": r"Returns a new `Date` instance. The _month_ is 0-based, such that `1` represents February.", "date": r"Returns the day of the month for the given _datetime_ value, in local time.", "day": r"Returns the day of the week for the given _datetime_ value, in local time.", "dayofyear": r"Returns the one-based day of the year for the given _datetime_ value, in local time.", "year": r"Returns the year for the given _datetime_ value, in local time.", "quarter": r"Returns the quarter of the year (0-3) for the given _datetime_ value, in local time.", "month": r"Returns the (zero-based) month for the given _datetime_ value, in local time.", "week": r"Returns the week number of the year for the given _datetime_, in local time. This function assumes Sunday-based weeks. Days before the first Sunday of the year are considered to be in week 0, the first Sunday of the year is the start of week 1, the second Sunday week 2, _etc._.", "hours": r"Returns the hours component for the given _datetime_ value, in local time.", "minutes": r"Returns the minutes component for the given _datetime_ value, in local time.", "seconds": r"Returns the seconds component for the given _datetime_ value, in local time.", "milliseconds": r"Returns the milliseconds component for the given _datetime_ value, in local time.", "time": r"Returns the epoch-based timestamp for the given _datetime_ value.", "timezoneoffset": r"Returns the timezone offset from the local timezone to UTC for the given _datetime_ value.", "timeOffset": r"Returns a new `Date` instance that offsets the given _date_ by the specified time [_unit_](../api/time/#time-units) in the local timezone. The optional _step_ argument indicates the number of time unit steps to offset by (default 1).", "timeSequence": r"Returns an array of `Date` instances from _start_ (inclusive) to _stop_ (exclusive), with each entry separated by the given time [_unit_](../api/time/#time-units) in the local timezone. The optional _step_ argument indicates the number of time unit steps to take between each sequence entry (default 1).", "utc": r"Returns a timestamp for the given UTC date. The _month_ is 0-based, such that `1` represents February.", "utcdate": r"Returns the day of the month for the given _datetime_ value, in UTC time.", "utcday": r"Returns the day of the week for the given _datetime_ value, in UTC time.", "utcdayofyear": r"Returns the one-based day of the year for the given _datetime_ value, in UTC time.", "utcyear": r"Returns the year for the given _datetime_ value, in UTC time.", "utcquarter": r"Returns the quarter of the year (0-3) for the given _datetime_ value, in UTC time.", "utcmonth": r"Returns the (zero-based) month for the given _datetime_ value, in UTC time.", "utcweek": r"Returns the week number of the year for the given _datetime_, in UTC time. This function assumes Sunday-based weeks. Days before the first Sunday of the year are considered to be in week 0, the first Sunday of the year is the start of week 1, the second Sunday week 2, _etc._.", "utchours": r"Returns the hours component for the given _datetime_ value, in UTC time.", "utcminutes": r"Returns the minutes component for the given _datetime_ value, in UTC time.", "utcseconds": r"Returns the seconds component for the given _datetime_ value, in UTC time.", "utcmilliseconds": r"Returns the milliseconds component for the given _datetime_ value, in UTC time.", "utcOffset": r"Returns a new `Date` instance that offsets the given _date_ by the specified time [_unit_](../api/time/#time-units) in UTC time. The optional _step_ argument indicates the number of time unit steps to offset by (default 1).", "utcSequence": r"Returns an array of `Date` instances from _start_ (inclusive) to _stop_ (exclusive), with each entry separated by the given time [_unit_](../api/time/#time-units) in UTC time. The optional _step_ argument indicates the number of time unit steps to take between each sequence entry (default 1).", "extent": r"Returns a new _[min, max]_ array with the minimum and maximum values of the input array, ignoring `null`, `undefined`, and `NaN` values.", "clampRange": r"Clamps a two-element _range_ array in a span-preserving manner. If the span of the input _range_ is less than _(max - min)_ and an endpoint exceeds either the _min_ or _max_ value, the range is translated such that the span is preserved and one endpoint touches the boundary of the _[min, max]_ range. If the span exceeds _(max - min)_, the range _[min, max]_ is returned.", "indexof": r"Returns the first index of _value_ in the input _array_, or the first index of _substring_ in the input _string_..", "inrange": r"Tests whether _value_ lies within (or is equal to either) the first and last values of the _range_ array.", "join": r"Returns a new string by concatenating all of the elements of the input _array_, separated by commas or a specified _separator_ string.", "lastindexof": r"Returns the last index of _value_ in the input _array_, or the last index of _substring_ in the input _string_..", "length": r"Returns the length of the input _array_, or the length of the input _string_.", "lerp": r"Returns the linearly interpolated value between the first and last entries in the _array_ for the provided interpolation _fraction_ (typically between 0 and 1). For example, `lerp([0, 50], 0.5)` returns 25.", "peek": r"Returns the last element in the input _array_. Similar to the built-in `Array.pop` method, except that it does not remove the last element. This method is a convenient shorthand for `array[array.length - 1]`.", "pluck": r"Retrieves the value for the specified *field* from a given *array* of objects. The input *field* string may include nested properties (e.g., `foo.bar.bz`).", "reverse": r"Returns a new array with elements in a reverse order of the input _array_. The first array element becomes the last, and the last array element becomes the first.", "sequence": r"Returns an array containing an arithmetic sequence of numbers. If _step_ is omitted, it defaults to 1. If _start_ is omitted, it defaults to 0. The _stop_ value is exclusive; it is not included in the result. If _step_ is positive, the last element is the largest _start + i * step_ less than _stop_; if _step_ is negative, the last element is the smallest _start + i * step_ greater than _stop_. If the returned array would contain an infinite number of values, an empty range is returned. The arguments are not required to be integers.", "slice": r"Returns a section of _array_ between the _start_ and _end_ indices. If the _end_ argument is negative, it is treated as an offset from the end of the array (_length(array) + end_).", "span": r"Returns the span of _array_: the difference between the last and first elements, or _array[array.length-1] - array[0]_. Or if input is a string: a section of _string_ between the _start_ and _end_ indices. If the _end_ argument is negative, it is treated as an offset from the end of the string (_length(string) + end_)..", "lower": r"Transforms _string_ to lower-case letters.", "pad": r"Pads a _string_ value with repeated instances of a _character_ up to a specified _length_. If _character_ is not specified, a space (' ') is used. By default, padding is added to the end of a string. An optional _align_ parameter specifies if padding should be added to the `'left'` (beginning), `'center'`, or `'right'` (end) of the input string.", "parseFloat": r"Parses the input _string_ to a floating-point value. Same as JavaScript's `parseFloat`.", "parseInt": r"Parses the input _string_ to an integer value. Same as JavaScript's `parseInt`.", "replace": r"Returns a new string with some or all matches of _pattern_ replaced by a _replacement_ string. The _pattern_ can be a string or a regular expression. If _pattern_ is a string, only the first instance will be replaced. Same as [JavaScript's String.replace](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace).", "split": r"Returns an array of tokens created by splitting the input _string_ according to a provided _separator_ pattern. The result can optionally be constrained to return at most _limit_ tokens.", "substring": r"Returns a section of _string_ between the _start_ and _end_ indices.", "trim": r"Returns a trimmed string with preceding and trailing whitespace removed.", "truncate": r"Truncates an input _string_ to a target _length_. The optional _align_ argument indicates what part of the string should be truncated: `'left'` (the beginning), `'center'`, or `'right'` (the end). By default, the `'right'` end of the string is truncated. The optional _ellipsis_ argument indicates the string to use to indicate truncated content; by default the ellipsis character `...` (`\\u2026`) is used.", "upper": r"Transforms _string_ to upper-case letters.", "merge": r"Merges the input objects _object1_, _object2_, etc into a new output object. Inputs are visited in sequential order, such that key values from later arguments can overwrite those from earlier arguments. Example: `merge({a:1, b:2}, {a:3}) -> {a:3, b:2}`.", "dayFormat": r"Formats a (0-6) _weekday_ number as a full week day name, according to the current locale. For example: `dayFormat(0) -> \"Sunday\"`.", "dayAbbrevFormat": r"Formats a (0-6) _weekday_ number as an abbreviated week day name, according to the current locale. For example: `dayAbbrevFormat(0) -> \"Sun\"`.", "format": r"Formats a numeric _value_ as a string. The _specifier_ must be a valid [d3-format specifier](https://github.com/d3/d3-format/) (e.g., `format(value, ',.2f')`.", "monthFormat": r"Formats a (zero-based) _month_ number as a full month name, according to the current locale. For example: `monthFormat(0) -> \"January\"`.", "monthAbbrevFormat": r"Formats a (zero-based) _month_ number as an abbreviated month name, according to the current locale. For example: `monthAbbrevFormat(0) -> \"Jan\"`.", "timeUnitSpecifier": r"Returns a time format specifier string for the given time [_units_](../api/time/#time-units). The optional _specifiers_ object provides a set of specifier sub-strings for customizing the format; for more, see the [timeUnitSpecifier API documentation](../api/time/#timeUnitSpecifier). The resulting specifier string can then be used as input to the [timeFormat](#timeFormat) or [utcFormat](#utcFormat) functions, or as the _format_ parameter of an axis or legend. For example: `timeFormat(date, timeUnitSpecifier('year'))` or `timeFormat(date, timeUnitSpecifier(['hours', 'minutes']))`.", "timeFormat": r"Formats a datetime _value_ (either a `Date` object or timestamp) as a string, according to the local time. The _specifier_ must be a valid [d3-time-format specifier](https://github.com/d3/d3-time-format/). For example: `timeFormat(timestamp, '%A')`.", "timeParse": r"Parses a _string_ value to a Date object, according to the local time. The _specifier_ must be a valid [d3-time-format specifier](https://github.com/d3/d3-time-format/). For example: `timeParse('June 30, 2015', '%B %d, %Y')`.", "utcFormat": r"Formats a datetime _value_ (either a `Date` object or timestamp) as a string, according to [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) time. The _specifier_ must be a valid [d3-time-format specifier](https://github.com/d3/d3-time-format/). For example: `utcFormat(timestamp, '%A')`.", "utcParse": r"Parses a _string_ value to a Date object, according to [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) time. The _specifier_ must be a valid [d3-time-format specifier](https://github.com/d3/d3-time-format/). For example: `utcParse('June 30, 2015', '%B %d, %Y')`.", "regexp": r"Creates a regular expression instance from an input _pattern_ string and optional _flags_. Same as [JavaScript's `RegExp`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp).", "test": r"Evaluates a regular expression _regexp_ against the input _string_, returning `true` if the string matches the pattern, `false` otherwise. For example: `test(/\\d{3}/, \"32-21-9483\") -> true`.", "rgb": r"Constructs a new [RGB](https://en.wikipedia.org/wiki/RGB_color_model) color. If _r_, _g_ and _b_ are specified, these represent the channel values of the returned color; an _opacity_ may also be specified. If a CSS Color Module Level 3 _specifier_ string is specified, it is parsed and then converted to the RGB color space. Uses [d3-color's rgb function](https://github.com/d3/d3-color#rgb).", "hsl": r"Constructs a new [HSL](https://en.wikipedia.org/wiki/HSL_and_HSV) color. If _h_, _s_ and _l_ are specified, these represent the channel values of the returned color; an _opacity_ may also be specified. If a CSS Color Module Level 3 _specifier_ string is specified, it is parsed and then converted to the HSL color space. Uses [d3-color's hsl function](https://github.com/d3/d3-color#hsl).", "lab": r"Constructs a new [CIE LAB](https://en.wikipedia.org/wiki/Lab_color_space#CIELAB) color. If _l_, _a_ and _b_ are specified, these represent the channel values of the returned color; an _opacity_ may also be specified. If a CSS Color Module Level 3 _specifier_ string is specified, it is parsed and then converted to the LAB color space. Uses [d3-color's lab function](https://github.com/d3/d3-color#lab).", "hcl": r"Constructs a new [HCL](https://en.wikipedia.org/wiki/Lab_color_space#CIELAB) (hue, chroma, luminance) color. If _h_, _c_ and _l_ are specified, these represent the channel values of the returned color; an _opacity_ may also be specified. If a CSS Color Module Level 3 _specifier_ string is specified, it is parsed and then converted to the HCL color space. Uses [d3-color's hcl function](https://github.com/d3/d3-color#hcl).", "luminance": r"Returns the luminance for the given color _specifier_ (compatible with [d3-color's rgb function](https://github.com/d3/d3-color#rgb)). The luminance is calculated according to the [W3C Web Content Accessibility Guidelines](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef).", "contrast": r"Returns the contrast ratio between the input color specifiers as a float between 1 and 21. The contrast is calculated according to the [W3C Web Content Accessibility Guidelines](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef).", "item": r"Returns the current scenegraph item that is the target of the event.", "group": r"Returns the scenegraph group mark item in which the current event has occurred. If no arguments are provided, the immediate parent group is returned. If a group name is provided, the matching ancestor group item is returned.", "xy": r"Returns the x- and y-coordinates for the current event as a two-element array. If no arguments are provided, the top-level coordinate space of the view is used. If a scenegraph _item_ (or string group name) is provided, the coordinate space of the group item is used.", "x": r"Returns the x coordinate for the current event. If no arguments are provided, the top-level coordinate space of the view is used. If a scenegraph _item_ (or string group name) is provided, the coordinate space of the group item is used.", "y": r"Returns the y coordinate for the current event. If no arguments are provided, the top-level coordinate space of the view is used. If a scenegraph _item_ (or string group name) is provided, the coordinate space of the group item is used.", "pinchDistance": r"Returns the pixel distance between the first two touch points of a multi-touch event.", "pinchAngle": r"Returns the angle of the line connecting the first two touch points of a multi-touch event.", "inScope": r"Returns true if the given scenegraph _item_ is a descendant of the group mark in which the event handler was defined, false otherwise.", "data": r"Returns the array of data objects for the Vega data set with the given _name_. If the data set is not found, returns an empty array.", "indata": r"Tests if the data set with a given _name_ contains a datum with a _field_ value that matches the input _value_. For example: `indata('table', 'category', value)`.", "scale": r"Applies the named scale transform (or projection) to the specified _value_. The optional _group_ argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.", "invert": r"Inverts the named scale transform (or projection) for the specified _value_. The optional _group_ argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.", "copy": r"Returns a copy (a new cloned instance) of the named scale transform of projection, or `undefined` if no scale or projection is found. The optional _group_ argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.", "domain": r"Returns the scale domain array for the named scale transform, or an empty array if the scale is not found. The optional _group_ argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.", "range": r"Returns the scale range array for the named scale transform, or an empty array if the scale is not found. The optional _group_ argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.", "bandwidth": r"Returns the current band width for the named band scale transform, or zero if the scale is not found or is not a band scale. The optional _group_ argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.", "bandspace": r"Returns the number of steps needed within a band scale, based on the _count_ of domain elements and the inner and outer padding values. While normally calculated within the scale itself, this function can be helpful for determining the size of a chart's layout.", "gradient": r"Returns a linear color gradient for the _scale_ (whose range must be a [continuous color scheme](../schemes)) and starting and ending points _p0_ and _p1_, each an _[x, y]_ array. The points _p0_ and _p1_ should be expressed in normalized coordinates in the domain [0, 1], relative to the bounds of the item being colored. If unspecified, _p0_ defaults to `[0, 0]` and _p1_ defaults to `[1, 0]`, for a horizontal gradient that spans the full bounds of an item. The optional _count_ argument indicates a desired target number of sample points to take from the color scale.", "panLinear": r"Given a linear scale _domain_ array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional _delta_. The _delta_ value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.", "panLog": r"Given a log scale _domain_ array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional _delta_. The _delta_ value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.", "panPow": r"Given a power scale _domain_ array with numeric or datetime values and the given _exponent_, returns a new two-element domain array that is the result of panning the domain by a fractional _delta_. The _delta_ value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.", "panSymlog": r"Given a symmetric log scale _domain_ array with numeric or datetime values parameterized by the given _constant_, returns a new two-element domain array that is the result of panning the domain by a fractional _delta_. The _delta_ value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.", "zoomLinear": r"Given a linear scale _domain_ array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a _scaleFactor_, centered at the provided fractional _anchor_. The _anchor_ value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.", "zoomLog": r"Given a log scale _domain_ array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a _scaleFactor_, centered at the provided fractional _anchor_. The _anchor_ value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.", "zoomPow": r"Given a power scale _domain_ array with numeric or datetime values and the given _exponent_, returns a new two-element domain array that is the result of zooming the domain by a _scaleFactor_, centered at the provided fractional _anchor_. The _anchor_ value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.", "zoomSymlog": r"Given a symmetric log scale _domain_ array with numeric or datetime values parameterized by the given _constant_, returns a new two-element domain array that is the result of zooming the domain by a _scaleFactor_, centered at the provided fractional _anchor_. The _anchor_ value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.", "geoArea": r"Returns the projected planar area (typically in square pixels) of a GeoJSON _feature_ according to the named _projection_. If the _projection_ argument is `null`, computes the spherical area in steradians using unprojected longitude, latitude coordinates. The optional _group_ argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection. Uses d3-geo's [geoArea](https://github.com/d3/d3-geo#geoArea) and [path.area](https://github.com/d3/d3-geo#path_area) methods.", "geoBounds": r"Returns the projected planar bounding box (typically in pixels) for the specified GeoJSON _feature_, according to the named _projection_. The bounding box is represented by a two-dimensional array: [[_x0_, _y0_], [_x1_, _y1_]], where _x0_ is the minimum x-coordinate, _y0_ is the minimum y-coordinate, _x1_ is the maximum x-coordinate, and _y1_ is the maximum y-coordinate. If the _projection_ argument is `null`, computes the spherical bounding box using unprojected longitude, latitude coordinates. The optional _group_ argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection. Uses d3-geo's [geoBounds](https://github.com/d3/d3-geo#geoBounds) and [path.bounds](https://github.com/d3/d3-geo#path_bounds) methods.", "geoCentroid": r"Returns the projected planar centroid (typically in pixels) for the specified GeoJSON _feature_, according to the named _projection_. If the _projection_ argument is `null`, computes the spherical centroid using unprojected longitude, latitude coordinates. The optional _group_ argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection. Uses d3-geo's [geoCentroid](https://github.com/d3/d3-geo#geoCentroid) and [path.centroid](https://github.com/d3/d3-geo#path_centroid) methods.", "treePath": r"For the hierarchy data set with the given _name_, returns the shortest path through from the _source_ node id to the _target_ node id. The path starts at the _source_ node, ascends to the least common ancestor of the _source_ node and the _target_ node, and then descends to the _target_ node.", "treeAncestors": r"For the hierarchy data set with the given _name_, returns the array of ancestors nodes, starting with the input _node_, then followed by each parent up to the root.", "containerSize": r"Returns the current CSS box size (`[el.clientWidth, el.clientHeight]`) of the parent DOM element that contains the Vega view. If there is no container element, returns `[undefined, undefined]`.", "screen": r"Returns the [`window.screen`](https://developer.mozilla.org/en-US/docs/Web/API/Window/screen) object, or `{}` if Vega is not running in a browser environment.", "windowSize": r"Returns the current window size (`[window.innerWidth, window.innerHeight]`) or `[undefined, undefined]` if Vega is not running in a browser environment.", "warn": r"Logs a warning message and returns the last argument. For the message to appear in the console, the visualization view must have the appropriate logging level set.", "info": r"Logs an informative message and returns the last argument. For the message to appear in the console, the visualization view must have the appropriate logging level set.", "debug": r"Logs a debugging message and returns the last argument. For the message to appear in the console, the visualization view must have the appropriate logging level set.", } # This maps vega expression function names to the Python name NAME_MAP = {"if": "if_"} class ExprFunc: def __init__(self, name, doc): self.name = name self.doc = doc self.__doc__ = """{}(*args)\n {}""".format(name, doc) def __call__(self, *args): return FunctionExpression(self.name, args) def __repr__(self): return "".format(self.name) def _populate_namespace(): globals_ = globals() for name, doc in FUNCTION_LISTING.items(): py_name = NAME_MAP.get(name, name) globals_[py_name] = ExprFunc(name, doc) yield py_name __all__ = list(_populate_namespace()) altair-5.0.1/altair/utils/000077500000000000000000000000001443422213100154015ustar00rootroot00000000000000altair-5.0.1/altair/utils/__init__.py000066400000000000000000000012101443422213100175040ustar00rootroot00000000000000from .core import ( infer_vegalite_type, infer_encoding_types, sanitize_dataframe, parse_shorthand, use_signature, update_nested, display_traceback, SchemaBase, ) from .html import spec_to_html from .plugin_registry import PluginRegistry from .deprecation import AltairDeprecationWarning from .schemapi import Undefined __all__ = ( "infer_vegalite_type", "infer_encoding_types", "sanitize_dataframe", "spec_to_html", "parse_shorthand", "use_signature", "update_nested", "display_traceback", "AltairDeprecationWarning", "SchemaBase", "Undefined", "PluginRegistry", ) altair-5.0.1/altair/utils/core.py000066400000000000000000000545151443422213100167150ustar00rootroot00000000000000""" Utility routines """ from collections.abc import Mapping from copy import deepcopy import json import itertools import re import sys import traceback import warnings from typing import Callable, TypeVar, Any import jsonschema import pandas as pd import numpy as np from altair.utils.schemapi import SchemaBase if sys.version_info >= (3, 10): from typing import ParamSpec else: from typing_extensions import ParamSpec try: from pandas.api.types import infer_dtype as _infer_dtype except ImportError: # Import for pandas < 0.20.0 from pandas.lib import infer_dtype as _infer_dtype # type: ignore[no-redef] _V = TypeVar("_V") _P = ParamSpec("_P") def infer_dtype(value): """Infer the dtype of the value. This is a compatibility function for pandas infer_dtype, with skipna=False regardless of the pandas version. """ if not hasattr(infer_dtype, "_supports_skipna"): try: _infer_dtype([1], skipna=False) except TypeError: # pandas < 0.21.0 don't support skipna keyword infer_dtype._supports_skipna = False else: infer_dtype._supports_skipna = True if infer_dtype._supports_skipna: return _infer_dtype(value, skipna=False) else: return _infer_dtype(value) TYPECODE_MAP = { "ordinal": "O", "nominal": "N", "quantitative": "Q", "temporal": "T", "geojson": "G", } INV_TYPECODE_MAP = {v: k for k, v in TYPECODE_MAP.items()} # aggregates from vega-lite version 4.6.0 AGGREGATES = [ "argmax", "argmin", "average", "count", "distinct", "max", "mean", "median", "min", "missing", "product", "q1", "q3", "ci0", "ci1", "stderr", "stdev", "stdevp", "sum", "valid", "values", "variance", "variancep", ] # window aggregates from vega-lite version 4.6.0 WINDOW_AGGREGATES = [ "row_number", "rank", "dense_rank", "percent_rank", "cume_dist", "ntile", "lag", "lead", "first_value", "last_value", "nth_value", ] # timeUnits from vega-lite version 4.17.0 TIMEUNITS = [ "year", "quarter", "month", "week", "day", "dayofyear", "date", "hours", "minutes", "seconds", "milliseconds", "yearquarter", "yearquartermonth", "yearmonth", "yearmonthdate", "yearmonthdatehours", "yearmonthdatehoursminutes", "yearmonthdatehoursminutesseconds", "yearweek", "yearweekday", "yearweekdayhours", "yearweekdayhoursminutes", "yearweekdayhoursminutesseconds", "yeardayofyear", "quartermonth", "monthdate", "monthdatehours", "monthdatehoursminutes", "monthdatehoursminutesseconds", "weekday", "weeksdayhours", "weekdayhoursminutes", "weekdayhoursminutesseconds", "dayhours", "dayhoursminutes", "dayhoursminutesseconds", "hoursminutes", "hoursminutesseconds", "minutesseconds", "secondsmilliseconds", "utcyear", "utcquarter", "utcmonth", "utcweek", "utcday", "utcdayofyear", "utcdate", "utchours", "utcminutes", "utcseconds", "utcmilliseconds", "utcyearquarter", "utcyearquartermonth", "utcyearmonth", "utcyearmonthdate", "utcyearmonthdatehours", "utcyearmonthdatehoursminutes", "utcyearmonthdatehoursminutesseconds", "utcyearweek", "utcyearweekday", "utcyearweekdayhours", "utcyearweekdayhoursminutes", "utcyearweekdayhoursminutesseconds", "utcyeardayofyear", "utcquartermonth", "utcmonthdate", "utcmonthdatehours", "utcmonthdatehoursminutes", "utcmonthdatehoursminutesseconds", "utcweekday", "utcweeksdayhours", "utcweekdayhoursminutes", "utcweekdayhoursminutesseconds", "utcdayhours", "utcdayhoursminutes", "utcdayhoursminutesseconds", "utchoursminutes", "utchoursminutesseconds", "utcminutesseconds", "utcsecondsmilliseconds", ] def infer_vegalite_type(data): """ From an array-like input, infer the correct vega typecode ('ordinal', 'nominal', 'quantitative', or 'temporal') Parameters ---------- data: Numpy array or Pandas Series """ # Otherwise, infer based on the dtype of the input typ = infer_dtype(data) if typ in [ "floating", "mixed-integer-float", "integer", "mixed-integer", "complex", ]: return "quantitative" elif typ == "categorical" and data.cat.ordered: return ("ordinal", data.cat.categories.tolist()) elif typ in ["string", "bytes", "categorical", "boolean", "mixed", "unicode"]: return "nominal" elif typ in [ "datetime", "datetime64", "timedelta", "timedelta64", "date", "time", "period", ]: return "temporal" else: warnings.warn( "I don't know how to infer vegalite type from '{}'. " "Defaulting to nominal.".format(typ), stacklevel=1, ) return "nominal" def merge_props_geom(feat): """ Merge properties with geometry * Overwrites 'type' and 'geometry' entries if existing """ geom = {k: feat[k] for k in ("type", "geometry")} try: feat["properties"].update(geom) props_geom = feat["properties"] except (AttributeError, KeyError): # AttributeError when 'properties' equals None # KeyError when 'properties' is non-existing props_geom = geom return props_geom def sanitize_geo_interface(geo): """Santize a geo_interface to prepare it for serialization. * Make a copy * Convert type array or _Array to list * Convert tuples to lists (using json.loads/dumps) * Merge properties with geometry """ geo = deepcopy(geo) # convert type _Array or array to list for key in geo.keys(): if str(type(geo[key]).__name__).startswith(("_Array", "array")): geo[key] = geo[key].tolist() # convert (nested) tuples to lists geo = json.loads(json.dumps(geo)) # sanitize features if geo["type"] == "FeatureCollection": geo = geo["features"] if len(geo) > 0: for idx, feat in enumerate(geo): geo[idx] = merge_props_geom(feat) elif geo["type"] == "Feature": geo = merge_props_geom(geo) else: geo = {"type": "Feature", "geometry": geo} return geo def sanitize_dataframe(df): # noqa: C901 """Sanitize a DataFrame to prepare it for serialization. * Make a copy * Convert RangeIndex columns to strings * Raise ValueError if column names are not strings * Raise ValueError if it has a hierarchical index. * Convert categoricals to strings. * Convert np.bool_ dtypes to Python bool objects * Convert np.int dtypes to Python int objects * Convert floats to objects and replace NaNs/infs with None. * Convert DateTime dtypes into appropriate string representations * Convert Nullable integers to objects and replace NaN with None * Convert Nullable boolean to objects and replace NaN with None * convert dedicated string column to objects and replace NaN with None * Raise a ValueError for TimeDelta dtypes """ df = df.copy() if isinstance(df.columns, pd.RangeIndex): df.columns = df.columns.astype(str) for col in df.columns: if not isinstance(col, str): raise ValueError( "Dataframe contains invalid column name: {0!r}. " "Column names must be strings".format(col) ) if isinstance(df.index, pd.MultiIndex): raise ValueError("Hierarchical indices not supported") if isinstance(df.columns, pd.MultiIndex): raise ValueError("Hierarchical indices not supported") def to_list_if_array(val): if isinstance(val, np.ndarray): return val.tolist() else: return val for col_name, dtype in df.dtypes.items(): if str(dtype) == "category": # Work around bug in to_json for categorical types in older versions of pandas # https://github.com/pydata/pandas/issues/10778 # https://github.com/altair-viz/altair/pull/2170 col = df[col_name].astype(object) df[col_name] = col.where(col.notnull(), None) elif str(dtype) == "string": # dedicated string datatype (since 1.0) # https://pandas.pydata.org/pandas-docs/version/1.0.0/whatsnew/v1.0.0.html#dedicated-string-data-type col = df[col_name].astype(object) df[col_name] = col.where(col.notnull(), None) elif str(dtype) == "bool": # convert numpy bools to objects; np.bool is not JSON serializable df[col_name] = df[col_name].astype(object) elif str(dtype) == "boolean": # dedicated boolean datatype (since 1.0) # https://pandas.io/docs/user_guide/boolean.html col = df[col_name].astype(object) df[col_name] = col.where(col.notnull(), None) elif str(dtype).startswith("datetime"): # Convert datetimes to strings. This needs to be a full ISO string # with time, which is why we cannot use ``col.astype(str)``. # This is because Javascript parses date-only times in UTC, but # parses full ISO-8601 dates as local time, and dates in Vega and # Vega-Lite are displayed in local time by default. # (see https://github.com/altair-viz/altair/issues/1027) df[col_name] = ( df[col_name].apply(lambda x: x.isoformat()).replace("NaT", "") ) elif str(dtype).startswith("timedelta"): raise ValueError( 'Field "{col_name}" has type "{dtype}" which is ' "not supported by Altair. Please convert to " "either a timestamp or a numerical value." "".format(col_name=col_name, dtype=dtype) ) elif str(dtype).startswith("geometry"): # geopandas >=0.6.1 uses the dtype geometry. Continue here # otherwise it will give an error on np.issubdtype(dtype, np.integer) continue elif str(dtype) in { "Int8", "Int16", "Int32", "Int64", "UInt8", "UInt16", "UInt32", "UInt64", "Float32", "Float64", }: # nullable integer datatypes (since 24.0) and nullable float datatypes (since 1.2.0) # https://pandas.pydata.org/pandas-docs/version/0.25/whatsnew/v0.24.0.html#optional-integer-na-support col = df[col_name].astype(object) df[col_name] = col.where(col.notnull(), None) elif np.issubdtype(dtype, np.integer): # convert integers to objects; np.int is not JSON serializable df[col_name] = df[col_name].astype(object) elif np.issubdtype(dtype, np.floating): # For floats, convert to Python float: np.float is not JSON serializable # Also convert NaN/inf values to null, as they are not JSON serializable col = df[col_name] bad_values = col.isnull() | np.isinf(col) df[col_name] = col.astype(object).where(~bad_values, None) elif dtype == object: # Convert numpy arrays saved as objects to lists # Arrays are not JSON serializable col = df[col_name].apply(to_list_if_array, convert_dtype=False) df[col_name] = col.where(col.notnull(), None) return df def parse_shorthand( shorthand, data=None, parse_aggregates=True, parse_window_ops=False, parse_timeunits=True, parse_types=True, ): """General tool to parse shorthand values These are of the form: - "col_name" - "col_name:O" - "average(col_name)" - "average(col_name):O" Optionally, a dataframe may be supplied, from which the type will be inferred if not specified in the shorthand. Parameters ---------- shorthand : dict or string The shorthand representation to be parsed data : DataFrame, optional If specified and of type DataFrame, then use these values to infer the column type if not provided by the shorthand. parse_aggregates : boolean If True (default), then parse aggregate functions within the shorthand. parse_window_ops : boolean If True then parse window operations within the shorthand (default:False) parse_timeunits : boolean If True (default), then parse timeUnits from within the shorthand parse_types : boolean If True (default), then parse typecodes within the shorthand Returns ------- attrs : dict a dictionary of attributes extracted from the shorthand Examples -------- >>> data = pd.DataFrame({'foo': ['A', 'B', 'A', 'B'], ... 'bar': [1, 2, 3, 4]}) >>> parse_shorthand('name') == {'field': 'name'} True >>> parse_shorthand('name:Q') == {'field': 'name', 'type': 'quantitative'} True >>> parse_shorthand('average(col)') == {'aggregate': 'average', 'field': 'col'} True >>> parse_shorthand('foo:O') == {'field': 'foo', 'type': 'ordinal'} True >>> parse_shorthand('min(foo):Q') == {'aggregate': 'min', 'field': 'foo', 'type': 'quantitative'} True >>> parse_shorthand('month(col)') == {'field': 'col', 'timeUnit': 'month', 'type': 'temporal'} True >>> parse_shorthand('year(col):O') == {'field': 'col', 'timeUnit': 'year', 'type': 'ordinal'} True >>> parse_shorthand('foo', data) == {'field': 'foo', 'type': 'nominal'} True >>> parse_shorthand('bar', data) == {'field': 'bar', 'type': 'quantitative'} True >>> parse_shorthand('bar:O', data) == {'field': 'bar', 'type': 'ordinal'} True >>> parse_shorthand('sum(bar)', data) == {'aggregate': 'sum', 'field': 'bar', 'type': 'quantitative'} True >>> parse_shorthand('count()', data) == {'aggregate': 'count', 'type': 'quantitative'} True """ if not shorthand: return {} valid_typecodes = list(TYPECODE_MAP) + list(INV_TYPECODE_MAP) units = { "field": "(?P.*)", "type": "(?P{})".format("|".join(valid_typecodes)), "agg_count": "(?Pcount)", "op_count": "(?Pcount)", "aggregate": "(?P{})".format("|".join(AGGREGATES)), "window_op": "(?P{})".format("|".join(AGGREGATES + WINDOW_AGGREGATES)), "timeUnit": "(?P{})".format("|".join(TIMEUNITS)), } patterns = [] if parse_aggregates: patterns.extend([r"{agg_count}\(\)"]) patterns.extend([r"{aggregate}\({field}\)"]) if parse_window_ops: patterns.extend([r"{op_count}\(\)"]) patterns.extend([r"{window_op}\({field}\)"]) if parse_timeunits: patterns.extend([r"{timeUnit}\({field}\)"]) patterns.extend([r"{field}"]) if parse_types: patterns = list(itertools.chain(*((p + ":{type}", p) for p in patterns))) regexps = ( re.compile(r"\A" + p.format(**units) + r"\Z", re.DOTALL) for p in patterns ) # find matches depending on valid fields passed if isinstance(shorthand, dict): attrs = shorthand else: attrs = next( exp.match(shorthand).groupdict() for exp in regexps if exp.match(shorthand) ) # Handle short form of the type expression if "type" in attrs: attrs["type"] = INV_TYPECODE_MAP.get(attrs["type"], attrs["type"]) # counts are quantitative by default if attrs == {"aggregate": "count"}: attrs["type"] = "quantitative" # times are temporal by default if "timeUnit" in attrs and "type" not in attrs: attrs["type"] = "temporal" # if data is specified and type is not, infer type from data if isinstance(data, pd.DataFrame) and "type" not in attrs: # Remove escape sequences so that types can be inferred for columns with special characters if "field" in attrs and attrs["field"].replace("\\", "") in data.columns: attrs["type"] = infer_vegalite_type(data[attrs["field"].replace("\\", "")]) # ordered categorical dataframe columns return the type and sort order as a tuple if isinstance(attrs["type"], tuple): attrs["sort"] = attrs["type"][1] attrs["type"] = attrs["type"][0] # If an unescaped colon is still present, it's often due to an incorrect data type specification # but could also be due to using a column name with ":" in it. if ( "field" in attrs and ":" in attrs["field"] and attrs["field"][attrs["field"].rfind(":") - 1] != "\\" ): raise ValueError( '"{}" '.format(attrs["field"].split(":")[-1]) + "is not one of the valid encoding data types: {}.".format( ", ".join(TYPECODE_MAP.values()) ) + "\nFor more details, see https://altair-viz.github.io/user_guide/encodings/index.html#encoding-data-types. " + "If you are trying to use a column name that contains a colon, " + 'prefix it with a backslash; for example "column\\:name" instead of "column:name".' ) return attrs def use_signature(Obj: Callable[_P, Any]): """Apply call signature and documentation of Obj to the decorated method""" def decorate(f: Callable[..., _V]) -> Callable[_P, _V]: # call-signature of f is exposed via __wrapped__. # we want it to mimic Obj.__init__ f.__wrapped__ = Obj.__init__ # type: ignore f._uses_signature = Obj # type: ignore # Supplement the docstring of f with information from Obj if Obj.__doc__: # Patch in a reference to the class this docstring is copied from, # to generate a hyperlink. doclines = Obj.__doc__.splitlines() doclines[0] = f"Refer to :class:`{Obj.__name__}`" if f.__doc__: doc = f.__doc__ + "\n".join(doclines[1:]) else: doc = "\n".join(doclines) try: f.__doc__ = doc except AttributeError: # __doc__ is not modifiable for classes in Python < 3.3 pass return f return decorate def update_nested(original, update, copy=False): """Update nested dictionaries Parameters ---------- original : dict the original (nested) dictionary, which will be updated in-place update : dict the nested dictionary of updates copy : bool, default False if True, then copy the original dictionary rather than modifying it Returns ------- original : dict a reference to the (modified) original dict Examples -------- >>> original = {'x': {'b': 2, 'c': 4}} >>> update = {'x': {'b': 5, 'd': 6}, 'y': 40} >>> update_nested(original, update) # doctest: +SKIP {'x': {'b': 5, 'c': 4, 'd': 6}, 'y': 40} >>> original # doctest: +SKIP {'x': {'b': 5, 'c': 4, 'd': 6}, 'y': 40} """ if copy: original = deepcopy(original) for key, val in update.items(): if isinstance(val, Mapping): orig_val = original.get(key, {}) if isinstance(orig_val, Mapping): original[key] = update_nested(orig_val, val) else: original[key] = val else: original[key] = val return original def display_traceback(in_ipython=True): exc_info = sys.exc_info() if in_ipython: from IPython.core.getipython import get_ipython ip = get_ipython() else: ip = None if ip is not None: ip.showtraceback(exc_info) else: traceback.print_exception(*exc_info) def infer_encoding_types(args, kwargs, channels): """Infer typed keyword arguments for args and kwargs Parameters ---------- args : tuple List of function args kwargs : dict Dict of function kwargs channels : module The module containing all altair encoding channel classes. Returns ------- kwargs : dict All args and kwargs in a single dict, with keys and types based on the channels mapping. """ # Construct a dictionary of channel type to encoding name # TODO: cache this somehow? channel_objs = (getattr(channels, name) for name in dir(channels)) channel_objs = ( c for c in channel_objs if isinstance(c, type) and issubclass(c, SchemaBase) ) channel_to_name = {c: c._encoding_name for c in channel_objs} name_to_channel = {} for chan, name in channel_to_name.items(): chans = name_to_channel.setdefault(name, {}) if chan.__name__.endswith("Datum"): key = "datum" elif chan.__name__.endswith("Value"): key = "value" else: key = "field" chans[key] = chan # First use the mapping to convert args to kwargs based on their types. for arg in args: if isinstance(arg, (list, tuple)) and len(arg) > 0: type_ = type(arg[0]) else: type_ = type(arg) encoding = channel_to_name.get(type_, None) if encoding is None: raise NotImplementedError("positional of type {}" "".format(type_)) if encoding in kwargs: raise ValueError("encoding {} specified twice.".format(encoding)) kwargs[encoding] = arg def _wrap_in_channel_class(obj, encoding): if isinstance(obj, SchemaBase): return obj if isinstance(obj, str): obj = {"shorthand": obj} if isinstance(obj, (list, tuple)): return [_wrap_in_channel_class(subobj, encoding) for subobj in obj] if encoding not in name_to_channel: warnings.warn( "Unrecognized encoding channel '{}'".format(encoding), stacklevel=1 ) return obj classes = name_to_channel[encoding] cls = classes["value"] if "value" in obj else classes["field"] try: # Don't force validation here; some objects won't be valid until # they're created in the context of a chart. return cls.from_dict(obj, validate=False) except jsonschema.ValidationError: # our attempts at finding the correct class have failed return obj return { encoding: _wrap_in_channel_class(obj, encoding) for encoding, obj in kwargs.items() } altair-5.0.1/altair/utils/data.py000066400000000000000000000242641443422213100166740ustar00rootroot00000000000000import json import os import random import hashlib import warnings import pandas as pd from toolz import curried from typing import Callable from .core import sanitize_dataframe from .core import sanitize_geo_interface from .deprecation import AltairDeprecationWarning from .plugin_registry import PluginRegistry # ============================================================================== # Data transformer registry # ============================================================================== DataTransformerType = Callable class DataTransformerRegistry(PluginRegistry[DataTransformerType]): _global_settings = {"consolidate_datasets": True} @property def consolidate_datasets(self): return self._global_settings["consolidate_datasets"] @consolidate_datasets.setter def consolidate_datasets(self, value): self._global_settings["consolidate_datasets"] = value # ============================================================================== # Data model transformers # # A data model transformer is a pure function that takes a dict or DataFrame # and returns a transformed version of a dict or DataFrame. The dict objects # will be the Data portion of the VegaLite schema. The idea is that user can # pipe a sequence of these data transformers together to prepare the data before # it hits the renderer. # # In this version of Altair, renderers only deal with the dict form of a # VegaLite spec, after the Data model has been put into a schema compliant # form. # # A data model transformer has the following type signature: # DataModelType = Union[dict, pd.DataFrame] # DataModelTransformerType = Callable[[DataModelType, KwArgs], DataModelType] # ============================================================================== class MaxRowsError(Exception): """Raised when a data model has too many rows.""" pass @curried.curry def limit_rows(data, max_rows=5000): """Raise MaxRowsError if the data model has more than max_rows. If max_rows is None, then do not perform any check. """ check_data_type(data) if hasattr(data, "__geo_interface__"): if data.__geo_interface__["type"] == "FeatureCollection": values = data.__geo_interface__["features"] else: values = data.__geo_interface__ elif isinstance(data, pd.DataFrame): values = data elif isinstance(data, dict): if "values" in data: values = data["values"] else: return data elif hasattr(data, "__dataframe__"): values = data if max_rows is not None and len(values) > max_rows: raise MaxRowsError( "The number of rows in your dataset is greater " f"than the maximum allowed ({max_rows}).\n\n" "See https://altair-viz.github.io/user_guide/large_datasets.html " "for information on how to plot large datasets, " "including how to install third-party data management tools and, " "in the right circumstance, disable the restriction" ) return data @curried.curry def sample(data, n=None, frac=None): """Reduce the size of the data model by sampling without replacement.""" check_data_type(data) if isinstance(data, pd.DataFrame): return data.sample(n=n, frac=frac) elif isinstance(data, dict): if "values" in data: values = data["values"] n = n if n else int(frac * len(values)) values = random.sample(values, n) return {"values": values} elif hasattr(data, "__dataframe__"): # experimental interchange dataframe support pi = import_pyarrow_interchange() pa_table = pi.from_dataframe(data) n = n if n else int(frac * len(pa_table)) indices = random.sample(range(len(pa_table)), n) return pa_table.take(indices) @curried.curry def to_json( data, prefix="altair-data", extension="json", filename="{prefix}-{hash}.{extension}", urlpath="", ): """ Write the data model to a .json file and return a url based data model. """ data_json = _data_to_json_string(data) data_hash = _compute_data_hash(data_json) filename = filename.format(prefix=prefix, hash=data_hash, extension=extension) with open(filename, "w") as f: f.write(data_json) return {"url": os.path.join(urlpath, filename), "format": {"type": "json"}} @curried.curry def to_csv( data, prefix="altair-data", extension="csv", filename="{prefix}-{hash}.{extension}", urlpath="", ): """Write the data model to a .csv file and return a url based data model.""" data_csv = _data_to_csv_string(data) data_hash = _compute_data_hash(data_csv) filename = filename.format(prefix=prefix, hash=data_hash, extension=extension) with open(filename, "w") as f: f.write(data_csv) return {"url": os.path.join(urlpath, filename), "format": {"type": "csv"}} @curried.curry def to_values(data): """Replace a DataFrame by a data model with values.""" check_data_type(data) if hasattr(data, "__geo_interface__"): if isinstance(data, pd.DataFrame): data = sanitize_dataframe(data) data = sanitize_geo_interface(data.__geo_interface__) return {"values": data} elif isinstance(data, pd.DataFrame): data = sanitize_dataframe(data) return {"values": data.to_dict(orient="records")} elif isinstance(data, dict): if "values" not in data: raise KeyError("values expected in data dict, but not present.") return data elif hasattr(data, "__dataframe__"): # experimental interchange dataframe support pi = import_pyarrow_interchange() pa_table = pi.from_dataframe(data) return {"values": pa_table.to_pylist()} def check_data_type(data): """Raise if the data is not a dict or DataFrame.""" if not isinstance(data, (dict, pd.DataFrame)) and not any( hasattr(data, attr) for attr in ["__geo_interface__", "__dataframe__"] ): raise TypeError( "Expected dict, DataFrame or a __geo_interface__ attribute, got: {}".format( type(data) ) ) # ============================================================================== # Private utilities # ============================================================================== def _compute_data_hash(data_str): return hashlib.md5(data_str.encode()).hexdigest() def _data_to_json_string(data): """Return a JSON string representation of the input data""" check_data_type(data) if hasattr(data, "__geo_interface__"): if isinstance(data, pd.DataFrame): data = sanitize_dataframe(data) data = sanitize_geo_interface(data.__geo_interface__) return json.dumps(data) elif isinstance(data, pd.DataFrame): data = sanitize_dataframe(data) return data.to_json(orient="records", double_precision=15) elif isinstance(data, dict): if "values" not in data: raise KeyError("values expected in data dict, but not present.") return json.dumps(data["values"], sort_keys=True) elif hasattr(data, "__dataframe__"): # experimental interchange dataframe support pi = import_pyarrow_interchange() pa_table = pi.from_dataframe(data) return json.dumps(pa_table.to_pylist()) else: raise NotImplementedError( "to_json only works with data expressed as " "a DataFrame or as a dict" ) def _data_to_csv_string(data): """return a CSV string representation of the input data""" check_data_type(data) if hasattr(data, "__geo_interface__"): raise NotImplementedError( "to_csv does not work with data that " "contains the __geo_interface__ attribute" ) elif isinstance(data, pd.DataFrame): data = sanitize_dataframe(data) return data.to_csv(index=False) elif isinstance(data, dict): if "values" not in data: raise KeyError("values expected in data dict, but not present") return pd.DataFrame.from_dict(data["values"]).to_csv(index=False) elif hasattr(data, "__dataframe__"): # experimental interchange dataframe support pi = import_pyarrow_interchange() import pyarrow as pa import pyarrow.csv as pa_csv pa_table = pi.from_dataframe(data) csv_buffer = pa.BufferOutputStream() pa_csv.write_csv(pa_table, csv_buffer) return csv_buffer.getvalue().to_pybytes().decode() else: raise NotImplementedError( "to_csv only works with data expressed as " "a DataFrame or as a dict" ) def pipe(data, *funcs): """ Pipe a value through a sequence of functions Deprecated: use toolz.curried.pipe() instead. """ warnings.warn( "alt.pipe() is deprecated, and will be removed in a future release. " "Use toolz.curried.pipe() instead.", AltairDeprecationWarning, stacklevel=1, ) return curried.pipe(data, *funcs) def curry(*args, **kwargs): """Curry a callable function Deprecated: use toolz.curried.curry() instead. """ warnings.warn( "alt.curry() is deprecated, and will be removed in a future release. " "Use toolz.curried.curry() instead.", AltairDeprecationWarning, stacklevel=1, ) return curried.curry(*args, **kwargs) def import_pyarrow_interchange(): import pkg_resources try: pkg_resources.require("pyarrow>=11.0.0") # The package is installed and meets the minimum version requirement import pyarrow.interchange as pi return pi except pkg_resources.DistributionNotFound as err: # The package is not installed raise ImportError( "Usage of the DataFrame Interchange Protocol requires the package 'pyarrow', but it is not installed." ) from err except pkg_resources.VersionConflict as err: # The package is installed but does not meet the minimum version requirement raise ImportError( "The installed version of 'pyarrow' does not meet the minimum requirement of version 11.0.0. " "Please update 'pyarrow' to use the DataFrame Interchange Protocol." ) from err altair-5.0.1/altair/utils/deprecation.py000066400000000000000000000033711443422213100202540ustar00rootroot00000000000000import warnings import functools class AltairDeprecationWarning(UserWarning): pass def deprecated(message=None): """Decorator to deprecate a function or class. Parameters ---------- message : string (optional) The deprecation message """ def wrapper(obj): return _deprecate(obj, message=message) return wrapper def _deprecate(obj, name=None, message=None): """Return a version of a class or function that raises a deprecation warning. Parameters ---------- obj : class or function The object to create a deprecated version of. name : string (optional) The name of the deprecated object message : string (optional) The deprecation message Returns ------- deprecated_obj : The deprecated version of obj Examples -------- >>> class Foo: pass >>> OldFoo = _deprecate(Foo, "OldFoo") >>> f = OldFoo() # doctest: +SKIP AltairDeprecationWarning: alt.OldFoo is deprecated. Use alt.Foo instead. """ if message is None: message = "alt.{} is deprecated. Use alt.{} instead." "".format( name, obj.__name__ ) if isinstance(obj, type): return type( name, (obj,), { "__doc__": obj.__doc__, "__init__": _deprecate(obj.__init__, "__init__", message), }, ) elif callable(obj): @functools.wraps(obj) def new_obj(*args, **kwargs): warnings.warn(message, AltairDeprecationWarning, stacklevel=1) return obj(*args, **kwargs) new_obj._deprecated = True return new_obj else: raise ValueError("Cannot deprecate object of type {}".format(type(obj))) altair-5.0.1/altair/utils/display.py000066400000000000000000000143761443422213100174330ustar00rootroot00000000000000import json import pkgutil import textwrap from typing import Callable, Dict, Optional import uuid from .plugin_registry import PluginRegistry from .mimebundle import spec_to_mimebundle from .schemapi import validate_jsonschema # ============================================================================== # Renderer registry # ============================================================================== MimeBundleType = Dict[str, object] RendererType = Callable[..., MimeBundleType] class RendererRegistry(PluginRegistry[RendererType]): entrypoint_err_messages = { "notebook": textwrap.dedent( """ To use the 'notebook' renderer, you must install the vega package and the associated Jupyter extension. See https://altair-viz.github.io/getting_started/installation.html for more information. """ ), "altair_viewer": textwrap.dedent( """ To use the 'altair_viewer' renderer, you must install the altair_viewer package; see http://github.com/altair-viz/altair_viewer/ for more information. """ ), } def set_embed_options( self, defaultStyle=None, renderer=None, width=None, height=None, padding=None, scaleFactor=None, actions=None, **kwargs, ): """Set options for embeddings of Vega & Vega-Lite charts. Options are fully documented at https://github.com/vega/vega-embed. Similar to the `enable()` method, this can be used as either a persistent global switch, or as a temporary local setting using a context manager (i.e. a `with` statement). Parameters ---------- defaultStyle : bool or string Specify a default stylesheet for embed actions. renderer : string The renderer to use for the view. One of "canvas" (default) or "svg" width : integer The view width in pixels height : integer The view height in pixels padding : integer The view padding in pixels scaleFactor : number The number by which to multiply the width and height (default 1) of an exported PNG or SVG image. actions : bool or dict Determines if action links ("Export as PNG/SVG", "View Source", "View Vega" (only for Vega-Lite), "Open in Vega Editor") are included with the embedded view. If the value is true, all action links will be shown and none if the value is false. This property can take a key-value mapping object that maps keys (export, source, compiled, editor) to boolean values for determining if each action link should be shown. **kwargs : Additional options are passed directly to embed options. """ options = { "defaultStyle": defaultStyle, "renderer": renderer, "width": width, "height": height, "padding": padding, "scaleFactor": scaleFactor, "actions": actions, } kwargs.update({key: val for key, val in options.items() if val is not None}) return self.enable(None, embed_options=kwargs) # ============================================================================== # VegaLite v1/v2 renderer logic # ============================================================================== class Displayable: """A base display class for VegaLite v1/v2. This class takes a VegaLite v1/v2 spec and does the following: 1. Optionally validates the spec against a schema. 2. Uses the RendererPlugin to grab a renderer and call it when the IPython/Jupyter display method (_repr_mimebundle_) is called. The spec passed to this class must be fully schema compliant and already have the data portion of the spec fully processed and ready to serialize. In practice, this means, the data portion of the spec should have been passed through appropriate data model transformers. """ renderers: Optional[RendererRegistry] = None schema_path = ("altair", "") def __init__(self, spec, validate=False): # type: (dict, bool) -> None self.spec = spec self.validate = validate self._validate() def _validate(self): # type: () -> None """Validate the spec against the schema.""" data = pkgutil.get_data(*self.schema_path) assert data is not None schema_dict = json.loads(data.decode("utf-8")) validate_jsonschema( self.spec, schema_dict, ) def _repr_mimebundle_(self, include=None, exclude=None): """Return a MIME bundle for display in Jupyter frontends.""" if self.renderers is not None: return self.renderers.get()(self.spec) else: return {} def default_renderer_base(spec, mime_type, str_repr, **options): """A default renderer for Vega or VegaLite that works for modern frontends. This renderer works with modern frontends (JupyterLab, nteract) that know how to render the custom VegaLite MIME type listed above. """ assert isinstance(spec, dict) bundle = {} metadata = {} bundle[mime_type] = spec bundle["text/plain"] = str_repr if options: metadata[mime_type] = options return bundle, metadata def json_renderer_base(spec, str_repr, **options): """A renderer that returns a MIME type of application/json. In JupyterLab/nteract this is rendered as a nice JSON tree. """ return default_renderer_base( spec, mime_type="application/json", str_repr=str_repr, **options ) class HTMLRenderer: """Object to render charts as HTML, with a unique output div each time""" def __init__(self, output_div="altair-viz-{}", **kwargs): self._output_div = output_div self.kwargs = kwargs @property def output_div(self): return self._output_div.format(uuid.uuid4().hex) def __call__(self, spec, **metadata): kwargs = self.kwargs.copy() kwargs.update(metadata) return spec_to_mimebundle( spec, format="html", output_div=self.output_div, **kwargs ) altair-5.0.1/altair/utils/execeval.py000066400000000000000000000030661443422213100175540ustar00rootroot00000000000000import ast import sys if sys.version_info > (3, 8): Module = ast.Module else: # Mock the Python >= 3.8 API def Module(nodelist, type_ignores): return ast.Module(nodelist) class _CatchDisplay: """Class to temporarily catch sys.displayhook""" def __init__(self): self.output = None def __enter__(self): self.old_hook = sys.displayhook sys.displayhook = self return self def __exit__(self, type, value, traceback): sys.displayhook = self.old_hook # Returning False will cause exceptions to propagate return False def __call__(self, output): self.output = output def eval_block(code, namespace=None, filename=""): """ Execute a multi-line block of code in the given namespace If the final statement in the code is an expression, return the result of the expression. """ tree = ast.parse(code, filename="", mode="exec") if namespace is None: namespace = {} catch_display = _CatchDisplay() if isinstance(tree.body[-1], ast.Expr): to_exec, to_eval = tree.body[:-1], tree.body[-1:] else: to_exec, to_eval = tree.body, [] for node in to_exec: compiled = compile(Module([node], []), filename=filename, mode="exec") exec(compiled, namespace) with catch_display: for node in to_eval: compiled = compile( ast.Interactive([node]), filename=filename, mode="single" ) exec(compiled, namespace) return catch_display.output altair-5.0.1/altair/utils/html.py000066400000000000000000000234411443422213100167230ustar00rootroot00000000000000import json import jinja2 HTML_TEMPLATE = jinja2.Template( """ {%- if fullhtml -%} {%- endif %} {%- if not requirejs %} {%- if mode == 'vega-lite' %} {%- endif %} {%- endif %} {%- if fullhtml %} {%- if requirejs %} {%- endif %} {%- endif %}
{%- if fullhtml %} {%- endif %} """ ) HTML_TEMPLATE_UNIVERSAL = jinja2.Template( """
""" ) # This is like the HTML_TEMPLATE template, but includes vega javascript inline # so that the resulting file is not dependent on external resources. This was # ported over from altair_saver. # # implies requirejs=False and full_html=True INLINE_HTML_TEMPLATE = jinja2.Template( """\
""" ) TEMPLATES = { "standard": HTML_TEMPLATE, "universal": HTML_TEMPLATE_UNIVERSAL, "inline": INLINE_HTML_TEMPLATE, } def spec_to_html( spec, mode, vega_version, vegaembed_version, vegalite_version=None, base_url="https://cdn.jsdelivr.net/npm", output_div="vis", embed_options=None, json_kwds=None, fullhtml=True, requirejs=False, template="standard", ): """Embed a Vega/Vega-Lite spec into an HTML page Parameters ---------- spec : dict a dictionary representing a vega-lite plot spec. mode : string {'vega' | 'vega-lite'} The rendering mode. This value is overridden by embed_options['mode'], if it is present. vega_version : string For html output, the version of vega.js to use. vegalite_version : string For html output, the version of vegalite.js to use. vegaembed_version : string For html output, the version of vegaembed.js to use. base_url : string (optional) The base url from which to load the javascript libraries. output_div : string (optional) The id of the div element where the plot will be shown. embed_options : dict (optional) Dictionary of options to pass to the vega-embed script. Default entry is {'mode': mode}. json_kwds : dict (optional) Dictionary of keywords to pass to json.dumps(). fullhtml : boolean (optional) If True (default) then return a full html page. If False, then return an HTML snippet that can be embedded into an HTML page. requirejs : boolean (optional) If False (default) then load libraries from base_url using
altair-5.0.1/doc/_static/custom.css000066400000000000000000000036761443422213100172200ustar00rootroot00000000000000.wy-nav-side p.caption { color: #F5F5F5; } div.wy-side-nav-search { background: #757575; } div.wy-side-nay { background: #212121; } table.field-list td li { line-height: 18px; } table.docutils td p { font-size: 14px !important; margin-bottom: 6px; } table.docutils td li { line-height: 18px; } table td.vl-type-def { max-width: 170px; overflow-x: clip; } /* Hide this empty container as it leads to a vertical scrollbar in the primary sidebar even if there is no need for such a scrollbar as all content would fit onto the screen */ #rtd-footer-container { display: none; } /* Default for the pydata theme is 25% */ .bd-sidebar-primary { max-width: 20% } /* By providing max-width above for .bd-sidebar-primary, we also overwrite the setting from the template for small screens, e.g. mobile phones. The values below are copied out of pydata-sphinx-theme.css so that the sidebar is again properly displayed on mobile devices and not restricted to 20% */ @media (max-width: 959.98px) { .bd-sidebar-primary { max-width: 350px; } } .properties-example .vega-bind-name { display: inline-block; min-width: 150px; } .properties-example .vega-bindings { padding-left: 20px; padding-bottom: 10px; } .properties-example .vega-bindings select { max-width: 180px; } .properties-example .vega-bindings input { vertical-align: text-bottom; margin-right: 3px; } .full-width-plot { width: 100%; } /* Configurations for the start page ------------------------------------ */ .lead { font-size: 1.3em; font-weight: 300; margin-top: 22px; margin-bottom: 22px; /* This pushes down the lead so that it is not rendered on top of the gallery (showcase) which has an absolute position. The value is calculated as height (showcase) + margin-bottom (showcase) + margin-top (lead) */ padding-top: 332px; } .lead strong { /* Default is bolder which is less */ font-weight: bold; } /* ---------------------------------- */altair-5.0.1/doc/_static/favicon.ico000066400000000000000000000104761443422213100173110ustar00rootroot00000000000000  (( @ ‰‰€€ÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿ€€€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿÿ(Éÿ.¹ó1Âÿ1Âÿ1Âÿ1Âÿ1Âÿ1ÂÿÂÿªžžÛ†Ά$ΆΆÛ’óªóªóªóª óªó¶1óÂUóÂUóÂUÿÂUóÂIÿÅQï¿P€ÿ-¿ü”-Àûû-Àûç,Àûë,Àûë,Àûë.Àûë2¿úëÂÿë« ¡ë׃ëω#ëщëχëÕŒëò§ëô©ëó¨ëô©ëô§ëõµ5ë÷ÄQëöÂNë÷ÃOë÷ÃOë÷ÃOç÷ÃOøøÃP­@¿ÿ,ÂüK0Ëÿÿ.Äÿÿ.Äÿÿ.Äÿÿ.Äÿÿ/Äÿÿ3ÄÿÿÇÿÿ°£¤ÿ܆ÿÔŒ#ÿÖ‹ÿÔŠÿÚÿø«ÿú®ÿù­ÿú­ÿù«ÿû¹6ÿýÉSÿýÇPÿýÇQÿýÈQÿùÄOÿÿÓUÿ÷ÃPÀÿÿÿÿ-ÀûÒ/Æÿÿ-Áüù-Àûý-Àûý-Àúý1ÀúýÂÿý«Ÿ ý׃ýω"ýшýЇýÕ‹ýò§ýô©ýó¨ýô©ýó¦ýõ´4ý÷ÅRý÷ÃOý÷ÃOý÷ÃOý÷ÃOùÿÌSÿøÃPº3™Ì-Àû0Îÿÿ-Àûú-Àûÿ-Àûÿ.Àûÿ2¿úÿÂÿÿ¬Ÿ ÿ׃ÿω"ÿшÿЇÿÕŒÿò§ÿõªÿô©ÿô©ÿô§ÿõµ5ÿ÷ÄQÿ÷ÂNÿ÷ÃOÿ÷ÃOÿ÷ÃOûÿÎSÿ÷ÃO¼Uªÿ-¿ÿ(-Áüù-Áýÿ-Áûý-Àûÿ.Àûÿ2¿úÿÂÿÿ¬Ÿ ÿ׃ÿω"ÿшÿЇÿÕŒÿò§ÿô©ÿó©ÿô©ÿó§ÿõµ5ÿ÷ÄQÿ÷ÃNÿ÷ÃOÿ÷ÃOÿ÷ÃOûÿÎSÿ÷ÃO¼ÿÿ-Àû·0Ëÿÿ-Àûû-Àûÿ.Àûÿ2¿úÿÂÿÿ¬Ÿ ÿ׃ÿω"ÿшÿЇÿÕŒÿò§ÿõªÿô©ÿô©ÿô§ÿõµ5ÿ÷ÄQÿ÷ÃOýøÃOü÷ÃOý÷ÃOùÿÌSÿøÃPº@¿ÿ-ÁüZ/Éÿÿ-Àûý-Àûþ.Àûÿ2¿úÿÂÿÿ¬Ÿ ÿ׃ÿω"ÿшÿЇÿÕŒÿò§ÿô©ÿô©ÿô©ÿô§ÿõµ6ÿ÷ÅRÿüÆPÿýÈQÿýÈQÿùÄOÿÿÓUÿ÷ÃPÀ€ÿ3»ÿ-Àüã.Æÿÿ.Àûü.Àûÿ2¿úÿÂÿÿ¬Ÿ ÿ׃ÿω"ÿшÿЇÿÕŒÿò§ÿô©ÿó©ÿô©ÿó§ÿõ´3ÿ÷ÂNÿøÃNîöÃPê÷ÃOë÷ÃOç÷ÃOøøÃP­@¿ÿ-¿ú“0Îÿÿ-Àûú.Àûÿ2¿úÿÂÿÿ¬Ÿ ÿ׃ÿω"ÿшÿЇÿÕŒÿò§ÿô©ÿó©ÿô©ÿô©þõ« ÿõ¬ÿó±+Aÿæf ô¿JóÂIÿÅQï¿P@¿ÿ.¿ú8.Ãÿÿ-Àûÿ.Àûþ2¿úÿÂÿÿ¬Ÿ ÿ׃ÿω"ÿшÿЇÿÕŒÿò§ÿõ©ÿô©ÿô©ÿô©þõªÿõ©ÿó¤*ÿÿ-ÀûÊ/Éÿÿ.Àûû2¿úÿÂÿÿ¬Ÿ ÿ׃ÿω"ÿшÿЇÿÕŒÿò§ÿôªÿó©ÿô©ÿô©þõªÿõªÿõ¬1ÿÌ3ÿ€€ÿÿ€ÿÿ@¿ÿ.¿úo0Ëÿÿ.Àûû2¿úÿÂÿÿ¬Ÿ ÿ׃ÿω"ÿшÿЇÿÕŒÿò§ÿôªÿó©ÿô©ÿõ©ýõªÿõªÿôª0ÿªÿÿ+Äÿ-Àûð/Ãÿÿ1ÀúýÂÿÿ¬Ÿ ÿ׃ÿω"ÿшÿЇÿÕŒÿò§ÿôªÿô©ÿô©ÿô©þõªÿõªÿôª0ÿªªÿ-Àú¥1Íÿÿ2ÀúúÂÿÿ¬Ÿ ÿ׃ÿω"ÿшÿχÿÔŒÿò§ÿôªþó¨ýô©ýõªûô©þô©þô¨/ÿª@¿ÿ-ÁüJ0Èÿÿ1ÀúýÃÿþ¬Ÿ ÿ׃ÿω"ÿшÿχÿÕŒÿó©þø¬ÿú­ÿú­ÿù­ÿü®ÿû®ÿõ§1ÿª€ÿ+ªÿ.ÀúÙ3ÇÿÿÂÿü¬Ÿ ÿ׃ÿω"ÿшÿχÿÕŒþð¦ÿô©õô©èô©êô©èô©ëô©êó¨,ÿª@¿ÿ.¿û„5ÌÿÿÃÿú¬Ÿ¡ÿ׃ÿω"ÿшÿшÿшûà“ÿÕ˜ÿÿé¦ò¦ò¦ò¦ÿ€Uªÿ2Âù.2¿úûÃÿÿ¬Ÿ þ׃ÿω"ÿшÿшÿЈûÞÿЇŒÿÿ3¿ù¸Íÿÿ­žžû׃ÿω"ÿшÿшÿшûß‘ÿшÿ™ÿÿÿ€ÿ€@¿ÿ9¾ú^Íÿÿ­ŸŸüׄþω"ÿшÿшÿшûß‘ÿшªªÿÿ`¿ïÂÿ毥©ÿ؃üω#ÿшÿшÿшûß‘ÿшÿª@¿¿Åÿ™²­´ÿØ‚øÐ‰$ýшýшýшùÝÿЈŽÿª@¿ÿÍÿ=±¦©ÿ܆ÿÔŒ ÿÖ‹ÿÖ‹ÿÒ‰ÿä•ÿÒˆ’ÿªÿÿÿÿÿ½—}¼Ó†õЈ èЈëЈëшçЈûч„ªªèt Ó…!ΆΆΆΆÓ…Õ€ ÿÿÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿÿaltair-5.0.1/doc/_static/gray-square.png000066400000000000000000000005101443422213100201220ustar00rootroot00000000000000‰PNG  IHDRddpâ•TbKGDÿÿÿ ½§“tIMEÛyfÍêIDATxœíÑÁ €@A5òÍ\#ÎÇA#Uï} ÛÇtœ«‡3sïòw3³ôëk÷¾$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$FAb‰$F€WÇ Î^ÛIEND®B`‚altair-5.0.1/doc/_static/theme_overrides.css000066400000000000000000000013671443422213100210650ustar00rootroot00000000000000/* override table width restrictions */ @media screen and (min-width: 767px) { .wy-table-responsive table td { /* !important prevents the common CSS stylesheets from overriding this as on RTD they are loaded after this stylesheet */ white-space: normal !important; } .wy-table-responsive { overflow: visible !important; } } .rst-content dl:not(.docutils) dt em { font-style: normal !important; line-height: 1.4em !important; } .rst-content div[class^='highlight'] { background: #fff !important; } img.logo { width: 120px !important; } /* Increas max-width of the content area slightly to accomodate larger screens */ .bd-main .bd-content .bd-article-container { max-width: 1000px; } altair-5.0.1/doc/_templates/000077500000000000000000000000001443422213100156675ustar00rootroot00000000000000altair-5.0.1/doc/_templates/class.rst000066400000000000000000000003611443422213100175260ustar00rootroot00000000000000:mod:`{{module}}`.{{objname}} {{ underline }}============== .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} {% block methods %} .. automethod:: __init__ {% endblock %} .. raw:: html
altair-5.0.1/doc/_templates/navbar-project.html000066400000000000000000000001421443422213100214670ustar00rootroot00000000000000

{{ project }}

altair-5.0.1/doc/_templates/sidebar-logo.html000066400000000000000000000007331443422213100211270ustar00rootroot00000000000000 altair-5.0.1/doc/case_studies/000077500000000000000000000000001443422213100162055ustar00rootroot00000000000000altair-5.0.1/doc/case_studies/exploring-weather.rst000066400000000000000000000227241443422213100224120ustar00rootroot00000000000000.. _exploring-weather: Exploring Seattle Weather ------------------------- (This tutorial is adapted from `Vega-Lite's documentation `_) In this tutorial, you’ll learn a few more techniques for creating visualizations in Altair. If you are not familiar with Altair, please read :ref:`starting` first. For this tutorial, we will create visualizations to explore weather data for Seattle, taken from NOAA. The dataset is a CSV file with columns for the temperature (in Celsius), precipitation (in millimeters), wind speed (in meter/second), and weather type. We have one row for each day from January 1st, 2012 to December 31st, 2015. Altair is designed to work with data in the form of Pandas_ dataframes, and contains a loader for this and other built-in datasets: .. altair-plot:: :output: repr from vega_datasets import data df = data.seattle_weather() df.head() The data is loaded from the web and stored in a Pandas DataFrame, and from here we can explore it with Altair. Let’s start by looking at the precipitation, using tick marks to see the distribution of precipitation values: .. altair-plot:: import altair as alt alt.Chart(df).mark_tick().encode( x='precipitation', ) It looks as though precipitation is skewed towards lower values; that is, when it rains in Seattle, it usually doesn’t rain very much. It is difficult to see patterns across continuous variables, and so to better see this, we can create a histogram of the precipitation data. For this we first discretize the precipitation values by adding a binning to ``x``. Additionally, we set our encoding channel ``y`` with ``count``. The result is a histogram of precipitation values: .. altair-plot:: alt.Chart(df).mark_bar().encode( alt.X('precipitation').bin(), y='count()' ) Next, let’s look at how precipitation in Seattle changes throughout the year. Altair natively supports dates and discretization of dates when we set the type to ``temporal`` (shorthand ``T``). For example, in the following plot, we compute the total precipitation for each month. To discretize the data into months, we can use a ``month`` binning (see :ref:`user-guide-timeunit-transform` for more information about this and other ``timeUnit`` binnings): .. altair-plot:: alt.Chart(df).mark_line().encode( x='month(date):T', y='average(precipitation)' ) This chart shows that in Seattle the precipitation in the winter is, on average, much higher than summer (an unsurprising observation to those who live there!). By changing the mapping of encoding channels to data features, you can begin to explore the relationships within the data. When looking at precipitation and temperature, we might want to aggregate by year *and* month (``yearmonth``) rather than just month. This allows us to see seasonal trends, with daily variation smoothed out. We might also wish to see the maximum and minimum temperature in each month: .. altair-plot:: alt.Chart(df).mark_line().encode( x='yearmonth(date):T', y='max(temp_max)', ) In this chart, it looks as though the maximum temperature is increasing from year to year over the course of this relatively short baseline. To look closer into this, let’s instead look at the mean of the maximum daily temperatures for each year: .. altair-plot:: alt.Chart(df).mark_line().encode( x='year(date):T', y='mean(temp_max)', ) This can be a little clearer if we use a bar plot and mark the year as an "ordinal" (ordered category) type. For aesthetic reasons, let's make the bar chart horizontal by assigning the ordinal value to the y-axis: .. altair-plot:: alt.Chart(df).mark_bar().encode( x='mean(temp_max)', y='year(date):O' ) The chart indicates that the annual average of the daily high temperatures increased over the course of these four years, a fact that you can confirm for minimum daily temperatures as well. You might also wonder how the daily temperature range changes throughout the year. For this, we have to add a computation to derive a new field, which can be done by adding a ``calculate`` transform: .. altair-plot:: alt.Chart(df).mark_bar().encode( x='mean(temp_range):Q', y='year(date):O' ).transform_calculate( temp_range="datum.temp_max - datum.temp_min" ) Note that this calculation doesn't actually do any data manipulation in Python, but rather encodes and stores the operations within the plot specification, where they will be calculated by the renderer. Of course, the same calculation could be done by using Pandas manipulations to explicitly add a column to the dataframe; the disadvantage there is that the derived values would have to be stored in the plot specification rather than computed on-demand in the browser. Next we will explore the ``weather`` field, which encodes a categorical variable describing the weather on a given day. We might wish to know how different kinds of weather (e.g. sunny days or rainy days) are distributed throughout the year. To answer this, we can discretize the date by month and then count the number of records on the y-Axis. We then break down the bars by the weather type by mapping this column to a color channel. When a bar chart has a field mapped to color, Altair will automatically stack the bars atop each other: .. altair-plot:: alt.Chart(df).mark_bar().encode( x='month(date):N', y='count()', color='weather', ) The default color palette’s semantics might not match our expectation. For example, we probably do not expect “sun†(sunny) to be purple. We can tune the chart by providing a color scale range that maps the values from the weather field to meaningful colors, using standard hex color codes: .. altair-plot:: :output: none scale = alt.Scale(domain=['sun', 'fog', 'drizzle', 'rain', 'snow'], range=['#e7ba52', '#c7c7c7', '#aec7e8', '#1f77b4', '#9467bd']) This scale can be passed to the color encoding to be applied to the plot style. In addition, we can customize the titles for the axis and legend to make the meaning of the plot more clear: .. altair-plot:: alt.Chart(df).mark_bar().encode( x=alt.X('month(date):N').title('Month of the year'), y='count()', color=alt.Color('weather', legend=alt.Legend(title='Weather type'), scale=scale), ) Combining the above ideas lets us create any number of flexible visualizations of this dataset. For example, here is a plot that uses the customizations we have developed above to explore the relationship between weather, precipitation, maximum temperature, and temperature range, configured to use a larger canvas and to allow interactive panning and zooming with the mouse: .. altair-plot:: alt.Chart(df).mark_point().encode( alt.X('temp_max').title('Maximum Daily Temperature (C)'), alt.Y('temp_range:Q').title('Daily Temperature Range (C)'), alt.Color('weather').scale(scale), alt.Size('precipitation').scale(range=[1, 200]) ).transform_calculate( "temp_range", "datum.temp_max - datum.temp_min" ).properties( width=600, height=400 ).interactive() This gives us even more insight into the weather patterns in Seattle: rainy and foggy days tend to be cooler with a narrower range of temperatures, while warmer days tend to be dry and sunny, with a wider spread between low and high temperature. You can take this even further using Altair's building blocks for multi-panel charts and interactions. For example, we might construct a histogram of days by weather type: .. altair-plot:: alt.Chart(df).mark_bar().encode( x='count()', y='weather:N', color=alt.Color('weather:N').scale(scale), ) And now we can vertically concatenate this histogram to the points plot above, and add a brush selection tool such that the histogram reflects the content of the selection (for more information on selections, see :ref:`user-guide-interactions`): .. altair-plot:: brush = alt.selection_interval() points = alt.Chart().mark_point().encode( alt.X('temp_max:Q').title('Maximum Daily Temperature (C)'), alt.Y('temp_range:Q').title('Daily Temperature Range (C)'), color=alt.condition(brush, 'weather:N', alt.value('lightgray'), scale=scale), size=alt.Size('precipitation:Q').scale(range=[1, 200]) ).transform_calculate( "temp_range", "datum.temp_max - datum.temp_min" ).properties( width=600, height=400 ).add_params( brush ) bars = alt.Chart().mark_bar().encode( x='count()', y='weather:N', color=alt.Color('weather:N').scale(scale), ).transform_calculate( "temp_range", "datum.temp_max - datum.temp_min" ).transform_filter( brush ).properties( width=600 ) alt.vconcat(points, bars, data=df) This chart, containing concatenations, data transformations, selections, and customized axes labels and data scales, shows the power of the grammar behind Altair: you can create a complex chart from a small number of building blocks. This is the end of this tutorial where you have seen various ways to bin and aggregate data, derive new fields, and customize your charts. You can find more visualizations in the :ref:`example-gallery`. If you want to further customize your charts, you can refer to Altair's :ref:`api`. .. _Pandas: http://pandas.pydata.org/ .. toctree:: :maxdepth: 1 :hidden: self altair-5.0.1/doc/conf.py000066400000000000000000000274441443422213100150440ustar00rootroot00000000000000# !/usr/bin/env python3 # # altair documentation build configuration file, created by # sphinx-quickstart on Wed Sep 7 12:52:48 2016. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import sys from datetime import datetime # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath("..")) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.autosummary", "sphinx.ext.doctest", "sphinx.ext.coverage", "sphinx.ext.githubpages", "numpydoc.numpydoc", "sphinxext_altair.altairplot", "sphinxext.altairgallery", "sphinxext.schematable", "sphinx_copybutton", "sphinx_design", ] altair_plot_links = {"editor": True, "source": False, "export": False} autodoc_default_flags = ["members", "inherited-members"] autodoc_member_order = "groupwise" # generate autosummary even if no references autosummary_generate = True # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = "Vega-Altair" copyright = "2016-{}, Vega-Altair Developers".format(datetime.now().year) author = "Vega-Altair Developers" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = "5.0.1" # The full version, including alpha/beta/rc tags. release = f"{version}" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = "en" # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. # pygments_style = 'colorful' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "pydata_sphinx_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { "navbar_start": ["navbar-logo", "navbar-project"], "navbar_center": ["navbar-nav"], "navbar_end": ["theme-switcher", "navbar-icon-links"], "primary_sidebar_end": [], "footer_items": [], "icon_links": [ { "name": "GitHub", "url": "https://github.com/altair-viz/altair", "icon": "fab fa-github fa-lg", "type": "fontawesome", }, { "name": "StackOverflow", "url": "https://stackoverflow.com/tags/altair", "icon": "fab fa-stack-overflow fa-xl", "type": "fontawesome", }, ], "header_links_before_dropdown": 6, "announcement": """This website is for version 5. You can find the documentation for version 4 here.""", } html_context = {"default_mode": "light"} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. # " v documentation" by default. # html_title = 'altair v1.0.0' # A shorter title for the navigation bar. Default is the same as html_title. html_short_title = "Altair" # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = "_static/altair-logo-light.png" # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = "_static/favicon.ico" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static", "_images"] # adapted from: http://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html # and # https://github.com/rtfd/sphinx_rtd_theme/issues/117 def setup(app): app.add_css_file("theme_overrides.css") app.add_css_file("custom.css") # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. html_sidebars = { "index": [], "**": ["sidebar-nav-bs"], } # Redirection of old page locations via the rediraffe sphinx-extension # It seems like only pages can be redirected, not headings within pages # rediraffe_redirects = { # 'case_studies/exploring-weather.rst': 'user_guide/case_studies/exploring-weather.rst' # } # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. html_show_sourcelink = False # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = "altairdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', # Latex figure (float) alignment # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( master_doc, "altair.tex", "altair Documentation", "Altair Developers", "manual", ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(master_doc, "altair", "altair Documentation", [author], 1)] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( master_doc, "altair", "altair Documentation", author, "altair", "One line description of project.", "Miscellaneous", ), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # Hide extra class members numpydoc_show_class_members = False # For the altairplot extension altairplot_links = {"editor": True, "source": True, "export": True} # Defaults for below are drawn from Altair; override here. # altairplot_vega_js_url = "https://cdn.jsdelivr.net/npm/vega@5" # altairplot_vegalite_js_url = "https://cdn.jsdelivr.net/npm/vega-lite@4" # altairplot_vegaembed_js_url = "https://cdn.jsdelivr.net/npm/vega-embed@6" altair-5.0.1/doc/getting_started/000077500000000000000000000000001443422213100167215ustar00rootroot00000000000000altair-5.0.1/doc/getting_started/getting_help.rst000066400000000000000000000015051443422213100221250ustar00rootroot00000000000000Getting Help ============ Altair is BSD-licensed and the source is available on `GitHub`_, where you can also report `bugs and feature requests`_. For general questions, please ask on `StackOverflow`_ using the `altair` tag. You can browse this documentation via the links in the top navigation panel or by viewing the full site :ref:`genindex`. In addition to reading this documentation page, it can be helpful to also browse the `Vega-Lite documentation `_. .. _GitHub: http://github.com/altair-viz/altair .. _Git Issues: http://github.com/altair-viz/altair/issues .. _Vega: http://vega.github.io/vega .. _Vega-Lite: http://vega.github.io/vega-lite .. _bugs and feature requests: https://github.com/altair-viz/altair/issues/new/choose .. _StackOverflow: https://stackoverflow.com/tags/altair altair-5.0.1/doc/getting_started/installation.rst000066400000000000000000000033201443422213100221520ustar00rootroot00000000000000.. currentmodule:: altair .. _installation: Installation ============ Altair can be installed, along with the example datasets in vega_datasets_, using: .. code-block:: bash pip install altair vega_datasets If you are using the conda_ package manager, the equivalent is: .. code-block:: bash conda install -c conda-forge altair vega_datasets At this point, you should be able to open `Jupyter Notebook`_ or `JupyterLab`_ and execute any of the code from the :ref:`example-gallery`. For more information on how to display charts in various notebook environments and non-notebook IDEs, see :ref:`displaying-charts`. Development Installation ======================== The `Altair source repository`_ is available on GitHub. Once you have cloned the repository and installed all the above dependencies, run the following command from the root of the repository to install the master version of Altair: .. code-block:: bash pip install -e . To install development dependencies as well, run .. code-block:: bash pip install -e .[dev] If you do not wish to clone the source repository, you can install the development version directly from GitHub using: .. code-block:: bash pip install -e git+https://github.com/altair-viz/altair.git Please see `CONTRIBUTING.md `_ for details on how to contribute to the Altair project. .. _conda: https://docs.conda.io/ .. _Vega-Lite: http://vega.github.io/vega-lite .. _vega_datasets: https://github.com/altair-viz/vega_datasets .. _JupyterLab: http://jupyterlab.readthedocs.io/ .. _Jupyter Notebook: https://jupyter-notebook.readthedocs.io/ .. _Altair source repository: http://github.com/altair-viz/altair altair-5.0.1/doc/getting_started/overview.rst000066400000000000000000000033611443422213100213240ustar00rootroot00000000000000.. _overview: Overview ======== Vega-Altair is a declarative statistical visualization library for Python, based on Vega_ and Vega-Lite_. It offers a powerful and concise grammar that enables you to quickly build a wide range of statistical visualizations. Here is an example of using the API to visualize a dataset with an interactive scatter plot: .. altair-plot:: # import altair with an abbreviated alias import altair as alt # load a sample dataset as a pandas DataFrame from vega_datasets import data cars = data.cars() # make the chart alt.Chart(cars).mark_point().encode( x='Horsepower', y='Miles_per_Gallon', color='Origin', ).interactive() The key idea is that you are declaring links between *data columns* and *visual encoding channels*, such as the x-axis, y-axis and color. The rest of the plot details are handled automatically. Building on this declarative system, a surprising range of plots, from simple to sophisticated, can be created using a concise grammar. The project is named after the `brightest star `_ in the constellation Aquila. From Earth's sky Altair appears close to Vega, the star from which our parent project drew its name. This documentation serves as the main reference for learning about Altair. Additional learning material and tutorials can be found in the :ref:`learning-resources` section. It can also be helpful to browse the `Vega-Lite documentation `_. .. _Vega: http://vega.github.io/vega .. _Vega-Lite: http://vega.github.io/vega-lite .. toctree:: :maxdepth: 1 :caption: Getting Started :hidden: self installation starting getting_help project_philosophy altair-5.0.1/doc/getting_started/project_philosophy.rst000066400000000000000000000062651443422213100234100ustar00rootroot00000000000000Project Philosophy ================== Many excellent plotting libraries exist in Python, including: * `Matplotlib `_ * `Bokeh `_ * `Seaborn `_ * `Lightning `_ * `Plotly `_ * `Pandas built-in plotting `_ * `HoloViews `_ * `VisPy `_ * `pygg `_ Each library does a particular set of things well. User Challenges --------------- However, such a proliferation of options creates great difficulty for users as they have to wade through all of these APIs to find which of them is the best for the task at hand. None of these libraries are optimized for high-level statistical visualization, so users have to assemble their own using a mishmash of APIs. For individuals just learning to work with data, this forces them to focus on learning APIs rather than exploring their data. Another challenge is current plotting APIs require the user to write code, even for incidental details of a visualization. This results in an unfortunate and unnecessary cognitive burden as the visualization type (histogram, scatterplot, etc.) can often be inferred using basic information such as the columns of interest and the data types of those columns. For example, if you are interested in the visualization of two numerical columns, a scatterplot is almost certainly a good starting point. If you add a categorical column to that, you probably want to encode that column using colors or facets. If inferring the visualization proves difficult at times, a simple user interface can construct a visualization without any coding. `Tableau `_ and the `Interactive Data Lab's `_ `Polestar `_ and `Voyager `_ are excellent examples of such UIs. Design Approach and Solution ---------------------------- We believe that these challenges can be addressed without the creation of yet another visualization library that has a programmatic API and built-in rendering. Vega-Altair's approach to building visualizations uses a layered design that leverages the full capabilities of existing visualization libraries: 1. Create a constrained, simple Python API (Vega-Altair) that is purely declarative 2. Use the API (Vega-Altair) to emit JSON output that follows the Vega-Lite spec 3. Render that spec using existing visualization libraries This approach enables users to perform exploratory visualizations with a much simpler API initially, pick an appropriate renderer for their usage case, and then leverage the full capabilities of that renderer for more advanced plot customization. We realize that a declarative API will necessarily be limited compared to the full programmatic APIs of Matplotlib, Bokeh, etc. That is a deliberate design choice we feel is needed to simplify the user experience of exploratory visualization. You can find a more detailed comparison between Plotly and Altair in `this StackOverflow answer `_. altair-5.0.1/doc/getting_started/starting.rst000066400000000000000000000213131443422213100213060ustar00rootroot00000000000000.. _starting: Basic Statistical Visualization =================================== (This tutorial is adapted from `Vega-Lite's documentation `_) .. currentmodule:: altair This tutorial will guide you through the basic process of creating visualizations in Altair. First, you will need to make sure you have the Altair package and its dependencies installed (see :ref:`installation`). This tutorial will assume you are working within a Jupyter notebook user interface (such as JupyterLab, Colab or VS Code), so that plots are automatically rendered. If you are using another interface, you may want to read about how Altair plots are displayed before proceeding (see :ref:`displaying-charts`). Here is the outline of this basic tutorial: - :ref:`basic-tutorial-data` - :ref:`basic-tutorial-encodings-and-marks` - :ref:`basic-tutorial-aggregation` - :ref:`basic-tutorial-customization` - :ref:`basic-tutorial-publishing` .. _basic-tutorial-data: The Data -------- Data in Altair is built around the Pandas Dataframe. One of the defining characteristics of statistical visualization is that it begins with `tidy `_ Dataframes. For the purposes of this tutorial, we'll start by importing Pandas and creating a simple DataFrame to visualize, with a categorical variable in column a and a numerical variable in column b: .. altair-plot:: :output: none import pandas as pd data = pd.DataFrame({'a': list('CCCDDDEEE'), 'b': [2, 7, 4, 1, 2, 6, 8, 4, 7]}) When using Altair, datasets are most commonly provided as a Dataframe. As we will see, the labeled columns of the dataframe are an essential piece of plotting with Altair. .. _basic-tutorial-chart-object: The Chart Object ---------------- The fundamental object in Altair is the :class:`Chart`, which takes a dataframe as a single argument: .. altair-plot:: :output: none import altair as alt chart = alt.Chart(data) So far, we have defined the Chart object, but we have not yet told the chart to *do* anything with the data. That will come next. .. _basic-tutorial-encodings-and-marks: Encodings and Marks ------------------- With this chart object in hand, we can now specify how we would like the data to be visualized. This is done via the ``mark`` attribute of the chart object, which is most conveniently accessed via the ``Chart.mark_*`` methods. For example, we can show the data as a point using :meth:`~Chart.mark_point`: .. altair-plot:: alt.Chart(data).mark_point() Here the rendering consists of one point per row in the dataset, all plotted on top of each other, since we have not yet specified positions for these points. To visually separate the points, we can map various *encoding channels*, or *channels* for short, to columns in the dataset. For example, we could *encode* the variable ``a`` of the data with the ``x`` channel, which represents the x-axis position of the points. This can be done straightforwardly via the :meth:`Chart.encode` method: .. altair-plot:: alt.Chart(data).mark_point().encode( x='a', ) The ``encode()`` method builds a key-value mapping between encoding channels (such as ``x``, ``y``, ``color``, ``shape``, ``size``, etc.) to columns in the dataset, accessed by column name. For pandas dataframes, Altair automatically determines the appropriate data type for the mapped column, which in this case is a *nominal* value, or an unordered categorical. Though we've now separated the data by one attribute, we still have multiple points overlapping within each category. Let's further separate these by adding a ``y`` encoding channel, mapped to the ``"b"`` column: .. altair-plot:: alt.Chart(data).mark_point().encode( x='a', y='b' ) The type of the data in the ``"b"`` column is again automatically-inferred by Altair, and this time is treated as a *quantitative* type (i.e. real-valued). Additionally, we see that grid lines and appropriate axis titles are automatically added as well. .. _basic-tutorial-aggregation: Data Transformation: Aggregation -------------------------------- To allow for more flexibility in how data are visualized, Altair has a built-in syntax for *aggregation* of data. For example, we can compute the average of all values by specifying this aggregate within the column identifier: .. altair-plot:: alt.Chart(data).mark_point().encode( x='a', y='average(b)' ) Now within each x-axis category, we see a single point reflecting the average of the values within that category. Typically, aggregated values are not represented by point markings, but by bar markings. We can do this by replacing :meth:`~Chart.mark_point` with :meth:`~Chart.mark_bar`: .. altair-plot:: alt.Chart(data).mark_bar().encode( x='a', y='average(b)' ) Because the categorical feature is mapped to the ``x``-axis, the result is a vertical bar chart. To get a horizontal bar chart, all we need is to swap the ``x`` and ``y`` keywords: .. altair-plot:: alt.Chart(data).mark_bar().encode( y='a', x='average(b)' ) Aside: Examining the JSON Output ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Recall that Altair's main purpose is to convert plot specifications to a JSON string that conforms to the Vega-Lite schema. It is instructive here to use the :meth:`~Chart.to_json` method to inspect the JSON specification that Altair is exporting and sending as JSON to Vega-Lite: .. altair-plot:: :output: stdout chart = alt.Chart(data).mark_bar().encode( x='a', y='average(b)', ) print(chart.to_json()) Notice here that ``encode(x='a')`` has been expanded to a JSON structure with a ``field`` name, and a ``type`` for the data. The ``encode(y='b')`` has been expanded similarly and includes an ``aggregate`` field. Altair's full shorthand syntax includes a way to specify the type of the column as well: .. altair-plot:: :output: stdout y = alt.Y('average(b):Q') print(y.to_json()) This short-hand is equivalent to spelling-out the parameters by name: .. altair-plot:: :output: repr y = alt.Y(field='b', type='quantitative', aggregate='average') print(y.to_json()) This more verbose means of specifying channels can be used directly in Altair chart specifications, a fact that becomes useful when using some of the more advanced field configurations: .. altair-plot:: alt.Chart(data).mark_bar().encode( alt.Y('a', type='nominal'), alt.X('b', type='quantitative', aggregate='average') ) .. _basic-tutorial-customization: Customizing your Visualization ------------------------------ By default, Altair via Vega-Lite makes some choices about default properties of the visualization. Altair also provides an API to customize the look of the visualization. For example, we can specify the axis titles using the :meth:`title` method of channel classes, and we can specify the color of the mark by setting the ``color`` keyword of the ``Chart.mark_*`` method to any valid HTML color string: .. altair-plot:: alt.Chart(data).mark_bar(color='firebrick').encode( alt.Y('a').title('category'), alt.X('average(b)').title('avg(b) by category') ) .. _basic-tutorial-publishing: Publishing your Visualization ----------------------------- Once you have visualized your data, perhaps you would like to publish it somewhere on the web. This can be done straightforwardly using the Vega-Embed_ Javascript package. A simple example of a stand-alone HTML document can be generated for any chart using the :meth:`Chart.save` method: .. code-block:: python chart = alt.Chart(data).mark_bar().encode( x='a', y='average(b)', ) chart.save('chart.html') The basic HTML template produces output that looks like this, where the JSON specification for your plot produced by :meth:`Chart.to_json` should be stored in the ``spec`` Javascript variable: .. code-block:: html
The :meth:`~Chart.save` method provides a convenient way to save such HTML output to file. For more information on embedding Altair/Vega-Lite, see the documentation of the Vega-Embed_ project. .. _Vega-Embed: https://github.com/vega/vega-embed altair-5.0.1/doc/index.rst000066400000000000000000000046471443422213100154060ustar00rootroot00000000000000:html_theme.sidebar_secondary.remove: Vega-Altair: Declarative Visualization in Python ================================================ .. role:: raw-html(raw) :format: html .. altair-minigallery:: :names: one_dot_per_zipcode, horizon_graph, world_projections, candlestick_chart, falkensee, errorbars_with_ci, scatter_linked_brush, line_with_ci, natural_disasters, bar_rounded, streamgraph, multiline_tooltip, choropleth, select_detail, interactive_cross_highlight, seattle_weather_interactive, london_tube, ridgeline_plot, violin_plot, strip_plot, table_bubble_plot_github, radial_chart, boxplot, mosaic_with_labels :size: 24 .. rst-class:: lead **Vega-Altair** is a declarative visualization library for Python. Its simple, friendly and consistent API, built on top of the powerful Vega-Lite_ grammar, empowers you to spend less time writing code and more time exploring your data. .. grid:: 1 1 2 2 :padding: 0 2 3 5 :gutter: 2 2 3 3 :class-container: startpage-grid .. grid-item-card:: Getting Started :link: overview :link-type: ref :link-alt: Getting started In the Getting Started section you can find installation instructions and a high-level overview of the main concepts. .. grid-item-card:: User Guide :link: user-guide-data :link-type: ref :link-alt: User guide Check out the User Guides for in-depth information on the key concepts of Vega-Altair. .. grid-item-card:: Examples :link: example-gallery :link-type: ref :link-alt: Examples The Examples gallery contains a selection of different visualizations which you can create with Vega-Altair. .. grid-item-card:: API :link: api :link-type: ref :link-alt: api The API reference guide contains detailed information on all of Vega-Altair's methods and classes. *The Vega-Altair open-source project is not affiliated with Altair Engineering, Inc.* .. toctree:: :maxdepth: 1 :hidden: Getting Started User Guide Examples API user_guide/ecosystem releases/changes .. _GitHub: http://github.com/altair-viz/altair .. _Git Issues: http://github.com/altair-viz/altair/issues .. _Vega-Lite: http://vega.github.io/vega-lite .. _bugs and feature requests: https://github.com/altair-viz/altair/issues/new/choose .. _StackOverflow: https://stackoverflow.com/tags/altair altair-5.0.1/doc/releases/000077500000000000000000000000001443422213100153355ustar00rootroot00000000000000altair-5.0.1/doc/releases/changes.rst000066400000000000000000000545361443422213100175140ustar00rootroot00000000000000.. _changes: Release Notes ============= Version 5.0.1 (released May 26, 2023) ------------------------------------- - Remove unwanted files during build to avoid littering site-packages folder (#3057). - Deprecate the ``.ref()`` function for selections, instead of removing it (#3063). - Fix bug in reconstructing layered charts with ``Chart.from_json()``/``Chart.from_dict()`` (#3068). Version 5.0.0 (released May 9, 2023) ------------------------------------ - Update Vega-Lite from version 4.17.0 to version 5.8.0; see `Vega-Lite Release Notes `_. Enhancements ~~~~~~~~~~~~ - As described in the release notes for `Vega-Lite 5.0.0 `_, the primary change in this release of Altair is the introduction of parameters. There are two types of parameters, selection parameters and variable parameters. Variable parameters are new to Altair, and while selections are not new, much of the old terminology has been deprecated. See :ref:`gallery_slider_cutoff` for an application of variable parameters (#2528). - Grouped bar charts and jitter are now supported using offset channels, see :ref:`gallery_grouped_bar_chart2` and :ref:`gallery_strip_plot_jitter` - `vl-convert `_ is now used as the default backend for saving Altair charts as svg and png files, which simplifies saving chart as it does not require external dependencies like `altair_saver `_ does (#2701). Currently, `altair_saver `_ does not support Altair 5 and it is recommended to switch to `vl-convert `_. See :ref:`saving-png` for more details. - Saving charts with HTML inline is now supported without having `altair_saver `_ installed (#2807). - The default chart width was changed from 400 to 300 (#2785). - Ordered pandas categorical data are now automatically encoded as sorted ordinal data (#2522) - The ``Title`` and ``Impute`` aliases were added for ``TitleParams`` and ``ImputeParams``, respectively (#2732). - The documentation page has been revamped, both in terms of appearance and content. - More informative autocompletion by removing deprecated methods (#2814) and for editors that rely on type hints (e.g. VS Code) we added support for completion in method chains (#2846) and extended keyword completion to cover additional methods (#2920). - Substantially improved error handling. Both in terms of finding the more relevant error (#2842), and in terms of improving the formatting and clarity of the error messages (#2824, #2568, #2979, #3009). - Include experimental support for the DataFrame Interchange Protocol (through ``__dataframe__`` attribute). This requires ``pyarrow>=11.0.0`` (#2888). - Support data type inference for columns with special characters (#2905). - Responsive width support using ``width="container"`` when saving charts to html or displaying them with the default `html` renderer (#2867). Grammar Changes ~~~~~~~~~~~~~~~ - Channel options can now be set via a more convenient method-based syntax in addition to the previous attribute-based syntax. For example, instead of ``alt.X(..., bin=alt.Bin(...))`` it is now recommend to use ``alt.X(...).bin(...)```) (#2795). See :ref:`method-based-attribute-setting` for details. - ``selection_single`` and ``selection_multi`` are now deprecated; use ``selection_point`` instead. Similarly, ``type=point`` should be used instead of ``type=single`` and ``type=multi``. - ``add_selection`` is deprecated; use ``add_params`` instead. - The ``selection`` keyword argument must in many cases be replaced by ``param`` (e.g., when specifying a filter transform). - The ``empty`` keyword argument for a selection parameter should be specified as ``True`` or ``False`` instead of ``all`` or ``none``, respectively. - The ``init`` keyword argument for a parameter is deprecated; use ``value`` instead. Bug Fixes ~~~~~~~~~ - Displaying a chart not longer changes the shorthand syntax of the stored spec (#2813). - Fixed ``disable_debug_mode`` (#2851). - Fixed issue where the webdriver was not working with Firefox's geckodriver (#2466). - Dynamically determine the jsonschema validator to avoid issues with recent jsonschema versions (#2812). Backward-Incompatible Changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Colons in column names must now be escaped to remove any ambiguity with encoding types. You now need to write ``"column\:name"`` instead of ``"column:name"`` (#2824). - Removed the Vega (v5) wrappers and deprecate rendering in Vega mode (save Chart as Vega format is still allowed) (#2829). - Removed the Vega-Lite 3 and 4 wrappers (#2847). - Removed the deprecated datasets.py (#3010). - In regards to the grammar changes listed above, the old terminology will still work in many basic cases. On the other hand, if that old terminology gets used at a lower level, then it most likely will not work. For example, in the current version of :ref:`gallery_scatter_with_minimap`, two instances of the key ``param`` are used in dictionaries to specify axis domains. Those used to be ``selection``, but that usage is not compatible with the current Vega-Lite schema. - Removed the ``altair.sphinxext`` module (#2792). The ``altair-plot`` Sphinx directive is now part of the `sphinxext-altair `_ package. Maintenance ~~~~~~~~~~~ - Vega-Altair now uses ``hatch`` for package management. - Vega-Altair now uses ``ruff`` for linting. Version 4.2.2 (released Jan 27, 2023) ------------------------------------- Bug Fixes ~~~~~~~~~ - Fix incompatibility with jsonschema < 4.5 which got introduced in Altair 4.2.1 (#2860). Version 4.2.1 (released Jan 26, 2023) ------------------------------------- Bug Fixes ~~~~~~~~~ - Disable uri-reference format check in jsonsschema (#2771). - Replace ``iteritems`` with ``items`` due to pandas deprecation (#2683). Maintenance ~~~~~~~~~~~ - Add deprecation and removal warnings for Vega-Lite v3 wrappers and Vega v5 wrappers (#2843). Version 4.2.0 (released Dec 29, 2021) ------------------------------------- - Update Vega-Lite from version 4.8.1 to version 4.17.0; see `Vega-Lite Release Notes `_. Enhancements ~~~~~~~~~~~~ - Pie charts are now supported through the use of ``mark_arc``. (Examples: eg. :ref:`gallery_pie_chart` and :ref:`gallery_radial_chart`.) - Support for the ``datum`` encoding specifications from Vega-Lite; see `Vega-Lite Datum Definition `_. (Examples: :ref:`gallery_line_chart_with_datum` and :ref:`gallery_line_chart_with_color_datum`.) - ``angle`` encoding can now be used to control point styles (Example: :ref:`gallery_wind_vector_map`) - Support for serialising pandas nullable data types for float data (#2399). - Automatically create an empty data object when ``Chart`` is called without a data parameter (#2515). - Allow the use of pathlib Paths when saving charts (#2355). - Support deepcopy for charts (#2403). Bug Fixes ~~~~~~~~~ - Fix ``to_dict()`` for nested selections (#2120). - Fix item access for expressions (#2099). Version 4.1.0 (released April 1, 2020) -------------------------------------- - Minimum Python version is now 3.6 - Update Vega-Lite to version 4.8.1; many new features and bug fixes from Vega-Lite versions 4.1 through 4.8; see `Vega-Lite Release Notes `_. Enhancements ~~~~~~~~~~~~ - ``strokeDash`` encoding can now be used to control line styles (Example: `Multi Series Line Chart `_) - ``chart.save()`` now relies on `altair_saver `_ for more flexibility (#1943). - New ``chart.show()`` method replaces ``chart.serve()``, and relies on `altair_viewer `_ to allow offline viewing of charts (#1988). Bug Fixes ~~~~~~~~~ - Support Python 3.8 (#1958) - Support multiple views in JupyterLab (#1986) - Support numpy types within specifications (#1914) - Support pandas nullable ints and string types (#1924) Maintenance ~~~~~~~~~~~ - Altair now uses `black `_ and `flake8 `_ for maintaining code quality & consistency. Version 4.0.1 (released Jan 14, 2020) ------------------------------------- Bug Fixes ~~~~~~~~~ - Update Vega-Lite version to 4.0.2 - Fix issue with duplicate chart divs in HTML renderer (#1888) Version 4.0.0 (released Dec 10, 2019) ------------------------------------- Version 4.0.0 is based on Vega-Lite version 4.0, which you can read about at https://github.com/vega/vega-lite/releases/tag/v4.0.0. It is the first version of Altair to drop Python 2 compatibility, and is tested on Python 3.5 and newer. Enhancements ~~~~~~~~~~~~ - Support for interactive legends: (see :ref:`gallery_interactive_legend`) - Responsive chart width and height: (see :ref:`customization-chart-size`) - Lookup transform responsive to selections: (see :ref:`user-guide-lookup-transform`) - Bins responsive to selections: (see :ref:`gallery_histogram_responsive`) - New Regression transform: (see :ref:`user-guide-regression-transform`) - New LOESS transform: (see :ref:`user-guide-loess-transform`) - New density transform: (see :ref:`user-guide-density-transform`) - New pivot transform: (see :ref:`user-guide-pivot-transform`) - Image mark (see :ref:`user-guide-image-marks`) - New default ``html`` renderer, directly compatible with Jupyter Notebook and JupyterLab without the need for frontend extensions, as well as tools like nbviewer and nbconvert, and related notebook environments such as Zeppelin, Colab, Kaggle Kernels, and DataBricks. To enable the old default renderer, use:: alt.renderers.enable('mimetype') - Support per-corner radius for bar marks: (see :ref:`gallery_bar_rounded`) Grammar Changes ~~~~~~~~~~~~~~~ - Sort-by-field can now use the field name directly. So instead of:: alt.Y('y:Q', sort=alt.EncodingSortField('x', order='descending')) you can now use:: alt.Y('y:Q', sort="-x") - The ``rangeStep`` argument to :class:`Scale` and :meth:`Chart.configure_scale` is deprecated. instead, use ``chart.properties(width={"step": rangeStep})`` or ``chart.configure_view(step=rangeStep)``. - ``align``, ``center``, ``spacing``, and ``columns`` are no longer valid chart properties, but are moved to the encoding classes to which they refer. Version 3.3.0 (released Nov 27, 2019) ------------------------------------- Last release to support Python 2 Enhancements ~~~~~~~~~~~~ - Add inheritance structure to low-level schema classes (#1803) - Add ``html`` renderer which works across frontends (#1793) - Support Python 3.8 (#1740, #1781) - Add ``:G`` shorthand for geojson type (#1714) - Add data generator interface: ``alt.sequence``, ``alt.graticule``, ``alt.sphere()`` (#1667, #1687) - Support geographic data sources via ``__geo_interface__`` (#1664) Bug Fixes ~~~~~~~~~ - Support ``pickle`` and ``copy.deepcopy`` for chart objects (#1805) - Fix bug when specifying ``count()`` within ``transform_joinaggregate()`` (#1751) - Fix ``LayerChart.add_selection`` (#1794) - Fix arguments to ``project()`` method (#1717) - Fix composition of multiple selections (#1707) Version 3.2.0 (released August 5, 2019) --------------------------------------- Upgraded to Vega-Lite version 3.4 (See `Vega-Lite 3.4 Release Notes `__). Following are changes to Altair in addition to those that came with VL 3.4: Enhancements ~~~~~~~~~~~~ - Selector values can be used directly in expressions (#1599) - Top-level chart repr is now truncated to improve readability of error messages (#1572) Bug Fixes ~~~~~~~~~ - top-level ``add_selection`` methods now delegate to sub-charts. Previously they produced invalid charts (#1607) - Unsupported ``mark_*()`` methods removed from LayerChart (#1607) - New encoding channels are properly parsed (#1597) - Data context is propagated when encodings are specified as lists (#1587) Backward-Incompatible Changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - ``alt.LayerChart`` no longer has ``mark_*()`` methods, because they never produced valid chart specifications) (#1607) Version 3.1.0 (Released June 6, 2019) ------------------------------------- Update includes full compatibility with version 3.3 of Vega-Lite. Enhancements ~~~~~~~~~~~~ - Added support for `vega themes `__ via ``alt.themes.enable(theme_name)`` (#1539) - Added an ``alt.renderers.disable_max_rows()`` method for disabling the maximum rows check (#1538) - Improved user-facing warnings/errors around layering and faceting (#1535). - ``data`` argument is now properly handled by ``Chart.properties`` (#1525) - Compound charts (layer, concat, hconcat, vconcat) now move data to the top level by default. In particular, this means that the ``facet()`` method can now be called directly on a layered chart without having to change how data is specified. (#1521) - ``alt.LayerChart`` now supports ``mark_*()`` methods. If a layer specifies a mark at the top level, all child charts will inherit it (unless they override it explicitly). - ``alt.Chart.facet()`` now handles wrapped facets; for example: ``python chart.facet('column_name', columns=5)`` See ``altair/examples/us_population_over_time_facet.py`` for a more complete example. Bug fixes ~~~~~~~~~ - Make ``chart.serve()`` and ``chart.save()`` respect the data transformer setting (#1538) - Fixed a deserialization bug for certain chart specs in schemapi (#1543) Backward-Incompatible Changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - ``alt.Chart.facet()`` now accepts a wrapped facet encoding as a first positional argument, rather than a row encoding. The following are examples of old invocations, and the equivalent new invocations: - ``chart.facet(row='col1', column='col2')``: unchanged - ``chart.facet('col1', 'col2')``: change to ``chart.facet(row='col1', column='col2')`` - ``chart.facet('col1')``: change to ``chart.facet(row='col1')`` In each case, the new invocations are compatible back to Altair 2.X. - Several of the encoding channels added in 3.0 have had their capitalization corrected to better match the names used in the schema: - ``alt.Fillopacity`` -> ``alt.FillOpacity`` - ``alt.Strokeopacity`` -> ``alt.StrokeOpacity`` - ``alt.Strokewidth`` -> ``alt.StrokeWidth`` - ``alt.Xerror`` -> ``alt.XError`` - ``alt.Xerror2`` -> ``alt.XError2`` - ``alt.Yerror`` -> ``alt.YError`` - ``alt.Yerror2`` -> ``alt.YError2`` Version 3.0.1 (Released May 1, 2019) ------------------------------------ Fix version info bug for HTML output and Colab & Kaggle renderers. Version 3.0.0 (Released April 26, 2019) --------------------------------------- Update to Vega-Lite 3.2 and Vega 5.3 & support all new features. See https://github.com/vega/vega-lite/releases/tag/v3.0.0 for Vega-Lite feature lists. Highlights: ~~~~~~~~~~~ - new compound marks: ``mark_boxplot()``, ``mark_errorband()``, ``mark_errorbar()`` - new transforms: ``transform_impute()``, ``transform_joinaggregate()``, ``transform_flatten()`` ``transform_fold()``, ``transform_sample()``, ``transform_stack()`` - new ``facet`` encoding that is similar to the ``row`` and ``column`` encoding, but allows for wrapped facets - new ``alt.concat()`` function that is similar to ``alt.hconcat`` and ``alt.vconcat``, but allows for more general wrapped concatenation - new ``columns`` keyword that allows wrapped faceting, repeating, and concatenation. - many, many bug fixes - tooltips can now be automatically populated using the ``tooltip`` mark configuration. - ability to specify initial conditions for selections Version 2.4.1 (Released February 21, 2019) ------------------------------------------ Enhancements ~~~~~~~~~~~~ - Several documentation cleanups & new examples Bug Fixes ~~~~~~~~~ - Fix incompatibility with pandas version 0.24 (#1315) Version 2.3.0 (Released December 7, 2018) ----------------------------------------- Includes many reworked examples in the example gallery. Enhancements ~~~~~~~~~~~~ - Better errors for non-string column names, as well as automatic conversion of ``pandas.RangeIndex`` columns to strings (#1107) - Renderers now have set\_embed\_options() method (#1203) - Added kaggle renderer & more HTML output options (#1123) Backward-incompatible changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Maintenance ~~~~~~~~~~~ - fix typing requirement in Python 3.6+ (#1185) - Added support & CI testing for Python 3.7 (#1008) Bug fixes ~~~~~~~~~ - Selection predicates now recognize all valid entries (#1143) - Python 2 support for ``chart.save()`` (#1134) Version 2.2.2 (Released August 17, 2018) ---------------------------------------- Bug Fixes ~~~~~~~~~ - fix missing JSON resource in ``altair.vega.v4`` (#1097) Version 2.2.1 (Released August 15, 2018) ---------------------------------------- Bug Fixes ~~~~~~~~~ - appropriate handling of InlineData in dataset consolidation (#1092) - fix admonition formatting in documentation page (#1094) Version 2.2.0 (Released August 14, 2018): ----------------------------------------- Enhancements ~~~~~~~~~~~~ - better handling of datetimes and timezones (#1053) - all inline datasets are now converted to named datasets and stored at the top level of the chart. This behavior can be disabled by setting ``alt.data_transformers.consolidate_datasets = False`` (#951 & #1046) - more streamlined shorthand syntax for window transforms (#957) Maintenance ~~~~~~~~~~~ - update from Vega-Lite 2.4.3 to Vega-Lite 2.6.0; see vega-lite change-logs `2.5.0 `__ `2.5.1 `__ `2.5.2 `__ `2.6.0 `__ Backward-incompatible changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - ``alt.SortField`` renamed to ``alt.EncodingSortField`` and ``alt.WindowSortField`` renamed to ``alt.SortField`` (#3741) Bug Fixes ~~~~~~~~~ - Fixed serialization of logical operands on selections within ``transform_filter()``: (#1075) - Fixed sphinx issue which embedded chart specs twice (#1088) - Avoid Selenium import until it is actually needed (#982) Version 2.1.0 (Released June 6, 2018): -------------------------------------- Enhancements ~~~~~~~~~~~~ - add a ``scale_factor`` argument to ``chart.save()`` to allow the size/resolution of saved figures to be adjusted. (#918) - add an ``add_selection()`` method to add selections to charts (#832) - add ``chart.serve()`` and ``chart.display()`` methods for more flexibility in displaying charts (#831) - allow multiple fields to be passed to encodings such as ``tooltip`` and ``detail`` (#830) - make ``timeUnit`` specifications more succinct, by parsing them in a manner similar to aggregates (#866) - make ``to_json()`` and ``to_csv()`` have deterministic filenames, so in json mode a single datasets will lead to a single on-disk serialization (#862) Breaking Changes ~~~~~~~~~~~~~~~~ - make ``data`` the first argument for all compound chart types to match the semantics of ``alt.Chart`` (this includes ``alt.FacetChart``, ``alt.LayerChart``, ``alt.RepeatChart``, ``alt.VConcatChart``, and ``alt.HConcatChart``) (#895). - update vega-lite to version 2.4.3 (#836) - Only API change is internal: ``alt.MarkProperties`` is now ``alt.MarkConfig`` Maintenance ~~~~~~~~~~~ - update vega to v3.3 & vega-embed to v3.11 in html output & colab renderer (#838) Version 2.0.0: May 2, 2018 -------------------------- - Complete rewrite of Altair, focused on supporting Vega-Lite 2.X Version 1.2.1: October 29, 2017 ------------------------------- This version of Altair is based on Vega-Lite 1.2.1. Major additions ~~~~~~~~~~~~~~~ - Support for JupyterLab/nteract through MIME based rendering. Enable this by calling ``enable_mime_rendering()`` before rendering visualizations (`#216 `__). - Change default import in all code and docs to ``import altair as alt`` - Check for missing and misspelled column names upon exporting or rendering, and raise ``FieldError`` (`#399 `__) if any problems are found. This can be disabled by setting ``Chart.validated_columns=False``. - Raise ``MaxRowsExceeded`` if the number of rows in the dataset is larger than ``Chart.max_rows`` to guard against sending large datasets to the browser. - Move the Vega-Lite 1.x api into ``altair.v1`` to make it easier for us to migrate to Vega-Lite 2.x and continue to support 1.x. No import change are needed as ``altair.v1`` is aliased to ``altair`` in this release\ ``altair.v1`` (`#377 `__). - Moved the example notebooks into a separate repository (https://github.com/altair-viz/altair\_notebooks) that has Binder support (`#391 `__). - Add ``$schema`` to top-level JSON spec (`#370 `__). - Minor documentation revisions. Bug fixes ~~~~~~~~~ - Make sure default mark is a point (`#344 `__). Version 1.2: Nov 7, 2016 ------------------------ Major additions ~~~~~~~~~~~~~~~ - Update to Vega-Lite 1.2 and make all its enhancements available to Altair - Add ``Chart.serve`` method (`#197 `__) - Add ``altair.expr`` machinery to specify transformations and filterings (`#215 `__) - Add ``Chart.savechart`` method, which can output JSON, HTML, and (if Node is installed) PNG and SVG. See https://altair-viz.github.io/documentation/displaying.html (`#213 `__) Bug fixes ~~~~~~~~~ - Countless minor bug fixes maintenance: ~~~~~~~~~~~~ - Update to Vega-Lite 1.2.1 and add its supported features - Create website: http://altair-viz.github.io/ - Set up Travis to run conda & pip; and to build documentation Version 1.0: July 11, 2016 -------------------------- - Initial release of Altair altair-5.0.1/doc/sync_website.sh000066400000000000000000000010711443422213100165630ustar00rootroot00000000000000#!/bin/bash # get git hash for commit message GITHASH=$(git rev-parse HEAD) MSG="doc build for commit $GITHASH" cd _build # clone the repo if needed if test -d altair-viz.github.io; then echo "using existing cloned altair directory"; else git clone https://github.com/altair-viz/altair-viz.github.io.git; fi # sync the website cd altair-viz.github.io git pull # remove all tracked files git ls-files -z | xargs -0 rm -f # sync files from html build rsync -r ../html/ ./ # add commit, and push to github git add . --all git commit -m "$MSG" git push origin master altair-5.0.1/doc/user_guide/000077500000000000000000000000001443422213100156655ustar00rootroot00000000000000altair-5.0.1/doc/user_guide/api.rst000066400000000000000000000267031443422213100172000ustar00rootroot00000000000000.. _api: API Reference ============= This is the class and function reference of Altair, and the following content is generated automatically from the code documentation strings. Please refer to the `full user guide `_ for further details, as this low-level documentation may not be enough to give full guidelines on their use. Top-Level Objects ----------------- .. currentmodule:: altair .. autosummary:: :toctree: generated/toplevel/ :nosignatures: Chart ConcatChart FacetChart HConcatChart LayerChart RepeatChart TopLevelMixin VConcatChart Encoding Channels ----------------- .. currentmodule:: altair .. autosummary:: :toctree: generated/channels/ :nosignatures: Angle AngleDatum AngleValue Color ColorDatum ColorValue Column Description DescriptionValue Detail Facet Fill FillDatum FillOpacity FillOpacityDatum FillOpacityValue FillValue Href HrefValue Key Latitude Latitude2 Latitude2Datum Latitude2Value LatitudeDatum Longitude Longitude2 Longitude2Datum Longitude2Value LongitudeDatum Opacity OpacityDatum OpacityValue Order OrderValue Radius Radius2 Radius2Datum Radius2Value RadiusDatum RadiusValue Row Shape ShapeDatum ShapeValue Size SizeDatum SizeValue Stroke StrokeDash StrokeDashDatum StrokeDashValue StrokeDatum StrokeOpacity StrokeOpacityDatum StrokeOpacityValue StrokeValue StrokeWidth StrokeWidthDatum StrokeWidthValue Text TextDatum TextValue Theta Theta2 Theta2Datum Theta2Value ThetaDatum ThetaValue Tooltip TooltipValue Url UrlValue X X2 X2Datum X2Value XDatum XError XError2 XError2Value XErrorValue XOffset XOffsetDatum XOffsetValue XValue Y Y2 Y2Datum Y2Value YDatum YError YError2 YError2Value YErrorValue YOffset YOffsetDatum YOffsetValue YValue API Functions ------------- .. currentmodule:: altair .. autosummary:: :toctree: generated/api/ :nosignatures: binding binding_checkbox binding_radio binding_range binding_select check_fields_and_encodings concat condition graticule hconcat layer param repeat selection selection_interval selection_multi selection_point selection_single sequence sphere topo_feature value vconcat Low-Level Schema Wrappers ------------------------- .. currentmodule:: altair .. autosummary:: :toctree: generated/core/ :nosignatures: Aggregate AggregateOp AggregateTransform AggregatedFieldDef Align AllSortString AnyMark AnyMarkConfig AreaConfig ArgmaxDef ArgminDef AutoSizeParams AutosizeType Axis AxisConfig AxisOrient AxisResolveMap BBox BarConfig BaseTitleNoValueRefs Baseline BinExtent BinParams BinTransform BindCheckbox BindDirect BindInput BindRadioSelect BindRange Binding Blend BoxPlot BoxPlotConfig BoxPlotDef BrushConfig CalculateTransform Categorical ColorDef ColorName ColorScheme CompositeMark CompositeMarkDef CompositionConfig ConcatSpecGenericSpec ConditionalAxisColor ConditionalAxisLabelAlign ConditionalAxisLabelBaseline ConditionalAxisLabelFontStyle ConditionalAxisLabelFontWeight ConditionalAxisNumber ConditionalAxisNumberArray ConditionalAxisPropertyAlignnull ConditionalAxisPropertyColornull ConditionalAxisPropertyFontStylenull ConditionalAxisPropertyFontWeightnull ConditionalAxisPropertyTextBaselinenull ConditionalAxisPropertynumberArraynull ConditionalAxisPropertynumbernull ConditionalAxisPropertystringnull ConditionalAxisString ConditionalMarkPropFieldOrDatumDef ConditionalMarkPropFieldOrDatumDefTypeForShape ConditionalParameterMarkPropFieldOrDatumDef ConditionalParameterMarkPropFieldOrDatumDefTypeForShape ConditionalParameterStringFieldDef ConditionalParameterValueDefGradientstringnullExprRef ConditionalParameterValueDefTextExprRef ConditionalParameterValueDefnumber ConditionalParameterValueDefnumberArrayExprRef ConditionalParameterValueDefnumberExprRef ConditionalParameterValueDefstringExprRef ConditionalParameterValueDefstringnullExprRef ConditionalPredicateMarkPropFieldOrDatumDef ConditionalPredicateMarkPropFieldOrDatumDefTypeForShape ConditionalPredicateStringFieldDef ConditionalPredicateValueDefAlignnullExprRef ConditionalPredicateValueDefColornullExprRef ConditionalPredicateValueDefFontStylenullExprRef ConditionalPredicateValueDefFontWeightnullExprRef ConditionalPredicateValueDefGradientstringnullExprRef ConditionalPredicateValueDefTextBaselinenullExprRef ConditionalPredicateValueDefTextExprRef ConditionalPredicateValueDefnumber ConditionalPredicateValueDefnumberArrayExprRef ConditionalPredicateValueDefnumberArraynullExprRef ConditionalPredicateValueDefnumberExprRef ConditionalPredicateValueDefnumbernullExprRef ConditionalPredicateValueDefstringExprRef ConditionalPredicateValueDefstringnullExprRef ConditionalStringFieldDef ConditionalValueDefGradientstringnullExprRef ConditionalValueDefTextExprRef ConditionalValueDefnumber ConditionalValueDefnumberArrayExprRef ConditionalValueDefnumberExprRef ConditionalValueDefstringExprRef ConditionalValueDefstringnullExprRef Config CsvDataFormat Cursor Cyclical Data DataFormat DataSource Datasets DateTime DatumDef Day DensityTransform DerivedStream Dict DictInlineDataset DictSelectionInit DictSelectionInitInterval Diverging DomainUnionWith DsvDataFormat Element Encoding EncodingSortField ErrorBand ErrorBandConfig ErrorBandDef ErrorBar ErrorBarConfig ErrorBarDef ErrorBarExtent EventStream EventType Expr ExprRef FacetEncodingFieldDef FacetFieldDef FacetMapping FacetSpec FacetedEncoding FacetedUnitSpec Feature FeatureCollection FeatureGeometryGeoJsonProperties Field FieldDefWithoutScale FieldEqualPredicate FieldGTEPredicate FieldGTPredicate FieldLTEPredicate FieldLTPredicate FieldName FieldOneOfPredicate FieldOrDatumDefWithConditionDatumDefGradientstringnull FieldOrDatumDefWithConditionDatumDefnumber FieldOrDatumDefWithConditionDatumDefnumberArray FieldOrDatumDefWithConditionDatumDefstringnull FieldOrDatumDefWithConditionMarkPropFieldDefGradientstringnull FieldOrDatumDefWithConditionMarkPropFieldDefTypeForShapestringnull FieldOrDatumDefWithConditionMarkPropFieldDefnumber FieldOrDatumDefWithConditionMarkPropFieldDefnumberArray FieldOrDatumDefWithConditionStringDatumDefText FieldOrDatumDefWithConditionStringFieldDefText FieldOrDatumDefWithConditionStringFieldDefstring FieldRange FieldRangePredicate FieldValidPredicate FilterTransform Fit FlattenTransform FoldTransform FontStyle FontWeight Generator GenericUnitSpecEncodingAnyMark GeoJsonFeature GeoJsonFeatureCollection GeoJsonProperties Geometry GeometryCollection Gradient GradientStop GraticuleGenerator GraticuleParams HConcatSpecGenericSpec Header HeaderConfig HexColor ImputeMethod ImputeParams ImputeSequence ImputeTransform InlineData InlineDataset Interpolate IntervalSelectionConfig IntervalSelectionConfigWithoutType JoinAggregateFieldDef JoinAggregateTransform JsonDataFormat LabelOverlap LatLongDef LatLongFieldDef LayerRepeatMapping LayerRepeatSpec LayerSpec LayoutAlign Legend LegendBinding LegendConfig LegendOrient LegendResolveMap LegendStreamBinding LineConfig LineString LinearGradient LocalMultiTimeUnit LocalSingleTimeUnit Locale LoessTransform LogicalAndPredicate LogicalNotPredicate LogicalOrPredicate LookupData LookupSelection LookupTransform Mark MarkConfig MarkDef MarkPropDefGradientstringnull MarkPropDefnumber MarkPropDefnumberArray MarkPropDefstringnullTypeForShape MarkType MergedStream Month MultiLineString MultiPoint MultiPolygon MultiTimeUnit NamedData NonArgAggregateOp NonLayerRepeatSpec NonNormalizedSpec NumberLocale NumericArrayMarkPropDef NumericMarkPropDef OffsetDef OrderFieldDef OrderValueDef Orient Orientation OverlayMarkDef Padding ParameterExtent ParameterName ParameterPredicate Parse ParseValue PivotTransform Point PointSelectionConfig PointSelectionConfigWithoutType PolarDef Polygon Position Position2Def PositionDatumDef PositionDatumDefBase PositionDef PositionFieldDef PositionFieldDefBase PositionValueDef Predicate PredicateComposition PrimitiveValue Projection ProjectionConfig ProjectionType QuantileTransform RadialGradient RangeConfig RangeEnum RangeRaw RangeRawArray RangeScheme RectConfig RegressionTransform RelativeBandSize RepeatMapping RepeatRef RepeatSpec Resolve ResolveMode Root RowColLayoutAlign RowColboolean RowColnumber RowColumnEncodingFieldDef SampleTransform Scale ScaleBinParams ScaleBins ScaleConfig ScaleDatumDef ScaleFieldDef ScaleInterpolateEnum ScaleInterpolateParams ScaleResolveMap ScaleType SchemaBase SchemeParams SecondaryFieldDef SelectionConfig SelectionInit SelectionInitInterval SelectionInitIntervalMapping SelectionInitMapping SelectionParameter SelectionResolution SelectionType SequenceGenerator SequenceParams SequentialMultiHue SequentialSingleHue ShapeDef SharedEncoding SingleDefUnitChannel SingleTimeUnit Sort SortArray SortByChannel SortByChannelDesc SortByEncoding SortField SortOrder Spec SphereGenerator StackOffset StackTransform StandardType Step StepFor Stream StringFieldDef StringFieldDefWithCondition StringValueDefWithCondition StrokeCap StrokeJoin StyleConfigIndex SymbolShape TextBaseline TextDef TextDirection TickConfig TickCount TimeInterval TimeIntervalStep TimeLocale TimeUnit TimeUnitParams TimeUnitTransform TitleAnchor TitleConfig TitleFrame TitleOrient TitleParams TooltipContent TopLevelConcatSpec TopLevelFacetSpec TopLevelHConcatSpec TopLevelLayerSpec TopLevelParameter TopLevelRepeatSpec TopLevelSelectionParameter TopLevelSpec TopLevelUnitSpec TopLevelVConcatSpec TopoDataFormat Transform Type TypeForShape TypedFieldDef URI UnitSpec UnitSpecWithFrame UrlData UtcMultiTimeUnit UtcSingleTimeUnit VConcatSpecGenericSpec ValueDefWithConditionMarkPropFieldOrDatumDefGradientstringnull ValueDefWithConditionMarkPropFieldOrDatumDefTypeForShapestringnull ValueDefWithConditionMarkPropFieldOrDatumDefnumber ValueDefWithConditionMarkPropFieldOrDatumDefnumberArray ValueDefWithConditionMarkPropFieldOrDatumDefstringnull ValueDefWithConditionStringFieldDefText ValueDefnumber ValueDefnumberwidthheightExprRef VariableParameter Vector10string Vector12string Vector2DateTime Vector2Vector2number Vector2boolean Vector2number Vector2string Vector3number Vector7string VegaLiteSchema ViewBackground ViewConfig WindowEventType WindowFieldDef WindowOnlyOp WindowTransform altair-5.0.1/doc/user_guide/compound_charts.rst000066400000000000000000000265431443422213100216210ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-compound: Layered and Multi-View Charts ----------------------------- Along with the basic :class:`Chart` object, Altair provides a number of compound plot types that can be used to create stacked, layered, faceted, and repeated charts. They are summarized in the following tables: ====================== =============================== =================== ====================== class functional form operator form reference ====================== =============================== =================== ====================== :class:`LayerChart` ``alt.layer(chart1, chart2)`` ``chart1 + chart2`` :ref:`layer-chart` :class:`HConcatChart` ``alt.hconcat(chart1, chart2)`` ``chart1 | chart2`` :ref:`hconcat-chart` :class:`VConcatChart` ``alt.vconcat(chart1, chart2)`` ``chart1 & chart2`` :ref:`vconcat-chart` ====================== =============================== =================== ====================== ====================== ==================================== ====================== class method form reference ====================== ==================================== ====================== :class:`RepeatChart` ``chart.repeat(row, column)`` :ref:`repeat-chart` :class:`FacetChart` ``chart.facet(facet, row, column)`` :ref:`facet-chart` ====================== ==================================== ====================== .. _layer-chart: Layered Charts ~~~~~~~~~~~~~~ Layered charts allow you to overlay two different charts on the same set of axes. They can be useful, for example, when you wish to draw multiple marks for the same data; for example: .. altair-plot:: import altair as alt from vega_datasets import data stocks = data.stocks.url base = alt.Chart(stocks).encode( x='date:T', y='price:Q', color='symbol:N' ).transform_filter( alt.datum.symbol == 'GOOG' ) base.mark_line() + base.mark_point() Here we have used the ``+`` operator to create a layered chart; alternatively we could use the ``alt.layer`` function, which accepts as its arguments any number of charts: .. altair-plot:: alt.layer( base.mark_line(), base.mark_point(), base.mark_rule() ).interactive() The output of both of these patterns is a :class:`LayerChart` object, which has properties and methods similar to the :class:`Chart` object. Order of Layers ^^^^^^^^^^^^^^^ In a layered chart, the order of layers is determined from the order in which they are specified. For example, when creating a chart using ``layer1 + layer2`` or ``alt.layer(layer1, layer2)``, ``layer1`` will appear below ``layer2``, and ``layer2`` may obscure the marks of ``layer1``. For example, consider the following chart where we plot points on top of a heat-map: .. altair-plot:: import altair as alt from vega_datasets import data source = data.movies.url heatmap = alt.Chart(source).mark_rect().encode( alt.X('IMDB_Rating:Q').bin(), alt.Y('Rotten_Tomatoes_Rating:Q').bin(), alt.Color('count()').scale(scheme='greenblue') ) points = alt.Chart(source).mark_circle( color='black', size=5, ).encode( x='IMDB_Rating:Q', y='Rotten_Tomatoes_Rating:Q', ) heatmap + points If we put the two layers in the opposite order, the points will be drawn first and will be obscured by the heatmap marks: .. altair-plot:: points + heatmap If you do not see the expected output when creating a layered chart, make certain that you are ordering the layers appropriately. .. _hconcat-chart: Horizontal Concatenation ~~~~~~~~~~~~~~~~~~~~~~~~ Displaying two plots side-by-side is most generally accomplished with the :class:`HConcatChart` object, which can be created using the :class:`hconcat` function or the ``|`` operator. For example, here is a scatter-plot concatenated with a histogram showing the distribution of its points: .. altair-plot:: import altair as alt from vega_datasets import data iris = data.iris.url chart1 = alt.Chart(iris).mark_point().encode( x='petalLength:Q', y='petalWidth:Q', color='species:N' ).properties( height=300, width=300 ) chart2 = alt.Chart(iris).mark_bar().encode( x='count()', y=alt.Y('petalWidth:Q').bin(maxbins=30), color='species:N' ).properties( height=300, width=100 ) chart1 | chart2 This example uses the ``|`` operator, but could similarly have been created with the :func:`hconcat` function: .. altair-plot:: alt.hconcat(chart1, chart2) The output of both of these is an :class:`HConcatChart` object, which has many of the same top-level methods and attributes as the :class:`Chart` object. Finally, keep in mind that for certain types of horizontally-concatenated charts, where each panel modifies just one aspect of the visualization, repeated and faceted charts are more convenient (see :ref:`repeat-chart` and :ref:`facet-chart` for more explanation). .. _vconcat-chart: Vertical Concatenation ~~~~~~~~~~~~~~~~~~~~~~ Similarly to :ref:`hconcat-chart` above, Altair offers vertical concatenation via the :func:`vconcat` function or the ``&`` operator. For example, here we vertically-concatenate two views of the same data, with a ``brush`` selection to add interaction: .. altair-plot:: import altair as alt from vega_datasets import data source = data.sp500.url brush = alt.selection_interval(encodings=['x']) base = alt.Chart(source).mark_area().encode( x = 'date:T', y = 'price:Q' ).properties( width=600, height=200 ) upper = base.encode(alt.X('date:T').scale(domain=brush)) lower = base.properties( height=60 ).add_params(brush) alt.vconcat(upper, lower) Note that we could just as well have used ``upper & lower`` rather than the more verbose ``alt.vconcat(upper, lower)``. As with horizontally-concatenated charts, keep in mind that for concatenations where only one data grouping or encoding is changing in each panel, using :ref:`repeat-chart` or :ref:`facet-chart` can be more efficient. .. _repeat-chart: Repeated Charts ~~~~~~~~~~~~~~~ The :class:`RepeatChart` object provides a convenient interface for a particular type of horizontal or vertical concatenation, in which the only difference between the concatenated panels is modification of *one or more encodings*. For example, suppose you would like to create a multi-panel scatter-plot to show different projections of a multi-dimensional dataset. Let's first create such a chart manually using ``hconcat`` and ``vconcat``, before showing how ``repeat`` can be used to build the chart more efficiently: .. altair-plot:: import altair as alt from vega_datasets import data iris = data.iris.url base = alt.Chart().mark_point().encode( color='species:N' ).properties( width=200, height=200 ).interactive() chart = alt.vconcat(data=iris) for y_encoding in ['petalLength:Q', 'petalWidth:Q']: row = alt.hconcat() for x_encoding in ['sepalLength:Q', 'sepalWidth:Q']: row |= base.encode(x=x_encoding, y=y_encoding) chart &= row chart In this example, we explicitly loop over different x and y encodings to create a 2 x 2 grid of charts showing different views of the data. The code is straightforward, if a bit verbose. The :class:`RepeatChart` pattern, accessible via the :meth:`Chart.repeat` method, makes this type of chart a bit easier to produce: .. altair-plot:: import altair as alt from vega_datasets import data iris = data.iris.url alt.Chart(iris).mark_point().encode( alt.X(alt.repeat("column"), type='quantitative'), alt.Y(alt.repeat("row"), type='quantitative'), color='species:N' ).properties( width=200, height=200 ).repeat( row=['petalLength', 'petalWidth'], column=['sepalLength', 'sepalWidth'] ).interactive() The :meth:`Chart.repeat` method is the key here: it lets you specify a set of encodings for the row and/or column which can be referred to in the chart's encoding specification using ``alt.repeat('row')`` or ``alt.repeat('column')``. Another option to use the ``repeat`` method is for layering. Here below the columns ``US_Gross`` and ``Worldwide_Gross`` are layered on the ``y``-axis using ``alt.repeat('layer')``: .. altair-plot:: import altair as alt from vega_datasets import data source = data.movies() alt.Chart(source).mark_line().encode( x=alt.X("IMDB_Rating").bin(), y=alt.Y(alt.repeat('layer')).aggregate('mean').title("Mean of US and Worldwide Gross"), color=alt.ColorDatum(alt.repeat('layer')) ).repeat(layer=["US_Gross", "Worldwide_Gross"]) Currently ``repeat`` can only be encodings (not, e.g., data transforms) but there is discussion within the Vega-Lite community about making this pattern more general in the future. .. _facet-chart: Faceted Charts ~~~~~~~~~~~~~~ Like repeated charts, Faceted charts provide a more convenient API for creating multiple views of a dataset for a specific type of chart: one where each panel contains a different subset of data. We could do this manually using a filter transform along with a horizontal concatenation: .. altair-plot:: import altair as alt from vega_datasets import data iris = data.iris.url base = alt.Chart(iris).mark_point().encode( x='petalLength:Q', y='petalWidth:Q', color='species:N' ).properties( width=160, height=160 ) chart = alt.hconcat() for species in ['setosa', 'versicolor', 'virginica']: chart |= base.transform_filter(alt.datum.species == species) chart As with the manual approach to :ref:`repeat-chart`, this is straightforward, if a bit verbose. Using ``alt.facet`` it becomes a bit cleaner: .. altair-plot:: alt.Chart(iris).mark_point().encode( x='petalLength:Q', y='petalWidth:Q', color='species:N' ).properties( width=180, height=180 ).facet( column='species:N' ) For simple charts like this, there is also a ``column`` encoding channel that can give the same results: .. altair-plot:: alt.Chart(iris).mark_point().encode( x='petalLength:Q', y='petalWidth:Q', color='species:N', column='species:N' ).properties( width=180, height=180 ) The advantage of using ``alt.facet`` is that it can create faceted views of more complicated compound charts. For example, here is a faceted view of a layered chart with a hover selection: .. altair-plot:: hover = alt.selection_point(on='mouseover', nearest=True, empty=False) base = alt.Chart(iris).encode( x='petalLength:Q', y='petalWidth:Q', color=alt.condition(hover, 'species:N', alt.value('lightgray')) ).properties( width=180, height=180, ) points = base.mark_point().add_params( hover ) text = base.mark_text(dy=-5).encode( text = 'species:N', opacity = alt.condition(hover, alt.value(1), alt.value(0)) ) alt.layer(points, text).facet( 'species:N', ) Though each of the above examples have faceted the data across columns, faceting across rows (or across rows *and* columns) is supported as well. altair-5.0.1/doc/user_guide/configuration.rst000066400000000000000000000220361443422213100212710ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-configuration: Top-Level Chart Configuration ============================= Many aspects of a chart's appearance can be configured at the top level using the ``configure_*()`` methods. These methods and the properties that they set are only valid at the top level of a chart, and can be thought of as a way of setting a chart theme: that is, they set the default styles for the entire chart, and these defaults can be overridden by specific style settings associated with chart elements. These methods and their arguments will be outlined below: - :ref:`config-chart` :meth:`Chart.configure` - :ref:`config-axis` :meth:`Chart.configure_axis` - :ref:`config-header` :meth:`Chart.configure_header` - :ref:`config-legend` :meth:`Chart.configure_legend` - :ref:`config-mark` :meth:`Chart.configure_mark` - :ref:`config-scale` :meth:`Chart.configure_scale` - :ref:`config-range` :meth:`Chart.configure_range` - :ref:`config-projection` :meth:`Chart.configure_projection` - :ref:`config-composition` :meth:`Chart.configure_concat`, :meth:`Chart.configure_facet` - :ref:`config-selection` :meth:`Chart.configure_selection` - :ref:`config-title` :meth:`Chart.configure_title` - :ref:`config-view` :meth:`Chart.configure_view` For more discussion of approaches to chart customization, see :ref:`user-guide-customization`. .. _config-chart: Chart Configuration ------------------- The :meth:`Chart.configure` method adds a :class:`Config` instance to the chart, and accepts the following parameters: .. altair-object-table:: altair.Config .. _config-axis: Axis Configuration ------------------ Axis configuration defines default settings for axes and can be set using the :meth:`Chart.configure_axis` method. Properties defined here are applied to all axes in the figure. Additional property blocks can target more specific axis types based on the orientation ("axisX", "axisY", "axisLeft", "axisTop", etc.) or band scale type ("axisBand"). For example, properties defined under the "axisBand" property will only apply to axes visualizing "band" scales. If multiple axis config blocks apply to a single axis, type-based options take precedence over orientation-based options, which in turn take precedence over general options. The methods are the following: - :meth:`Chart.configure_axis` - :meth:`Chart.configure_axisBand` - :meth:`Chart.configure_axisBottom` - :meth:`Chart.configure_axisLeft` - :meth:`Chart.configure_axisRight` - :meth:`Chart.configure_axisTop` - :meth:`Chart.configure_axisX` - :meth:`Chart.configure_axisY` - :meth:`Chart.configure_axisDiscrete` - :meth:`Chart.configure_axisPoint` - :meth:`Chart.configure_axisQuantitative` - :meth:`Chart.configure_axisTemporal` - :meth:`Chart.configure_axisXBand` - :meth:`Chart.configure_axisXDiscrete` - :meth:`Chart.configure_axisXPoint` - :meth:`Chart.configure_axisXQuantitative` - :meth:`Chart.configure_axisXTemporal` - :meth:`Chart.configure_axisYBand` - :meth:`Chart.configure_axisYDiscrete` - :meth:`Chart.configure_axisYPoint` - :meth:`Chart.configure_axisYQuantitative` - :meth:`Chart.configure_axisYTemporal` They have the following properties: .. altair-object-table:: altair.AxisConfig .. _config-header: Header Configuration -------------------- Header configuration defines default settings for headers including the font, color, size, and position of the title and labels and can be set using the :meth:`Chart.configure_header` method. Here is an example: .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars.url chart = alt.Chart(source).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N', column='Origin:N' ).properties( width=180, height=180 ) chart.configure_header( titleColor='green', titleFontSize=14, labelColor='red', labelFontSize=14 ) Additional property blocks can target more specific header types. The methods are the following: - :meth:`Chart.configure_header` - :meth:`Chart.configure_headerColumn` - :meth:`Chart.configure_headerFacet` - :meth:`Chart.configure_headerRow` They have the following properties: .. altair-object-table:: altair.HeaderConfig .. _config-legend: Legend Configuration -------------------- The :meth:`Chart.configure_legend` allows you to customize the appearance of chart legends, including location, fonts, bounding boxes, colors, and more. Here is an example: .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars.url chart = alt.Chart(source).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N' ) chart.configure_legend( strokeColor='gray', fillColor='#EEEEEE', padding=10, cornerRadius=10, orient='top-right' ) Additional properties are summarized in the following table: .. altair-object-table:: altair.LegendConfig .. _config-mark: Mark and Mark Style Configuration --------------------------------- The mark configuration can be set using the :meth:`Chart.configure_mark` method, which sets the default properties for all marks in the chart. In addition, the config object also provides mark-specific configuration using the mark type (e.g. :meth:`Chart.configure_area`) for defining default properties for each mark. For general configuration of all mark types, use: - :meth:`Chart.configure_mark` For configurations specific to particular mark types, use: - :meth:`Chart.configure_arc` - :meth:`Chart.configure_area` - :meth:`Chart.configure_bar` - :meth:`Chart.configure_boxplot` - :meth:`Chart.configure_circle` - :meth:`Chart.configure_errorband` - :meth:`Chart.configure_errorbar` - :meth:`Chart.configure_geoshape` - :meth:`Chart.configure_image` - :meth:`Chart.configure_line` - :meth:`Chart.configure_point` - :meth:`Chart.configure_rect` - :meth:`Chart.configure_rule` - :meth:`Chart.configure_square` - :meth:`Chart.configure_text` - :meth:`Chart.configure_tick` - :meth:`Chart.configure_trail` Each of the above methods accepts the following properties: .. altair-object-table:: altair.MarkConfig In addition to the default mark properties above, default values can be further customized using named styles defined as keyword arguments to the :meth:`Chart.configure_style` method. Styles can then be invoked by including a style property within a mark definition object. .. _config-scale: Scale Configuration ------------------- Scales can be configured using :meth:`Chart.configure_scale`, which has the following properties: .. altair-object-table:: altair.ScaleConfig .. _config-range: Scale Range Configuration ------------------------- Scale ranges can be configured using :meth:`Chart.configure_range`, which has the following properties: .. altair-object-table:: altair.RangeConfig .. _config-projection: Projection Configuration ------------------------ Projections can be configured using :meth:`Chart.configure_projection`, which has the following properties: .. altair-object-table:: altair.ProjectionConfig .. _config-composition: Concat and Facet Configuration ------------------------------ Various aspects of concat and facet charts can be configured using :meth:`Chart.configure_concat` and :meth:`Chart.configure_facet`, which have the following properties: .. altair-object-table:: altair.CompositionConfig .. _config-selection: Selection Configuration ----------------------- Selections can be configured using :meth:`Chart.configure_selection`, which has the following properties: .. altair-object-table:: altair.SelectionConfig .. _config-title: Title Configuration ------------------- The :meth:`Chart.configure_title` method allows configuration of the chart title, including the font, color, placement, and orientation. Here is an example: .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars.url chart = alt.Chart(source).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', ).properties( title='Cars Data' ) chart.configure_title( fontSize=20, font='Courier', anchor='start', color='gray' ) Additional title configuration options are listed in the following table: .. altair-object-table:: altair.TitleConfig .. _config-view: View Configuration ------------------ The :meth:`Chart.configure_view` method allows you to configure aspects of the chart's *view*, i.e. the area of the screen in which the data and scales are drawn. Here is an example to demonstrate some of the visual features that can be controlled: .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars.url chart = alt.Chart(source).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', ) chart.configure_view( continuousHeight=200, continuousWidth=200, strokeWidth=4, fill='#FFEEDD', stroke='red', ) Additional properties are summarized in the following table: .. altair-object-table:: altair.ViewConfig altair-5.0.1/doc/user_guide/custom_renderers.rst000066400000000000000000000043711443422213100220070ustar00rootroot00000000000000 .. _customizing-renderers: Customizing Renderers ===================== A renderer, as introduced in :ref:`renderers`, is a function that accepts a Vega-Lite or Vega visualization specification as a Python ``dict``, and returns a Python ``dict`` in Jupyter's `MIME Bundle format `_. This dictionary will be returned by a charts ``_repr_mimebundle_`` method. The keys of the MIME bundle should be MIME types (such as ``image/png``) and the values should be the data for that MIME type (text, base64 encoded binary or JSON). Altair's default ``html`` renderer returns a cross-platform HTML representation using the ``"text/html"`` mimetype; schematically it looks like this:: def default_renderer(spec): bundle = {'text/html': generate_html(spec)} metadata = {} return bundle, metadata If a renderer needs to do custom display logic that doesn't use the frontend's display system, it can also return an empty MIME bundle dict:: def empty_bundle_renderer(spec): # Custom display logic that uses the spec ... # Return empty MIME bundle return {} As a simple example of a custom renderer, imagine we would like to add a ``plaintext`` renderer that renders a chart description in plain text. We could do it this way:: def plaintext_mimetype(spec): return {'text/plain': "description: " + spec.get('description', 'none')} alt.renderers.register('plaintext', plaintext_mimetype) The ``alt.renderers`` registry allows the user to define and enable new renderers. Now you can enable this mimetype and then when your chart is displayed you will see this description:: alt.renderers.enable('plaintext') alt.Chart('data.txt').mark_point().encode( x='x:Q', y='y:Q' ).properties( description='This is a simple chart' ) .. code-block:: none description: This is a simple chart This is a simple example, but it shows you the flexibility of this approach. If you have a frontend that recognizes ``_repr_mimebundle_`` as a means of obtaining a MIME type representation of a Python object, then you can define a function that will process the chart content in any way before returning any mimetype. altair-5.0.1/doc/user_guide/customization.rst000066400000000000000000000525371443422213100213430ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-customization: Customizing Visualizations ========================== Altair's goal is to automatically choose useful plot settings and configurations so that the user is free to think about the data rather than the mechanics of plotting. That said, once you have a useful visualization, you will often want to adjust certain aspects of it. This section of the documentation outlines some of the ways to make these adjustments. Global Config vs. Local Config vs. Encoding ------------------------------------------- There are often two or three different ways to specify the look of your plots depending on the situation. For example, suppose we are creating a scatter plot of the ``cars`` dataset: .. altair-plot:: import altair as alt from vega_datasets import data cars = data.cars.url alt.Chart(cars).mark_point().encode( x='Acceleration:Q', y='Horsepower:Q' ) Suppose you wish to change the color of the points to red, and the opacity of the points to 20%. There are three possible approaches to these: 1. “Global Config†acts on an entire chart object 2. “Local Config†acts on one mark of the chart 3. “Encoding†channels can also be used to set some chart properties Global Config ~~~~~~~~~~~~~ First, every chart type has a ``"config"`` property at the top level that acts as a sort of theme for the whole chart and all of its sub-charts. Here you can specify things like axes properties, mark properties, selection properties, and more. Altair allows you to access these through the ``configure_*`` methods of the chart. Here we will use the :meth:`~Chart.configure_mark` property: .. altair-plot:: alt.Chart(cars).mark_point().encode( x='Acceleration:Q', y='Horsepower:Q' ).configure_mark( opacity=0.2, color='red' ) There are a couple things to be aware of when using this kind of global configuration: 1. By design configurations will affect *every mark* used within the chart 2. The global configuration is only permissible at the top-level; so, for example, if you tried to layer the above chart with another, it would result in an error. For a full discussion of global configuration options, see :ref:`user-guide-configuration`. Local Config ~~~~~~~~~~~~ If you would like to configure the look of the mark locally, such that the setting only affects the particular chart property you reference, this can be done via a local configuration setting. In the case of mark properties, the best approach is to set the property as an argument to the ``mark_*`` method. Here we will use :meth:`~Chart.mark_point`: .. altair-plot:: alt.Chart(cars).mark_point(opacity=0.2, color='red').encode( x='Acceleration:Q', y='Horsepower:Q' ) Unlike when using the global configuration, here it is possible to use the resulting chart as a layer or facet in a compound chart. Local config settings like this one will always override global settings. Encoding ~~~~~~~~ Finally, it is possible to set chart properties via the encoding channel (see :ref:`user-guide-encoding`). Rather than mapping a property to a data column, you can map a property directly to a value using the :func:`value` function: .. altair-plot:: alt.Chart(cars).mark_point().encode( x='Acceleration:Q', y='Horsepower:Q', opacity=alt.value(0.2), color=alt.value('red') ) Note that only a limited set of mark properties can be bound to encodings, so for some (e.g. ``fillOpacity``, ``strokeOpacity``, etc.) the encoding approach is not available. Encoding settings will always override local or global configuration settings. Which to Use? ~~~~~~~~~~~~~ The precedence order for the three approaches is (from lowest to highest) *global config*, *local config*, *encoding*. That is, if a chart property is set both globally and locally, the local setting will win-out. If a property is set both via a configuration and an encoding, the encoding will win-out. In most usage, we recommend always using the highest-precedence means of setting properties; i.e. an encoding, or a local configuration for properties that are not tied to an encoding. Global configurations should be reserved for creating themes that are applied just before the chart is rendered. Adjusting the Title ------------------- By default an Altair chart does not have a title, as seen in this example. .. altair-plot:: import altair as alt from vega_datasets import data iowa = data.iowa_electricity.url alt.Chart(iowa).mark_area().encode( x="year:T", y=alt.Y("net_generation:Q").stack("normalize"), color="source:N" ) You can add a simple title by passing the `title` keyword argument with the data. .. altair-plot:: alt.Chart(iowa, title="Iowa's green energy boom").mark_area().encode( x="year:T", y=alt.Y("net_generation:Q").stack("normalize"), color="source:N" ) It is also possible to add a subtitle by passing in an `alt.Title` object. .. altair-plot:: alt.Chart( iowa, title=alt.Title( "Iowa's green energy boom", subtitle="A growing share of the state's energy has come from renewable sources" ) ).mark_area().encode( x="year:T", y=alt.Y("net_generation:Q").stack("normalize"), color="source:N" ) The subtitle can run to two lines by passing a list where each list item is a line (if you don't want to create this list manually as in the example below, you can use the ``wrap`` function from the `textwrap library `_ to split a string into a list of substrings of a certain length). .. altair-plot:: alt.Chart( iowa, title=alt.Title( "Iowa's green energy boom", subtitle=["A growing share of the state's energy", "has come from renewable sources"] ) ).mark_area().encode( x="year:T", y=alt.Y("net_generation:Q").stack("normalize"), color="source:N" ) The ``Title`` object can also configure a number of other attributes, e.g., the position of the title and subtitle (see see :ref:`user-guide-customization` for details). .. altair-plot:: alt.Chart( iowa, title=alt.Title( "Iowa's green energy boom", subtitle=["A growing share of the state's energy", "has come from renewable sources"], anchor='start', orient='bottom', offset=20 ) ).mark_area().encode( x="year:T", y=alt.Y("net_generation:Q").stack("normalize"), color="source:N" ) Adjusting Axis Limits --------------------- The default axis limit used by Altair is dependent on the type of the data. To fine-tune the axis limits beyond these defaults, you can use the :class:`Scale` property of the axis encodings. For example, consider the following plot: .. altair-plot:: import altair as alt from vega_datasets import data cars = data.cars.url alt.Chart(cars).mark_point().encode( x='Acceleration:Q', y='Horsepower:Q' ) Altair inherits from Vega-Lite the convention of always including the zero-point in quantitative axes; if you would like to turn this off, you can add a :class:`Scale` property to the :class:`X` encoding that specifies ``zero=False``: .. altair-plot:: alt.Chart(cars).mark_point().encode( alt.X('Acceleration:Q').scale(zero=False), y='Horsepower:Q' ) To specify exact axis limits, you can use the ``domain`` property of the scale: .. altair-plot:: alt.Chart(cars).mark_point().encode( alt.X('Acceleration:Q').scale(domain=(5, 20)), y='Horsepower:Q' ) The problem is that the data still exists beyond the scale, and we need to tell Altair what to do with this data. One option is to "clip" the data by setting the ``"clip"`` property of the mark to True: .. altair-plot:: alt.Chart(cars).mark_point(clip=True).encode( alt.X('Acceleration:Q').scale(domain=(5, 20)), y='Horsepower:Q' ) Another option is to "clamp" the data; that is, to move points beyond the limit to the edge of the domain: .. altair-plot:: alt.Chart(cars).mark_point().encode( alt.X('Acceleration:Q').scale(domain=(5, 20), clamp=True), y='Horsepower:Q' ).interactive() For interactive charts like the one above, the clamping happens dynamically, which can be useful for keeping in mind outliers as you pan and zoom on the chart. Adjusting Axis Labels --------------------- Altair also gives you tools to easily configure the appearance of axis labels. For example consider this plot: .. altair-plot:: import pandas as pd df = pd.DataFrame({'x': [0.03, 0.04, 0.05, 0.12, 0.07, 0.15], 'y': [10, 35, 39, 50, 24, 35]}) alt.Chart(df).mark_circle().encode( x='x', y='y' ) To fine-tune the formatting of the tick labels and to add a custom title to each axis, we can pass to the :class:`X` and :class:`Y` encoding a custom :class:`Axis` definition. Here is an example of formatting the x labels as a percentage, and the y labels as a dollar value: .. altair-plot:: alt.Chart(df).mark_circle().encode( alt.X('x').axis(format='%').title('percentage'), alt.Y('y').axis(format='$').title('dollar amount') ) Axis labels can also be easily removed: .. altair-plot:: alt.Chart(df).mark_circle().encode( alt.X('x').axis(labels=False), alt.Y('y').axis(labels=False) ) Additional formatting codes are available; for a listing of these see the `d3 Format Code Documentation `_. Adjusting the Legend -------------------- A legend is added to the chart automatically when the `color`, `shape` or `size` arguments are passed to the :func:`encode` function. In this example we'll use `color`. .. altair-plot:: import altair as alt from vega_datasets import data iris = data.iris() alt.Chart(iris).mark_point().encode( x='petalWidth', y='petalLength', color='species' ) In this case, the legend can be customized by introducing the :class:`Color` class and taking advantage of its `legend` argument. The `shape` and `size` arguments have their own corresponding classes. The legend option on all of them expects a :class:`Legend` object as its input, which accepts arguments to customize many aspects of its appearance. One simple example is giving the legend a `title`. .. altair-plot:: import altair as alt from vega_datasets import data iris = data.iris() alt.Chart(iris).mark_point().encode( x='petalWidth', y='petalLength', color=alt.Color('species').title("Species by color") ) Another thing you can do is move the legend to another position with the `orient` argument. .. altair-plot:: import altair as alt from vega_datasets import data iris = data.iris() alt.Chart(iris).mark_point().encode( x='petalWidth', y='petalLength', color=alt.Color('species').legend(orient="left") ) You can remove the legend entirely by submitting a null value. .. altair-plot:: import altair as alt from vega_datasets import data iris = data.iris() alt.Chart(iris).mark_point().encode( x='petalWidth', y='petalLength', color=alt.Color('species').legend(None), ) Removing the Chart Border ------------------------- Basic Altair charts are drawn with both a grid and an outside border. To create a chart with no border, you will need to remove them both. As an example, let's start with a simple scatter plot. .. altair-plot:: import altair as alt from vega_datasets import data iris = data.iris() alt.Chart(iris).mark_point().encode( x='petalWidth', y='petalLength', color='species' ) First remove the grid using the :meth:`Chart.configure_axis` method. .. altair-plot:: import altair as alt from vega_datasets import data iris = data.iris() alt.Chart(iris).mark_point().encode( x='petalWidth', y='petalLength', color='species' ).configure_axis( grid=False ) You'll note that while the inside rules are gone, the outside border remains. Hide it by setting ``stroke=None`` inside :meth:`Chart.configure_view` (``strokeWidth=0`` and ``strokeOpacity=0`` also works): .. altair-plot:: import altair as alt from vega_datasets import data iris = data.iris() alt.Chart(iris).mark_point().encode( x='petalWidth', y='petalLength', color='species' ).configure_axis( grid=False ).configure_view( stroke=None ) It is also possible to completely remove all borders and axes by combining the above option with setting ``axis`` to ``None`` during encoding. .. altair-plot:: import altair as alt from vega_datasets import data iris = data.iris() alt.Chart(iris).mark_point().encode( alt.X('petalWidth').axis(None), alt.Y('petalLength').axis(None), color='species' ).configure_axis( grid=False ).configure_view( stroke=None ) Customizing Colors ------------------ As discussed in :ref:`type-legend-scale`, Altair chooses a suitable default color scheme based on the type of the data that the color encodes. These defaults can be customized using the `scale` argument of the :class:`Color` class. The :class:`Scale` class passed to the `scale` argument provides a number of options for customizing the color scale; we will discuss a few of them here. Color Schemes ~~~~~~~~~~~~~ Altair includes a set of named color schemes for both categorical and sequential data, defined by the vega project; see the `Vega documentation `_ for a full gallery of available color schemes. These schemes can be passed to the `scheme` argument of the :class:`Scale` class: .. altair-plot:: import altair as alt from vega_datasets import data iris = data.iris() alt.Chart(iris).mark_point().encode( x='petalWidth', y='petalLength', color=alt.Color('species').scale(scheme='dark2') ) Color Domain and Range ~~~~~~~~~~~~~~~~~~~~~~ To make a custom mapping of discrete values to colors, use the `domain` and `range` parameters of the :class:`Scale` class for values and colors respectively. .. altair-plot:: import altair as alt from vega_datasets import data iris = data.iris() domain = ['setosa', 'versicolor', 'virginica'] range_ = ['red', 'green', 'blue'] alt.Chart(iris).mark_point().encode( x='petalWidth', y='petalLength', color=alt.Color('species').scale(domain=domain, range=range_) ) Raw Color Values ~~~~~~~~~~~~~~~~ The ``scale`` is what maps the raw input values into an appropriate color encoding for displaying the data. If your data entries consist of raw color names or codes, you can set ``scale=None`` to use those colors directly: .. altair-plot:: import pandas as pd import altair as alt data = pd.DataFrame({ 'x': range(6), 'color': ['red', 'steelblue', 'chartreuse', '#F4D03F', '#D35400', '#7D3C98'] }) alt.Chart(data).mark_point( filled=True, size=100 ).encode( x='x', color=alt.Color('color').scale(None) ) Adjusting the Width of Bar Marks -------------------------------- The width of the bars in a bar plot are controlled through the ``size`` property in the :meth:`~Chart.mark_bar()`: .. altair-plot:: import altair as alt import pandas as pd data = pd.DataFrame({'name': ['a', 'b'], 'value': [4, 10]}) alt.Chart(data).mark_bar(size=10).encode( x='name:O', y='value:Q' ) But since ``mark_bar(size=10)`` only controls the width of the bars, it might become possible that the width of the chart is not adjusted accordingly: .. altair-plot:: alt.Chart(data).mark_bar(size=30).encode( x='name:O', y='value:Q' ) The width of the chart containing the bar plot can be controlled through setting the ``width`` property of the chart, either to a pixel value for any chart, or to a step value in the case of discrete scales. Here is an example of setting the width to a single value for the whole chart: .. altair-plot:: alt.Chart(data).mark_bar(size=30).encode( x='name:O', y='value:Q' ).properties(width=200) The width of the bars are set using ``mark_bar(size=30)`` and the width of the chart is set using ``properties(width=100)`` Here is an example of setting the step width for a discrete scale: .. altair-plot:: alt.Chart(data).mark_bar(size=30).encode( x='name:N', y='value:Q' ).properties(width=alt.Step(100)) The width of the bars are set using ``mark_bar(size=30)`` and the width that is allocated for each bar bar in the the chart is set using ``width=alt.Step(100)`` .. _customization-chart-size: Adjusting Chart Size -------------------- The size of charts can be adjusted using the ``width`` and ``height`` properties. For example: .. altair-plot:: import altair as alt from vega_datasets import data cars = data.cars() alt.Chart(cars).mark_bar().encode( x='Origin', y='count()' ).properties( width=200, height=150 ) Note that in the case of faceted or other compound charts, this width and height applies to the subchart rather than to the overall chart: .. altair-plot:: alt.Chart(cars).mark_bar().encode( x='Origin', y='count()', column='Cylinders:Q' ).properties( width=100, height=100 ) If you want your chart size to respond to the width of the HTML page or container in which it is rendererd, you can set ``width`` or ``height`` to the string ``"container"``: .. altair-plot:: :div_class_: full-width-plot alt.Chart(cars).mark_bar().encode( x='Origin', y='count()', ).properties( width='container', height=200 ) Note that this will only scale with the container if its parent element has a size determined outside the chart itself; For example, the container may be a ``
`` element that has style ``width: 100%; height: 300px``. .. _chart-themes: Chart Themes ------------ Altair makes available a theme registry that lets users apply chart configurations globally within any Python session. This is done via the ``alt.themes`` object. The themes registry consists of functions which define a specification dictionary that will be added to every created chart. For example, the default theme configures the default size of a single chart: >>> import altair as alt >>> default = alt.themes.get() >>> default() {'config': {'view': {'continuousWidth': 300, 'continuousHeight': 300}}} You can see that any chart you create will have this theme applied, and these configurations added to its specification: .. altair-plot:: :output: repr import altair as alt from vega_datasets import data chart = alt.Chart(data.cars.url).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q' ) chart.to_dict() The rendered chart will then reflect these configurations: .. altair-plot:: chart Changing the Theme ~~~~~~~~~~~~~~~~~~ If you would like to enable any other theme for the length of your Python session, you can call ``alt.themes.enable(theme_name)``. For example, Altair includes a theme in which the chart background is opaque rather than transparent: .. altair-plot:: :output: repr alt.themes.enable('opaque') chart.to_dict() .. altair-plot:: chart Notice that the background color of the chart is now set to white. If you would like no theme applied to your chart, you can use the theme named ``'none'``: .. altair-plot:: :output: repr alt.themes.enable('none') chart.to_dict() .. altair-plot:: chart Because the view configuration is not set, the chart is smaller than the default rendering. If you would like to use any theme just for a single chart, you can use the ``with`` statement to enable a temporary theme: .. altair-plot:: :output: none with alt.themes.enable('default'): spec = chart.to_json() Currently Altair does not offer many built-in themes, but we plan to add more options in the future. Defining a Custom Theme ~~~~~~~~~~~~~~~~~~~~~~~ The theme registry also allows defining and registering custom themes. A theme is simply a function that returns a dictionary of default values to be added to the chart specification at rendering time, which is then registered and activated. For example, here we define a theme in which all marks are drawn with black fill unless otherwise specified: .. altair-plot:: import altair as alt from vega_datasets import data # define the theme by returning the dictionary of configurations def black_marks(): return { 'config': { 'view': { 'height': 300, 'width': 300, }, 'mark': { 'color': 'black', 'fill': 'black' } } } # register the custom theme under a chosen name alt.themes.register('black_marks', black_marks) # enable the newly registered theme alt.themes.enable('black_marks') # draw the chart cars = data.cars.url alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q' ) If you want to restore the default theme, use: .. altair-plot:: :output: none alt.themes.enable('default') For more ideas on themes, see the `Vega Themes`_ repository. .. _Vega Themes: https://github.com/vega/vega-themes/ altair-5.0.1/doc/user_guide/data.rst000066400000000000000000000567251443422213100173470ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-data: Specifying Data --------------- The basic data model used by Altair is tabular data, similar to a spreadsheet or database table. Individual datasets are assumed to contain a collection of records (rows), which may contain any number of named data fields (columns). Each top-level chart object (i.e. :class:`Chart`, :class:`LayerChart`, :class:`VConcatChart`, :class:`HConcatChart`, :class:`RepeatChart`, and :class:`FacetChart`) accepts a dataset as its first argument. There are many different ways of specifying a dataset: - as a `Pandas DataFrame `_ - as a DataFrame that supports the DataFrame Interchange Protocol (contains a ``__dataframe__`` attribute), e.g. polars and pyarrow. This is experimental. - as a :class:`Data` or related object (i.e. :class:`UrlData`, :class:`InlineData`, :class:`NamedData`) - as a url string pointing to a ``json`` or ``csv`` formatted text file - as a `geopandas GeoDataFrame `_, `Shapely Geometries `_, `GeoJSON Objects `_ or other objects that support the ``__geo_interface__`` - as a generated dataset such as numerical sequences or geographic reference elements When data is specified as a pandas DataFrame, Altair uses the data type information provided by pandas to automatically determine the data types required in the encoding. For example, here we specify data via a pandas DataFrame and Altair automatically detects that the x-column should be visualized on a categorical (nominal) scale and that the y-column should be visualized on a quantitative scale: .. altair-plot:: import altair as alt import pandas as pd data = pd.DataFrame({'x': ['A', 'B', 'C', 'D', 'E'], 'y': [5, 3, 6, 7, 2]}) alt.Chart(data).mark_bar().encode( x='x', y='y', ) By comparison, all other ways of specifying the data (including non-pandas DataFrames) requires encoding types to be declared explicitly. Here we create the same chart as above using a :class:`Data` object, with the data specified as a JSON-style list of records: .. altair-plot:: import altair as alt data = alt.Data(values=[{'x': 'A', 'y': 5}, {'x': 'B', 'y': 3}, {'x': 'C', 'y': 6}, {'x': 'D', 'y': 7}, {'x': 'E', 'y': 2}]) alt.Chart(data).mark_bar().encode( x='x:N', # specify nominal data y='y:Q', # specify quantitative data ) Notice the extra markup required in the encoding; because Altair cannot infer the types within a :class:`Data` object, we must specify them manually (here we use :ref:`shorthand-description` to specify *nominal* (``N``) for ``x`` and *quantitative* (``Q``) for ``y``; see :ref:`encoding-data-types`). Similarly, we must also specify the data type when referencing data by URL: .. altair-plot:: import altair as alt from vega_datasets import data url = data.cars.url alt.Chart(url).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q' ) Encodings and their associated types are further discussed in :ref:`user-guide-encoding`. Below we go into more detail about the different ways of specifying data in an Altair chart. Pandas DataFrame ~~~~~~~~~~~~~~~~ .. _data-in-index: Including Index Data ^^^^^^^^^^^^^^^^^^^^ By design Altair only accesses dataframe columns, not dataframe indices. At times, relevant data appears in the index. For example: .. altair-plot:: :output: repr import numpy as np rand = np.random.RandomState(0) data = pd.DataFrame({'value': rand.randn(100).cumsum()}, index=pd.date_range('2018', freq='D', periods=100)) data.head() If you would like the index to be available to the chart, you can explicitly turn it into a column using the ``reset_index()`` method of Pandas dataframes: .. altair-plot:: alt.Chart(data.reset_index()).mark_line().encode( x='index:T', y='value:Q' ) If the index object does not have a ``name`` attribute set, the resulting column will be called ``"index"``. More information is available in the `Pandas documentation `_. .. _data-long-vs-wide: Long-form vs. Wide-form Data ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ There are two common conventions for storing data in a dataframe, sometimes called *long-form* and *wide-form*. Both are sensible patterns for storing data in a tabular format; briefly, the difference is this: - **wide-form data** has one row per *independent variable*, with metadata recorded in the *row and column labels*. - **long-form data** has one row per *observation*, with metadata recorded within the table as *values*. Altair's grammar works best with **long-form** data, in which each row corresponds to a single observation along with its metadata. A concrete example will help in making this distinction more clear. Consider a dataset consisting of stock prices of several companies over time. The wide-form version of the data might be arranged as follows: .. altair-plot:: :output: repr :chart-var-name: wide_form wide_form = pd.DataFrame({'Date': ['2007-10-01', '2007-11-01', '2007-12-01'], 'AAPL': [189.95, 182.22, 198.08], 'AMZN': [89.15, 90.56, 92.64], 'GOOG': [707.00, 693.00, 691.48]}) print(wide_form) Notice that each row corresponds to a single time-stamp (here time is the independent variable), while metadata for each observation (i.e. company name) is stored within the column labels. The long-form version of the same data might look like this: .. altair-plot:: :output: repr :chart-var-name: long_form long_form = pd.DataFrame({'Date': ['2007-10-01', '2007-11-01', '2007-12-01', '2007-10-01', '2007-11-01', '2007-12-01', '2007-10-01', '2007-11-01', '2007-12-01'], 'company': ['AAPL', 'AAPL', 'AAPL', 'AMZN', 'AMZN', 'AMZN', 'GOOG', 'GOOG', 'GOOG'], 'price': [189.95, 182.22, 198.08, 89.15, 90.56, 92.64, 707.00, 693.00, 691.48]}) print(long_form) Notice here that each row contains a single observation (i.e. price), along with the metadata for this observation (the date and company name). Importantly, the column and index labels no longer contain any useful metadata. As mentioned above, Altair works best with this long-form data, because relevant data and metadata are stored within the table itself, rather than within the labels of rows and columns: .. altair-plot:: alt.Chart(long_form).mark_line().encode( x='Date:T', y='price:Q', color='company:N' ) Wide-form data can be similarly visualized using e.g. layering (see :ref:`layer-chart`), but it is far less convenient within Altair's grammar. If you would like to convert data from wide-form to long-form, there are two possible approaches: it can be done as a preprocessing step using pandas, or as a transform step within the chart itself. We will detail to two approaches below. .. _data-converting-long-form: Converting with Pandas """""""""""""""""""""" This sort of data manipulation can be done as a preprocessing step using Pandas_, and is discussed in detail in the `Reshaping and Pivot Tables`_ section of the Pandas documentation. For converting wide-form data to the long-form data used by Altair, the ``melt`` method of dataframes can be used. The first argument to ``melt`` is the column or list of columns to treat as index variables; the remaining columns will be combined into an indicator variable and a value variable whose names can be optionally specified: .. altair-plot:: :output: repr wide_form.melt('Date', var_name='company', value_name='price') For more information on the ``melt`` method, see the `Pandas melt documentation`_. In case you would like to undo this operation and convert from long-form back to wide-form, the ``pivot`` method of dataframes is useful. .. altair-plot:: :output: repr long_form.pivot(index='Date', columns='company', values='price').reset_index() For more information on the ``pivot`` method, see the `Pandas pivot documentation`_. Converting with Fold Transform """""""""""""""""""""""""""""" If you would like to avoid data preprocessing, you can reshape your data using Altair's Fold Transform (see :ref:`user-guide-fold-transform` for a full discussion). With it, the above chart can be reproduced as follows: .. altair-plot:: alt.Chart(wide_form).transform_fold( ['AAPL', 'AMZN', 'GOOG'], as_=['company', 'price'] ).mark_line().encode( x='Date:T', y='price:Q', color='company:N' ) Notice that unlike the pandas ``melt`` function we must explicitly specify the columns to be folded. The ``as_`` argument is optional, with the default being ``["key", "value"]``. .. _data-generated: Generated Data ~~~~~~~~~~~~~~ At times it is convenient to not use an external data source, but rather generate data for display within the chart specification itself. The benefit is that the chart specification can be made much smaller for generated data than for embedded data. Sequence Generator ^^^^^^^^^^^^^^^^^^ Here is an example of using the :func:`sequence` function to generate a sequence of *x* data, along with a :ref:`user-guide-calculate-transform` to compute *y* data. .. altair-plot:: import altair as alt # Note that the following generator is functionally similar to # data = pd.DataFrame({'x': np.arange(0, 10, 0.1)}) data = alt.sequence(0, 10, 0.1, as_='x') alt.Chart(data).transform_calculate( y='sin(datum.x)' ).mark_line().encode( x='x:Q', y='y:Q', ) Graticule Generator ^^^^^^^^^^^^^^^^^^^ Another type of data that is convenient to generate in the chart itself is the latitude/longitude lines on a geographic visualization, known as a graticule. These can be created using Altair's :func:`graticule` generator function. Here is a simple example: .. altair-plot:: import altair as alt data = alt.graticule(step=[15, 15]) alt.Chart(data).mark_geoshape(stroke='black').project( 'orthographic', rotate=[0, -45, 0] ) Sphere Generator ^^^^^^^^^^^^^^^^ Finally when visualizing the globe a sphere can be used as a background layer within a map to represent the extent of the Earth. This sphere data can be created using Altair's :func:`sphere` generator function. Here is an example: .. altair-plot:: import altair as alt sphere_data = alt.sphere() grat_data = alt.graticule(step=[15, 15]) background = alt.Chart(sphere_data).mark_geoshape(fill='aliceblue') lines = alt.Chart(grat_data).mark_geoshape(stroke='lightgrey') alt.layer(background, lines).project('naturalEarth1') .. _Pandas: http://pandas.pydata.org/ .. _Pandas pivot documentation: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.pivot.html .. _Pandas melt documentation: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.melt.html#pandas.DataFrame.melt .. _Reshaping and Pivot Tables: https://pandas.pydata.org/pandas-docs/stable/reshaping.html Spatial Data ~~~~~~~~~~~~ In this section, we explain different methods for reading spatial data into Altair. To learn more about how to work with this data after you have read it in, please see the :ref:`user-guide-geoshape-marks` mark page. .. _spatial-data-gdf: GeoPandas GeoDataFrame ^^^^^^^^^^^^^^^^^^^^^^ It is convenient to use GeoPandas as the source for your spatial data. GeoPandas can read many types of spatial data and Altair works well with GeoDataFrames. Here we define four polygon geometries into a GeoDataFrame and visualize these using the ``mark_geoshape``. .. altair-plot:: :output: repr from shapely import geometry import geopandas as gpd import altair as alt data_geoms = [ {"color": "#F3C14F", "geometry": geometry.Polygon([[1.45, 3.75], [1.45, 0], [0, 0], [1.45, 3.75]])}, {"color": "#4098D7", "geometry": geometry.Polygon([[1.45, 0], [1.45, 3.75], [2.57, 3.75], [2.57, 0], [2.33, 0], [1.45, 0]])}, {"color": "#66B4E2", "geometry": geometry.Polygon([[2.33, 0], [2.33, 2.5], [3.47, 2.5], [3.47, 0], [3.2, 0], [2.57, 0], [2.33, 0]])}, {"color": "#A9CDE0", "geometry": geometry.Polygon([[3.2, 0], [3.2, 1.25], [4.32, 1.25], [4.32, 0], [3.47, 0], [3.2, 0]])}, ] gdf_geoms = gpd.GeoDataFrame(data_geoms) gdf_geoms Since the spatial data in our example is not geographic, we use ``project`` configuration ``type="identity", reflectY=True`` to draw the geometries without applying a geographic projection. By using ``alt.Color(...).scale(None)`` we disable the automatic color assignment in Altair and instead directly use the provided Hex color codes. .. altair-plot:: alt.Chart(gdf_geoms, title="Vega-Altair").mark_geoshape().encode( alt.Color("color:N").scale(None) ).project(type="identity", reflectY=True) .. _spatial-data-inline-geojson: Inline GeoJSON Object ^^^^^^^^^^^^^^^^^^^^^ If your source data is a GeoJSON file and you do not want to load it into a GeoPandas GeoDataFrame you can provide it as a dictionary to the Altair ``Data`` class. A GeoJSON file normally consists of a ``FeatureCollection`` with a list of ``features`` where the information for each geometry is specified within a ``properties`` dictionary. In the following example a GeoJSON-like data object is specified into a ``Data`` class using the ``property`` value of the ``key`` that contain the nested list (here named ``features``). .. altair-plot:: :output: repr obj_geojson = { "type": "FeatureCollection", "features":[ {"type": "Feature", "properties": {"location": "left"}, "geometry": {"type": "Polygon", "coordinates": [[[1.45, 3.75], [1.45, 0], [0, 0], [1.45, 3.75]]]}}, {"type": "Feature", "properties": {"location": "middle-left"}, "geometry": {"type": "Polygon", "coordinates": [[[1.45, 0], [1.45, 3.75], [2.57, 3.75], [2.57, 0], [2.33, 0], [1.45, 0]]]}}, {"type": "Feature", "properties": {"location": "middle-right"}, "geometry": {"type": "Polygon", "coordinates": [[[2.33, 0], [2.33, 2.5], [3.47, 2.5], [3.47, 0], [3.2, 0], [2.57, 0], [2.33, 0]]]}}, {"type": "Feature", "properties": {"location": "right"}, "geometry": {"type": "Polygon", "coordinates": [[[3.2, 0], [3.2, 1.25], [4.32, 1.25], [4.32, 0], [3.47, 0], [3.2, 0]]]}} ] } data_obj_geojson = alt.Data(values=obj_geojson, format=alt.DataFormat(property="features")) data_obj_geojson The label for each objects location is stored within the ``properties`` dictionary. To access these values you can specify a nested variable name (here ``properties.location``) within the color channel encoding. Here we change the coloring encoding to be based on this location label, and apply a ``magma`` color scheme instead of the default one. The ``:O`` suffix indicates that we want Altair to treat these values as ordinal, and you can read more about it in the :ref:`encoding-data-types` page. for the ordinal structured data. .. altair-plot:: alt.Chart(data_obj_geojson, title="Vega-Altair - ordinal scale").mark_geoshape().encode( alt.Color("properties.location:O").scale(scheme='magma') ).project(type="identity", reflectY=True) .. _spatial-data-remote-geojson: GeoJSON File by URL ^^^^^^^^^^^^^^^^^^^ Altair can load GeoJSON resources directly from a web URL. Here we use an example from geojson.xyz. As is explained in :ref:`spatial-data-inline-geojson`, we specify ``features`` as the value for the ``property`` parameter in the ``alt.DataFormat()`` object and prepend the attribute we want to plot (``continent``) with the name of the nested dictionary where the information of each geometry is stored (``properties``). .. altair-plot:: :output: repr url_geojson = "https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_110m_admin_0_countries.geojson" data_url_geojson = alt.Data(url=url_geojson, format=alt.DataFormat(property="features")) data_url_geojson .. altair-plot:: alt.Chart(data_url_geojson).mark_geoshape().encode(color='properties.continent:N') .. _spatial-data-inline-topojson: Inline TopoJSON Object ^^^^^^^^^^^^^^^^^^^^^^ TopoJSON is an extension of GeoJSON, where the geometry of the features are referred to from a top-level object named arcs. Each shared arc is only stored once to reduce the size of the data. A TopoJSON file object can contain multiple objects (eg. boundary border and province border). When defining a TopoJSON object for Altair we specify the ``topojson`` data format type and the name of the object we like to visualize using the ``feature`` parameter. Here the name of this object key is ``MY_DATA``, but this differs in each dataset. .. altair-plot:: :output: repr obj_topojson = { "arcs": [ [[1.0, 1.0], [0.0, 1.0], [0.0, 0.0], [1.0, 0.0]], [[1.0, 0.0], [2.0, 0.0], [2.0, 1.0], [1.0, 1.0]], [[1.0, 1.0], [1.0, 0.0]], ], "objects": { "MY_DATA": { "geometries": [ {"arcs": [[-3, 0]], "properties": {"name": "abc"}, "type": "Polygon"}, {"arcs": [[1, 2]], "properties": {"name": "def"}, "type": "Polygon"}, ], "type": "GeometryCollection", } }, "type": "Topology", } data_obj_topojson = alt.Data( values=obj_topojson, format=alt.DataFormat(feature="MY_DATA", type="topojson") ) data_obj_topojson .. altair-plot:: alt.Chart(data_obj_topojson).mark_geoshape( ).encode( color="properties.name:N" ).project( type='identity', reflectY=True ) .. _spatial-data-remote-topojson: TopoJSON File by URL ^^^^^^^^^^^^^^^^^^^^ Altair can load TopoJSON resources directly from a web URL. As explained in :ref:`spatial-data-inline-topojson`, we have to use the ``feature`` parameter to specify the object name (here ``boroughs``) and define the type of data as ``topjoson`` in the ``alt.DataFormat()`` object. .. altair-plot:: :output: repr from vega_datasets import data url_topojson = data.londonBoroughs.url data_url_topojson = alt.Data( url=url_topojson, format=alt.DataFormat(feature="boroughs", type="topojson") ) data_url_topojson Note: There also exist a shorthand to extract the objects from a topojson file if this file is accessible by URL: ``alt.topo_feature(url=url_topojson, feature="boroughs")`` We color encode the Boroughs by there names as they are stored as an unique identifier (``id``). We use a ``symbolLimit`` of 33 in two columns to display all entries in the legend and change the color scheme to have more distinct colors. We also add a tooltip which shows the name of the borough as we hover over it with the mouse. .. altair-plot:: alt.Chart(data_url_topojson, title="London-Boroughs").mark_geoshape( tooltip=True ).encode( alt.Color("id:N").scale(scheme='tableau20').legend(columns=2, symbolLimit=33) ) Similar to the ``feature`` option, there also exists the ``mesh`` parameter. This parameter extracts a named TopoJSON object set. Unlike the feature option, the corresponding geo data is returned as a single, unified mesh instance, not as individual GeoJSON features. Extracting a mesh is useful for more efficiently drawing borders or other geographic elements that you do not need to associate with specific regions such as individual countries, states or counties. Here below we draw the same Boroughs of London, but now as mesh only. Note: you have to explicitly define ``filled=False`` to draw multi(lines) without fill color. .. altair-plot:: from vega_datasets import data url_topojson = data.londonBoroughs.url data_url_topojson_mesh = alt.Data( url=url_topojson, format=alt.DataFormat(mesh="boroughs", type="topojson") ) alt.Chart(data_url_topojson_mesh, title="Border London-Boroughs").mark_geoshape( filled=False ) .. _spatial-data-nested-geojson: Nested GeoJSON Objects ^^^^^^^^^^^^^^^^^^^^^^ GeoJSON data can also be nested within another dataset. In this case it is possible to use the ``shape`` encoding channel in combination with the ``:G`` suffix to visualize the nested features as GeoJSON objects. In the following example the GeoJSON object are nested within ``geo`` in the list of dictionaries: .. altair-plot:: nested_features = [ {"color": "#F3C14F", "geo": {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[1.45, 3.75], [1.45, 0], [0, 0], [1.45, 3.75]]]}}}, {"color": "#4098D7", "geo": {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[1.45, 0], [1.45, 3.75], [2.57, 3.75], [2.57, 0], [2.33, 0], [1.45, 0]]]}}}, {"color": "#66B4E2", "geo": {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[2.33, 0], [2.33, 2.5], [3.47, 2.5], [3.47, 0], [3.2, 0], [2.57, 0], [2.33, 0]]]}}}, {"color": "#A9CDE0", "geo": {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[3.2, 0], [3.2, 1.25], [4.32, 1.25], [4.32, 0], [3.47, 0], [3.2, 0]]]}}}, ] data_nested_features = alt.Data(values=nested_features) alt.Chart(data_nested_features, title="Vega-Altair").mark_geoshape().encode( shape="geo:G", color=alt.Color("color:N").scale(None) ).project(type="identity", reflectY=True) .. _data-projections: Projections ^^^^^^^^^^^ For geographic data it is best to use the World Geodetic System 1984 as its geographic coordinate reference system with units in decimal degrees. Try to avoid putting projected data into Altair, but reproject your spatial data to EPSG:4326 first. If your data comes in a different projection (eg. with units in meters) and you don't have the option to reproject the data, try using the project configuration ``(type: 'identity', reflectY': True)``. It draws the geometries without applying a projection. .. _data-winding-order: Winding Order ^^^^^^^^^^^^^ LineString, Polygon and MultiPolygon geometries contain coordinates in an order: lines go in a certain direction, and polygon rings do too. The GeoJSON-like structure of the ``__geo_interface__`` recommends the right-hand rule winding order for Polygon and MultiPolygons. Meaning that the exterior rings should be counterclockwise and interior rings are clockwise. While it recommends the right-hand rule winding order, it does not reject geometries that do not use the right-hand rule. Altair does NOT follow the right-hand rule for geometries, but uses the left-hand rule. Meaning that exterior rings should be clockwise and interior rings should be counterclockwise. If you face a problem regarding winding order, try to force the left-hand rule on your data before usage in Altair using GeoPandas for example as such: .. code:: python from shapely.ops import orient gdf.geometry = gdf.geometry.apply(orient, args=(-1,)) .. toctree:: :maxdepth: 1 :caption: User Guide :hidden: self encodings/index marks/index transform/index interactions compound_charts scale_resolve times_and_dates customization configuration saving_charts .. toctree:: :maxdepth: 1 :caption: Advanced Usage :hidden: internals display_frontends custom_renderers data_transformers large_datasets altair-5.0.1/doc/user_guide/data_transformers.rst000066400000000000000000000153601443422213100221420ustar00rootroot00000000000000.. _data-transformers: Data Transformers ================= Before a Vega-Lite or Vega specification can be passed to a renderer, it typically has to be transformed in a number of ways: * Pandas Dataframe has to be sanitized and serialized to JSON. * The rows of a Dataframe might need to be sampled or limited to a maximum number. * The Dataframe might be written to a ``.csv`` of ``.json`` file for performance reasons. These data transformations are managed by the data transformation API of Altair. .. note:: The data transformation API of Altair should not be confused with the ``transform`` API of Vega and Vega-Lite. A data transformer is a Python function that takes a Vega-Lite data ``dict`` or Pandas ``DataFrame`` and returns a transformed version of either of these types:: from typing import Union Data = Union[dict, pd.DataFrame] def data_transformer(data: Data) -> Data: # Transform and return the data return transformed_data Dataset Consolidation ~~~~~~~~~~~~~~~~~~~~~ Datasets passed as Pandas dataframes can be represented in the chart in two ways: - As literal dataset values in the ``data`` attribute at any level of the specification - As a named dataset in the ``datasets`` attribute of the top-level specification. The former is a bit more simple, but common patterns of usage in Altair can often lead to full datasets being listed multiple times in their entirety within a single specification. For this reason, Altair 2.2 and newer will by default move all directly-specified datasets into the top-level ``datasets`` entry, and reference them by a unique name determined from the hash of the data representation. The benefit of using a hash-based name is that even if the user specifies a dataset in multiple places when building the chart, the specification will only include one copy. This behavior can be modified by setting the ``consolidate_datasets`` attribute of the data transformer. For example, consider this simple layered chart: .. altair-plot:: :chart-var-name: chart import altair as alt import pandas as pd df = pd.DataFrame({'x': range(5), 'y': [1, 3, 4, 3, 5]}) line = alt.Chart(df).mark_line().encode(x='x', y='y') points = alt.Chart(df).mark_point().encode(x='x', y='y') chart = line + points If we look at the resulting specification, we see that although the dataset was specified twice, only one copy of it is output in the spec: .. altair-plot:: :output: stdout from pprint import pprint pprint(chart.to_dict()) This consolidation of datasets is an extra bit of processing that is turned on by default in all renderers. If you would like to disable this dataset consolidation for any reason, you can do so by setting ``alt.data_transformers.consolidate_datasets = False``, or by using the ``enable()`` context manager to do it only temporarily: .. altair-plot:: :output: stdout with alt.data_transformers.enable(consolidate_datasets=False): pprint(chart.to_dict()) Notice that now the dataset is not specified within the top-level ``datasets`` attribute, but rather as values within the ``data`` attribute of each individual layer. This duplication of data is the reason that dataset consolidation is set to ``True`` by default. Built-in Data Transformers ~~~~~~~~~~~~~~~~~~~~~~~~~~ Altair includes a default set of data transformers with the following signatures. Raise a ``MaxRowsError`` if a Dataframe has more than ``max_rows`` rows:: limit_rows(data, max_rows=5000) Randomly sample a DataFrame (without replacement) before visualizing:: sample(data, n=None, frac=None) Convert a Dataframe to a separate ``.json`` file before visualization:: to_json(data, prefix='altair-data'): Convert a Dataframe to a separate ``.csv`` file before visualization:: to_csv(data, prefix='altair-data'): Convert a Dataframe to inline JSON values before visualization:: to_values(data): Piping ~~~~~~ Multiple data transformers can be piped together using ``pipe``:: from altair import limit_rows, to_values from toolz.curried import pipe pipe(data, limit_rows(10000), to_values) Managing Data Transformers ~~~~~~~~~~~~~~~~~~~~~~~~~~ Altair maintains a registry of data transformers, which includes a default data transformer that is automatically applied to all Dataframes before rendering. To see the registered transformers:: >>> import altair as alt >>> alt.data_transformers.names() ['default', 'json', 'csv'] The default data transformer is the following:: def default_data_transformer(data): return pipe(data, limit_rows, to_values) The ``json`` and ``csv`` data transformers will save a Dataframe to a temporary ``.json`` or ``.csv`` file before rendering. There are a number of performance advantages to these two data transformers: * The full dataset will not be saved in the notebook document. * The performance of the Vega-Lite/Vega JavaScript appears to be better for standalone JSON/CSV files than for inline values. There are disadvantages of the JSON/CSV data transformers: * The Dataframe will be exported to a temporary ``.json`` or ``.csv`` file that sits next to the notebook. * That notebook will not be able to re-render the visualization without that temporary file (or re-running the cell). In our experience, the performance improvement is significant enough that we recommend using the ``json`` data transformer for any large datasets:: alt.data_transformers.enable('json') We hope that others will write additional data transformers - imagine a transformer which saves the dataset to a JSON file on S3, which could be registered and enabled as:: alt.data_transformers.register('s3', lambda data: pipe(data, to_s3('mybucket'))) alt.data_transformers.enable('s3') Storing JSON Data in a Separate Directory ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When creating many charts with ``alt.data_transformers.enable('json')`` the working directory can get a bit cluttered. To avoid this we can build a simple custom data transformer that stores all JSON files in separate directory.:: import os import altair as alt from toolz.curried import pipe def json_dir(data, data_dir='altairdata'): os.makedirs(data_dir, exist_ok=True) return pipe(data, alt.to_json(filename=data_dir + '/{prefix}-{hash}.{extension}') ) alt.data_transformers.register('json_dir', json_dir) alt.data_transformers.enable('json_dir', data_dir='mydata') After enabling this data transformer, the JSON files will be stored in what ``data_dir`` was set to when enabling the transformer or 'altairdata' by default. All we had to do was to prefix the ``filename`` argument of the ``alt.to_json`` function with our desired directory and make sure that the directory actually exists. altair-5.0.1/doc/user_guide/display_frontends.rst000066400000000000000000000537411443422213100221600ustar00rootroot00000000000000.. _displaying-charts: Displaying Altair Charts ======================== Altair produces `Vega-Lite`_ visualizations, which require a Javascript frontend to display the charts. Because notebook environments combine a Python backend with a Javascript frontend, many users find them convenient for using Altair. Altair charts work out-of-the-box on `Jupyter Notebook`_, `JupyterLab`_, `Zeppelin`_, and related notebook environments, so long as there is a web connection to load the required javascript libraries. Altair can also be used with various IDEs that are enabled to display Altair charts, and can be used offline in most platforms with an appropriate frontend extension enabled; details are below. .. _renderers: Altair's Renderer Framework --------------------------- Because different display systems have different requirements and constraints, Altair provides an API to switch between various *renderers* to tune Altair's chart representation. These can be chosen with the renderer registry in ``alt.renderers``. The most used built-in renderers are: ``alt.renderers.enable("html")`` *(the default)* Output an HTML representation of the chart. The HTML renderer works in JupyterLab_, `Jupyter Notebook`_, `Zeppelin`_, `VSCode-Python`_ and many related notebook frontends, as well as Jupyter ecosystem tools like nbviewer_ and nbconvert_ HTML output. It requires a web connection in order to load relevant Javascript libraries. ``alt.renderers.enable("mimetype")`` *(default prior to Altair 4.0):* Output a vega-lite specific mimetype that can be interpreted by appropriate frontend extensions to display charts. This also outputs a PNG representation of the plot, which is useful to view plots offline or on platforms that don't support rendering vegaspecs, such as GitHub. It works with newer versions of JupyterLab_, nteract_, and `VSCode-Python`_, but does not work with the `Jupyter Notebook`_, or with tools like nbviewer_ and nbconvert_. In addition, Altair includes the following renderers: - ``"default"``, ``"colab"``, ``"kaggle"``, ``"zeppelin"``: identical to ``"html"`` - ``"jupyterlab"``, ``"nteract"``: identical to ``"mimetype"`` - ``"png"``: renderer that renders and converts the chart to PNG, outputting it using the ``"image/png"`` MIME type. - ``"svg"``: renderer that renders and converts the chart to an SVG image, outputting it using the ``"image/svg+xml"`` MIME type. - ``"json"``: renderer that outputs the raw JSON chart specification, using the ``"application/json"`` MIME type. You can use ``alt.renderers.names()`` to return all registered renderers as a Python list. Other renderers can be installed by third-party packages via Python's entrypoints system or you can create your own, see :ref:`customizing-renderers`. .. _display-jupyterlab: Displaying in JupyterLab ------------------------ JupyterLab 1.0 and later will work with Altair's default renderer with a live web connection: no render enable step is required. Optionally, for offline rendering in JupyterLab, you can use the mimetype renderer:: # Optional in JupyterLab: requires an up-to-date vega labextension. alt.renderers.enable('mimetype') and ensure you have the proper version of the vega labextension installed; for Altair 4 this can be installed with: .. code-block:: bash $ jupyter labextension install @jupyterlab/vega5-extension In JupyterLab version 2.0 or newer, this extension is installed by default, though the version available in the JupyterLab release often takes a few months to catch up with new Altair releases. .. _display-notebook: Displaying in Jupyter Notebook ------------------------------ The classic Jupyter Notebook will work with Altair's default renderer with a live web connection: no render enable step is required. Optionally, for offline rendering in Jupyter Notebook, you can use the notebook renderer:: # Optional in Jupyter Notebook: requires an up-to-date vega nbextension. alt.renderers.enable('notebook') This renderer is provided by the `ipyvega`_ notebook extension, which can be installed and enabled either using pip: .. code-block:: bash $ pip install vega or conda: .. code-block:: bash $ conda install vega --channel conda-forge In older versions of the notebook (<5.3) you need to additionally enable the extension: .. code-block:: bash $ jupyter nbextension install --sys-prefix --py vega .. _display-nteract: Displaying in nteract --------------------- nteract_ cannot display HTML outputs natively, and so Altair's default ``html`` renderer will not work. However, nteract natively includes vega and vega-lite mimetype-based rendering. To use Altair in nteract, ensure you are using a version that supports the Vega-Lite v5 mimetype, and use:: alt.renderers.enable('mimetype') .. _display-vscode: Displaying in VSCode -------------------- `VSCode-Python`_ works with Altair's default renderer with a live web connection: no render enable step is required. Optionally, for offline rendering, you can use the mimetype renderer:: # Optional in VS Code alt.renderers.enable('mimetype') .. _display-general: Working in non-Notebook Environments ------------------------------------ The Vega-Lite specifications produced by Altair can be produced in any Python environment, but to render these specifications currently requires a javascript engine. For this reason, Altair works most seamlessly with the browser-based environments mentioned above. If you would like to render plots from another Python interface that does not have a built-in javascript engine, you'll need to somehow connect your charts to a second tool that can execute javascript. There are a few options available for this: Vega-enabled IDEs ~~~~~~~~~~~~~~~~~ Some IDEs have extensions that natively recognize and display Altair charts. Examples are: - The `VSCode-Python`_ extension, which supports native Altair and Vega-Lite chart display as of November 2019. - The Hydrogen_ project, which is built on nteract_ and renders Altair charts via the ``mimetype`` renderer. Altair Viewer ~~~~~~~~~~~~~ For non-notebook IDEs, a useful companion is the `Altair Viewer`_ package, which provides an Altair renderer that works directly from any Python terminal. Start by installing the package:: $ pip install altair_viewer When enabled, this will serve charts via a local HTTP server and automatically open a browser window in which to view them, with subsequent charts displayed in the same window. If you are using an IPython-compatible terminal ``altair_viewer`` can be enabled via Altair's standard renderer framework:: import altair as alt alt.renderers.enable('altair_viewer') If you prefer to manually trigger chart display, you can use the built-in :meth:`Chart.show` method to manually trigger chart display:: import altair as alt # load a simple dataset as a pandas DataFrame from vega_datasets import data cars = data.cars() chart = alt.Chart(cars).mark_point().encode( x='Horsepower', y='Miles_per_Gallon', color='Origin', ).interactive() chart.show() This command will block the Python interpreter until the browser window containing the chart is closed. Manual ``save()`` and display ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you would prefer, you can save your chart to a file (html, png, etc.) first and then display it. See :ref:`user-guide-saving` for more information. .. _display-troubleshooting: Troubleshooting --------------- Altair has a number of moving parts: it creates data structures in Python, those structures are passed to front-end renderers, and the renderers run JavaScript code to generate the output. This complexity means that it's possible to get into strange states where things don't immediately work as expected. This section summarizes some of the most common problems and their solutions. .. _troubleshooting-general: General Troubleshooting ~~~~~~~~~~~~~~~~~~~~~~~ Chart does not display at all ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you are expecting a chart output and see nothing at all, it means that the Javascript rendering libraries are not being invoked. This can happen for several reasons: 1. You have an old browser that doesn't support JavaScript's `ECMAScript 6`_: in this case, charts may not display properly or at all. For example, Altair charts will not render in any version of Internet Explorer. If this is the case, you will likely see syntax errors in your browser's `Javascript Console`_. 2. Your browser is unable to load the javascript libraries. This may be due to a local firewall, an adblocker, or because your browser is offline. Check your browser's `Javascript Console`_ to see if there are errors. 3. You may be failing to trigger the notebook's display mechanism (see below). If you are working in a notebook environment, the chart is only displayed if the **last line of the cell evaluates to a chart object** By analogy, consider the output of simple Python operations:: >>> x = 4 # no output here >>> x # output here, because x is evaluated 4 >>> x * 2 # output here, because the expression is evaluated 8 If the last thing you type consists of an assignment operation, there will be no output displayed. This turns out to be true of Altair charts as well: .. altair-plot:: :output: none import altair as alt from vega_datasets import data cars = data.cars.url chart = alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N', ) The last statement is an assignment, so there is no output and the chart is not shown. If you have a chart assigned to a variable, you need to end the cell with an evaluation of that variable: .. altair-plot:: chart = alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N', ) chart Alternatively, you can evaluate a chart directly, and not assign it to a variable, in which case the object definition itself is the final statement and will be displayed as an output: .. altair-plot:: alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N', ) Plot displays, but the content is empty ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sometimes charts may appear, but the content is empty; for example: .. altair-plot:: import altair as alt alt.Chart('nonexistent_file.csv').mark_line().encode( x='x:Q', y='y:Q', ) If this is the case, it generally means one of two things: 1. your data is specified by a URL that is invalid or inaccessible 2. your encodings do not match the columns in your data source In the above example, ``nonexistent_file.csv`` doesn't exist, and so the chart does not render (associated warnings will be visible in the `Javascript Console`_). Some other specific situations that may cause this: You have an adblocker active Charts that reference data by URL can sometimes trigger false positives in your browser's adblocker. Check your browser's `Javascript Console`_ for errors, and try disabling your adblocker. You are loading data cross-domain If you save a chart to HTML and open it using a ``file://`` url in your browser, most browsers will not allow the javascript to load datasets from an ``http://`` domain. This is a security feature in your browser that cannot be disabled. To view such charts locally, a good approach is to use a simple local HTTP server like the one provided by Python:: $ python -m http.server Your encodings do not match your data A similar blank chart results if you refer to a field that does not exist in the data, either because of a typo in your field name, or because the column contains special characters (see below). Here is an example of a mis-specified field name leading to a blank chart: .. altair-plot:: import pandas as pd data = pd.DataFrame({'x': [1, 2, 3], 'y': [3, 1, 4]}) alt.Chart(data).mark_point().encode( x='x:Q', y='y:Q', color='color:Q' # <-- this field does not exist in the data! ) Altair does not check whether fields are valid, because there are many avenues by which a field can be specified within the full schema, and it is too difficult to account for all corner cases. Improving the user experience in this is a priority; see https://github.com/vega/vega-lite/issues/3576. Encodings with special characters ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The Vega-Lite grammar on which Altair is based allows for encoding names to use special characters to access nested properties (See Vega-Lite's Field_ documentation). This can lead to errors in Altair when trying to use such columns in your chart. For example, the following chart is invalid: .. altair-plot:: import pandas as pd data = pd.DataFrame({'x.value': [1, 2, 3]}) alt.Chart(data).mark_point().encode( x='x.value:Q', ) To plot this data directly, you must escape the period in the field name: .. altair-plot:: import pandas as pd data = pd.DataFrame({'x.value': [1, 2, 3]}) alt.Chart(data).mark_point().encode( x=r'x\.value:Q', ) In general, it is better to avoid special characters like ``"."``, ``"["``, and ``"]"`` in your data sources where possible. .. _troubleshooting-jupyterlab: Troubleshooting in JupyterLab ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. _jupyterlab-vega-lite-4-object: VegaLite 4 Object ^^^^^^^^^^^^^^^^^ *If you are using the Jupyter notebook rather than JupyterLab, then refer to* :ref:`notebook-vega-lite-4-object` If you are using JupyterLab (not Jupyter notebook) and see the following output:: This means that you have enabled the ``mimetype`` renderer, but that your JupyterLab frontend does not support the VegaLite 4 mimetype. The easiest solution is to use the default renderer:: alt.renderers.enable('default') and rerun the cell with the chart. If you would like to use the mimetype rendering with the JupyterLab frontend extension, then make certain the extension is installed and enabled: $ jupyter labextension install @jupyterlab/vega5-extension and then restart your jupyter frontend. .. _jupyterlab-vega-lite-3-object: VegaLite 3 Object ^^^^^^^^^^^^^^^^^ *If you are using the Jupyter notebook rather than JupyterLab, then refer to* :ref:`notebook-vega-lite-3-object` If you are using JupyterLab (not Jupyter notebook) and see the following output:: This most likely means that you are using too old a version of JupyterLab. Altair 3.0 or later works best with JupyterLab version 1.0 or later; check the version with:: $ jupyter lab --version 1.2.0 If you have an older jupyterlab version, then use ``pip install -U jupyterlab`` or ``conda update jupyterlab`` to update JupyterLab, depending on how you first installed it. JavaScript output is disabled in JupyterLab ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you are using JupyterLab and see the following output:: JavaScript output is disabled in JupyterLab it can mean one of two things is wrong 1. You are using an old version of Altair. JupyterLab only works with Altair version 2.0 or newer; you can check the altair version by executing the following in a notebook code cell:: import altair as alt alt.__version__ If the version is older than 2.0, then exit JupyterLab and follow the installation instructions at :ref:`display-jupyterlab`. 2. You have enabled the wrong renderer. JupyterLab works with the default renderer, but if you have used ``alt.renderers.enable()`` to enable another renderer, charts will no longer render correctly in JupyterLab. You can check which renderer is active by running:: import altair as alt print(alt.renderers.active) JupyterLab rendering will work only if the active renderer is ``"default"`` or ``"jupyterlab"``. You can re-enable the default renderer by running:: import altair as alt alt.renderers.enable('default') (Note that the default renderer is enabled, well, by default, and so this is only necessary if you've somewhere changed the renderer explicitly). .. _jupyterlab-textual-chart-representation: Textual Chart Representation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *If you are using the Notebook rather than the JupyterLab, then refer to* :ref:`notebook-textual-chart-representation` If you are using JupyterLab and see a textual representation of the Chart object similar to this:: Chart({ data: 'https://vega.github.io/vega-datasets/data/cars.json', encoding: FacetedEncoding({ x: X({ shorthand: 'Horsepower' }) }), mark: 'point' }) it probably means that you are using an older Jupyter kernel. You can confirm this by running:: import IPython; IPython.__version__ # 6.2.1 Altair will not display correctly if using a kernel with IPython version 4.X or older. The easiest way to address this is to change your kernel: choose "Kernel"->"Change Kernel" and then use the first kernel that appears. .. _jupyterlab-notebook-backend: Javascript Error: require is not defined ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you are using JupyterLab and see the error:: Javascript Error: require is not defined This likely means that you have enabled the notebook renderer, which is not supported in JupyterLab: that is, you have somewhere run ``alt.renderers.enable('notebook')``. JupyterLab supports Altair's default renderer, which you can re-enable using:: alt.renderers.enable('default') .. _troubleshooting-notebook: Troubleshooting in Notebooks ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. _notebook-vega-lite-4-object: Notebook: VegaLite 4 object ^^^^^^^^^^^^^^^^^^^^^^^^^^^ *If you are using JupyterLab rather than the Jupyter notebook, then refer to* :ref:`jupyterlab-vega-lite-3-object` If you are using Jupyter Notebook (not JupyterLab) and see the following output:: This means that you have enabled the ``mimetype`` renderer. The easiest solution is to use the default renderer:: alt.renderers.enable('default') and rerun the cell with the chart. .. _notebook-vega-lite-3-object: Notebook: VegaLite 3 object ^^^^^^^^^^^^^^^^^^^^^^^^^^^ *If you are using JupyterLab rather than the Jupyter notebook, then refer to* :ref:`jupyterlab-vega-lite-3-object` If you are using the notebook (not JupyterLab) and see the the following output:: it means that either: 1. You have forgotten to enable the notebook renderer. As mentioned in :ref:`display-notebook`, you need to install version 2.0 or newer of the ``vega`` package and Jupyter extension, and then enable it using:: import altair as alt alt.renderers.enable('notebook') in order to render charts in the classic notebook. If the above code gives an error:: NoSuchEntryPoint: No 'notebook' entry point found in group 'altair.vegalite.v2.renderer' This means that you have not installed the vega package. If you see this error, please make sure to follow the standard installation instructions at :ref:`display-notebook`. 2. You have too old a version of Jupyter notebook. Run:: $ jupyter notebook --version and make certain you have version 5.3 or newer. If not, then update the notebook using either ``pip install -U jupyter notebook`` or ``conda update jupyter notebook`` depending on how you first installed the packages. If you have done the above steps and charts still do not render, it likely means that you are using a different *Kernel* within your notebook. Switch to the kernel named *Python 2* if you are using Python 2, or *Python 3* if you are using Python 3. .. _notebook-textual-chart-representation: Notebook: Textual Chart Representation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *If you are using the Notebook rather than the JupyterLab, then refer to* :ref:`jupyterlab-textual-chart-representation` *If you are not using a Jupyter notebook environment, then refer to* :ref:`troubleshooting-non-notebook`. If you are using Jupyter notebook and see a textual representation of the Chart object similar to this:: Chart({ data: 'https://vega.github.io/vega-datasets/data/cars.json', encoding: FacetedEncoding({ x: X({ shorthand: 'Horsepower' }) }), mark: 'point' }) it probably means that you are using an older Jupyter kernel. You can confirm this by running:: import IPython; IPython.__version__ # 6.2.1 Altair will not display correctly if using a kernel with IPython version 4.X or older. The easiest way to address this is to change your kernel: choose "Kernel"->"Change Kernel" and then select "Python 2" or "Python 3", depending on what version of Python you used when installing Altair. .. _troubleshooting-non-notebook: Troubleshooting outside of Jupyter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you are using Altair outside of a Jupyter notebook environment (such as a Python or IPython terminal) charts will be displayed as a textual representation. Rendering of Altair charts requires executing Javascript code, which your Python terminal cannot do natively. For recommendations on how to use Altair outside of notebook environments, see :ref:`display-general`. .. _`ECMAScript 6`: https://www.w3schools.com/js/js_es6.asp .. _`Javascript Console`: https://webmasters.stackexchange.com/questions/8525/how-do-i-open-the-javascript-console-in-different-browsers .. _Field: https://vega.github.io/vega-lite/docs/field.html .. _ipyvega: https://github.com/vega/ipyvega/ .. _JupyterLab: http://jupyterlab.readthedocs.io/en/stable/ .. _nteract: https://nteract.io .. _nbconvert: https://nbconvert.readthedocs.io/ .. _nbviewer: https://nbviewer.jupyter.org/ .. _Altair Viewer: https://github.com/altair-viz/altair_viewer/ .. _Colab: https://colab.research.google.com .. _Hydrogen: https://github.com/nteract/hydrogen .. _Jupyter Notebook: https://jupyter-notebook.readthedocs.io/en/stable/ .. _Vega-Lite: http://vega.github.io/vega-lite .. _Vega: https://vega.github.io/vega/ .. _VSCode-Python: https://code.visualstudio.com/docs/python/python-tutorial .. _Zeppelin: https://zeppelin.apache.org/ altair-5.0.1/doc/user_guide/ecosystem.rst000066400000000000000000000126541443422213100204420ustar00rootroot00000000000000.. _ecosystem: Resources ========= We hope to make it easier to find learning resources and projects related to Altair by listing them here. If you know of a project that should be added, please let us know by opening an `Issue on GitHub `_. .. _learning-resources: Learning Material ----------------- This is a list of learning material that complements the official documentation and can help you learn more about how to use Altair. `Visualization Curriculum`_ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ A data visualization curriculum from the UW data group that developed Vega-Lite. .. List of links. .. _`Visualization Curriculum`: https://uwdata.github.io/visualization-curriculum `Jupyter Notebook Tutorials`_ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Jupyter Notebook tutorials and examples from the Altair authors. .. List of links. .. _`Jupyter Notebook Tutorials`: https://github.com/altair-viz/altair_notebooks `Pycon Tutorial`_ ~~~~~~~~~~~~~~~~~ Altair tutorial given at PyCon 2018 by the Altair author Jake VanderPlas. .. List of links. .. _`Pycon tutorial`: https://altair-viz.github.io/altair-tutorial `Data Visualization Course`_ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This course covers how to create common statistical visualizations, tell stories with data, create geographical visualizations, and bring plots to life by adding interactive elements. Created at the University of British Columbia and can either be audited or taken as part of the `Key Capabilities for Data Science`_ certificate program. .. List of links. .. _`Data Visualization Course`: https://viz-learn.mds.ubc.ca .. _`Key Capabilities for Data Science`: https://extendedlearning.ubc.ca/programs/key-capabilities-data-science `Brief Introduction Videos`_ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Calmcode provides a few videos that give a brief overview of the Altair library. .. List of links. .. _`Brief Introduction Videos`: https://calmcode.io/altair/introduction.html .. _altair-ecosystem: Related Projects ---------------- This is a list of projects which are directly related to Altair. There are many other packages that can be used in tandem with Altair, e.g. `dashboard packages which you can read more about in the answers to this StackOverflow question`_. .. List of links. .. _`dashboard packages which you can read more about in the answers to this StackOverflow question`: https://stackoverflow.com/questions/49833866/making-dashboards-using-altair Vega-Lite_ ~~~~~~~~~~ The higher-level visualization grammar that Altair implements in Python. .. List of links. .. _Vega-Lite: https://vega.github.io/vega-lite vl-convert_ ~~~~~~~~~~~ Python library for converting Altair/Vega-Lite chart specifications into static images (SVG or PNG) or Vega chart specifications without any external dependencies. .. List of links. .. _vl-convert: https://github.com/vega/vl-convert VegaFusion_ ~~~~~~~~~~~ VegaFusion provides server-side scaling for Altair charts, which can accelerate interactive charts, extract transformed data, and perform data-intensive aggregations on the server and prune unused columns from the source dataset yielding smaller size visualizations. .. List of links. .. _VegaFusion: https://vegafusion.io/ altair_saver_ ~~~~~~~~~~~~~ Enables saving charts to a variety of output types. .. List of links. .. _altair_saver: https://github.com/altair-viz/altair_saver altair_data_server_ ~~~~~~~~~~~~~~~~~~~ Data transformer plugin that transparently serves data for charts. .. List of links. .. _altair_data_server: https://github.com/altair-viz/altair_data_server altair_pandas_ ~~~~~~~~~~~~~~ Altair backend for the pandas plotting API. .. List of links. .. _altair_pandas: https://github.com/altair-viz/altair_pandas vega_datasets_ ~~~~~~~~~~~~~~ Offline access to the Vega datasets used in the Altair documentation. .. List of links. .. _vega_datasets: https://github.com/altair-viz/vega_datasets altair_recipes_ ~~~~~~~~~~~~~~~ altair_recipes provides a collection of ready-made statistical graphics for Altair. See the `docs `__. .. List of links. .. _altair_recipes: https://github.com/piccolbo/altair_recipes nx_altair_ ~~~~~~~~~~ nx_altair is a library for drawing NetworkX_ graphs using Altair. It offers a similar draw API as NetworkX but returns Altair Charts instead. This allows users to apply Altair's rich interactive API to networks graphs. See the `docs `__. .. List of links. .. _nx_altair: https://github.com/Zsailer/nx_altair .. _NetworkX: https://networkx.github.io/ `Altair Ally`_ ~~~~~~~~~~~~~~ Altair Ally is a companion package to Altair, which provides a few shortcuts to create common plots for exploratory data analysis, particularly those involving visualization of an entire dataframe. .. List of links. .. _`Altair Ally`: https://joelostblom.github.io/altair_ally gif_ ~~~~ gif is the extension for Altair and matplotlib animations. The library provides a simple, high-level decorator interface to create frames in a regular for-loop that can be stitched together on save. See the `docs `__. .. List of links. .. _gif: https://github.com/maxhumber/gif `Altair in R`_ ~~~~~~~~~~~~~~ Altair in R provides an R interface to the Altair Python package. See the `docs `__. .. List of links. .. _`Altair in R`: https://github.com/vegawidget/altair altair-5.0.1/doc/user_guide/encodings/000077500000000000000000000000001443422213100176365ustar00rootroot00000000000000altair-5.0.1/doc/user_guide/encodings/channel_options.rst000066400000000000000000000063171443422213100235620ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-encoding-channel-options: Channel Options --------------- Some encoding channels allow for additional options to be expressed. These can control things like axis properties, scale properties, headers and titles, binning parameters, aggregation, sorting, and many more. The section titles below refer to the channels introduced in :ref:`user-guide-encoding-channels` and show the accepted options for these channels. X and Y ~~~~~~~ The :class:`X` and :class:`Y` encodings accept the following options: .. altair-object-table:: altair.PositionFieldDef Color, Fill, and Stroke ~~~~~~~~~~~~~~~~~~~~~~~ The :class:`Color`, :class:`Fill`, and :class:`Stroke` encodings accept the following options: .. altair-object-table:: altair.FieldOrDatumDefWithConditionMarkPropFieldDefGradientstringnull Shape ~~~~~ The :class:`Shape` encoding accepts the following options: .. altair-object-table:: altair.FieldOrDatumDefWithConditionMarkPropFieldDefTypeForShapestringnull Order ~~~~~ The :class:`Order` encoding accepts the following options: .. altair-object-table:: altair.OrderFieldDef Angle, FillOpacity, Opacity, Size, StrokeOpacity, and StrokeWidth ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :class:`Angle`, :class:`FillOpacity`, :class:`Opacity`, :class:`Size`, :class:`StrokeOpacity`, and :class:`StrokeWidth` encodings accept the following options: .. altair-object-table:: altair.FieldOrDatumDefWithConditionMarkPropFieldDefnumber StrokeDash ~~~~~~~~~~ The :class:`StrokeDash` encoding accepts the following options: .. altair-object-table:: altair.FieldOrDatumDefWithConditionMarkPropFieldDefnumberArray Row and Column ~~~~~~~~~~~~~~ The :class:`Row` and :class:`Column`, and :class:`Facet` encodings accept the following options: .. altair-object-table:: altair.RowColumnEncodingFieldDef Facet ~~~~~ The :class:`Facet` encoding accepts the following options: .. altair-object-table:: altair.FacetEncodingFieldDef Text ~~~~ The :class:`Text` encoding accepts the following options: .. altair-object-table:: altair.FieldOrDatumDefWithConditionStringFieldDefText Href, Tooltip, Url ~~~~~~~~~~~~~~~~~~ The :class:`Href`, :class:`Tooltip`, and :class:`Url` encodings accept the following options: .. altair-object-table:: altair.StringFieldDefWithCondition Detail ~~~~~~ The :class:`Detail` encoding accepts the following options: .. altair-object-table:: altair.FieldDefWithoutScale Latitude and Longitude ~~~~~~~~~~~~~~~~~~~~~~ The :class:`Latitude` and :class:`Longitude` encodings accept the following options: .. altair-object-table:: altair.LatLongFieldDef Radius and Theta ~~~~~~~~~~~~~~~~ The :class:`Radius` and :class:`Theta` encodings accept the following options: .. altair-object-table:: altair.PositionFieldDefBase Latitude2, Longitude2, Radius2, Theta2, X2, Y2, XError, YError, XError2, and YError2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :class:`Latitude2`, :class:`Longitude2`, :class:`Radius2`, :class:`Theta2`, :class:`X2`, :class:`Y2`, :class:`XError`, :class:`YError`, :class:`XError2`, and :class:`YError2` encodings accept the following options: .. altair-object-table:: altair.SecondaryFieldDef altair-5.0.1/doc/user_guide/encodings/channels.rst000066400000000000000000000221701443422213100221650ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-encoding-channels: Channels -------- Altair provides a number of encoding channels that can be useful in different circumstances. The following sections summarize them: Position ~~~~~~~~ ========== =================== ================================= =================================== Channel Altair Class Description Example ========== =================== ================================= =================================== x :class:`X` The x-axis value :ref:`gallery_scatter_tooltips` y :class:`Y` The y-axis value :ref:`gallery_scatter_tooltips` x2 :class:`X2` Second x value for ranges :ref:`gallery_gantt_chart` y2 :class:`Y2` Second y value for ranges :ref:`gallery_candlestick_chart` longitude :class:`Longitude` Longitude for geo charts :ref:`gallery_point_map` latitude :class:`Latitude` Latitude for geo charts :ref:`gallery_point_map` longitude2 :class:`Longitude2` Second longitude value for ranges :ref:`gallery_airport_connections` latitude2 :class:`Latitude2` Second latitude value for ranges :ref:`gallery_airport_connections` xError :class:`XError` The x-axis error value N/A yError :class:`YError` The y-axis error value N/A xError2 :class:`XError2` The second x-axis error value N/A yError2 :class:`YError2` The second y-axis error value N/A xOffset :class:`XOffset` Offset to the x position :ref:`gallery_grouped_bar_chart2` yOffset :class:`YOffset` Offset to the y position :ref:`gallery_strip_plot_jitter` theta :class:`Theta` The start arc angle :ref:`gallery_radial_chart` theta2 :class:`Theta2` The end arc angle (radian) :ref:`gallery_pacman_chart` ========== =================== ================================= =================================== Mark Property ~~~~~~~~~~~~~ ============= ====================== ============================== ========================================= Channel Altair Class Description Example ============= ====================== ============================== ========================================= angle :class:`Angle` The angle of the mark :ref:`gallery_wind_vector_map` color :class:`Color` The color of the mark :ref:`gallery_simple_heatmap` fill :class:`Fill` The fill for the mark :ref:`gallery_ridgeline_plot` fillopacity :class:`FillOpacity` The opacity of the mark's fill N/A opacity :class:`Opacity` The opacity of the mark :ref:`gallery_horizon_graph` radius :class:`Radius` The radius or the mark :ref:`gallery_radial_chart` shape :class:`Shape` The shape of the mark :ref:`gallery_us_incomebrackets_by_state_facet` size :class:`Size` The size of the mark :ref:`gallery_table_bubble_plot_github` stroke :class:`Stroke` The stroke of the mark N/A strokeDash :class:`StrokeDash` The stroke dash style :ref:`gallery_multi_series_line` strokeOpacity :class:`StrokeOpacity` The opacity of the line N/A strokeWidth :class:`StrokeWidth` The width of the line N/A ============= ====================== ============================== ========================================= Text and Tooltip ^^^^^^^^^^^^^^^^ ======= ================ ======================== ========================================= Channel Altair Class Description Example ======= ================ ======================== ========================================= text :class:`Text` Text to use for the mark :ref:`gallery_scatter_with_labels` tooltip :class:`Tooltip` The tooltip value :ref:`gallery_scatter_tooltips` ======= ================ ======================== ========================================= .. _hyperlink-channel: Hyperlink ~~~~~~~~~ ======= ================ ======================== ========================================= Channel Altair Class Description Example ======= ================ ======================== ========================================= href :class:`Href` Hyperlink for points :ref:`gallery_scatter_href` ======= ================ ======================== ========================================= Detail ~~~~~~ Grouping data is an important operation in data visualization. For line and area marks, mapping an unaggregated data field to any non-position channel will group the lines and stacked areas by that field. For aggregated plots, all unaggregated fields encoded are used as grouping fields in the aggregation (similar to fields in ``GROUP BY`` in SQL). The ``detail`` channel specifies an additional grouping field (or fields) for grouping data without mapping the field(s) to any visual properties. ======= ================ =============================== ========================================= Channel Altair Class Description Example ======= ================ =============================== ========================================= detail :class:`Detail` Additional property to group by :ref:`gallery_ranged_dot_plot` ======= ================ =============================== ========================================= For example here is a line chart showing stock prices of 5 tech companies over time. We map the ``symbol`` variable to ``detail`` to use them to group lines. .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_line().encode( x="date:T", y="price:Q", detail="symbol:N" ) Order ~~~~~ The ``order`` option and :class:`Order` channel can sort how marks are drawn on the chart. For stacked marks, this controls the order of components of the stack. Here, the elements of each bar are sorted alphabetically by the name of the nominal data in the color channel. .. altair-plot:: import altair as alt from vega_datasets import data barley = data.barley() alt.Chart(barley).mark_bar().encode( x='variety:N', y='sum(yield):Q', color='site:N', order=alt.Order("site").sort("ascending") ) The order can be reversed by changing the sort option to `descending`. .. altair-plot:: import altair as alt from vega_datasets import data barley = data.barley() alt.Chart(barley).mark_bar().encode( x='variety:N', y='sum(yield):Q', color='site:N', order=alt.Order("site").sort("descending") ) The same approach works for other mark types, like stacked areas charts. .. altair-plot:: import altair as alt from vega_datasets import data barley = data.barley() alt.Chart(barley).mark_area().encode( x='variety:N', y='sum(yield):Q', color='site:N', order=alt.Order("site").sort("ascending") ) Note that unlike the ``sort`` parameter to positional encoding channels, the :class:`Order` channel cannot take a list of values to sort by and is not automatically sorted when an ordered pandas categorical column is passed. If we want to sort stacked segments in a custom order, we can `follow the approach in this issue comment `_, although there might be edge cases where this is not fully supported. This workaround also makes the order of the segments align with the order that the colors shows up in a legend that uses custom sorting for the color domain. For line marks, the :class:`Order` channel encodes the order in which data points are connected. This can be useful for creating a scatter plot that draws lines between the dots using a different field than the x and y axes. .. altair-plot:: import altair as alt from vega_datasets import data driving = data.driving() alt.Chart(driving).mark_line(point=True).encode( alt.X('miles').scale(zero=False), alt.Y('gas').scale(zero=False), order='year' ) Facet ~~~~~ For more information, see :ref:`facet-chart`. ======= ================ =============================================== ============================================= Channel Altair Class Description Example ======= ================ =============================================== ============================================= column :class:`Column` The column of a faceted plot :ref:`gallery_trellis_scatter_plot` row :class:`Row` The row of a faceted plot :ref:`gallery_beckers_barley_trellis_plot` facet :class:`Facet` The row and/or column of a general faceted plot :ref:`gallery_us_population_over_time_facet` ======= ================ =============================================== ============================================= altair-5.0.1/doc/user_guide/encodings/index.rst000066400000000000000000000605271443422213100215110ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-encoding: Encodings --------- The key to creating meaningful visualizations is to map *properties of the data* to *visual properties* in order to effectively communicate information. In Altair, this mapping of visual properties to data columns is referred to as an **encoding**, and is most often expressed through the :meth:`Chart.encode` method. For example, here we will visualize the cars dataset using four of the available **encoding channels** (see :ref:`user-guide-encoding-channels` for details): ``x`` (the x-axis value), ``y`` (the y-axis value), ``color`` (the color of the marker), and ``shape`` (the shape of the point marker): .. altair-plot:: import altair as alt from vega_datasets import data cars = data.cars() alt.Chart(cars).mark_point().encode( x='Horsepower', y='Miles_per_Gallon', color='Origin', shape='Origin' ) Channel Options ~~~~~~~~~~~~~~~ Each encoding channel accepts a number of **channel options** (see :ref:`user-guide-encoding-channel-options` for details) which can be used to further configure the chart. Altair 5.0 introduced a method-based syntax for setting channel options as a more convenient alternative to the traditional attribute-based syntax described in :ref:`attribute-based-attribute-setting` (but you can still use the attribute-based syntax if you prefer). .. note:: With the release of Altair 5, the documentation was updated to prefer the method-based syntax. The gallery examples still include the attribute-based syntax in addition to the method-based syntax. .. _method-based-attribute-setting: Method-Based Syntax ^^^^^^^^^^^^^^^^^^^ The method-based syntax replaces *keyword arguments* with *methods*. For example, an ``axis`` option of the ``x`` channel encoding would traditionally be set using the ``axis`` keyword argument: ``x=alt.X('Horsepower', axis=alt.Axis(tickMinStep=50))``. To define the same :class:`X` object using the method-based syntax, we can instead use the more succinct ``x=alt.X('Horsepower').axis(tickMinStep=50)``. The same technique works with all encoding channels and all channel options. For example, notice how we make the analogous change with respect to the ``title`` option of the ``y`` channel. The following produces the same chart as the previous example. .. altair-plot:: alt.Chart(cars).mark_point().encode( alt.X('Horsepower').axis(tickMinStep=50), alt.Y('Miles_per_Gallon').title('Miles per Gallon'), color='Origin', shape='Origin' ) These option-setter methods can also be chained together, as in the following, in which we set the ``axis``, ``bin``, and ``scale`` options of the ``x`` channel by using the corresponding methods (``axis``, ``bin``, and ``scale``). We can break the ``x`` definition over multiple lines to improve readability. (This is valid syntax because of the enclosing parentheses from ``encode``.) .. altair-plot:: alt.Chart(cars).mark_point().encode( alt.X('Horsepower') .axis(ticks=False) .bin(maxbins=10) .scale(domain=(30,300), reverse=True), alt.Y('Miles_per_Gallon').title('Miles per Gallon'), color='Origin', shape='Origin' ) .. _attribute-based-attribute-setting: Attribute-Based Syntax ^^^^^^^^^^^^^^^^^^^^^^ The two examples from the section above would look as follows with the traditional attribute-based syntax: .. altair-plot:: alt.Chart(cars).mark_point().encode( alt.X('Horsepower', axis=alt.Axis(tickMinStep=50)), alt.Y('Miles_per_Gallon', title="Miles per Gallon"), color='Origin', shape='Origin' ) For specs making extensive use of channel options, the attribute-based syntax can become quite verbose: .. altair-plot:: alt.Chart(cars).mark_point().encode( alt.X( 'Horsepower', axis=alt.Axis(ticks=False), bin=alt.Bin(maxbins=10), scale=alt.Scale(domain=(30,300), reverse=True) ), alt.Y('Miles_per_Gallon', title='Miles per Gallon'), color='Origin', shape='Origin' ) .. _encoding-data-types: Encoding Data Types ~~~~~~~~~~~~~~~~~~~ The details of any mapping depend on the *type* of the data. Altair recognizes five main data types: ============ ============== ================================================ Data Type Shorthand Code Description ============ ============== ================================================ quantitative ``Q`` a continuous real-valued quantity ordinal ``O`` a discrete ordered quantity nominal ``N`` a discrete unordered category temporal ``T`` a time or date value geojson ``G`` a geographic shape ============ ============== ================================================ For data specified as a DataFrame, Altair can automatically determine the correct data type for each encoding, and creates appropriate scales and legends to represent the data. If types are not specified for data input as a DataFrame, Altair defaults to ``quantitative`` for any numeric data, ``temporal`` for date/time data, and ``nominal`` for string data, but be aware that these defaults are by no means always the correct choice! The types can either be expressed in a long-form using the channel encoding classes such as :class:`X` and :class:`Y`, or in short-form using the :ref:`Shorthand Syntax ` discussed below. For example, the following two methods of specifying the type will lead to identical plots: .. altair-plot:: alt.Chart(cars).mark_point().encode( x='Acceleration:Q', y='Miles_per_Gallon:Q', color='Origin:N' ) .. altair-plot:: alt.Chart(cars).mark_point().encode( alt.X('Acceleration', type='quantitative'), alt.Y('Miles_per_Gallon', type='quantitative'), alt.Color('Origin', type='nominal') ) The shorthand form, ``x="name:Q"``, is useful for its lack of boilerplate when doing quick data explorations. The long-form, ``alt.X('name', type='quantitative')``, is useful when doing more fine-tuned adjustments to the encoding using channel options such as binning, axis, and scale. Specifying the correct type for your data is important, as it affects the way Altair represents your encoding in the resulting plot. .. _type-legend-scale: Effect of Data Type on Color Scales ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ As an example of this, here we will represent the same data three different ways, with the color encoded as a *quantitative*, *ordinal*, and *nominal* type, using three vertically-concatenated charts (see :ref:`vconcat-chart`): .. altair-plot:: base = alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', ).properties( width=150, height=150 ) alt.vconcat( base.encode(color='Cylinders:Q').properties(title='quantitative'), base.encode(color='Cylinders:O').properties(title='ordinal'), base.encode(color='Cylinders:N').properties(title='nominal'), ) The type specification influences the way Altair, via Vega-Lite, decides on the color scale to represent the value, and influences whether a discrete or continuous legend is used. .. _type-axis-scale: Effect of Data Type on Axis Scales ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Similarly, for x and y axis encodings, the type used for the data will affect the scales used and the characteristics of the mark. For example, here is the difference between a ``quantitative`` and ``ordinal`` scale for an column that contains integers specifying a year: .. altair-plot:: pop = data.population.url base = alt.Chart(pop).mark_bar().encode( alt.Y('mean(people):Q').title('total population') ).properties( width=200, height=200 ) alt.hconcat( base.encode(x='year:Q').properties(title='year=quantitative'), base.encode(x='year:O').properties(title='year=ordinal') ) Because quantitative values do not have an inherent width, the bars do not fill the entire space between the values. This view also makes clear the missing year of data that was not immediately apparent when we treated the years as categories. This kind of behavior is sometimes surprising to new users, but it emphasizes the importance of thinking carefully about your data types when visualizing data: a visual encoding that is suitable for categorical data may not be suitable for quantitative data, and vice versa. .. _shorthand-description: Encoding Shorthands ~~~~~~~~~~~~~~~~~~~ For convenience, Altair allows the specification of the variable name along with the aggregate and type within a simple shorthand string syntax. This makes use of the type shorthand codes listed in :ref:`encoding-data-types` as well as the aggregate names listed in :ref:`encoding-aggregates`. The following table shows examples of the shorthand specification alongside the long-form equivalent: =================== ======================================================= Shorthand Equivalent long-form =================== ======================================================= ``x='name'`` ``alt.X('name')`` ``x='name:Q'`` ``alt.X('name', type='quantitative')`` ``x='sum(name)'`` ``alt.X('name', aggregate='sum')`` ``x='sum(name):Q'`` ``alt.X('name', aggregate='sum', type='quantitative')`` ``x='count():Q'`` ``alt.X(aggregate='count', type='quantitative')`` =================== ======================================================= Escaping special characters in column names ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Seeing that Altair uses ``:`` as a special character to indicate the encoding data type, you might wonder what happens when the column name in your data includes a colon. When this is the case you will need to either rename the column or escape the colon. This is also true for other special characters such as ``.`` and ``[]`` which are used to access nested attributes in some data structures. The recommended thing to do when you have special characters in a column name is to rename your columns. For example, in Pandas you could replace ``:`` with ``_`` via ``df.rename(columns = lambda x: x.replace(':', '_'))``. If you don't want to rename your columns you will need to escape the special characters using a backslash: .. altair-plot:: import pandas as pd source = pd.DataFrame({ 'col:colon': [1, 2, 3], 'col.period': ['A', 'B', 'C'], 'col[brackets]': range(3), }) alt.Chart(source).mark_bar().encode( x='col\:colon', # Remove the backslash in the title y=alt.Y('col\.period').title('col.period'), # Specify the data type color='col\[brackets\]:N', ) As can be seen above, indicating the data type is optional just as for columns without escaped characters. Note that the axes titles include the backslashes by default and you will need to manually set the title strings to remove them. If you are using the long form syntax for encodings, you do not need to escape colons as the type is explicit, e.g. ``alt.X(field='col:colon', type='quantitative')`` (but periods and brackets still need to be escaped in the long form syntax unless they are used to index nested data structures). .. _encoding-aggregates: Binning and Aggregation ~~~~~~~~~~~~~~~~~~~~~~~ Beyond simple channel encodings, Altair's visualizations are built on the concept of the database-style grouping and aggregation; that is, the `split-apply-combine `_ abstraction that underpins many data analysis approaches. For example, building a histogram from a one-dimensional dataset involves splitting data based on the bin it falls in, aggregating the results within each bin using a *count* of the data, and then combining the results into a final figure. In Altair, such an operation looks like this: .. altair-plot:: alt.Chart(cars).mark_bar().encode( alt.X('Horsepower').bin(), y='count()' # could also use alt.Y(aggregate='count', type='quantitative') ) Notice here we use the shorthand version of expressing an encoding channel (see :ref:`shorthand-description`) with the ``count`` aggregation, which is the one aggregation that does not require a field to be specified. Similarly, we can create a two-dimensional histogram using, for example, the size of points to indicate counts within the grid (sometimes called a "Bubble Plot"): .. altair-plot:: alt.Chart(cars).mark_point().encode( alt.X('Horsepower').bin(), alt.Y('Miles_per_Gallon').bin(), size='count()', ) There is no need, however, to limit aggregations to counts alone. For example, we could similarly create a plot where the color of each point represents the mean of a third quantity, such as acceleration: .. altair-plot:: alt.Chart(cars).mark_circle().encode( alt.X('Horsepower').bin(), alt.Y('Miles_per_Gallon').bin(), size='count()', color='mean(Acceleration):Q' ) Aggregation Functions ^^^^^^^^^^^^^^^^^^^^^ In addition to ``count`` and ``mean``, there are a large number of available aggregation functions built into Altair: ========= =========================================================================== ===================================== Aggregate Description Example ========= =========================================================================== ===================================== argmin An input data object containing the minimum field value. N/A argmax An input data object containing the maximum field value. :ref:`gallery_line_chart_with_custom_legend` average The mean (average) field value. Identical to mean. :ref:`gallery_layer_line_color_rule` count The total count of data objects in the group. :ref:`gallery_simple_heatmap` distinct The count of distinct field values. N/A max The maximum field value. :ref:`gallery_boxplot` mean The mean (average) field value. :ref:`gallery_scatter_with_layered_histogram` median The median field value :ref:`gallery_boxplot` min The minimum field value. :ref:`gallery_boxplot` missing The count of null or undefined field values. N/A q1 The lower quartile boundary of values. :ref:`gallery_boxplot` q3 The upper quartile boundary of values. :ref:`gallery_boxplot` ci0 The lower boundary of the bootstrapped 95% confidence interval of the mean. :ref:`gallery_sorted_error_bars_with_ci` ci1 The upper boundary of the bootstrapped 95% confidence interval of the mean. :ref:`gallery_sorted_error_bars_with_ci` stderr The standard error of the field values. N/A stdev The sample standard deviation of field values. N/A stdevp The population standard deviation of field values. N/A sum The sum of field values. :ref:`gallery_streamgraph` valid The count of field values that are not null or undefined. N/A values A list of data objects in the group. N/A variance The sample variance of field values. N/A variancep The population variance of field values. N/A ========= =========================================================================== ===================================== Sort Option ~~~~~~~~~~~ Some channels accept a :class:`sort` option which determines the order of the scale being used for the channel. By default the scale is sorted in ascending alphabetical order, unless an `ordered pandas categorical column `_ is passed (without an explicit type specification) in which case Altair will use the column's inherent order to sort the scale. There are a number of different options available to change the sort order: - ``sort='ascending'`` (Default) will sort the field's value in ascending order. For string data, this uses standard alphabetical order. - ``sort='descending'`` will sort the field's value in descending order - Passing the name of an encoding channel to ``sort``, such as ``"x"`` or ``"y"``, allows for sorting by that channel. An optional minus prefix can be used for a descending sort. For example ``sort='-x'`` would sort by the x channel in descending order. - Passing a list to ``sort`` allows you to explicitly set the order in which you would like the encoding to appear - Passing a :class:`EncodingSortField` class to ``sort`` allows you to sort an axis by the value of some other field in the dataset. Here is an example of applying these five different sort approaches on the x-axis, using the barley dataset: .. altair-plot:: import altair as alt from vega_datasets import data barley = data.barley() base = alt.Chart(barley).mark_bar().encode( y='mean(yield):Q', color=alt.Color('mean(yield):Q').legend(None) ).properties(width=100, height=100) # Sort x in ascending order ascending = base.encode( alt.X('site:N').sort('ascending') ).properties( title='Ascending' ) # Sort x in descending order descending = base.encode( alt.X('site:N').sort('descending') ).properties( title='Descending' ) # Sort x in an explicitly-specified order explicit = base.encode( alt.X('site:N').sort( ['Duluth', 'Grand Rapids', 'Morris', 'University Farm', 'Waseca', 'Crookston'] ) ).properties( title='Explicit' ) # Sort according to encoding channel sortchannel = base.encode( alt.X('site:N').sort('y') ).properties( title='By Channel' ) # Sort according to another field sortfield = base.encode( alt.X('site:N').sort(field='yield', op='mean') ).properties( title='By Yield' ) alt.concat( ascending, descending, explicit, sortchannel, sortfield, columns=3 ) The last two charts are the same because the default aggregation (see :ref:`encoding-aggregates`) is ``mean``. To highlight the difference between sorting via channel and sorting via field consider the following example where we don't aggregate the data: .. altair-plot:: import altair as alt from vega_datasets import data barley = data.barley() base = alt.Chart(barley).mark_point().encode( y='yield:Q', ).properties(width=200) # Sort according to encoding channel sortchannel = base.encode( alt.X('site:N').sort('y') ).properties( title='By Channel' ) # Sort according to another field sortfield = base.encode( alt.X('site:N').sort(field='yield', op='max') ).properties( title='By Min Yield' ) sortchannel | sortfield By passing a :class:`EncodingSortField` class to ``sort`` we have more control over the sorting process. Sorting Legends ^^^^^^^^^^^^^^^ While the above examples show sorting of axes by specifying ``sort`` in the :class:`X` and :class:`Y` encodings, legends can be sorted by specifying ``sort`` in the :class:`Color` encoding: .. altair-plot:: alt.Chart(barley).mark_rect().encode( alt.X('mean(yield):Q').sort('ascending'), alt.Y('site:N').sort('descending'), alt.Color('site:N').sort([ 'Morris', 'Duluth', 'Grand Rapids', 'University Farm', 'Waseca', 'Crookston' ]) ) Here the y-axis is sorted reverse-alphabetically, while the color legend is sorted in the specified order, beginning with ``'Morris'``. Datum and Value ~~~~~~~~~~~~~~~ So far we always mapped an encoding channel to a column in our dataset. However, sometimes it is also useful to map to a single constant value. In Altair, you can do this with * ``datum``, which encodes a constant domain value via a scale using the same units as the underlying data * ``value``, which encodes a constant visual value, using absolute units such as an exact position in pixels, the name or RGB value of a color, the name of shape, etc ``datum`` is particularly useful for annotating a specific data value. For example, you can use it with a rule mark to highlight a threshold value (e.g., 300 dollars stock price). .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() base = alt.Chart(source) lines = base.mark_line().encode( x="date:T", y="price:Q", color="symbol:N" ) rule = base.mark_rule(strokeDash=[2, 2]).encode( y=alt.datum(300) ) lines + rule If we instead used ``alt.value`` in this example, we would position the rule 300 pixels from the top of the chart border rather than at the 300 dollars position. Since the default charts height is 300 pixels, this will show the dotted line just on top of the x-axis -line: .. altair-plot:: rule = base.mark_rule(strokeDash=[2, 2]).encode( y=alt.value(300) ) lines + rule If we want to use ``datum`` to highlight a certain year on the x-axis, we can't simply type in the year as an integer, but instead need to use ``datum`` together with :class:`DateTime`. Here we also set the color for the rule to the same one as the line for the symbol ``MSFT`` with ``alt.datum("MSFT")``. .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() base = alt.Chart(source) lines = base.mark_line().encode( x="date:T", y="price:Q", color="symbol:N" ) rule = base.mark_rule(strokeDash=[2, 2]).encode( x=alt.datum(alt.DateTime(year=2006)), color=alt.datum("MSFT") ) lines + rule Similar to when mapping to a data column, when using ``datum`` different encoding channels may support ``band``, ``scale``, ``axis``, ``legend``, ``format``, or ``condition`` properties. However, data transforms (e.g. ``aggregate``, ``bin``, ``timeUnit``, ``sort``) cannot be applied. Expanding on the example above, if you would want to color the ``rule`` mark regardless of the color scale used for the lines, you can use ``value``, e.g. ``alt.value("red")``: .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() base = alt.Chart(source) lines = base.mark_line().encode( x="date:T", y="price:Q", color="symbol:N" ) rule = base.mark_rule(strokeDash=[2, 2]).encode( x=alt.datum(alt.DateTime(year=2006)), color=alt.value("red") ) lines + rule One caution is that ``alt.datum`` and ``alt.value`` do not possess the (newly introduced as of Altair 5.0) method-based syntax to set channel options described in :ref:`method-based-attribute-setting`. For example, if you are using ``alt.datum`` for the ``y`` channel encoding and you wish to use an option setter method (e.g., ``scale``), then you can use :class:`YDatum` instead. Here is a simple example. .. altair-plot:: import altair as alt alt.Chart().mark_bar().encode( y=alt.YDatum(220).scale(domain=(0,500)), color=alt.value("darkkhaki") ) If you were to instead use ``y=alt.datum(220).scale(domain=(0,500))``, an ``AttributeError`` would be raised, due to the fact that ``alt.datum(220)`` simply returns a Python dictionary and does not possess a ``scale`` attribute. If you insisted on producing the preceding example using ``alt.datum``, one option would be to use ``y=alt.datum(220, scale={"domain": (0,500)})``. Nevertheless, the ``alt.YDatum`` approach is strongly preferred to this "by-hand" approach of supplying a dictionary to ``scale``. As one benefit, tab-completions are available using the ``alt.YDatum`` approach. For example, typing ``alt.YDatum(220).scale(do`` and hitting ``tab`` in an environment such as JupyterLab will offer ``domain``, ``domainMax``, ``domainMid``, and ``domainMin`` as possible completions. .. toctree:: :hidden: channels channel_options altair-5.0.1/doc/user_guide/interactions.rst000066400000000000000000001136551443422213100211340ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-interactions: Interactive Charts ================== One of the unique features of Altair, inherited from Vega-Lite, is a declarative grammar of not just visualization, but also *interaction*. This is both convenient and powerful, as we will see in this section. There are three core concepts of this grammar: - Parameters are the basic building blocks in the grammar of interaction. They can either be simple variables or more complex selections that map user input (e.g., mouse clicks and drags) to data queries. - Conditions and filters can respond to changes in parameter values and update chart elements based on that input. - Widgets and other chart input elements can bind to parameters so that charts can be manipulated via drop-down menus, radio buttons, sliders, legends, etc. Parameters ~~~~~~~~~~ Parameters are the building blocks of interaction in Altair. There are two types of parameters: *variables* and *selections*. We introduce these concepts through a series of examples. .. note:: This material was changed considerably with the release of Altair 5. .. _basic variable: Variables: Reusing Values ^^^^^^^^^^^^^^^^^^^^^^^^^ Variable parameters allow for a value to be defined once and then reused throughout the rest of the chart. Here is a simple scatter-plot created from the ``cars`` dataset: .. altair-plot:: import altair as alt from vega_datasets import data cars = data.cars.url alt.Chart(cars).mark_circle().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N' ) Variable parameters are created using the :func:`param` function. Here, we create a parameter with a default value of 0.1 using the ``value`` property: .. altair-plot:: :output: none op_var = alt.param(value=0.1) In order to use this variable in the chart specification, we explicitly add it to the chart using the :meth:`add_params` method, and we can then reference the variable within the chart specification. Here we set the opacity using our ``op_var`` parameter. .. altair-plot:: op_var = alt.param(value=0.1) alt.Chart(cars).mark_circle(opacity=op_var).encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N' ).add_params( op_var ) It's reasonable to ask whether all this effort is necessary. Here is a more natural way to accomplish the same thing that avoids the use of both :func:`param` and ``add_params``. .. altair-plot:: op_var2 = 0.1 alt.Chart(cars).mark_circle(opacity=op_var2).encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N' ) The benefit of using :func:`param` doesn't become apparent until we incorporate an additional component. In the following example we use the ``bind`` property of the parameter, so that the parameter becomes bound to an input element. In this example, that input element is a slider widget. .. altair-plot:: slider = alt.binding_range(min=0, max=1, step=0.05, name='opacity:') op_var = alt.param(value=0.1, bind=slider) alt.Chart(cars).mark_circle(opacity=op_var).encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N' ).add_params( op_var ) Now we can dynamically change the opacity of the points in our chart using the slider. You will learn much more about binding parameters to input elements such as widgets in the section :ref:`binding-parameters`. .. note:: A noteworthy aspect of Altair's interactivity is that these effects are controlled entirely within the web browser. This means that you can save charts as HTML files and share them with your colleagues who can access the interactivity via their browser without the need to install Python. Selections: Capturing Chart Interactions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Selection parameters define data queries that are driven by interactive manipulation of the chart by the user (e.g., via mouse clicks or drags). There are two types of selections: :func:`selection_interval` and :func:`selection_point`. Here we will create a simple chart and then add an selection interval to it. We could create a selection interval via ``param(select="interval")``, but it is more convenient to use the shorter ``selection_interval``. Here is a simple scatter-plot created from the ``cars`` dataset: .. altair-plot:: import altair as alt from vega_datasets import data cars = data.cars.url alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N' ) First we'll create an interval selection using the :func:`selection_interval` function (an interval selection is also referred to as a "brush"): .. altair-plot:: :output: none brush = alt.selection_interval() We can now add this selection interval to our chart via ``add_params``: .. altair-plot:: alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N' ).add_params( brush ) The result above is a chart that allows you to click and drag to create a selection region, and to move this region once the region is created. So far this example is very similar to what we did in the :ref:`variable example `: we created a selection parameter using ``brush = alt.selection_interval()``, and we attached that parameter to the chart using ``add_params``. One difference is that here we have not defined how the chart should respond to the selection; you will learn this in the next section. Conditions & Filters ~~~~~~~~~~~~~~~~~~~~ Conditional Encodings ^^^^^^^^^^^^^^^^^^^^^ The example above is neat, but the selection interval doesn't actually *do* anything yet. To make the chart respond to this selection, we need to reference the selection in within the chart specification. Here, we will use the :func:`condition` function to create a conditional color encoding: we'll tie the color to the ``"Origin"`` column for points in the selection, and set the color to ``"lightgray"`` for points outside the selection: .. altair-plot:: alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color=alt.condition(brush, 'Origin:N', alt.value('lightgray')) ).add_params( brush ) As you can see, the color of the points now changes depending on whether they are inside or outside the selection. Above we are using the selection parameter ``brush`` as a *predicate* (something that evaluates as `True` or `False`). This is controlled by the line ``color=alt.condition(brush, 'Origin:N', alt.value('lightgray'))``. Data points which fall within the selection evaluate as ``True``, and data points which fall outside the selection evaluate to ``False``. The ``'Origin:N'`` specifies how to color the points which fall within the selection, and the ``alt.value('lightgray')`` specifies that the outside points should be given a constant color value; you can remember this as ``alt.condition(, , )``. This approach becomes even more powerful when the selection behavior is tied across multiple views of the data within a compound chart. For example, here we create a ``chart`` object using the same code as above, and horizontally concatenate two versions of this chart: one with the x-encoding tied to ``"Horsepower"``, and one with the x-encoding tied to ``"Acceleration"`` .. altair-plot:: chart = alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color=alt.condition(brush, 'Origin:N', alt.value('lightgray')) ).properties( width=250, height=250 ).add_params( brush ) chart | chart.encode(x='Acceleration:Q') Because both copies of the chart reference the same selection object, the renderer ties the selections together across panels, leading to a dynamic display that helps you gain insight into the relationships within the dataset. Each selection type has attributes through which its behavior can be customized; for example we might wish for our brush to be tied only to the ``"x"`` encoding to emphasize that feature in the data. We can modify the brush definition, and leave the rest of the code unchanged: .. altair-plot:: brush = alt.selection_interval(encodings=['x']) chart = alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color=alt.condition(brush, 'Origin:N', alt.value('lightgray')) ).properties( width=250, height=250 ).add_params( brush ) chart | chart.encode(x='Acceleration:Q') Filtering Data ^^^^^^^^^^^^^^ Using a selection parameter to filter data works in much the same way as using it within ``condition``, For example, in ``transform_filter(brush)``, we are again using the selection parameter ``brush`` as a predicate. Data points which evaluate to ``True`` (i.e., data points which lie within the selection) are kept, and data points which evaluate to ``False`` are filtered out. It is not possible to both select and filter in the same chart, so typically this functionality will be used when at least two sub-charts are present. In the following example, we attach the selection parameter to the upper chart, and then filter data in the lower chart based on the selection in the upper chart. You can explore how the counts change in the bar chart depending on the size and position of the selection in the scatter plot. .. altair-plot:: brush = alt.selection_interval() points = alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N' ).add_params( brush ) bars = alt.Chart(cars).mark_bar().encode( x='count()', y='Origin:N', color='Origin:N' ).transform_filter( brush ) points & bars Selection Types ~~~~~~~~~~~~~~~ Now that we have seen the basics of how we can use a selection to interact with a chart, let's take a more systematic look at some of the types of selection parameters available in Altair. For simplicity, we'll use a common chart in all the following examples; a simple heat-map based on the ``cars`` dataset. For convenience, let's write a quick Python function that will take a selection object and create a chart with the color of the chart elements linked to this selection: .. altair-plot:: :output: none def make_example(selector): cars = data.cars.url return alt.Chart(cars).mark_rect().encode( x="Cylinders:O", y="Origin:N", color=alt.condition(selector, 'count()', alt.value('lightgray')) ).properties( width=300, height=180 ).add_params( selector ) Next we'll use this function to demonstrate the properties of various selections. Interval Selections ^^^^^^^^^^^^^^^^^^^ An *interval* selection allows you to select chart elements by clicking and dragging. You can create such a selection using the :func:`selection_interval` function: .. altair-plot:: interval = alt.selection_interval() make_example(interval) As you click and drag on the plot, you'll find that your mouse creates a box that can be subsequently moved to change the selection. The :func:`selection_interval` function takes a few additional arguments; for example we can bind the interval to only the x-axis, and set it such that the empty selection contains none of the points: .. altair-plot:: interval_x = alt.selection_interval(encodings=['x'], empty=False) make_example(interval_x) Point Selections ^^^^^^^^^^^^^^^^ A *point* selection allows you to select chart elements one at a time via mouse actions. By default, points are selected on click: .. altair-plot:: point = alt.selection_point() make_example(point) By changing some arguments, we can select points on mouseover rather than on click. We can also set the ``nearest`` flag to ``True`` so that the nearest point is highlighted: .. altair-plot:: point_nearest = alt.selection_point(on='mouseover', nearest=True) make_example(point_nearest) Point selections also allow for multiple chart objects to be selected. By default, chart elements can be added to and removed from the selection by clicking on them while holding the *shift* key, you can try in the two charts above. Selection Targets ~~~~~~~~~~~~~~~~~ For any but the simplest selections, the user needs to think about exactly what is targeted by the selection, and this can be controlled with either the ``fields`` or ``encodings`` arguments. These control what data properties are used to determine which points are part of the selection. For example, here we create a small chart that acts as an interactive legend, by targeting the Origin field using ``fields=['Origin']``. Clicking on points in the upper-right plot (the legend) will propagate a selection for all points with a matching ``Origin``. .. altair-plot:: selection = alt.selection_point(fields=['Origin']) color = alt.condition( selection, alt.Color('Origin:N').legend(None), alt.value('lightgray') ) scatter = alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color=color, tooltip='Name:N' ) legend = alt.Chart(cars).mark_point().encode( alt.Y('Origin:N').axis(orient='right'), color=color ).add_params( selection ) scatter | legend The above could be equivalently replace ``fields=['Origin']`` with ``encodings=['color']``, because in this case the chart maps ``color`` to ``'Origin'``. Also note that there is a shortcut to create interactive legends in Altair described in the section :ref:`legend-binding`. Similarly, we can specify multiple fields and/or encodings that must be matched in order for a datum to be included in a selection. For example, we could modify the above chart to create a two-dimensional clickable legend that will select points by both Origin and number of cylinders: .. altair-plot:: selection = alt.selection_point(fields=['Origin', 'Cylinders']) color = alt.condition( selection, alt.Color('Origin:N').legend(None), alt.value('lightgray') ) scatter = alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color=color, tooltip='Name:N' ) legend = alt.Chart(cars).mark_rect().encode( alt.Y('Origin:N').axis(orient='right'), x='Cylinders:O', color=color ).add_params( selection ) scatter | legend By fine-tuning the behavior of selections in this way, they can be used to create a wide variety of linked interactive chart types. Parameter Composition ~~~~~~~~~~~~~~~~~~~~~ Altair also supports combining multiple parameters using the ``&``, ``|`` and ``~`` for respectively ``AND``, ``OR`` and ``NOT`` logical composition operands. Returning to our heatmap examples, we can construct a scenario where there are two people who can make an interval selection in the same chart. The person Alex makes a selection box when the alt-key (macOS: option-key) is selected and Morgan can make a selection box when the shift-key is selected. We use the ``Brushconfig`` to give the selection box of Morgan a different style. Now, we color the rectangles when they fall within Alex's or Morgan's selection (note that you need to create both selections before seeing the effect). .. altair-plot:: alex = alt.selection_interval( on="[mousedown[event.altKey], mouseup] > mousemove", name='alex' ) morgan = alt.selection_interval( on="[mousedown[event.shiftKey], mouseup] > mousemove", mark=alt.BrushConfig(fill="#fdbb84", fillOpacity=0.5, stroke="#e34a33"), name='morgan' ) alt.Chart(cars).mark_rect().encode( x='Cylinders:O', y='Origin:O', color=alt.condition(alex | morgan, 'count()', alt.ColorValue("grey")) ).add_params( alex, morgan ).properties( width=300, height=180 ) With these operators, selections can be combined in arbitrary ways: - ``~(alex & morgan)``: to select the rectangles that fall outside Alex's and Morgan's selections. - ``alex | ~morgan``: to select the rectangles that fall within Alex's selection or outside the selection of Morgan .. _binding-parameters: Bindings & Widgets ~~~~~~~~~~~~~~~~~~ With an understanding of the parameter types and conditions, you can now bind parameters to chart elements (e.g. legends) and widgets (e.g. drop-downs and sliders). This is done using the ``bind`` option inside ``param`` and ``selection``. As specified by `the Vega-lite binding docs `_, there are three types of bindings available: 1. Point and interval selections can be used for data-driven interactive elements, such as highlighting and filtering based on values in the data. 2. Sliders and checkboxes can be used for logic-driven interactive elements, such as highlighting and filtering based on the absolute values in these widgets. 3. Interval selections can be bound to a scale, such as zooming in on a map. The following table summarizes the input elements that are supported in Vega-Lite: ========================= =========================================================================== =============================================== Input Element Description Example ========================= =========================================================================== =============================================== :class:`binding_checkbox` Renders as checkboxes allowing for multiple selections of items. :ref:`gallery_multiple_interactions` :class:`binding_radio` Radio buttons that force only a single selection :ref:`gallery_multiple_interactions` :class:`binding_select` Drop down box for selecting a single item from a list :ref:`gallery_multiple_interactions` :class:`binding_range` Shown as a slider to allow for selection along a scale. :ref:`gallery_us_population_over_time` :class:`binding` General method that supports many HTML input elements ========================= =========================================================================== =============================================== Widget Binding ^^^^^^^^^^^^^^ Widgets are HTML input elements, such as drop-downs, sliders, radio buttons, and search boxes. There are a three strategies for how variable and selection parameters can be used together with widgets: data-driven lookups, data-driven comparisons, and logic-driven comparisons. Data-Driven Lookups ------------------- Data-driven lookups use the active value(s) of the widget together with a ``selection`` parameter to look up points with matching values in the chart's dataset. For example, we can establish a binding between an input widget and a point selection to filter the data as in the example below where a drop-down is used to highlight cars of a specific ``Origin``: .. altair-plot:: input_dropdown = alt.binding_select(options=['Europe','Japan','USA'], name='Region ') selection = alt.selection_point(fields=['Origin'], bind=input_dropdown) color = alt.condition( selection, alt.Color('Origin:N').legend(None), alt.value('lightgray') ) alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color=color, ).add_params( selection ) Note that although it looks like a value is selected in the dropdown from the start, we need to set `value=` to actually start out with an initial selection in the chart. We did this previously with variable parameters and selection parameters follow the same pattern as you will see further down in the :ref:`encoding-channel-binding` section. As you can see above, we are still using ``conditions`` to make the chart respond to the selection, just as we did without widgets. Bindings and input elements can also be used to filter data allowing the user to see just the selected points as in the example below. In this example, we also add an empty selection to illustrate how to revert to showing all points after a selection has been made in a radio button or drop-down (which cannot be deselected). .. altair-plot:: # Make radio button less cramped by adding a space after each label # The spacing will only show up in your IDE, not on this doc page options = ['Europe', 'Japan', 'USA'] labels = [option + ' ' for option in options] input_dropdown = alt.binding_radio( # Add the empty selection which shows all when clicked options=options + [None], labels=labels + ['All'], name='Region: ' ) selection = alt.selection_point( fields=['Origin'], bind=input_dropdown, ) alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', # We need to set a constant domain to preserve the colors # when only one region is shown at a time color=alt.Color('Origin:N').scale(domain=options), ).add_params( selection ).transform_filter( selection ) In addition to the widgets listed in the table above, Altair has access to `any html widget `_ via the more general ``binding`` function. In the example below, we use a search input to filter points that match the search string exactly. You can hover over the points to see the car names and try typing one into the search box, e.g. ``vw pickup`` to see the point highlighted (you need to type out the full name). .. altair-plot:: search_input = alt.selection_point( fields=['Name'], empty=False, # Start with no points selected bind=alt.binding( input='search', placeholder="Car model", name='Search ', ) ) alt.Chart(data.cars.url).mark_point(size=60).encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', tooltip='Name:N', opacity=alt.condition( search_input, alt.value(1), alt.value(0.05) ) ).add_params( search_input ) It is not always useful to require an exact match to the search syntax, and when we will be learning about :ref:`expressions`, we will see how we can match partial strings via a regex instead. Data-Driven Comparisons ----------------------- So far we have seen the use of selections to lookup points with precisely matching values in our data. This is often useful, but sometimes we might want to make a more complex comparison than an exact match. For example, we might want to create a condition we select the points in the data that are above or below a threshold value, which is specified via a slider. For this workflow it is recommended to use variable parameters via ``param`` and as you can see below, we use the special syntax ``datum.xval`` to reference the column to compare against. Prefixing the column name with ``datum`` tells Altair that we want to compare to a column in the dataframe, rather than to a Python variable called ``xval``, which would have been the case if we just wrote ``xval < selector``. .. altair-plot:: import numpy as np import pandas as pd rand = np.random.RandomState(42) df = pd.DataFrame({ 'xval': range(100), 'yval': rand.randn(100).cumsum() }) slider = alt.binding_range(min=0, max=100, step=1, name='Cutoff ') selector = alt.param(name='SelectorName', value=50, bind=slider) alt.Chart(df).mark_point().encode( x='xval', y='yval', color=alt.condition( alt.datum.xval < selector, # 'datum.xval < SelectorName', # An equivalent alternative alt.value('red'), alt.value('blue') ) ).add_params( selector ) In this particular case we could actually have used a selection parameter since selection values can be accessed directly and used in expressions that affect the chart. For example, here we create a slider to choose a cutoff value, and color points based on whether they are smaller or larger than the value: .. altair-plot:: slider = alt.binding_range(min=0, max=100, step=1, name='Cutoff ') selector = alt.selection_point( name="SelectorName", fields=['cutoff'], bind=slider, value=[{'cutoff': 50}] ) alt.Chart(df).mark_point().encode( x='xval', y='yval', color=alt.condition( alt.datum.xval < selector.cutoff, # 'datum.xval < SelectorName.cutoff', # An equivalent alternative alt.value('red'), alt.value('blue') ) ).add_params( selector ) While it can be useful to know how to access selection values in expression strings, using the parameters syntax introduced in Altair 5 often provides a more convenient syntax for simple interactions like this one since they can also be accessed in expression strings as we saw above. Similarly, it is often possible to use equality statements such as ``alt.datum.xval == selector`` to lookup exact values but it is often more convenient to switch to a selection parameter and specify a field/encoding. Logic-Driven Comparisons ------------------------ A logic comparison is a type of comparison that is based on logical rules and conditions, rather than on the actual data values themselves. For example, for a checkbox widget we want to check if the state of the checkbox is True or False and execute some action depending on whether it is checked or not. When we are using a checkbox as a toggle like this, we need to use `param` instead of `selection_point`, since we don't want to check if there are True/False values in our data, just if the value of the check box is True (checked) or False (unchecked): .. altair-plot:: bind_checkbox = alt.binding_checkbox(name='Scale point size by "Acceleration": ') param_checkbox = alt.param(bind=bind_checkbox) alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', size=alt.condition( param_checkbox, 'Acceleration:Q', alt.value(25) ) ).add_params( param_checkbox ) Another example of creating a widget binding that is independent of the data, involves an interesting use case for the more general ``binding`` function. In the next example, this function introduces a color picker where the user can choose the colors of the chart interactively: .. altair-plot:: color_usa = alt.param(value="#317bb4", bind=alt.binding(input='color', name='USA ')) color_europe = alt.param(value="#ffb54d", bind=alt.binding(input='color', name='Europe ')) color_japan = alt.param(value="#adadad", bind=alt.binding(input='color', name='Japan ')) alt.Chart(data.cars.url).mark_circle().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color=alt.Color( 'Origin:N', scale=alt.Scale( domain=['USA', 'Europe', 'Japan'], range=[color_usa, color_europe, color_japan] ) ) ).add_params( color_usa, color_europe, color_japan ) .. _encoding-channel-binding: Encoding Channel Binding ^^^^^^^^^^^^^^^^^^^^^^^^ There is no direct way to map an encoding channel to a widget in order to dynamically display different charts based on different column choices, such as ``y=column_param``. The underlying reason this is not possible is that in Vega-Lite, the ``field`` property does not accept a parameter as value; see the `field Vega-Lite documentation `_. You can follow the discussion in this issue https://github.com/vega/vega-lite/issues/7365, and in the meantime, you can use parameters for a convenient workaround which let's you achieve the same functionality and change the plotted columns based on a widget selection (the x-axis title cannot be changed dynamically, but a text mark could be used instead if desired): .. altair-plot:: dropdown = alt.binding_select( options=['Horsepower', 'Displacement', 'Weight_in_lbs', 'Acceleration'], name='X-axis column ' ) xcol_param = alt.param( value='Horsepower', bind=dropdown ) alt.Chart(data.cars.url).mark_circle().encode( x=alt.X('x:Q').title(''), y='Miles_per_Gallon:Q', color='Origin:N' ).transform_calculate( x=f'datum[{xcol_param.name}]' ).add_params( xcol_param ) It was possible to achieve something similar before the introduction of parameters in Altair 5 by using ``transform_fold`` and ``transform_filter``, but the spec for this is more complex (as can be seen in `this SO answer `_) so the solution above is to prefer. .. _legend-binding: Legend Binding ^^^^^^^^^^^^^^ An interactive legend can often be helpful to assist in focusing in on groups of data. Instead of manually having to build a separate chart to use as a legend, Altair provides the ``bind='legend'`` option to facilitate the creation of clickable legends: .. altair-plot:: selection = alt.selection_point(fields=['Origin'], bind='legend') alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N', opacity=alt.condition(selection, alt.value(0.8), alt.value(0.2)) ).add_params( selection ) Scale Binding ^^^^^^^^^^^^^ With interval selections, the ``bind`` property can be set to the value of ``"scales"``. In these cases, the binding will automatically respond to the panning and zooming along the chart: .. altair-plot:: selection = alt.selection_interval(bind='scales') alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N', ).add_params( selection ) Because this is such a common pattern, Altair provides the :meth:`interactive` method which creates a scale-bound selection more concisely: .. altair-plot:: alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N', ).interactive() .. _expressions: Expressions ~~~~~~~~~~~ Altair allows custom interactions by utilizing the `expression language of Vega `_ for writing basic formulas. A Vega expression string is a well-defined set of JavaScript-style operations. To simplify building these expressions in Python, Altair provides the ``expr`` module, which offers constants and functions to construct expressions using Python syntax. Both JavaScript-syntax and Python-syntax are supported within Altair to define an expression and an introductory example of each is available in the :ref:`user-guide-calculate-transform` transform documentation so we recommend checking out that page before continuing. In the following example, we define a range connected to a parameter named ``param_width``. We then assign two expressions via ``param`` using both JavaScript and Python-syntax. Using these two expressions defined as a parameter, we can connect them to an encoding channel option, such as the title color of the axis. If the width is below ``200``, then the color is ``red``; otherwise, the color is ``blue``. .. altair-plot:: bind_range = alt.binding_range(min=100, max=300, name='Slider value: ') param_width = alt.param(bind=bind_range) # Examples of how to write both js and python expressions param_color_js_expr = alt.param(expr=f"{param_width.name} < 200 ? 'red' : 'black'") param_color_py_expr = alt.param(expr=alt.expr.if_(param_width < 200, 'red', 'black')) chart = alt.Chart(df).mark_point().encode( alt.X('xval').axis(titleColor=param_color_js_expr), alt.Y('yval').axis(titleColor=param_color_py_expr) ).add_params( param_width, param_color_js_expr, param_color_py_expr ) chart In the example above, we used a JavaScript-style ternary operator ``f"{param_width.name} < 200 ? 'red' : 'blue'"`` which is equivalent to the Python function ``expr.if_(param_width < 200, 'red', 'blue')``. The expressions defined as parameters also needed to be added to the chart within ``.add_params()``. In addition to assigning an expression within a parameter definition as shown above, the ``expr()`` utility function allows us to define expressions inline, ``add_params``. In the next example, we modify the chart above to change the size of the points based on an inline expression. Instead of creating a conditional statement, we use the value of the expression as the size directly and therefore only need to specify the name of the parameter. .. altair-plot:: chart.mark_point(size=alt.expr(param_width.name)) Inline expressions defined by ``expr(...)`` are not parameters so they can be added directly in the chart spec instead of via ``add_params``. Another option to include an expression within a chart specification is as a value definition to an encoding channel. Here, we make the exact same modification to the chart as in the previous example via this alternate approach: .. altair-plot:: chart.encode(size=alt.value(alt.expr(param_width.name))) `Some parameter names have special meaning in Vega-Lite `_, for example, naming a parameter ``width`` will automatically link it to the width of the chart. In the example below, we also modify the chart title to show the value of the parameter: .. altair-plot:: bind_range = alt.binding_range(min=100, max=300, name='Chart width: ') param_width = alt.param('width', bind=bind_range) # In Javascript, a number is converted to a string when added to an existing string, # which is why we use this nested quotation. title=alt.Title(alt.expr(f'"This chart is " + {param_width.name} + " px wide"')) alt.Chart(df, title=title).mark_point().encode( alt.X('xval'), alt.Y('yval') ).add_params( param_width, ) Now that we know the basics of expressions, let's see how we can improve on our search input example and make the search string match via a regex pattern. To do this we need to use ``expr.regex`` to define the regex string, and ``expr.test`` to test it against another string (in this case the string in the ``Name`` column). The ``i`` option makes the regex case insensitive, and you can see that we have switched to using ``param`` instead of ``selection_point`` since we are doing something more complex than looking up values with an exact match in the data. To try this out, you can type ``mazda|ford`` in the search input box below. .. altair-plot:: search_input = alt.param( value='', bind=alt.binding( input='search', placeholder="Car model", name='Search ', ) ) alt.Chart(data.cars.url).mark_point(size=60).encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', tooltip='Name:N', opacity=alt.condition( alt.expr.test(alt.expr.regexp(search_input, 'i'), alt.datum.Name), # f"test(regexp({search_input.name}, 'i'), datum.Name)", # Equivalent js alternative alt.value(1), alt.value(0.05) ) ).add_params( search_input ) And remember, all this interactivity is client side. You can save this chart as an HTML file or put it on a static site generator such as GitHub/GitLab pages and anyone can interact with it without having to install Python. Quite powerful! To summarize expressions: - Altair can utilize the expression language of Vega for writing basic formulas to enable custom interactions. - Both JavaScript-style syntax and Python-style syntax are supported in Altair to define expressions. - Altair provides the ``expr`` module which allows expressions to be constructed with Python syntax. - Expressions can be included within a chart specification using two approaches: through a ``param(expr=...)`` parameter definition or inline using the ``expr(...)`` utility function. - Expressions can be used anywhere the documentation mentions that an `ExprRef` is an accepted value. This is mainly in three locations within a chart specification: mark properties, encoding channel options, and within a value definition for an encoding channel. They are also supported in the chart title, but not yet for subtitles or guide titles (i.e. axis and legends, see https://github.com/vega/vega-lite/issues/7408 for details). Further Examples ~~~~~~~~~~~~~~~~ Now that you understand the basics of Altair selections and bindings, you might wish to look through the :ref:`gallery-category-Interactive Charts` section of the example gallery for ideas about how they can be applied to more interesting charts. For more information on how to fine-tune selections, including specifying other mouse and keystroke options, see the `Vega-Lite Selection documentation `_. Limitations ~~~~~~~~~~~ Some possible use cases for the above interactivity are not currently supported by Vega-Lite, and hence are not currently supported by Altair. Here are some examples. 1. If we are using a ``selection_point``, it would be natural to want to return information about the chosen data point, and then process that information using Python. This is not currently possible, so any data processing will have to be handled using tools such as ``transform_calculate``, etc. You can follow the progress on this in the following issue: https://github.com/altair-viz/altair/issues/1153. - The dashboarding package ``Panel`` has added support for processing Altair selections with custom callbacks in their 0.13 release. This is currently the only Python dashboarding package that supports custom callbacks for Altair selections and you can read more about how to use this functionality in `the Panel documentation `_. altair-5.0.1/doc/user_guide/internals.rst000066400000000000000000000207131443422213100204210ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-internals: Altair Internals ================ This section will provide some details about how the Altair API relates to the Vega-Lite visualization specification, and how you can use that knowledge to use the package more effectively. First of all, it is important to realize that when stripped down to its core, Altair itself cannot render visualizations. Altair is an API that does one very well-defined thing: - **Altair provides a Python API for generating validated Vega-Lite specifications** That's it. In order to take those specifications and turn them into actual visualizations requires a frontend that is correctly set up, but strictly speaking that rendering is generally not controlled by the Altair package. Altair Chart to Vega-Lite Spec ------------------------------ Since Altair is fundamentally about constructing chart specifications, the central functionality of any chart object are the :meth:`~Chart.to_dict` and :meth:`~Chart.to_json` methods, which output the chart specification as a Python dict or JSON string, respectively. For example, here is a simple scatter chart, from which we can output the JSON representation: .. altair-plot:: :output: stdout import altair as alt from vega_datasets import data chart = alt.Chart(data.cars.url).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N', ).configure_view( continuousHeight=300, continuousWidth=300, ) print(chart.to_json(indent=2)) Before returning the dict or JSON output, Altair validates it against the `Vega-Lite schema `_ using the `jsonschema `_ package. The Vega-Lite schema defines valid attributes and values that can appear within the specification of a Vega-Lite chart. With the JSON schema in hand, it can then be passed to a library such as `Vega-Embed `_ that knows how to read the specification and render the chart that it describes, and the result is the following visualization: .. altair-plot:: :hide-code: chart Whenever you use Altair within JupyterLab, Jupyter notebook, or other frontends, it is frontend extensions that extract the JSON output from the Altair chart object and pass that specification along to the appropriate rendering code. Altair's Low-Level Object Structure ----------------------------------- The standard API methods used in Altair (e.g. :meth:`~Chart.mark_point`, :meth:`~Chart.encode`, ``configure_*()``, ``transform_*()``, etc.) are higher-level convenience functions that wrap the low-level API. That low-level API is essentially a Python object hierarchy that mirrors that of the JSON schema definition. For example, we can choose to avoid the convenience methods and rather construct the above chart using these low-level object types directly: .. altair-plot:: alt.Chart( data=alt.UrlData( url='https://vega.github.io/vega-datasets/data/cars.json' ), mark='point', encoding=alt.FacetedEncoding( x=alt.PositionFieldDef( field='Horsepower', type='quantitative' ), y=alt.PositionFieldDef( field='Miles_per_Gallon', type='quantitative' ), color=alt.StringFieldDefWithCondition( field='Origin', type='nominal' ) ), config=alt.Config( view=alt.ViewConfig( continuousHeight=300, continuousWidth=300 ) ) ) This low-level approach is much more verbose than the typical idiomatic approach to creating Altair charts, but it makes much more clear the mapping between Altair's python object structure and Vega-Lite's schema definition structure. One of the nice features of Altair is that this low-level object hierarchy is not constructed by hand, but rather *programmatically generated* from the Vega-Lite schema, using the ``generate_schema_wrapper.py`` script that you can find in `Altair's repository `_. This auto-generation of code propagates descriptions from the vega-lite schema into the Python class docstrings, from which the `API Reference `_ within Altair's documentation are in turn automatically generated. This means that as the Vega-Lite schema evolves, Altair can very quickly be brought up-to-date, and only the higher-level chart methods need to be updated by hand. Converting Vega-Lite to Altair ------------------------------ With this knowledge in mind, and with a bit of practice, it is fairly straightforward to construct an Altair chart from a Vega-Lite spec. For example, consider the `Simple Bar Chart `_ example from the Vega-Lite documentation, which has the following JSON specification:: { "$schema": "https://vega.github.io/schema/vega-lite/v5.json", "description": "A simple bar chart with embedded data.", "data": { "values": [ {"a": "A","b": 28}, {"a": "B","b": 55}, {"a": "C","b": 43}, {"a": "D","b": 91}, {"a": "E","b": 81}, {"a": "F","b": 53}, {"a": "G","b": 19}, {"a": "H","b": 87}, {"a": "I","b": 52} ] }, "mark": {"type": "bar"}, "encoding": { "x": {"field": "a", "type": "ordinal"}, "y": {"field": "b", "type": "quantitative"} } } At the lowest level, we can use the :meth:`~Chart.from_json` class method to construct an Altair chart object from this string of Vega-Lite JSON: .. altair-plot:: import altair as alt alt.Chart.from_json(""" { "$schema": "https://vega.github.io/schema/vega-lite/v5.json", "description": "A simple bar chart with embedded data.", "data": { "values": [ {"a": "A","b": 28}, {"a": "B","b": 55}, {"a": "C","b": 43}, {"a": "D","b": 91}, {"a": "E","b": 81}, {"a": "F","b": 53}, {"a": "G","b": 19}, {"a": "H","b": 87}, {"a": "I","b": 52} ] }, "mark": {"type": "bar"}, "encoding": { "x": {"field": "a", "type": "ordinal"}, "y": {"field": "b", "type": "quantitative"} } } """) Likewise, if you have the Python dictionary equivalent of the JSON string, you can use the :meth:`~Chart.from_dict` method to construct the chart object: .. altair-plot:: import altair as alt alt.Chart.from_dict({ "$schema": "https://vega.github.io/schema/vega-lite/v5.json", "description": "A simple bar chart with embedded data.", "data": { "values": [ {"a": "A","b": 28}, {"a": "B","b": 55}, {"a": "C","b": 43}, {"a": "D","b": 91}, {"a": "E","b": 81}, {"a": "F","b": 53}, {"a": "G","b": 19}, {"a": "H","b": 87}, {"a": "I","b": 52} ] }, "mark": {"type": "bar"}, "encoding": { "x": {"field": "a", "type": "ordinal"}, "y": {"field": "b", "type": "quantitative"} } }) With a bit more effort and some judicious copying and pasting, we can manually convert this into more idiomatic Altair code for the same chart, including constructing a Pandas dataframe from the data values: .. altair-plot:: import altair as alt import pandas as pd data = pd.DataFrame.from_records([ {"a": "A","b": 28}, {"a": "B","b": 55}, {"a": "C","b": 43}, {"a": "D","b": 91}, {"a": "E","b": 81}, {"a": "F","b": 53}, {"a": "G","b": 19}, {"a": "H","b": 87}, {"a": "I","b": 52} ]) alt.Chart(data).mark_bar().encode( x='a:O', y='b:Q' ) The key is to realize that ``"encoding"`` properties are usually set using the :meth:`~Chart.encode` method, encoding types are usually computed from short-hand type codes, ``"transform"`` and ``"config"`` properties come from the ``transform_*()`` and ``configure_*()`` methods, and so on. This approach is the process by which Altair contributors constructed many of the initial examples in the `Altair Example Gallery `_, drawing inspiration from the `Vega-Lite Example Gallery `_. Becoming familiar with the mapping between Altair and Vega-Lite at this level is useful in making use of the Vega-Lite documentation in places where Altair's documentation is weak or incomplete. altair-5.0.1/doc/user_guide/large_datasets.rst000066400000000000000000000316531443422213100214110ustar00rootroot00000000000000.. _large-datasets: Large Datasets -------------- If you try to create a plot that will directly embed a dataset with more than 5000 rows, you will see a ``MaxRowsError``: .. altair-plot:: :output: none import altair as alt import pandas as pd data = pd.DataFrame({"x": range(10000)}) alt.Chart(data).mark_point() .. code-block:: none MaxRowsError: The number of rows in your dataset is greater than the maximum allowed (5000). For information on how to plot larger datasets in Altair, see the documentation. This is not because Altair cannot handle larger datasets, but it is because it is important for the user to think carefully about how large datasets are handled. The following sections describe various considerations as well as approaches to deal with large datasets. If you are certain you would like to embed your full dataset within the visualization specification, you can disable the ``MaxRows`` check:: alt.data_transformers.disable_max_rows() Challenges ~~~~~~~~~~ By design, Altair does not produce plots consisting of pixels, but plots consisting of data plus a visualization specification. For example, here is a simple chart made from a dataframe with three rows of data: .. altair-plot:: :output: none import altair as alt import pandas as pd data = pd.DataFrame({'x': [1, 2, 3], 'y': [2, 1, 2]}) chart = alt.Chart(data).mark_line().encode( x='x', y='y' ) from pprint import pprint pprint(chart.to_dict()) .. code-block:: none {'$schema': 'https://vega.github.io/schema/vega-lite/v2.4.1.json', 'config': {'view': {'height': 300, 'width': 300}}, 'data': {'values': [{'x': 1, 'y': 2}, {'x': 2, 'y': 1}, {'x': 3, 'y': 2}]}, 'encoding': {'x': {'field': 'x', 'type': 'quantitative'}, 'y': {'field': 'y', 'type': 'quantitative'}}, 'mark': 'line'} The resulting specification includes a representation of the data converted to JSON format, and this specification is embedded in the notebook or web page where it can be used by Vega-Lite to render the plot. As the size of the data grows, this explicit data storage can lead to some very large specifications which can have various negative implications: * large notebook files which can slow down your notebook environment such as JupyterLab * if you display the chart on a website it slows down the loading of the page * slow evaluation of transforms as the calculations are performed in JavaScript which is not the fastest language for processing large amounts of data .. _passing-data-by-url: Passing Data by URL ~~~~~~~~~~~~~~~~~~~ A common approach when working with large datasets is to not embed the data directly, but rather store it separately and pass it to the chart by URL. This not only addresses the issue of large notebooks, but also leads to better interactivity performance with large datasets. Local Data Server ^^^^^^^^^^^^^^^^^ A convenient way to do this is by using the `altair_data_server `_ package. It serves your data from a local threaded server. First install the package: .. code-block:: none pip install altair_data_server And then enable the data transformer:: import altair as alt alt.data_transformers.enable('data_server') Note that this approach may not work on some cloud-based Jupyter notebook services. A disadvantage of this method is that if you reopen the notebook, the plot may no longer display as the data server is no longer running. Local Filesystem ^^^^^^^^^^^^^^^^ You can also persist the data to disk and then pass the path to Altair: .. altair-plot:: :output: none url = 'data.json' data.to_json(url, orient='records') chart = alt.Chart(url).mark_line().encode( x='x:Q', y='y:Q' ) pprint(chart.to_dict()) .. code-block:: none {'$schema': 'https://vega.github.io/schema/vega-lite/v2.4.1.json', 'config': {'view': {'height': 300, 'width': 300}}, 'data': {'url': 'data.json'}, 'encoding': {'x': {'field': 'x', 'type': 'quantitative'}, 'y': {'field': 'y', 'type': 'quantitative'}}, 'mark': 'line'} Altair also has a ``JSON`` data transformer that will do this transparently when enabled:: alt.data_transformers.enable('json') There is a similar CSV data transformer, but it must be used more carefully because CSV does not preserve data types as JSON does. Note that the filesystem approach may not work on some cloud-based Jupyter notebook services. A disadvantage of this method is also a loss of portability: if the notebook is ever moved, the data file must accompany it or the plot may not display. Vega Datasets ^^^^^^^^^^^^^ If you are working with one of the vega datasets, you can pass the data by URL using the ``url`` attribute: .. code-block:: python from vega_datasets import data source = data.cars.url alt.Chart(source).mark_point() # etc. PNG and SVG Renderers ~~~~~~~~~~~~~~~~~~~~~ The approaches presented in :ref:`passing-data-by-url` have the disadvantage that the data is no longer contained in the notebook and you therefore lose portability or don't see the charts when you reopen the notebook. Furthermore, the data still needs to be sent to the frontend, e.g. your browser, and any calculations will happen there. You might achieve a speedup by enabling either the PNG or SVG renderer as described in :ref:`renderers`. Instead of a Vega-Lite specification, they will prerender the visualization and send only a static image to your notebook. This can greatly reduce the amount of data that is being transmitted. The downside with this approach is, that you loose all interactivity features of Altair. Both renderers require you to install either the `vl-convert`_ or the `altair_saver`_ package, see :ref:`saving-png`, whereas `vl-convert`_ is expected to provide the better performance. .. _preaggregate-and-filter: Preaggregate and Filter in Pandas ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Another common approach is to perform data transformations such as aggregations and filters using Pandas before passing the data to Altair. For example, to create a bar chart for the ``barley`` dataset summing up ``yield`` grouped by ``site``, it is convenient to pass the unaggregated data to Altair: .. altair-plot:: import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( x="sum(yield):Q", y=alt.Y("site:N").sort("-x") ) The above works well for smaller datasets but let's imagine that the ``barley`` dataset is larger and the resulting Altair chart slows down your notebook environment. To reduce the data being passed to Altair, you could subset the dataframe to only the necessary columns: .. code-block:: python alt.Chart(source[["yield", "site"]]).mark_bar().encode( x="sum(yield):Q", y=alt.Y("site:N").sort("-x") ) You could also precalculate the sum in Pandas which would reduce the size of the dataset even more: .. altair-plot:: import altair as alt from vega_datasets import data source = data.barley() source_aggregated = ( source.groupby("site")["yield"].sum().rename("sum_yield").reset_index() ) alt.Chart(source_aggregated).mark_bar().encode( x="sum_yield:Q", y=alt.Y("site:N").sort("-x") ) Preaggregate Boxplot ^^^^^^^^^^^^^^^^^^^^ A boxplot is a useful way to visualize the distribution of data and it is simple to create in Altair. .. altair-plot:: import altair as alt from vega_datasets import data df = data.cars() alt.Chart(df).mark_boxplot().encode( x="Miles_per_Gallon:Q", y="Origin:N", color=alt.Color("Origin").legend(None) ) If you have a lot of data, you can perform the necessary calculations in Pandas and only pass the resulting summary statistics to Altair. First, let's define a few parameters where ``k`` stands for the multiplier which is used to calculate the boundaries of the whiskers. .. altair-plot:: :output: none import altair as alt import pandas as pd from vega_datasets import data k = 1.5 group_by_column = "Origin" value_column = "Miles_per_Gallon" In the next step, we will calculate the summary statistics which are needed for the boxplot. .. altair-plot:: :output: repr :chart-var-name: agg_stats df = data.cars() agg_stats = df.groupby(group_by_column)[value_column].describe() agg_stats["iqr"] = agg_stats["75%"] - agg_stats["25%"] agg_stats["min_"] = agg_stats["25%"] - k * agg_stats["iqr"] agg_stats["max_"] = agg_stats["75%"] + k * agg_stats["iqr"] data_points = df[[value_column, group_by_column]].merge( agg_stats.reset_index()[[group_by_column, "min_", "max_"]] ) # Lowest data point which is still above or equal to min_ # This will be the lower end of the whisker agg_stats["lower"] = ( data_points[data_points[value_column] >= data_points["min_"]] .groupby(group_by_column)[value_column] .min() ) # Highest data point which is still below or equal to max_ # This will be the upper end of the whisker agg_stats["upper"] = ( data_points[data_points[value_column] <= data_points["max_"]] .groupby(group_by_column)[value_column] .max() ) # Store all outliers as a list agg_stats["outliers"] = ( data_points[ (data_points[value_column] < data_points["min_"]) | (data_points[value_column] > data_points["max_"]) ] .groupby(group_by_column)[value_column] .apply(list) ) agg_stats = agg_stats.reset_index() # Show whole dataframe pd.set_option("display.max_columns", 15) print(agg_stats) And finally, we can create the same boxplot as above but we only pass the calculated summary statistics to Altair instead of the full dataset. .. altair-plot:: base = alt.Chart(agg_stats).encode( y="Origin:N" ) rules = base.mark_rule().encode( x=alt.X("lower").title("Miles_per_Gallon"), x2="upper", ) bars = base.mark_bar(size=14).encode( x="25%", x2="75%", color=alt.Color("Origin").legend(None), ) ticks = base.mark_tick(color="white", size=14).encode( x="50%" ) outliers = base.transform_flatten( flatten=["outliers"] ).mark_point( style="boxplot-outliers" ).encode( x="outliers:Q", color="Origin", ) rules + bars + ticks + outliers VegaFusion ~~~~~~~~~~ `VegaFusion`_ is a third-party package that re-implements most Vega-Lite transforms for evaluation in the Python kernel, which makes it possible to scale Altair charts to millions of rows of data. VegaFusion provides two rendering modes that are useful in different situations. Mime Renderer ^^^^^^^^^^^^^ The `VegaFusion mime renderer`_ is a good choice for charts that contain aggregations and that do not re-aggregate or re-filter data in response to selections (so it offers similar but more advanced functionality as the ``altair-transform`` package). It is enabled with: .. code-block:: python import vegafusion as vf vf.enable() The mime renderer automates the :ref:`preaggregate-and-filter` workflow described above. Right before a chart is rendered, VegaFusion extracts the datasets and supported transforms and evaluates them in the Python kernel. It then removes any unused columns and inlines the transformed data into the chart specification for rendering. Charts rendered this way are self-contained and do not require the Python kernel or a custom notebook extension to display. They are rendered with the same frontend functionality that is already used to display regular Altair charts. Widget Renderer ^^^^^^^^^^^^^^^ The `VegaFusion widget renderer`_ is a good choice for displaying unaggregated data and for aggregated charts that re-aggregate or re-filter data in response to selections (so it offers similar but more advanced functionality as the ``altair-data-server`` package). It is enabled with: .. code-block:: python import vegafusion as vf vf.enable_widget() The widget renderer uses a Jupyter Widget extension to maintain a live connection between the displayed chart and the Python kernel. This makes it possible for transforms to be evaluated interactively in response to changes in selections, and to send the datasets to the client in arrow format separately instead of inlining them in the chart json spec. Charts rendered this way require a running Python kernel and Jupyter Widget extension to display, which works in many frontends including locally in the classic notebook, JupyterLab, and VSCode, as well as remotely in Colab and Binder. .. _VegaFusion: https://vegafusion.io .. _VegaFusion mime renderer: https://vegafusion.io/mime_renderer.html .. _VegaFusion widget renderer: https://vegafusion.io/widget_renderer.html .. _vl-convert: https://github.com/vega/vl-convert .. _altair_saver: https://github.com/altair-viz/altair_saver/ altair-5.0.1/doc/user_guide/marks/000077500000000000000000000000001443422213100170025ustar00rootroot00000000000000altair-5.0.1/doc/user_guide/marks/arc.rst000066400000000000000000000072611443422213100203070ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-arc-marks: Arc ~~~ Arc marks are circular arcs defined by a center point plus angular and radial extents. Arc marks are typically used for radial plots such as pie and donut charts. Arc Mark Properties ------------------- .. altair-plot:: :hide-code: :div_class: properties-example import altair as alt import numpy as np import pandas as pd rad_slider = alt.binding_range(min=0, max=100, step=1) rad_var = alt.param(bind=rad_slider, value=0, name="radius") rad2_slider = alt.binding_range(min=0, max=100, step=1) rad_var2 = alt.param(bind=rad_slider, value=50, name="radius2") theta_slider = alt.binding_range(min=-2 * np.pi, max=2 * np.pi) theta_var = alt.param(bind=theta_slider, value=-0.73, name="theta_single_arc") theta_slider2 = alt.binding_range(min=-2 * np.pi, max=2 * np.pi) theta2_var = alt.param(bind=theta_slider, value=0.73, name="theta2_single_arc") corner_slider = alt.binding_range(min=0, max=50, step=1) corner_var = alt.param(bind=corner_slider, value=0, name="cornerRadius") pad_slider = alt.binding_range(min=0, max=np.pi / 2) pad_var = alt.param(bind=pad_slider, value=0, name="padAngle") source = pd.DataFrame({"category": [1, 2, 3, 4, 5, 6], "value": [4, 6, 10, 3, 7, 8]}) c1 = alt.Chart(source, title="Single Arc").mark_arc( radius=rad_var, radius2=rad_var2, theta=theta_var, theta2=theta2_var, cornerRadius=corner_var, padAngle=pad_var, ) c2 = ( alt.Chart(source, title="Stacked Arcs") .mark_arc( radius=rad_var, radius2=rad_var2, cornerRadius=corner_var, padAngle=pad_var, ) .encode( theta=alt.Theta(field="value", type="quantitative"), color=alt.Color(field="category", type="nominal"), ) ) alt.hconcat(c1.properties(width=200), c2.properties(width=200)).add_params( rad_var, rad_var2, theta_var, theta2_var, corner_var, pad_var ) An ``arc`` mark definition can contain any :ref:`standard mark properties ` and the following special properties: .. altair-object-table:: altair.MarkDef :properties: radius radius2 innerRadius outerRadius theta theta2 cornerRadius padAngle radiusOffset radius2Offset thetaOffset theta2Offset Examples -------- We can create a pie chart by encoding ``theta`` or ``color`` arc marks. .. altair-plot:: import pandas as pd import altair as alt source = pd.DataFrame({"category": [1, 2, 3, 4, 5, 6], "value": [4, 6, 10, 3, 7, 8]}) alt.Chart(source).mark_arc().encode( theta=alt.Theta(field="value", type="quantitative"), color=alt.Color(field="category", type="nominal"), ) Setting ``innerRadius`` to non-zero values will create a donut chart. .. altair-plot:: import pandas as pd import altair as alt source = pd.DataFrame({"category": [1, 2, 3, 4, 5, 6], "value": [4, 6, 10, 3, 7, 8]}) alt.Chart(source).mark_arc(innerRadius=50).encode( theta=alt.Theta(field="value", type="quantitative"), color=alt.Color(field="category", type="nominal"), ) You can also add a text layer to add labels to a pie chart. .. altair-plot:: import pandas as pd import altair as alt source = pd.DataFrame( {"category": ["a", "b", "c", "d", "e", "f"], "value": [4, 6, 10, 3, 7, 8]} ) base = alt.Chart(source).encode( theta=alt.Theta("value:Q").stack(True), color=alt.Color("category:N").legend(None), ) pie = base.mark_arc(outerRadius=120) text = base.mark_text(radius=140, size=20).encode( text="category:N" ) pie + text altair-5.0.1/doc/user_guide/marks/area.rst000066400000000000000000000134471443422213100204550ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-area-marks: Area ~~~~~~~~~~ ``area`` represent multple data element as a single area shape. Area marks are often used to show change over time, using either a single area or stacked areas. Area Mark Properties -------------------- .. altair-plot:: :hide-code: :div_class: properties-example import altair as alt import pandas as pd interpolate_select = alt.binding_select( options=[ "basis", "cardinal", "catmull-rom", "linear", "monotone", "natural", "step", "step-after", "step-before", ], name="interpolate", ) interpolate_var = alt.param(bind=interpolate_select, value="linear") tension_slider = alt.binding_range(min=0, max=1, step=0.05, name="tension") tension_var = alt.param(bind=tension_slider, value=0) source = pd.DataFrame({"u": [1, 2, 3, 4, 5, 6], "v": [28, 55, 42, 34, 36, 38]}) alt.Chart(source).mark_area(interpolate=interpolate_var, tension=tension_var).encode( x="u", y="v" ).add_params(interpolate_var, tension_var) An ``area`` mark definition can contain any :ref:`standard mark properties ` and the following line interpolation as well as line and point overlay properties: .. altair-object-table:: altair.MarkDef :properties: align baseline orient interpolate tension line point Examples -------- Area Chart ^^^^^^^^^^ Using ``area`` mark with one temporal or ordinal field (typically on ``x``) and one quantitative field (typically on ``y``) produces an area chart. For example, the following area chart shows a number of unemployment people in the US over time. .. altair-plot:: import altair as alt from vega_datasets import data source = data.unemployment_across_industries.url alt.Chart(source).mark_area().encode( x="yearmonth(date):T", y="sum(count):Q", ).properties(width=300, height=200) Area Chart with Overlaying Lines and Point Markers ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ By setting ``line`` and ``point`` properties of the mark definition to ``true`` or an object defining a property of the overlaying point marks, we can overlay line and point markers on top of area. .. altair-plot:: import altair as alt from vega_datasets import data from altair.expr import datum source = data.stocks.url alt.Chart(source).mark_area(line=True, point=True).encode( x="date:T", y="price:Q", ).transform_filter( alt.datum.symbol == "GOOG" ) Instead of using a single color as the fill color of the area, we can set it to a gradient. In this example, we are also customizing the overlay. For more information about gradient options see the `Vega-Lite Gradient documentation `_. .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).transform_filter(alt.datum.symbol == "GOOG").mark_area( line={"color": "darkgreen"}, color=alt.Gradient( gradient="linear", stops=[ alt.GradientStop(color="white", offset=0), alt.GradientStop(color="darkgreen", offset=1), ], x1=1, x2=1, y1=1, y2=0, ), ).encode( alt.X("date:T"), alt.Y("price:Q"), ) Stacked Area Chart ^^^^^^^^^^^^^^^^^^ Adding a color field to area chart creates stacked area chart by default. For example, here we split the area chart by industry. .. altair-plot:: import altair as alt from vega_datasets import data source = data.unemployment_across_industries.url alt.Chart(source).mark_area().encode( alt.X("yearmonth(date):T").axis(format="%Y", domain=False, tickSize=0), alt.Y("sum(count):Q"), alt.Color("series:N").scale(scheme="category20b"), ) Normalized Stacked Area Chart ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can also create a normalized stacked area chart by setting ``stack`` to ``"normalize"`` in the encoding channel. Here we can easily see the percentage of unemployment across industries. .. altair-plot:: import altair as alt from vega_datasets import data source = data.unemployment_across_industries.url alt.Chart(source).mark_area().encode( alt.X("yearmonth(date):T").axis(format="%Y", domain=False, tickSize=0), alt.Y("sum(count):Q").stack("normalize"), alt.Color("series:N").scale(scheme="category20b"), ) Steamgraph ^^^^^^^^^^^ We can also shift the stacked area chart's baseline to center and produces a streamgraph by setting ``stack`` to ``"center"`` in the encoding channel. Adding the ``interactive`` method allows for zooming and panning the x-scale. .. altair-plot:: import altair as alt from vega_datasets import data source = data.unemployment_across_industries.url alt.Chart(source).mark_area().encode( alt.X("yearmonth(date):T").axis(format="%Y", domain=False, tickSize=0), alt.Y("sum(count):Q").stack("center").axis(None), alt.Color("series:N").scale(scheme="category20b"), ).interactive() Ranged Area ^^^^^^^^^^^ Specifying ``X2`` or ``Y2`` for the quantitative axis of area marks produce ranged areas. For example, we can use ranged area to highlight the mininium and maximum measured temperatures over time, aggregated by ``monthdate``. .. altair-plot:: import altair as alt from vega_datasets import data source = data.seattle_weather() alt.Chart(source).mark_area(opacity=0.7).encode( alt.X("monthdate(date):T").title("Date"), alt.Y("mean(temp_max):Q").title("Daily Temperature Range (C)"), alt.Y2("mean(temp_min):Q"), ).properties(width=600, height=300) altair-5.0.1/doc/user_guide/marks/bar.rst000066400000000000000000000103131443422213100202760ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-bar-marks: Bar ~~~ Bar marks are useful in many visualizations, including bar charts, stacked bar charts, and timelines. Bar Mark Properties ------------------- .. altair-plot:: :hide-code: :div_class: properties-example import altair as alt import pandas as pd corner_slider = alt.binding_range(min=0, max=50, step=1) corner_var = alt.param(bind=corner_slider, value=0, name="cornerRadius") source = pd.DataFrame( { "a": ["A", "B", "C", "D", "E", "F", "G", "H", "I"], "b": [28, 55, 43, 91, 81, 53, 19, 87, 52], } ) alt.Chart(source).mark_bar(cornerRadius=corner_var).encode( x=alt.X("a:N").axis(labelAngle=0), y="b:Q", ).add_params(corner_var) A ``bar`` mark definition can contain any :ref:`standard mark properties ` and the following special properties: .. altair-object-table:: altair.MarkDef :properties: width height orient align baseline binSpacing cornerRadius cornerRadiusEnd cornerRadiusTopLeft cornerRadiusTopRight cornerRadiusBottomRight cornerRadiusBottomLeft Examples -------- Single Bar Chart ^^^^^^^^^^^^^^^^ Mapping a quantitative field to either ``x`` or ``y`` of the ``bar`` mark produces a single bar chart. .. altair-plot:: import altair as alt from altair import datum from vega_datasets import data source = data.population.url alt.Chart(source).mark_bar().encode( alt.X("sum(people):Q").title("Population") ).transform_filter( datum.year == 2000 ) Bar Chart ^^^^^^^^^ If we map a different discrete field to the ``y`` channel, we can produce a horizontal bar chart. Specifying ``alt.Step(20)`` will adjust the bar's height per discrete step. .. altair-plot:: import altair as alt from altair import datum from vega_datasets import data source = data.population.url alt.Chart(source).mark_bar().encode( alt.X("sum(people):Q").title("Population"), alt.Y("age:O"), ).transform_filter( datum.year == 2000 ).properties(height=alt.Step(20)) Bar Chart with a Temporal Axis ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ While the ``bar`` mark typically uses the ``x`` and ``y`` channels to encode a pair of discrete and continuous fields, it can also be used with continuous fields on both channels. For example, given a bar chart with a temporal field on ``x``, we can see that the x-scale is a continuous scale. By default, the size of bars on continuous scales will be set based on the ``continuousBandSize`` config. .. altair-plot:: import altair as alt from vega_datasets import data source = data.seattle_weather() alt.Chart(source).mark_bar().encode( alt.X("month(date):T").title("Date"), alt.Y("mean(precipitation):Q"), ) Histograms ^^^^^^^^^^ If the data is not pre-aggregated (i.e. each record in the data field represents one item), mapping a binned quantitative field to ``x`` and aggregate ``count`` to ``y`` produces a histogram. .. altair-plot:: import altair as alt from vega_datasets import data source = data.movies.url alt.Chart(source).mark_bar().encode( alt.X("IMDB_Rating:Q").bin(), y='count()', ) Stacked Bar Chart ^^^^^^^^^^^^^^^^^ Adding color to the bar chart (by using the ``color`` attribute) creates a stacked bar chart by default. Here we also customize the color’s scale range to make the color a little nicer. (See ``stack`` for more details about customizing stack.) .. altair-plot:: import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( x="variety", y="sum(yield)", color="site" ) Grouped Bar Chart with Offset ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. altair-plot:: import altair as alt import pandas as pd source = pd.DataFrame( { "category": ["A", "A", "B", "B", "C", "C"], "group": ["x", "y", "z", "x", "y", "z"], "value": [0.1, 0.6, 0.9, 0.7, 0.2, 0.6], } ) alt.Chart(source).mark_bar().encode( x=alt.X("category:N"), xOffset="group:N", y=alt.Y("value:Q"), color=alt.Color("group:N"), ) altair-5.0.1/doc/user_guide/marks/boxplot.rst000066400000000000000000000104731443422213100212300ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-boxplot-marks: Box Plot ~~~~~~~~~ A box plot summarizes a distribution of quantitative values using a set of summary statistics. The median tick in the box represents the median. The lower and upper parts of the box represent the first and third quartile respectively. Depending on the type of box plot, the ends of the whiskers can represent multiple things. To create a box plot, use the ``mark_boxplot`` method. Box Plot Mark Properties ^^^^^^^^^^^^^^^^^^^^^^^^ A box plot's mark definition can contain the following properties: .. altair-object-table:: altair.BoxPlotDef :properties: extent orient size color opacity Besides the properties listed above, ``box``, ``median``, ``rule``, ``outliers``, and ``ticks`` can be used to specify the underlying mark properties for different parts of the box plots as well. Types of Box Plot ^^^^^^^^^^^^^^^^^ Altair supports two types of box plots, defined by the ``extent`` property in the mark definition object. 1. Tukey Box Plot is the default box plot in Altair. For a Tukey box plot, the whisker spans from the smallest data to the largest data within the range [Q1 - k * IQR, Q3 + k * IQR] where Q1 and Q3 are the first and third quartiles while IQR is the interquartile range (Q3-Q1). In this type of box plot, you can specify the constant k by setting the ``extent``. If there are outlier points beyond the whisker, they will be displayed using point marks. By default, the extent is ``1.5``. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_boxplot().encode( alt.X("Miles_per_Gallon:Q").scale(zero=False) ) 2. ``min-max`` Box Plot is a box plot where the lower and upper whiskers are defined as the min and max respectively. No points will be considered as outliers for this type of box plots. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_boxplot(extent="min-max").encode( alt.X("Miles_per_Gallon:Q").scale(zero=False), alt.Y("Origin:N"), ) Dimension and Orientation ^^^^^^^^^^^^^^^^^^^^^^^^^ Altair supports bot 1D and 2D box plots: 1D box plot shows the distribution of a continuous field. A box plot’s orientation is automatically determined by the continuous field axis. For example, you can create a vertical 1D box plot by encoding a continuous field on the y axis. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_boxplot().encode( alt.Y("Miles_per_Gallon:Q").scale(zero=False) ) 2D box plot shows the distribution of a continuous field, broken down by categories. For 2D box plots with one continuous field and one discrete field, the box plot will be horizontal if the continuous field is on the x axis. Color, Size, and Opacity Encoding Channels ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can customize the color, size, and opacity of the box in the box plot by using the ``color``, ``size``, and ``opacity`` encoding channels. The ``size`` is applied to only the box and median tick. The ``color`` is applied to only the box and the outlier points. Meanwhile, the ``opacity`` is applied to the whole ``boxplot``. An example of a box plot where the ``color`` encoding channel is specified. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_boxplot(extent="min-max").encode( alt.X("Origin:N"), alt.Y("Miles_per_Gallon:Q").scale(zero=False), alt.Color("Origin:N").legend(None), ) Tooltip Encoding Channels ^^^^^^^^^^^^^^^^^^^^^^^^^ You can add custom tooltips to box plots. The custom tooltip will override the default box plot's tooltips. If the field in the tooltip encoding is unaggregated, it replaces the tooltips of the outlier marks. On the other hand, if the field in the tooltip encoding is aggregated, it replaces the tooltips of the box and whisker marks. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_boxplot(extent="min-max").encode( alt.X("Miles_per_Gallon:Q").scale(zero=False), alt.Y("Origin:N"), alt.Tooltip("mean(Miles_per_Gallon)"), ) altair-5.0.1/doc/user_guide/marks/circle.rst000066400000000000000000000014071443422213100207770ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-circle-marks: Circle ~~~~~~ ``circle`` mark is similar to ``point`` mark, except that (1) the ``shape`` value is always set to ``circle`` (2) they are filled by default. Circle Mark Properties ^^^^^^^^^^^^^^^^^^^^^^ A ``circle`` mark definition can contain any :ref:`standard mark properties ` and the following special properties: .. altair-object-table:: altair.MarkDef :properties: size Scatter Plot with Circle ^^^^^^^^^^^^^^^^^^^^^^^^ Here is an example scatter plot with ``circle`` marks: .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars.url alt.Chart(source).mark_circle().encode( x=("Horsepower:Q"), y=("Miles_per_Gallon:Q"), ) altair-5.0.1/doc/user_guide/marks/errorband.rst000066400000000000000000000120251443422213100215120ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-errorband-marks: Error Band ~~~~~~~~~~ An error band summarizes an error range of quantitative values using a set of summary statistics, representing by area. Error band in Altair can either be used to aggregate raw data or directly visualize aggregated data. To create an error band, use ``mark_errorband``. Error Band Mark Properties ^^^^^^^^^^^^^^^^^^^^^^^^^^ An ``errorband`` mark definition can contain the following properties: .. altair-object-table:: altair.ErrorBandDef :properties: extent orient color opacity interpolate tension Besides the properties listed above, ``band`` and ``borders`` can be used to specify the underlying mark properties for different parts of the error band as well. Comparing the usage of Error Band to the usage of Error Bar ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ All the properties and usage of error band are identical to error bar’s, except the ``band`` and ``borders`` that replace the error bar’s ``rule`` and ``ticks``. **Error Band** .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars.url alt.Chart(source).mark_errorband(extent="ci", borders=True).encode( x="year(Year)", y=alt.Y( "Miles_per_Gallon:Q", scale=alt.Scale(zero=False), title="Miles per Gallon (95% CIs)", ), ) **Error Bar** .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars.url alt.Chart(source).mark_errorbar(extent="ci", ticks=True).encode( x="year(Year)", y=alt.Y( "Miles_per_Gallon:Q", scale=alt.Scale(zero=False), title="Miles per Gallon (95% CIs)", ), ) Using Error Band to Aggregate Raw Data ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If the data is not aggregated yet, Altair will aggregate the data based on the ``extent`` properties in the mark definition as done in the error band showing confidence interval above. All other ``extent`` values are defined in Error Bar. Using Error Band to Visualize Aggregated Data ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1. Data is aggregated with low and high values of the error band If the data is already pre-aggregated with low and high values of the error band, you can directly specify ``x`` and ``x2`` (or ``y`` and ``y2``) to use error band as a ranged mark. .. altair-plot:: import altair as alt import pandas as pd source = pd.DataFrame( { "ci1": [23.5007, 25.8214, 26.4472, 27.7074], "ci0": [19.6912, 20.8554, 21.9749, 22.6203], "center": [21.5735, 23.3750, 24.0611, 25.0931], "Year": [189302400000, 220924800000, 252460800000, 283996800000], } ) band = alt.Chart(source).mark_errorband().encode( alt.Y( "ci1:Q", scale=alt.Scale(zero=False), title="Mean of Miles per Gallon (95% CIs)" ), alt.Y2("ci0:Q"), alt.X("year(Year)"), ) line = alt.Chart(source).mark_line().encode( alt.Y("center:Q"), alt.X("year(Year)") ) band + line 2. Data is aggregated with center and error value(s) If the data is already pre-aggregated with center and error values of the error band, you can use ``x/y``, ``x/yError``, and ``x/yError2`` as defined in Error Bar. Dimension ^^^^^^^^^ Altair supports both 1D and 2D error bands: A **1D error band** shows the error range of a continuous field; it can be used to show the global error range of the whole plot. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars.url band = alt.Chart(source).mark_errorband(extent="stdev").encode( alt.Y("Miles_per_Gallon:Q").title("Miles per Gallon") ) points = alt.Chart(source).mark_point().encode( x="Horsepower:Q", y="Miles_per_Gallon:Q", ) band + points A **2D error** band shows the error range of a continuous field for each dimension value such as year. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() line = alt.Chart(source).mark_line().encode( x="Year", y="mean(Miles_per_Gallon)" ) band = alt.Chart(source).mark_errorband(extent="ci").encode( x="Year", y=alt.Y("Miles_per_Gallon").title("Miles/Gallon"), ) band + line Color and Opacity Encoding Channels ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can customize the color and opacity of the bands by using the ``color`` and ``opacity`` encoding channels. Here is an example of a ``errorband`` with the ``color`` encoding channel set to ``alt.value('black')``. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars.url alt.Chart(source).mark_errorband(extent="ci", borders=True).encode( x="year(Year)", y=alt.Y("Miles_per_Gallon:Q") .scale(zero=False) .title("Miles per Gallon (95% CIs)"), color=alt.value("black") ) altair-5.0.1/doc/user_guide/marks/errorbar.rst000066400000000000000000000174311443422213100213600ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-errorbar-marks: Error Bar ~~~~~~~~~~ An error bar summarizes an error range of quantitative values using a set of summary statistics, representing by rules (and optional end ticks). Error bars in Altair can either be used to aggregate raw data or directly visualize aggregated data. To create an error bar, use ``mark_errorbar``. Error Bar Mark Properties ^^^^^^^^^^^^^^^^^^^^^^^^^ An ``errorbar`` mark definition can contain the following properties: .. altair-object-table:: altair.ErrorBarDef :properties: extent orient color opacity Besides the properties listed above, ``rule`` and ``ticks`` can be used to specify the underlying mark properties for different parts of the error bar as well. Using Error Bars to Aggregate Raw Data ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If the data is not aggregated yet, Altair will aggregate the data based on the ``extent`` properties in the mark definition. 1. **Error bars showing standard error** is the default error bar in Vega-Lite. It can also be explicitly specified by setting ``extent`` to ``"stderr"``. The length of lower and upper rules represent standard error. By default, the rule marks expand from the mean. .. altair-plot:: import altair as alt from vega_datasets import data source = data.barley() error_bars = alt.Chart(source).mark_errorbar().encode( x=alt.X('yield:Q').scale(zero=False), y=alt.Y('variety:N') ) points = alt.Chart(source).mark_point( filled=True, color="black", ).encode( x=alt.X("mean(yield)"), y=alt.Y("variety:N"), ) error_bars + points 2. **Error bar showing standard deviation** can be specified by setting ``extent`` to ``"stdev"``. For this type of error bar, the length of lower and upper rules represent standard deviation. Like an error bar that shows Standard Error, the rule marks expand from the mean by default. .. altair-plot:: import altair as alt from vega_datasets import data source = data.barley() error_bars = alt.Chart(source).mark_errorbar(extent="stdev").encode( x=alt.X("yield:Q").scale(zero=False), y=alt.Y("variety:N"), ) points = alt.Chart(source).mark_point(filled=True, color="black").encode( x=alt.X("mean(yield)"), y=alt.Y("variety:N"), ) error_bars + points 3. **Error bars showing interquartile range** can be specified by setting ``extent`` to ``"iqr"``. For this type of error bar, the rule marks expand from the first quartile to the third quartile. .. altair-plot:: import altair as alt from vega_datasets import data source = data.barley() error_bars = alt.Chart(source).mark_errorbar(extent="iqr").encode( x=alt.X("yield:Q").scale(zero=False), y=alt.Y("variety:N"), ) points = alt.Chart(source).mark_point( filled=True, color="black" ).encode( x=alt.X("mean(yield)"), y=alt.Y("variety:N"), ) error_bars + points Using Error Bars to Visualize Aggregated Data ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1. Data is aggregated with low and high values of the error bars If the data is already pre-aggregated with low and high values of the error bars, you can directly specify ``x`` and ``x2`` (or ``y`` and ``y2``) to use error bar as a ranged mark. .. altair-plot:: import altair as alt import pandas as pd source = pd.DataFrame({ "lower_yield": [23.1311, 23.9503, 24.7778, 21.7823], "upper_yield": [43.5522, 38.9775, 46.9167, 48.9732], "center": [32.4, 30.96667, 33.966665, 30.45], "variety": ["Glabron", "Manchuria", "No. 457", "No. 462"], }) bar = alt.Chart(source).mark_errorbar().encode( alt.X("upper_yield:Q").scale(zero=False).title("yield"), alt.X2("lower_yield:Q"), alt.Y("variety:N"), ) point = alt.Chart(source).mark_point( filled=True, color="black" ).encode( alt.X("center:Q"), alt.Y("variety:N") ) point + bar 2. Data is aggregated with center and error value(s) If the data is already pre-aggregated with center and error values of the error bars, you can directly specify ``x`` as center, ``xError`` and ``xError2`` as error values extended from center (or ``y``, ``yError``, and ``yError2``). If ``x/yError2`` is omitted, error bars have symmetric error values. .. altair-plot:: import altair as alt import pandas as pd source = pd.DataFrame({ "yield_error": [7.5522, 6.9775, 3.9167, 11.9732], "yield_center": [32.4, 30.96667, 33.966665, 30.45], "variety": ["Glabron", "Manchuria", "No. 457", "No. 462"], }) bar = alt.Chart(source).mark_errorbar().encode( x=alt.X("yield_center:Q").scale(zero=False).title("yield"), xError=("yield_error:Q"), y=alt.Y("variety:N"), ) point = alt.Chart(source).mark_point( filled=True, color="black" ).encode( alt.X("yield_center:Q"), alt.Y("variety:N"), ) point + bar **Note** if error is pre-aggregated with asymmetric error values one of ``x/yError`` and ``x/yError2`` has to be positive value and other has to be negative value. Dimension & Orientation ^^^^^^^^^^^^^^^^^^^^^^^ Altair supports both 1D and 2D error bands: A **1D error band** shows the error range of a continuous field. The orientation of an error bar is automatically determined by the continuous field axis. For example, you can create a vertical 1D error bar by encoding a continuous field on the y axis. .. altair-plot:: import altair as alt from vega_datasets import data source = data.barley() error_bars = alt.Chart(source).mark_errorbar().encode( alt.Y("yield:Q").scale(zero=False) ) points = alt.Chart(source).mark_point( filled=True, color="black" ).encode( alt.Y("mean(yield)") ) error_bars + points A **2D error bar** shows the error range of a continuous field, broken down by categories. For 2D error bars with one continuous field and one discrete field, the error bars will be horizontal if the continuous field is on the x axis. Alternatively, if the continuous field is on the y axis, the error bar will be vertical. .. altair-plot:: import altair as alt from vega_datasets import data source = data.barley() error_bars = alt.Chart(source).mark_errorbar(extent="stdev").encode( alt.Y("yield:Q").scale(zero=False), alt.X("variety:N"), ) points = alt.Chart(source).mark_point( filled=True, color="black", ).encode( alt.Y("mean(yield)"), alt.X("variety:N"), ) error_bars + points Color, and Opacity Encoding Channels ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can customize the color and opacity of the bars by using the ``color`` and ``opacity`` encoding channels. Here is an example of a ``errorbar`` with the ``color`` encoding channel set to ``alt.value("#4682b4")``. .. altair-plot:: import altair as alt from vega_datasets import data source = data.barley() error_bars = alt.Chart(source).mark_errorbar(ticks=True).encode( alt.X("yield:Q").scale(zero=False), alt.Y("variety:N"), color=alt.value("#4682b4"), ) points = alt.Chart(source).mark_point( filled=True, color="black" ).encode( alt.X("mean(yield)"), alt.Y("variety:N"), ) error_bars + points Tooltip Encoding Channels ^^^^^^^^^^^^^^^^^^^^^^^^^ You can add custom tooltips to error bars. The custom tooltip will override the default error bar’s tooltips. .. altair-plot:: import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_errorbar().encode( alt.X("yield:Q").scale(zero=False), alt.Y("variety:N"), tooltip="variety:N", ) altair-5.0.1/doc/user_guide/marks/geoshape.rst000066400000000000000000000553251443422213100213410ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-geoshape-marks: Geoshape ^^^^^^^^^^^^^ ``mark_geoshape`` represents an arbitrary shapes whose geometry is determined by specified spatial data. Geoshape Mark Properties ^^^^^^^^^^^^^^^^^^^^^^^^ A ``geoshape`` mark can contain any :ref:`standard mark properties `. Basic Map ^^^^^^^^^ Altair can work with many different geographical data formats, including geojson and topojson files. Often, the most convenient input format to use is a ``GeoDataFrame``. Here we load the Natural Earth 110m Cultural Vectors dataset and create a basic map using ``mark_geoshape``: .. altair-plot:: import altair as alt from vega_datasets import data import geopandas as gpd url = "https://naciscdn.org/naturalearth/110m/cultural/ne_110m_admin_0_countries.zip" gdf_ne = gpd.read_file(url) # zipped shapefile gdf_ne = gdf_ne[["NAME", "CONTINENT", "POP_EST", 'geometry']] alt.Chart(gdf_ne).mark_geoshape() In the example above, Altair applies a default blue ``fill`` color and uses a default map projection (``equalEarth``). We can customize the colors and boundary stroke widths using standard mark properties. Using the ``project`` method we can also define a custom map projection manually: .. altair-plot:: alt.Chart(gdf_ne).mark_geoshape( fill='lightgrey', stroke='white', strokeWidth=0.5 ).project( type='albers' ) Focus & Filtering ^^^^^^^^^^^^^^^^^ By default Altair automatically adjusts the projection so that all the data fits within the width and height of the chart. Multiple approaches can be used to focus on specific regions of your spatial data. Namely: 1. Filter the source data within your GeoDataFrame. 2. Filter the source data using a ``transform_filter``. 3. Specify ``scale`` (zoom level) and ``translate`` (panning) within the ``project`` method. 4. Specify ``fit`` (extent) within the ``project`` & ``clip=True`` in the mark properties. The following examples applies these approaches to focus on continental Africa: 1. Filter the source data within your GeoDataFrame: .. altair-plot:: gdf_sel = gdf_ne.query("CONTINENT == 'Africa'") alt.Chart(gdf_sel).mark_geoshape() 2. Filter the source data using a ``transform_filter``: .. altair-plot:: alt.Chart(gdf_ne).mark_geoshape().transform_filter( alt.datum.CONTINENT == 'Africa' ) 3. Specify ``scale`` (zoom level) and ``translate`` (panning) within the ``project`` method: .. altair-plot:: alt.Chart(gdf_ne).mark_geoshape().project( scale=200, translate=[160, 160] # lon, lat ) 4. Specify ``fit`` (extent) within the ``project`` method & ``clip=True`` in the mark properties: .. altair-plot:: extent_roi = gdf_ne.query("CONTINENT == 'Africa'") xmin, ymin, xmax, ymax = extent_roi.total_bounds # fit object should be a GeoJSON-like Feature or FeatureCollection extent_roi_feature = { "type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[ [xmax, ymax], [xmax, ymin], [xmin, ymin], [xmin, ymax], [xmax, ymax]]]}, "properties": {} } alt.Chart(gdf_ne).mark_geoshape(clip=True).project( fit=extent_roi_feature ) Cartesian coordinates ^^^^^^^^^^^^^^^^^^^^^ The default projection of Altair is ``equalEarth``, which accurately represents the areas of the world's landmasses relative each other. This default assumes that your geometries are in degrees and referenced by longitude and latitude values. Another widely used coordinate system for data visualization is the 2d cartesian coordinate system. This coordinate system does not take into account the curvature of the Earth. In the following example the input geometry is not projected and is instead rendered directly in raw coordinates using the ``identity`` projection type. We have to define the ``reflectY`` as well since Canvas and SVG treats positive ``y`` as pointing down. .. altair-plot:: alt.Chart(gdf_sel).mark_geoshape().project( type='identity', reflectY=True ) Mapping Polygons ^^^^^^^^^^^^^^^^ The following example maps the visual property of the ``NAME`` column using the ``color`` encoding. .. altair-plot:: alt.Chart(gdf_sel).mark_geoshape().encode( color='NAME:N' ) Since each country is represented by a (multi)polygon, we can separate the ``stroke`` and ``fill`` definitions as such: .. altair-plot:: alt.Chart(gdf_sel).mark_geoshape( stroke='white', strokeWidth=1.5 ).encode( fill='NAME:N' ) Mapping Lines ^^^^^^^^^^^^^ By default Altair assumes for ``mark_geoshape`` that the mark's color is used for the fill color instead of the stroke color. This means that if your source data contain (multi)lines, you will have to explicitly define the ``filled`` value as ``False``. Compare: .. altair-plot:: gs_line = gpd.GeoSeries.from_wkt(['LINESTRING (0 0, 1 1, 0 2, 2 2, -1 1, 1 0)']) alt.Chart(gs_line).mark_geoshape().project( type='identity', reflectY=True ) With: .. altair-plot:: gs_line = gpd.GeoSeries.from_wkt(['LINESTRING (0 0, 1 1, 0 2, 2 2, -1 1, 1 0)']) alt.Chart(gs_line).mark_geoshape( filled=False ).project( type='identity', reflectY=True ) Using this approach one can also style Polygons as if they are Linestrings: .. altair-plot:: alt.Chart(gdf_sel).mark_geoshape( filled=False, strokeWidth=1.5 ).encode( stroke='NAME:N' ) Mapping Points ^^^^^^^^^^^^^^ Points can be drawn when they are defined as ``Points`` within a GeoDataFrame using ``mark_geoshape``. We first assign the centroids of Polygons as Point geometry and plot these: .. altair-plot:: # .copy() to prevent changing the original `gdf_sel` variable # derive centroid in a projected CRS (in meters) and visualize in a geographic CRS (in degrees). gdf_centroid = gpd.GeoDataFrame( data=gdf_sel.copy(), geometry=gdf_sel.geometry.to_crs(epsg=3857).centroid.to_crs(epsg=4326) ) alt.Chart(gdf_centroid).mark_geoshape() Caveat: To use the ``size`` encoding for the Points you will need to use the ``mark_circle`` in combination with the ``latitude`` and ``longitude`` encoding channel definitions. .. altair-plot:: gdf_centroid["lon"] = gdf_centroid.geometry.x gdf_centroid["lat"] = gdf_centroid.geometry.y alt.Chart(gdf_centroid).mark_circle().encode( longitude="lon:Q", latitude="lat:Q", size="POP_EST:Q" ) Altair also contains expressions related to geographical features. We can for example define the ``centroids`` using a ``geoCentroid`` expression: .. altair-plot:: from altair.expr import datum, geoCentroid basemap = alt.Chart(gdf_sel).mark_geoshape( fill='lightgray', stroke='white', strokeWidth=0.5 ) bubbles = alt.Chart(gdf_sel).transform_calculate( centroid=geoCentroid(None, datum) ).mark_circle( stroke='black' ).encode( longitude='centroid[0]:Q', latitude='centroid[1]:Q', size="POP_EST:Q" ) (basemap + bubbles).project( type='identity', reflectY=True ) Choropleths ^^^^^^^^^^^ An alternative to showing the population sizes as bubbles, is to create a "Choropleth" map. These are geographical heatmaps where the color or each region are mapped to the values of a column in the dataframe. .. altair-plot:: alt.Chart(gdf_sel).mark_geoshape().encode( color='POP_EST' ) When we create choropleth maps, we need to be careful, because although the color changes according to the value of the column we are interested in, the size is tied to the area of each country and we might miss interesting values in small countries just because we can't easily see them on the map (e.g. if we were to visualize population density). Lookup datasets ^^^^^^^^^^^^^^^ Sometimes your data is separated in two datasets. One ``DataFrame`` with the data and one ``GeoDataFrame`` with the geometries. In this case you can use the ``lookup`` transform to collect related information from the other dataset. You can use the ``lookup`` transform in two directions: 1. Use a ``GeoDataFrame`` with geometries as source and lookup related information in another ``DataFrame``. 2. Use a ``DataFrame`` as source and lookup related geometries in a ``GeoDataFrame``. Depending on your use-case one or the other is more favorable. First we show an example of the first approach. Here we lookup the field ``rate`` from the ``df_us_unemp`` DataFrame, where the ``gdf_us_counties`` GeoDataFrame is used as source: .. altair-plot:: import altair as alt from vega_datasets import data import geopandas as gpd gdf_us_counties = gpd.read_file(data.us_10m.url, driver='TopoJSON', layer='counties') df_us_unemp = data.unemployment() alt.Chart(gdf_us_counties).mark_geoshape().transform_lookup( lookup='id', from_=alt.LookupData(data=df_us_unemp, key='id', fields=['rate']) ).encode( alt.Color('rate:Q') ).project( type='albersUsa' ) Next, we show an example of the second approach. Here we lookup the geometries through the fields ``geometry`` and ``type`` from the ``gdf_us_counties`` GeoDataFrame, where the ``df_us_unemp`` DataFrame is used as source. .. altair-plot:: alt.Chart(df_us_unemp).mark_geoshape().transform_lookup( lookup='id', from_=alt.LookupData(data=gdf_us_counties, key='id', fields=['geometry', 'type']) ).encode( alt.Color('rate:Q') ).project( type='albersUsa' ) Choropleth Classification ^^^^^^^^^^^^^^^^^^^^^^^^^ In addition to displaying a continuous quantitative variable, choropleths can also be used to show discrete levels of a variable. While we should generally be careful to not create artificial groups when discretizing a continuous variable, it can be very useful when we have natural cutoff levels of a variable that we want to showcase clearly. We first define a utility function ``classify()`` that we will use to showcase different approaches to make a choropleth map. We apply it to define a choropleth map of the unemployment statistics of 2018 of US counties using a ``linear`` scale. .. altair-plot:: import altair as alt from vega_datasets import data import geopandas as gpd def classify(type, domain=None, nice=False, title=None): # define data us_counties = alt.topo_feature(data.us_10m.url, "counties") us_unemp = data.unemployment.url # define choropleth scale if "threshold" in type: scale = alt.Scale(type=type, domain=domain, scheme="inferno") else: scale = alt.Scale(type=type, nice=nice, scheme="inferno") # define title if title is None: title = type # define choropleth chart choropleth = ( alt.Chart(us_counties, title=title) .mark_geoshape() .transform_lookup( lookup="id", from_=alt.LookupData(data=us_unemp, key="id", fields=["rate"]) ) .encode( alt.Color( "rate:Q", scale=scale, legend=alt.Legend( direction="horizontal", orient="bottom", format=".1%" ), ) ) .project(type="albersUsa") ) return choropleth classify(type='linear') We visualize the unemployment ``rate`` in percentage of 2018 with a ``linear`` scale range using a ``mark_geoshape()`` to present the spatial patterns on a map. Each value/ county has defined a `unique` color. This gives a bit of insight, but often we like to group the distribution into classes. By grouping values in classes, you can classify the dataset so all values/geometries in each class get assigned the same color. Here we present a number of scale methods how Altair can be used: - ``quantile``, this type will divide your dataset (`domain`) into intervals of similar sizes. Each class contains more or less the same number of values/geometries (`equal counts`). The scale definition will look as follow: .. code:: python alt.Scale(type='quantile') And applied in our utility function: .. altair-plot:: classify(type='quantile', title=['quantile', 'equal counts']) - ``quantize``, this type will divide the extent of your dataset (`range`) in equal intervals. Each class contains different number of values, but the step size is equal (`equal range`). The scale definition will look as follow: .. code:: python alt.Scale(type='quantize') And applied in our utility function: .. altair-plot:: classify(type='quantize', title=['quantize', 'equal range']) The ``quantize`` method can also be used in combination with ``nice``. This will `"nice"` the domain before applying quantization. As such: .. code:: python alt.Scale(type='quantize', nice=True) And applied in our utility function: .. altair-plot:: classify(type='quantize', nice=True, title=['quantize', 'equal range nice']) - ``threshold``, this type will divide your dataset in separate classes by manually specifying the cut values. Each class is separated by defined classes. The scale definition will look as follow: .. code:: python alt.Scale(type='threshold', domain=[0.05, 0.20]) And applied in our utility function: .. altair-plot:: classify(type='threshold', domain=[0.05, 0.20]) The definition above will create 3 classes. One class with values below `0.05`, one class with values from `0.05` to `0.20` and one class with values higher than `0.20`. So which method provides the optimal data classification for choropleth maps? As usual, it depends. There is another popular method that aid in determining class breaks. This method will maximize the similarity of values in a class while maximizing the distance between the classes (`natural breaks`). The method is also known as the Fisher-Jenks algorithm and is similar to k-Means in 1D: - By using the external Python package ``jenskpy`` we can derive these `optimum` breaks as such: .. code:: python >>> from jenkspy import JenksNaturalBreaks >>> jnb = JenksNaturalBreaks(5) >>> jnb.fit(df_us_unemp['rate']) >>> jnb.inner_breaks_ [0.061, 0.088, 0.116, 0.161] And applied in our utility function: .. altair-plot:: classify(type='threshold', domain=[0.061, 0.088, 0.116, 0.161], title=['threshold Jenks','natural breaks']) Caveats: - For the type ``quantize`` and ``quantile`` scales we observe that the default number of classes is 5. You can change the number of classes using a ``SchemeParams()`` object. In the above specification we can change ``scheme='turbo'`` into ``scheme=alt.SchemeParams('turbo', count=2)`` to manually specify usage of 2 classes for the scheme within the scale. - The natural breaks method will determine the optimal class breaks given the required number of classes, but how many classes should you pick? One can investigate usage of goodness of variance fit (GVF), aka Jenks optimization method, to determine this. Repeat a Map ^^^^^^^^^^^^ The :class:`RepeatChart` pattern, accessible via the :meth:`Chart.repeat` method provides a convenient interface for a particular type of horizontal or vertical concatenation of a multi-dimensional dataset. In the following example we have a dataset referenced as ``source`` from which we use three columns defining the ``population``, ``engineers`` and ``hurricanes`` of each US state. The ``states`` is defined by making use of :func:`topo_feature` using ``url`` and ``feature`` as parameters. This is a convenience function for extracting features from a topojson url. These variables we provide as list in the ``.repeat()`` operator, which we refer to within the color encoding as ``alt.repeat('row')`` .. altair-plot:: import altair as alt from vega_datasets import data states = alt.topo_feature(data.us_10m.url, 'states') source = data.population_engineers_hurricanes.url variable_list = ['population', 'engineers', 'hurricanes'] alt.Chart(states).mark_geoshape(tooltip=True).encode( alt.Color(alt.repeat('row'), type='quantitative') ).transform_lookup( lookup='id', from_=alt.LookupData(source, 'id', variable_list) ).project( type='albersUsa' ).repeat( row=variable_list ).resolve_scale( color='independent' ) Facet a Map ^^^^^^^^^^^ The :class:`FacetChart` pattern, accessible via the :meth:`Chart.facet` method provides a convenient interface for a particular type of horizontal or vertical concatenation of a dataset where one field contain multiple ``variables``. Unfortunately, the following open issue https://github.com/altair-viz/altair/issues/2369 will make the following not work for geographic visualization: .. altair-plot:: source = data.population_engineers_hurricanes().melt(id_vars=['state', 'id']) us_states = gpd.read_file(data.us_10m.url, driver='TopoJSON', layer='states') gdf_comb = gpd.GeoDataFrame(source.join(us_states, on='id', rsuffix='_y')) alt.Chart(gdf_comb).mark_geoshape().encode( color=alt.Color('value:Q'), facet=alt.Facet('variable:N').columns(3) ).properties( width=180, height=130 ).resolve_scale('independent') For now, the following workaround can be adopted to facet a map, manually filter the data in pandas, and create a small multiples chart via concatenation. For example: .. altair-plot:: alt.concat( *( alt.Chart(gdf_comb[gdf_comb.variable == var], title=var) .mark_geoshape() .encode( color=alt.Color( "value:Q", legend=alt.Legend(orient="bottom", direction="horizontal") ) ) .project('albersUsa') .properties(width=180, height=130) for var in gdf_comb.variable.unique() ), columns=3 ).resolve_scale(color="independent") Interaction ^^^^^^^^^^^ Often a map does not come alone, but is used in combination with another chart. Here we provide an example of an interactive visualization of a bar chart and a map. The data shows the states of the US in combination with a bar chart showing the 15 most populous states. Using an ``alt.selection_point()`` we define a selection parameter that connects to our left-mouseclick. .. altair-plot:: import altair as alt from vega_datasets import data import geopandas as gpd # load the data us_states = gpd.read_file(data.us_10m.url, driver="TopoJSON", layer="states") us_population = data.population_engineers_hurricanes()[["state", "id", "population"]] # define a pointer selection click_state = alt.selection_point(fields=["state"]) # create a choropleth map using a lookup transform # define a condition on the opacity encoding depending on the selection choropleth = ( alt.Chart(us_states) .mark_geoshape() .transform_lookup( lookup="id", from_=alt.LookupData(us_population, "id", ["population", "state"]) ) .encode( color="population:Q", opacity=alt.condition(click_state, alt.value(1), alt.value(0.2)), tooltip=["state:N", "population:Q"], ) .project(type="albersUsa") ) # create a bar chart with a similar condition on the opacity encoding. bars = ( alt.Chart( us_population.nlargest(15, "population"), title="Top 15 states by population" ) .mark_bar() .encode( x="population", opacity=alt.condition(click_state, alt.value(1), alt.value(0.2)), color="population", y=alt.Y("state").sort("-x"), ) ) (choropleth & bars).add_params(click_state) The interaction is two-directional. If you click (shift-click for multi-selection) on a geometry or bar the selection receive an ``opacity`` of ``1`` and the remaining an ``opacity`` of ``0.2``. Expression ^^^^^^^^^^ Altair expressions can be used within a geographical visualization. The following example visualize earthquakes on the globe using an ``orthographic`` projection. Where we can rotate the earth on a single-axis. (``rotate0``). The utility function :func:`sphere` is adopted to get a disk of the earth as background. The GeoDataFrame with the earthquakes has an ``XYZ``` point geometry, where each coordinate represent ``lon``, ``lat`` and ``depth`` respectively. We use here an elegant way to access the nested point coordinates from the geometry column directly to draw circles. Using this approach we do not need to assign them to three separate columns first. .. altair-plot:: import altair as alt from vega_datasets import data import geopandas as gpd # load data gdf_quakies = gpd.read_file(data.earthquakes.url, driver="GeoJSON") gdf_world = gpd.read_file(data.world_110m.url, driver="TopoJSON") # define parameters range0 = alt.binding_range(min=-180, max=180, step=5, name='rotate longitude ') rotate0 = alt.param(value=120, bind=range0) hover = alt.selection_point(on="mouseover", clear="mouseout") # world disk sphere = alt.Chart(alt.sphere()).mark_geoshape( fill="aliceblue", stroke="black", strokeWidth=1.5 ) # countries as shapes world = alt.Chart(gdf_world).mark_geoshape( fill="mintcream", stroke="black", strokeWidth=0.35 ) # earthquakes as circles with fill for depth and size for magnitude # the hover param is added on the mar_circle only quakes = ( alt.Chart(gdf_quakies) .mark_circle(opacity=0.35, tooltip=True, stroke="black") .transform_calculate( lon="datum.geometry.coordinates[0]", lat="datum.geometry.coordinates[1]", depth="datum.geometry.coordinates[2]", ) .transform_filter( ((rotate0 * -1 - 90 < alt.datum.lon) & (alt.datum.lon < rotate0 * -1 + 90)).expr ) .encode( longitude="lon:Q", latitude="lat:Q", strokeWidth=alt.condition(hover, alt.value(1, empty=False), alt.value(0)), size=alt.Size( "mag:Q", scale=alt.Scale(type="pow", range=[1, 1000], domain=[0, 6], exponent=4), ), fill=alt.Fill( "depth:Q", scale=alt.Scale(scheme="lightorange", domain=[0, 400]) ), ) .add_params(hover, rotate0) ) # define projection and add the rotation param for all layers comb = alt.layer(sphere, world, quakes).project( type="orthographic", rotate=alt.expr(f"[{rotate0.name}, 0, 0]") ) comb The earthquakes are displayed using a ``mark_geoshape`` and filtered once out of sight of the visible part of the world. A hover highlighting is added to get more insight of each earthquake. altair-5.0.1/doc/user_guide/marks/image.rst000066400000000000000000000023431443422213100206200ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-image-marks: Image ~~~~~~ Image marks allow external images, such as icons or photographs, to be included in Altair visualizations. Image files such as PNG or JPG images are loaded from provided URLs. Image Mark Properties ^^^^^^^^^^^^^^^^^^^^^ An ``image`` mark can contain any :ref:`standard mark properties ` and the following special properties: .. altair-object-table:: altair.MarkDef :properties: url aspect align baseline Scatter Plot with Image Marks ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. altair-plot:: import altair as alt import pandas as pd source = pd.DataFrame.from_records( [ { "x": 0.5, "y": 0.5, "img": "https://vega.github.io/vega-datasets/data/ffox.png", }, { "x": 1.5, "y": 1.5, "img": "https://vega.github.io/vega-datasets/data/gimp.png", }, { "x": 2.5, "y": 2.5, "img": "https://vega.github.io/vega-datasets/data/7zip.png", }, ] ) alt.Chart(source).mark_image(width=50, height=50).encode(x="x", y="y", url="img") altair-5.0.1/doc/user_guide/marks/index.rst000066400000000000000000000122161443422213100206450ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-marks: Marks ~~~~~ We saw in :ref:`user-guide-encoding` that the :meth:`~Chart.encode` method is used to map columns to visual attributes of the plot. The ``mark`` property is what specifies how exactly those attributes should be represented on the plot. Altair supports the following primitive mark types: ========================================= ========================================= ================================================================================ Mark Method Description ========================================= ========================================= ================================================================================ :ref:`user-guide-arc-marks` :meth:`~Chart.mark_arc` A pie chart. :ref:`user-guide-area-marks` :meth:`~Chart.mark_area` A filled area plot. :ref:`user-guide-bar-marks` :meth:`~Chart.mark_bar` A bar plot. :ref:`user-guide-circle-marks` :meth:`~Chart.mark_circle` A scatter plot with filled circles. :ref:`user-guide-geoshape-marks` :meth:`~Chart.mark_geoshape` Visualization containing spatial data :ref:`user-guide-image-marks` :meth:`~Chart.mark_image` A scatter plot with image markers. :ref:`user-guide-line-marks` :meth:`~Chart.mark_line` A line plot. :ref:`user-guide-point-marks` :meth:`~Chart.mark_point` A scatter plot with configurable point shapes. :ref:`user-guide-rect-marks` :meth:`~Chart.mark_rect` A filled rectangle, used for heatmaps :ref:`user-guide-rule-marks` :meth:`~Chart.mark_rule` A vertical or horizontal line spanning the axis. :ref:`user-guide-square-marks` :meth:`~Chart.mark_square` A scatter plot with filled squares. :ref:`user-guide-text-marks` :meth:`~Chart.mark_text` A scatter plot with points represented by text. :ref:`user-guide-tick-marks` :meth:`~Chart.mark_tick` A vertical or horizontal tick mark. :ref:`user-guide-trail-marks` :meth:`~Chart.mark_trail` A line with variable widths. ========================================= ========================================= ================================================================================ In addition, Altair provides the following composite marks: ========================================= ============================== ================================ ================================== Mark Name Method Description Example ========================================= ============================== ================================ ================================== :ref:`user-guide-boxplot-marks` :meth:`~Chart.mark_boxplot` A box plot. :ref:`gallery_boxplot` :ref:`user-guide-errorband-marks` :meth:`~Chart.mark_errorband` A continuous band around a line. :ref:`gallery_line_with_ci` :ref:`user-guide-errorbar-marks` :meth:`~Chart.mark_errorbar` An error bar around a point. :ref:`gallery_errorbars_with_ci` ========================================= ============================== ================================ ================================== In Altair, marks can be most conveniently specified by the ``mark_*`` methods of the Chart object (e.g. ``mark_bar``), which take optional keyword arguments to configure the look of the marks. .. _mark-properties: Mark Properties _______________ This section lists standard mark properties for primitive mark types. Additionally, some marks may have special mark properties (listed in their documentation page). General Mark Properties ^^^^^^^^^^^^^^^^^^^^^^^ .. altair-object-table:: altair.MarkDef :properties: aria description style tooltip clip invalid order Position and Offset Properties ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. altair-object-table:: altair.MarkDef :properties: x x2 width height y y2 xOffset x2Offset yOffset y2Offset Color Properties ^^^^^^^^^^^^^^^^ .. altair-object-table:: altair.MarkDef :properties: filled color fill stroke blend opacity fillOpacity strokeOpacity Stroke Style Properties ^^^^^^^^^^^^^^^^^^^^^^^ .. altair-object-table:: altair.MarkDef :properties: strokeCap strokeDash strokeDashOffset strokeJoin strokeMiterLimit strokeWidth Hyperlink Properties ^^^^^^^^^^^^^^^^^^^^ Marks can act as hyperlinks when the ``href`` property or :ref:`channel ` is defined. When the ``href`` property is specified, the ``cursor`` mark property is set to ``"pointer"`` by default to serve as affordance for hyperlinks. .. altair-object-table:: altair.MarkDef :properties: href cursor .. toctree:: :hidden: arc area bar boxplot circle errorband errorbar geoshape image line point rect rule square text tick trailaltair-5.0.1/doc/user_guide/marks/line.rst000066400000000000000000000243361443422213100204730ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-line-marks: Line ~~~~ The ``line`` mark represents the data points stored in a field with a line connecting all of these points. Line marks are commonly used to depict trajectories or change over time. Unlike most other marks that represent one data element per mark, one line mark represents multiple data element as a single line, akin to ``area`` and ``trail``. Note: For line segments that connect (x,y) positions to (x2,y2) positions, please use ``rule`` marks. For continuous lines with varying size, please use ``trail`` marks. Line Mark Properties -------------------- .. altair-plot:: :hide-code: :div_class: properties-example import altair as alt import pandas as pd interpolate_select = alt.binding_select( options=[ "basis", "cardinal", "catmull-rom", "linear", "monotone", "natural", "step", "step-after", "step-before", ], name="interpolate", ) interpolate_var = alt.param(bind=interpolate_select, value="linear") tension_slider = alt.binding_range(min=0, max=1, step=0.05, name="tension") tension_var = alt.param(bind=tension_slider, value=0) strokeWidth_slider = alt.binding_range(min=0, max=10, step=0.5, name="strokeWidth") strokeWidth_var = alt.param(bind=strokeWidth_slider, value=2) strokeCap_select = alt.binding_select( options=["butt", "round", "square"], name="strokeCap", ) strokeCap_var = alt.param(bind=strokeCap_select, value="butt") strokeDash_select = alt.binding_select( options=[[1, 0], [8, 8], [8, 4], [4, 4], [4, 2], [2, 1], [1, 1]], name="strokeDash", ) strokeDash_var = alt.param(bind=strokeDash_select, value=[1, 0]) source = pd.DataFrame({"u": [1, 2, 3, 4, 5, 6], "v": [28, 55, 42, 34, 36, 38]}) alt.Chart(source).mark_line( interpolate=interpolate_var, tension=tension_var, strokeWidth=strokeWidth_var, strokeCap=strokeCap_var, strokeDash=strokeDash_var, ).encode(x="u", y="v").add_params( interpolate_var, tension_var, strokeWidth_var, strokeCap_var, strokeDash_var ) A ``line`` mark definition can contain any :ref:`standard mark properties ` and the following line interpolation and point overlay properties: .. altair-object-table:: altair.MarkDef :properties: orient interpolate tension point Examples -------- Line Chart ^^^^^^^^^^ Using line with one temporal or ordinal field (typically on ``x``) and another quantitative field (typically on ``y``) produces a simple line chart with a single line. .. altair-plot:: import altair as alt from altair import datum from vega_datasets import data source = data.stocks() alt.Chart(source).mark_line().encode( x="date", y="price", ).transform_filter(datum.symbol == "GOOG") We can add create multiple lines by grouping along different attributes, such as ``color`` or ``detail``. Multi-series Colored Line Chart ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Adding a field to a mark property channel such as ``color`` groups data points into different series, producing a multi-series colored line chart. .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_line().encode( x="date", y="price", color="symbol", ) We can further apply selection to highlight a certain line on hover. .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() highlight = alt.selection_point( on="mouseover", fields=["symbol"], nearest=True ) base = alt.Chart(source).encode( x="date:T", y="price:Q", color="symbol:N" ) points = base.mark_circle().encode( opacity=alt.value(0) ).add_params( highlight ).properties( width=600 ) lines = base.mark_line().encode( size=alt.condition(~highlight, alt.value(1), alt.value(3)) ) points + lines Multi-series Line Chart with Varying Dashes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Adding a field to ``strokeDash`` also produces a multi-series line chart. .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_line().encode( x="date", y="price", strokeDash="symbol", ) We can also use line grouping to create a line chart that has multiple parts with varying styles. .. altair-plot:: import altair as alt import pandas as pd source = pd.DataFrame({ "a": ["A", "B", "D", "E", "E", "G", "H"], "b": [28, 55, 91, 81, 81, 19, 87], "predicted": [False, False, False, False, True, True, True] }) alt.Chart(source).mark_line().encode( x="a:O", y="b:Q", strokeDash="predicted:N" ) Multi-series Line Chart with the Detail Channel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To group lines by a field without mapping the field to any visual properties, we can map the field to the ``detail`` channel to create a multi-series line chart with the same color. .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_line().encode( x="date", y="price", detail="symbol", ) The same method can be used to group lines for a ranged dot plot. .. altair-plot:: import altair as alt from vega_datasets import data source = data.countries() base = alt.Chart(source).encode( alt.X("life_expect:Q") .scale(zero=False) .title("Life Expectancy (years)"), alt.Y("country:N") .axis(offset=5, ticks=False, minExtent=70, domain=False) .title("Country") ).transform_filter( alt.FieldOneOfPredicate(field="country", oneOf=["China", "India", "United States", "Indonesia", "Brazil"]) ) line = base.mark_line().encode( detail="country", color=alt.value("#db646f") ).transform_filter( alt.FieldOneOfPredicate(field="year", oneOf=[1995, 2000]) ) point = base.mark_point(filled=True).encode( alt.Color("year").scale(range=["#e6959c", "#911a24"], domain=[1995, 2000]), size=alt.value(100), opacity=alt.value(1), ) line + point Line Chart with Point Markers ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ By setting the ``point`` property of the mark definition to ``True`` or an object defining a property of the overlaying point marks, we can overlay point markers on top of a line. .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_line(point=True).encode( x="year(date)", y="mean(price):Q", color="symbol:N" ) This is equivalent to adding another layer of filled point marks. Note that the overlay point marks have ``opacity`` = 1 by default (instead of semi-transparent like normal point marks). Here we create stroked points by setting ``filled`` to ``False`` and ``fill`` to ``"white"``. .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_line( point=alt.OverlayMarkDef(filled=False, fill="white") ).encode( x="year(date)", y="mean(price):Q", color="symbol:N" ) Connected Scatter Plot (Line Chart with Custom Path) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The line’s path (order of points in the line) is determined by data values on the temporal/ordinal field by default. However, a field can be mapped to the ``order`` channel for determining a custom path. For example, to show a pattern of data change over time between gasoline price and average miles driven per capita we use ``order`` channel to sort the points in the line by time field (year). In this example, we also use the ``point`` property to overlay point marks over the line marks to highlight each data point. .. altair-plot:: import altair as alt from vega_datasets import data source = data.driving() alt.Chart(source).mark_line(point=True).encode( alt.X("miles").scale(zero=False), alt.Y("gas").scale(zero=False), order="year", ) Line interpolation ^^^^^^^^^^^^^^^^^^ The ``interpolate`` property of a mark definition can be used to change line interpolation method. For example, we can set ``interpolate`` to ``"monotone"``. .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_line(interpolate="monotone").encode( x="date", y="price", ).transform_filter( alt.datum.symbol == "GOOG" ) We can also set ``interpolate`` to ``"step-after"`` to create a step-chart. .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_line(interpolate="step-after").encode( x="date", y="price" ).transform_filter( alt.datum.symbol == "GOOG" ) Geo Line ^^^^^^^^ By mapping geographic coordinate data to ``longitude`` and ``latitude`` channels of a corresponding projection, we can draw lines through geographic points. .. altair-plot:: import altair as alt from vega_datasets import data import pandas as pd airports = data.airports.url flights_airport = data.flights_airport.url states = alt.topo_feature(data.us_10m.url, feature="states") lookup_data = alt.LookupData( airports, key="iata", fields=["state", "latitude", "longitude"] ) source = pd.DataFrame({ "airport": ["SEA", "SFO", "LAX", "LAS", "DFW", "DEN", "ORD", "JFK"], "order": [1, 2, 3, 4, 5, 6, 7, 8], }) background = alt.Chart(states).mark_geoshape( fill="lightgray", stroke="white" ).properties( width=750, height=500, ).project("albersUsa") line = alt.Chart(source).mark_line().encode( latitude="latitude:Q", longitude="longitude:Q", order="order" ).transform_lookup( lookup="airport", from_=lookup_data ) background + line altair-5.0.1/doc/user_guide/marks/point.rst000066400000000000000000000135011443422213100206650ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-point-marks: Point ~~~~~ ``point`` mark represents each data point with a symbol. Point marks are commonly used in visualizations like scatter plots. Point Mark Properties --------------------- .. altair-plot:: :hide-code: :div_class: properties-example import altair as alt from vega_datasets import data source = data.cars() shape_select = alt.binding_select( options=[ "arrow", "circle", "square", "cross", "diamond", "triangle", "triangle-up", "triangle-down", "triangle-right", "triangle-left", "wedge", "stroke", "M-1,-1H1V1H-1Z", "M0,.5L.6,.8L.5,.1L1,-.3L.3,-.4L0,-1L-.3,-.4L-1,-.3L-.5,.1L-.6,.8L0,.5Z", ], name="shape", ) shape_var = alt.param(bind=shape_select, value="circle") angle_slider = alt.binding_range(min=-360, max=360, step=1, name="angle") angle_var = alt.param(bind=angle_slider, value=0) size_slider = alt.binding_range(min=0, max=500, step=10, name="size") size_var = alt.param(bind=size_slider, value=50) strokeWidth_slider = alt.binding_range(min=0, max=10, step=0.5, name="strokeWidth") strokeWidth_var = alt.param(bind=strokeWidth_slider, value=2) alt.Chart(source).mark_point( shape=shape_var, angle=angle_var, size=size_var, strokeWidth=strokeWidth_var, ).encode(x="Horsepower:Q", y="Miles_per_Gallon:Q").add_params( shape_var, angle_var, size_var, strokeWidth_var ) A ``point`` mark definition can contain any :ref:`standard mark properties ` and the following special properties: .. altair-object-table:: altair.MarkDef :properties: shape size Examples -------- Dot Plot ^^^^^^^^ Mapping a field to either only ``x`` or only ``y`` of point marks creates a dot plot. .. altair-plot:: import altair as alt from vega_datasets import data source = data.movies() alt.Chart(source).mark_point().encode( x="IMDB_Rating:Q" ) Scatter Plot ^^^^^^^^^^^^ Mapping fields to both the ``x`` and ``y`` channels creates a scatter plot. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_point().encode( x="Horsepower:Q", y="Miles_per_Gallon:Q", ) By default, ``point`` marks only have borders and are transparent inside. You can create a filled point by setting ``filled`` to ``True``. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_point(filled=True).encode( x="Horsepower:Q", y="Miles_per_Gallon:Q", ) Bubble Plot ^^^^^^^^^^^ By mapping a third field to the ``size`` channel in the scatter plot, we can create a bubble plot instead. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_point().encode( x="Horsepower:Q", y="Miles_per_Gallon:Q", size="Acceleration:Q", ) Scatter Plot with Color and/or Shape ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Fields can also be encoded in the scatter plot using the ``color`` or ``shape`` channels. For example, this specification encodes the field ``Origin`` with both ``color`` and ``shape``. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_point().encode( alt.X("Miles_per_Gallon:Q").scale(zero=False), alt.Y("Horsepower:Q").scale(zero=False), color="Origin:N", shape="Origin:N", ) Dot Plot with Jittering ^^^^^^^^^^^^^^^^^^^^^^^ To jitter points on a discrete scale, you can add a random offset: .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_point().encode( x="Horsepower:Q", y="Cylinders:O", yOffset="random:Q", ).transform_calculate( random="random()" ).properties( height=alt.Step(50) ) Wind Vector Example ^^^^^^^^^^^^^^^^^^^ We can also use point mark with ``wedge`` as ``shape`` and ``angle`` encoding to create a wind vector map. Other shape options are: ``"circle"``, ``"square"``, ``"cross"``, ``"diamond"``, ``"triangle-up"``, ``"triangle-down"``, ``"triangle-right"``, ``"triangle-left"``, ``"stroke"``, ``"arrow"``, and ``"triangle"``. .. altair-plot:: import altair as alt from vega_datasets import data source = data.windvectors() alt.Chart(source).mark_point(shape="wedge", filled=True).encode( latitude="latitude", longitude="longitude", color=alt.Color("dir").scale(domain=[0, 360], scheme="rainbow").legend(None), angle=alt.Angle("dir").scale(domain=[0, 360], range=[180, 540]), size=alt.Size("speed").scale(rangeMax=500), ).project("equalEarth") Geo Point ^^^^^^^^^ By mapping geographic coordinate data to ``longitude`` and ``latitude`` channels of a corresponding projection, we can visualize geographic points. The example below shows major airports in the US. .. altair-plot:: import altair as alt from vega_datasets import data airports = data.airports() states = alt.topo_feature(data.us_10m.url, feature="states") # US states background background = alt.Chart(states).mark_geoshape( fill="lightgray", stroke="white" ).properties( width=500, height=300, ).project("albersUsa") # airport positions on background points = alt.Chart(airports).mark_circle( size=10, color="steelblue", ).encode( longitude="longitude:Q", latitude="latitude:Q", tooltip=["name", "city", "state"], ) background + points altair-5.0.1/doc/user_guide/marks/rect.rst000066400000000000000000000051361443422213100204760ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-rect-marks: Rect ~~~~ The ``rect`` mark represents an arbitrary rectangle. Rect Mark Properties -------------------- .. altair-plot:: :hide-code: :div_class: properties-example import altair as alt x_slider = alt.binding_range(min=1, max=100, step=1, name="x") x_var = alt.param(bind=x_slider, value=25) x2_slider = alt.binding_range(min=1, max=100, step=1, name="x2") x2_var = alt.param(bind=x2_slider, value=75) y_slider = alt.binding_range(min=1, max=100, step=1, name="y") y_var = alt.param(bind=y_slider, value=25) y2_slider = alt.binding_range(min=1, max=100, step=1, name="y2") y2_var = alt.param(bind=y2_slider, value=75) cornerRadius_slider = alt.binding_range(min=0, max=50, step=1) cornerRadius_var = alt.param(bind=cornerRadius_slider, value=0, name="cornerRadius") alt.Chart().mark_rect(cornerRadius=cornerRadius_var, color="orange").encode( x=alt.XDatum(x_var, type="quantitative", scale=alt.Scale(domain=[0, 100])), x2=alt.X2Datum(x2_var), y=alt.XDatum(y_var, type="quantitative", scale=alt.Scale(domain=[0, 100])), y2=alt.X2Datum(y2_var), ).add_params(x_var, x2_var, y_var, y2_var, cornerRadius_var) A ``rect`` mark definition can contain any :ref:`standard mark properties ` and the following special properties: .. altair-object-table:: altair.MarkDef :properties: width height align baseline cornerRadius Examples -------- Heatmap ^^^^^^^ Using the ``rect`` marks with discrete fields on ``x`` and ``y`` channels creates a heatmap. .. altair-plot:: import altair as alt from vega_datasets import data source = data.seattle_weather() alt.Chart(source).mark_rect().encode( alt.X("date(date):O").axis(labelAngle=0, format="%e").title("Day"), alt.Y("month(date):O").title("Month"), alt.Color("max(temp_max):Q").title("Max Temp"), ) Ranged Rectangles ^^^^^^^^^^^^^^^^^ Specifying both ``x`` and ``x2`` and/or ``y`` and ``y2`` creates a rectangle that spans over certain x and/or y values. For example, we can use ``rect`` to create an annotation ``layer`` that provides a shading between global ``min`` and ``max`` values. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() point = alt.Chart(source).mark_point().encode( x="Horsepower:Q", y="Miles_per_Gallon:Q", ) rect = alt.Chart(source).mark_rect().encode( y="max(Miles_per_Gallon)", y2="min(Miles_per_Gallon)", opacity=alt.value(0.2), ) point + rect altair-5.0.1/doc/user_guide/marks/rule.rst000066400000000000000000000103411443422213100205020ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-rule-marks: Rule ~~~~ The ``rule`` mark represents each data point as a line segment. It can be used in two ways. First, as a line segment that spans the complete width or height of a view. Second, a rule can be used to draw a line segment between two positions. Rule Mark Properties -------------------- .. altair-plot:: :hide-code: :div_class: properties-example import altair as alt import pandas as pd x_slider = alt.binding_range(min=1, max=100, step=1) x_var = alt.param(bind=x_slider, value=35, name="x") x2_slider = alt.binding_range(min=1, max=100, step=1) x2_var = alt.param(bind=x2_slider, value=75, name="x2") y_slider = alt.binding_range(min=1, max=100, step=1) y_var = alt.param(bind=y_slider, value=25, name="y") y2_slider = alt.binding_range(min=1, max=100, step=1) y2_var = alt.param(bind=y2_slider, value=75, name="y2") strokeWidth_slider = alt.binding_range(min=0, max=10, step=0.5) strokeWidth_var = alt.param(bind=strokeWidth_slider, value=2, name="strokeWidth") strokeCap_select = alt.binding_select(options=["butt", "round", "square"]) strokeCap_var = alt.param(bind=strokeCap_select, value="butt", name="strokeCap") strokeDash_select = alt.binding_select( options=[[1, 0], [8, 8], [8, 4], [4, 4], [4, 2], [2, 1], [1, 1]] ) strokeDash_var = alt.param(bind=strokeDash_select, value=[1, 0], name="strokeDash") alt.Chart().mark_rule( color="orange", strokeWidth=strokeWidth_var, strokeCap=strokeCap_var, strokeDash=strokeDash_var, ).encode( x=alt.datum(x_var, type="quantitative", scale=alt.Scale(domain=[0, 100])), y=alt.datum(y_var, type="quantitative", scale=alt.Scale(domain=[0, 100])), x2=alt.datum(x2_var), y2=alt.datum(y2_var), ).add_params( x_var, x2_var, y_var, y2_var, strokeWidth_var, strokeCap_var, strokeDash_var, ) A ``rule`` mark definition can contain any :ref:`standard mark properties `. Examples -------- Width/Height-Spanning Rules ^^^^^^^^^^^^^^^^^^^^^^^^^^^ If the ``rule`` mark only has ``y`` encoding, the output view produces horizontal rules that spans the complete width. Similarly, if the ``rule`` mark only has ``x`` encoding, the output view produces vertical rules that spans the height. We can use rules to show the average price of different stocks akin to ``tick`` marks. .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_rule().encode( y="mean(price):Q", size=alt.value(2), color="symbol:N" ) The fact that rule marks span the width or the height of a single view make them useful as an annotation layer. For example, we can use rules to show average values of different stocks alongside the price curve. .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() base = alt.Chart(source).properties(width=550) line = base.mark_line().encode( x="date", y="price", color="symbol" ) rule = base.mark_rule().encode( y="average(price)", color="symbol", size=alt.value(2) ) line + rule We can also use a rule mark to show global mean value over a histogram. .. altair-plot:: import altair as alt from vega_datasets import data source = data.movies.url base = alt.Chart(source) bar = base.mark_bar().encode( x=alt.X("IMDB_Rating:Q").bin().axis(None), y="count()" ) rule = base.mark_rule(color="red").encode( x="mean(IMDB_Rating):Q", size=alt.value(5), ) bar + rule Ranged Rules ^^^^^^^^^^^^ To control the spans of horizontal/vertical rules, ``x`` and ``x2``/ ``y`` and ``y2`` channels can be specified. For example, we can use ``y`` and ``y2`` show the ``"min"`` and ``"max"`` values of horsepowers for cars from different locations. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_rule().encode( x="Origin", y="min(Horsepower)", y2="max(Horsepower)", ) altair-5.0.1/doc/user_guide/marks/square.rst000066400000000000000000000013051443422213100210330ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-square-marks: Square ~~~~~~ ``square`` mark is similar to ``point`` mark, except that (1) the ``shape`` value is always set to ``square`` (2) they are filled by default. Square Mark Properties ---------------------- A ``square`` mark definition can contain any :ref:`standard mark properties ` and the following special properties: .. altair-object-table:: altair.MarkDef :properties: size Scatter Plot with Square ------------------------ .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_square().encode( x="Horsepower:Q", y="Miles_per_Gallon:Q", ) altair-5.0.1/doc/user_guide/marks/text.rst000066400000000000000000000141171443422213100205240ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-text-marks: Text ~~~~~~ ``text`` mark represents each data point with a text instead of a point. Text Mark Properties -------------------- .. altair-plot:: :hide-code: :div_class: properties-example import altair as alt import pandas as pd angle_slider = alt.binding_range(min=-180, max=180, step=1) angle_var = alt.param(bind=angle_slider, value=0, name="angle") dx_slider = alt.binding_range(min=-20, max=20, step=1) dx_var = alt.param(bind=dx_slider, value=5, name="dx") dy_slider = alt.binding_range(min=-20, max=20, step=1) dy_var = alt.param(bind=dy_slider, value=0, name="dy") xOffset_slider = alt.binding_range(min=-20, max=20, step=1) xOffset_var = alt.param(bind=xOffset_slider, value=0, name="xOffset") yOffset_slider = alt.binding_range(min=-20, max=20, step=1) yOffset_var = alt.param(bind=yOffset_slider, value=0, name="yOffset") fontSize_slider = alt.binding_range(min=1, max=36, step=1) fontSize_var = alt.param(bind=fontSize_slider, value=14, name="fontSize") limit_slider = alt.binding_range(min=0, max=150, step=1) limit_var = alt.param(bind=limit_slider, value=0, name="limit") align_select = alt.binding_select(options=["left", "center", "right"]) align_var = alt.param(bind=align_select, value="left", name="align") baseline_select = alt.binding_select(options=["alphabetic", "top", "middle", "bottom"]) baseline_var = alt.param(bind=baseline_select, value="midle", name="baseline") font_select = alt.binding_select(options=["sans-serif", "serif", "monospace"]) font_var = alt.param(bind=font_select, value="sans-serif", name="font") fontWeight_select = alt.binding_select(options=["normal", "bold"]) fontWeight_var = alt.param(bind=fontWeight_select, value="normal", name="fontWeight") fontStyle_select = alt.binding_select(options=["normal", "italic"]) fontStyle_var = alt.param(bind=fontStyle_select, value="normal", name="fontStyle") source = pd.DataFrame( { "a": [30, 25, 70], "b": [28, 65, 43], "label": ["Andy", "Brian", "Charlie"], } ) base = alt.Chart(source).encode( x=alt.X("a:Q").axis(labelAngle=0).scale(domain=[0, 100]), y=alt.Y("b:Q").scale(domain=[0, 100]), ) pts = base.mark_point() text = base.mark_text( dx=dx_var, dy=dy_var, xOffset=xOffset_var, yOffset=yOffset_var, angle=angle_var, align=align_var, baseline=baseline_var, font=font_var, fontSize=fontSize_var, fontStyle=fontStyle_var, fontWeight=fontWeight_var, limit=limit_var, ).encode(text="label:N") (pts + text).add_params( dx_var, dy_var, xOffset_var, yOffset_var, angle_var, align_var, baseline_var, font_var, fontSize_var, fontStyle_var, fontWeight_var, limit_var, ) A ``text`` mark definition can contain any :ref:`standard mark properties ` and the following special properties: .. altair-object-table:: altair.MarkDef :properties: angle align baseline dir dx dy ellipsis font fontSize fontStyle fontWeight limit lineHeight radius text theta Examples -------- Text Table Heatmap ^^^^^^^^^^^^^^^^^^ .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() base = alt.Chart(source).transform_aggregate( num_cars="count()", groupby=["Origin", "Cylinders"], ).encode( alt.X("Cylinders:O").scale(paddingInner=0), alt.Y("Origin:O").scale(paddingInner=0), ) heatmap = base.mark_rect().encode( alt.Color("num_cars:Q") .scale(scheme="viridis") .legend(direction="horizontal") ) text = base.mark_text(baseline="middle").encode( text="num_cars:Q", color=alt.condition( alt.datum.num_cars > 100, alt.value("black"), alt.value("white"), ), ) heatmap + text Labels ^^^^^^ You can also use ``text`` marks as labels for other marks and set offset (``dx`` or ``dy``), ``align``, and ``baseline`` properties of the mark definition. .. altair-plot:: import altair as alt import pandas as pd source = pd.DataFrame({ "a": ["A", "B", "C"], "b": [28, 55, 43] }) bar = alt.Chart(source).mark_bar().encode( y="a:N", x=alt.X("b:Q").scale(domain=[0, 60]) ) text = bar.mark_text( align="left", baseline="middle", dx=3 ).encode(text="b") bar + text Scatter Plot with Text ^^^^^^^^^^^^^^^^^^^^^^ Mapping a field to ``text`` channel of text mark sets the mark’s text value. For example, we can make a colored scatter plot with text marks showing the initial character of its origin, instead of ``point`` marks. .. altair-plot:: import altair as alt from vega_datasets import data from altair import datum source = data.cars() alt.Chart(source).mark_text().encode( x="Horsepower:Q", y="Miles_per_Gallon:Q", color="Origin:N", text="Origin[0]:N", ) Geo Text ^^^^^^^^ By mapping geographic coordinate data to ``longitude`` and ``latitude`` channels of a corresponding projection, we can show text at accurate locations. The example below shows the name of every US state capital at the location of the capital. .. altair-plot:: import altair as alt from vega_datasets import data states = alt.topo_feature(data.us_10m.url, feature="states") source = data.us_state_capitals() background = alt.Chart(states).mark_geoshape( fill="lightgray", stroke="white", ).properties( width=750, height=500, ).project("albersUsa") line = alt.Chart(source).mark_text(dy=-10).encode( latitude="lat:Q", longitude="lon:Q", text="city:N" ) point = alt.Chart(source).mark_circle().encode( latitude="lat:Q", longitude="lon:Q", color=alt.value("orange"), ) background + line + point altair-5.0.1/doc/user_guide/marks/tick.rst000066400000000000000000000026611443422213100204730ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-tick-marks: Tick ~~~~ The ``tick`` mark represents each data point as a short line. This is a useful mark for displaying the distribution of values in a field. Tick Mark Properties -------------------- A ``tick`` mark definition can contain any :ref:`standard mark properties ` and the following special properties: .. altair-object-table:: altair.MarkDef :properties: cornerRadius orient Examples -------- Dot Plot ^^^^^^^^ The following dot plot uses tick marks to show the distribution of precipitation in Seattle. .. altair-plot:: import altair as alt from vega_datasets import data source = data.seattle_weather() alt.Chart(source).mark_tick().encode( x="precipitation:Q" ) Strip Plot ^^^^^^^^^^ By adding a ``y`` field, a strip plot can be created that shows the distribution of horsepower by number of cylinders. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_tick().encode( x="Horsepower:Q", y="Cylinders:O", ) Customizing Tick’s Size and Thickness ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. altair-plot:: import altair as alt from vega_datasets import data source = data.seattle_weather() alt.Chart(source).mark_tick().encode( x="precipitation:Q" ).configure_tick( thickness=2, bandSize=10, ) altair-5.0.1/doc/user_guide/marks/trail.rst000066400000000000000000000042061443422213100206510ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-trail-marks: Trail ~~~~~ The ``trail`` mark represents the data points stored in a field with a line connecting all of these points. Trail is similar to the ``line`` mark but a trail can have variable widths determined by backing data. Unlike lines, trails do not support different interpolation methods and use ``fill`` (not ``stroke``) for their color. Trail marks are useful if you want to draw lines with changing size to reflect the underlying data. Trail Mark Properties --------------------- A ``trail`` mark definition can contain any :ref:`standard mark properties ` and the following special properties: .. altair-object-table:: altair.MarkDef :properties: orient Examples -------- Line Chart with Varying Size ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_trail().encode( x="date", y="price", color="symbol", size="price", ) Comet Chart Showing Changes Between Two States ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. altair-plot:: import altair as alt from vega_datasets import data alt.Chart(data.barley.url).transform_pivot( "year", value="yield", groupby=["variety", "site"] ).transform_fold( ["1931", "1932"], as_=["year", "yield"] ).transform_calculate( calculate="datum['1932'] - datum['1931']", as_="delta" ).mark_trail().encode( alt.X("year:O").title(None), alt.Y("variety:N").title("Variety"), alt.Size("yield:Q") .scale(range=[0, 12]) .legend(values=[20, 60]) .title("Barley Yield (bushels/acre)"), alt.Color("delta:Q") .scale(domainMid=0) .title("Yield Delta (%)"), alt.Tooltip(["year:O", "yield:Q"]), alt.Column("site:N").title("Site"), ).configure_legend( orient='bottom', direction='horizontal' ).configure_view( stroke=None ).properties( title="Barley Yield comparison between 1932 and 1931" ) altair-5.0.1/doc/user_guide/saving_charts.rst000066400000000000000000000144741443422213100212640ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-saving: Saving Altair Charts -------------------- Altair chart objects have a :meth:`Chart.save` method which allows charts to be saved in a variety of formats. .. saving-json: JSON format ~~~~~~~~~~~ The fundamental chart representation output by Altair is a JSON string format; one of the core methods provided by Altair is :meth:`Chart.to_json`, which returns a JSON string that represents the chart content. Additionally, you can save a chart to a JSON file using :meth:`Chart.save`, by passing a filename with a ``.json`` extension. For example, here we save a simple scatter-plot to JSON: .. code-block:: python import altair as alt from vega_datasets import data chart = alt.Chart(data.cars.url).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N' ) chart.save('chart.json') The contents of the resulting file will look something like this: .. code-block:: json { "$schema": "https://vega.github.io/schema/vega-lite/v5.json", "config": { "view": { "continuousHeight": 300, "continuousWidth": 300 } }, "data": { "url": "https://vega.github.io/vega-datasets/data/cars.json" }, "encoding": { "color": { "field": "Origin", "type": "nominal" }, "x": { "field": "Horsepower", "type": "quantitative" }, "y": { "field": "Miles_per_Gallon", "type": "quantitative" } }, "mark": {"type": "point"} } This JSON can then be inserted into any web page using the vegaEmbed_ library. .. saving-html: HTML format ~~~~~~~~~~~ If you wish for Altair to take care of the HTML embedding for you, you can save a chart directly to an HTML file using .. code-block:: python chart.save('chart.html') This will create a simple HTML template page that loads Vega, Vega-Lite, and vegaEmbed, such that when opened in a browser the chart will be rendered. For example, saving the above scatter-plot to HTML creates a file with the following contents, which can be opened and rendered in any modern javascript-enabled web browser: .. code-block:: HTML
You can view the result here: `chart.html `_. By default, ``canvas`` is used for rendering the visualization in vegaEmbed. To change to ``svg`` rendering, use the ``embed_options`` as such: .. code-block:: python chart.save('chart.html', embed_options={'renderer':'svg'}) .. note:: This is not the same as ``alt.renderers.enable('svg')``, what renders the chart as a static ``svg`` image within a Jupyter notebook. .. _saving-png: PNG, SVG, and PDF format ~~~~~~~~~~~~~~~~~~~~~~~~ To save an Altair chart object as a PNG, SVG, or PDF image, you can use .. code-block:: python chart.save('chart.png') chart.save('chart.svg') chart.save('chart.pdf') Saving these images requires an additional extension to run the javascript code necessary to interpret the Vega-Lite specification and output it in the form of an image. There are two packages that can be used to enable image export: vl-convert_ or altair_saver_. vl-convert ^^^^^^^^^^ The vl-convert_ package can be installed with:: conda install -c conda-forge vl-convert-python or:: pip install vl-convert-python .. note:: Conda packages are not yet available for the Apple Silicon architecture. See `conda-forge/vl-convert-python-feedstock#9 `_. Unlike altair_saver_, vl-convert_ does not require any external dependencies. However, it only supports saving charts to PNG and SVG formats. To save directly to PDF, altair_saver_ is still required. See the vl-convert documentation for information on other `limitations `_. altair_saver ^^^^^^^^^^^^ .. note:: altair_saver does not yet support Altair 5. The altair_saver_ package can be installed with:: conda install -c conda-forge altair_saver or:: pip install altair_saver See the altair_saver_ documentation for information about additional installation requirements. Engine Argument ^^^^^^^^^^^^^^^ If both vl-convert and altair_saver are installed, vl-convert will take precedence. The engine argument to :meth:`Chart.save` can be used to override this default behavior. For example, to use altair_saver for PNG export when vl-convert is also installed you can use:: chart.save('chart.png', engine="altair_saver") Figure Size/Resolution ^^^^^^^^^^^^^^^^^^^^^^ When using ``chart.save()`` above, the resolution of the resulting PNG is controlled by the resolution of your screen. The easiest way to produce a higher-resolution PNG image is to scale the image to make it larger, and thus to contain more pixels at a given resolution. This can be done with the ``scale_factor`` argument, which defaults to 1.0:: chart.save('chart.png', scale_factor=2.0) .. _vl-convert: https://github.com/vega/vl-convert .. _altair_saver: http://github.com/altair-viz/altair_saver/ .. _vegaEmbed: https://github.com/vega/vega-embed altair-5.0.1/doc/user_guide/scale_resolve.rst000066400000000000000000000060441443422213100212510ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-resolve: Scale and Guide Resolution -------------------------- When creating compound charts (see :ref:`user-guide-compound`), altair defaults to using shared chart scales and guides (e.g. axes, legends, etc.). This default can be adjusted using the :meth:`Chart.resolve_scale`, :meth:`Chart.resolve_axis`, and :meth:`Chart.resolve_legend` functions. For example, suppose you would like to concatenate two charts with separate color scales; the default behavior is for the color scale to be created for a union of the two color encoding domains: .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() base = alt.Chart(source).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q' ).properties( width=200, height=200 ) alt.concat( base.encode(color='Origin:N'), base.encode(color='Cylinders:O') ) This default can be changed by setting the scale resolution for the color to ``"independent"`` (rather than the default, ``"shared"``): .. altair-plot:: alt.concat( base.encode(color='Origin:N'), base.encode(color='Cylinders:O') ).resolve_scale( color='independent' ) Dual Y Axis ~~~~~~~~~~~ A common technique for combining chart containing different measures is using a dual y axis. There are two strategies to achieve this result using altair. The first is to manually specify the mark color and associated axis title color of each layer. .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars() base = alt.Chart(source).encode(x='year(Year):T') line_A = base.mark_line(color='#5276A7').encode( alt.Y('average(Horsepower):Q').axis(titleColor='#5276A7') ) line_B = base.mark_line(color='#F18727').encode( alt.Y('average(Miles_per_Gallon):Q').axis(titleColor='#F18727') ) alt.layer(line_A, line_B).resolve_scale(y='independent') In this case the axis colors act as a pseudo-legend. Alternatively if you want a legend the :ref:`user-guide-filter-transform` and :ref:`user-guide-fold-transform` must be applied. Legends are only created in Vega-Lite to represent an encoding. .. altair-plot:: base = alt.Chart(source).mark_line().transform_fold( ['Horsepower', 'Miles_per_Gallon'], as_=['Measure', 'Value'] ).encode( alt.Color('Measure:N'), alt.X('year(Year):T') ) line_A = base.transform_filter( alt.datum.Measure == 'Horsepower' ).encode( alt.Y('average(Value):Q').title('Horsepower') ) line_B = base.transform_filter( alt.datum.Measure == 'Miles_per_Gallon' ).encode( alt.Y('average(Value):Q').title('Miles_per_Gallon') ) alt.layer(line_A, line_B).resolve_scale(y='independent') Note that dual axis charts might be misleading about relationships in your data. For further reading on the topic see `The case against dual axis charts `__ by Lisa Charlotte Rost. altair-5.0.1/doc/user_guide/times_and_dates.rst000066400000000000000000000202251443422213100215430ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-time: Times and Dates =============== Working with dates, times, and timezones is often one of the more challenging aspects of data analysis. In Altair, the difficulties are compounded by the fact that users are writing Python code, which outputs JSON-serialized timestamps, which are interpreted by Javascript, and then rendered by your browser. At each of these steps, there are things that can go wrong, but Altair and Vega-Lite do their best to ensure that dates are interpreted and visualized in a consistent way. Altair and Pandas Datetimes --------------------------- Altair is designed to work best with `Pandas timeseries`_. A standard timezone-agnostic date/time column in a Pandas dataframe will be both interpreted and displayed as local user time. For example, here is a dataset containing hourly temperatures measured in Seattle: .. altair-plot:: :output: repr import altair as alt from vega_datasets import data temps = data.seattle_temps() temps.head() We can see from the ``dtypes`` attribute that the times are encoded as a standard 64-bit datetime, without any specified timezone: .. altair-plot:: :output: repr temps.dtypes We can use Altair to visualize this datetime data; for clarity in this example, we'll limit ourselves to the first two weeks of data: .. altair-plot:: temps = temps[temps.date < '2010-01-15'] alt.Chart(temps).mark_line().encode( x='date:T', y='temp:Q' ) (notice that for date/time values we use the ``T`` to indicate a temporal encoding: while this is optional for pandas datetime input, it is good practice to specify a type explicitly; see :ref:`encoding-data-types` for more discussion). For date-time inputs like these, it can sometimes be useful to extract particular time units (e.g. hours of the day, dates of the month, etc.). In Altair, this can be done with a time unit transform, discussed in detail in :ref:`user-guide-timeunit-transform`. For example, we might decide we want a heatmap with hour of the day on the x-axis, and day of the month on the y-axis: .. altair-plot:: alt.Chart(temps).mark_rect().encode( alt.X('hoursminutes(date):O').title('hour of day'), alt.Y('monthdate(date):O').title('date'), alt.Color('temp:Q').title('temperature (F)') ) Unless you are using a non-ES6 browser (See :ref:`note-browser-compliance`), you will notice that the chart created by this code reflects hours starting at 00:00:00 on January 1st, just as in the data we input. This is because both the input timestamps and the plot outputs are using local time. Specifying Time Zones --------------------- If you are viewing the above visualizations in a supported browser (see :ref:`note-browser-compliance`), the times are both serialized and rendered in local time, so that the ``January 1st 00:00:00`` row renders in the chart as ``00:00`` on ``January 1st``. In Altair, simple dates without an explicit timezone are treated as local time, and in Vega-Lite, unless otherwise specified, times are rendered in the local time of the browser that does the rendering. If you would like your dates to instead be time-zone aware, you can set the timezone explicitly in the input dataframe. Since Seattle is in the ``US/Pacific`` timezone, we can localize the timestamps in Pandas as follows: .. altair-plot:: :output: repr temps['date_pacific'] = temps['date'].dt.tz_localize('US/Pacific') temps.dtypes Notice that the timezone is now part of the pandas datatype. If we repeat the above chart with this timezone-aware data, the result will render **according to the timezone of the browser rendering it**: .. altair-plot:: alt.Chart(temps).mark_rect().encode( alt.X('hoursminutes(date_pacific):O').title('hour of day'), alt.Y('monthdate(date_pacific):O').title('date'), alt.Color('temp:Q').title('temperature (F)') ) If you are viewing this chart on a computer whose time is set to the west coast of the US, it should appear identical to the first version. If you are rendering the chart in any other timezone, it will render using a timezone correction computed from the location set in your system. .. _explicit-utc-time: Using UTC Time -------------- This user-local rendering can sometimes be confusing, because it leads to the same output being visualized differently by different users. If you want timezone-aware data to appear the same to every user regardless of location, the best approach is to adopt a standard timezone in which to render the data. One commonly-used standard is `Coordinated Universal Time (UTC)`_. In Altair, any of the ``timeUnit`` bins can be prefixed with ``utc`` in order to extract UTC time units. Here is the above chart visualized in UTC time, which will render the same way regardless of the system location: .. altair-plot:: alt.Chart(temps).mark_rect().encode( alt.X('utchoursminutes(date_pacific):O').title('UTC hour of day'), alt.Y('utcmonthdate(date_pacific):O').title('UTC date'), alt.Color('temp:Q').title('temperature (F)') ) To make your charts as portable as possible (even in non-ES6 browsers which parse timezone-agnostic times as UTC), you can explicitly work in UTC time, both on the Pandas side and on the Vega-Lite side: .. altair-plot:: temps['date_utc'] = temps['date'].dt.tz_localize('UTC') alt.Chart(temps).mark_rect().encode( alt.X('utchoursminutes(date_utc):O').title('hour of day'), alt.Y('utcmonthdate(date_utc):O').title('date'), alt.Color('temp:Q').title('temperature (F)') ) This is somewhat less convenient than the default behavior for timezone-agnostic dates, in which both Pandas and Vega-Lite assume times are local (except in non-ES6 browsers; see :ref:`note-browser-compliance`), but it gets around browser incompatibilities by explicitly working in UTC, which gives similar results even in older browsers. .. _note-browser-compliance: Note on Browser Compliance -------------------------- .. note:: Warning about non-ES6 Browsers The discussion below applies to modern browsers which support `ECMAScript 6`_, in which time strings like ``"2018-01-01T12:00:00"`` without a trailing ``"Z"`` are treated as local time rather than `Coordinated Universal Time (UTC)`_. For example, recent versions of Chrome and Firefox are ES6-compliant, while Safari 11 is not. If you are using a non-ES6 browser, this means that times displayed in Altair charts may be rendered with a timezone offset, unless you explicitly use UTC time (see :ref:`explicit-utc-time`). The following chart will help you determine if your browser parses dates in the way that Altair expects: .. altair-plot:: :links: none import altair as alt import pandas as pd df = pd.DataFrame({'local': ['2018-01-01T00:00:00'], 'utc': ['2018-01-01T00:00:00Z']}) alt.Chart(df).transform_calculate( compliant="hours(datum.local) != hours(datum.utc) ? true : false", ).mark_text(size=20, baseline='middle').encode( text=alt.condition('datum.compliant', alt.value('OK'), alt.value('not OK')), color=alt.condition('datum.compliant', alt.value('green'), alt.value('red')) ).properties(width=80, height=50) If the above output contains a red "not OK": .. altair-plot:: :hide-code: :links: none alt.Chart(df).mark_text(size=10, baseline='middle').encode( alt.TextValue('not OK'), alt.ColorValue('red') ).properties(width=40, height=25) it means that your browser's date parsing is not ES6-compliant. If it contains a green "OK": .. altair-plot:: :hide-code: :links: none alt.Chart(df).mark_text(size=10, baseline='middle').encode( alt.TextValue('OK'), alt.ColorValue('green') ).properties(width=40, height=25) then it means that your browser parses dates as Altair expects, either because it is ES6-compliant or because your computer locale happens to be set to the UTC+0 (GMT) timezone. .. _Coordinated Universal Time (UTC): https://en.wikipedia.org/wiki/Coordinated_Universal_Time .. _Pandas timeseries: https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html .. _ECMAScript 6: http://www.ecma-international.org/ecma-262/6.0/ altair-5.0.1/doc/user_guide/transform/000077500000000000000000000000001443422213100177005ustar00rootroot00000000000000altair-5.0.1/doc/user_guide/transform/aggregate.rst000066400000000000000000000037351443422213100223700ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-aggregate-transform: Aggregate ~~~~~~~~~ There are two ways to aggregate data within Altair: within the encoding itself, or using a top level aggregate transform. The aggregate property of a field definition can be used to compute aggregate summary statistics (e.g., median, min, max) over groups of data. If at least one fields in the specified encoding channels contain aggregate, the resulting visualization will show aggregate data. In this case, all fields without aggregation function specified are treated as group-by fields in the aggregation process. For example, the following bar chart aggregates mean of ``acceleration``, grouped by the number of Cylinders. .. altair-plot:: import altair as alt from vega_datasets import data cars = data.cars.url alt.Chart(cars).mark_bar().encode( y='Cylinders:O', x='mean(Acceleration):Q', ) The Altair shorthand string:: # ... x='mean(Acceleration):Q', # ... is made available for convenience, and is equivalent to the longer form:: # ... x=alt.X(field='Acceleration', aggregate='mean', type='quantitative'), # ... For more information on shorthand encodings specifications, see :ref:`encoding-aggregates`. The same plot can be shown using an explicitly computed aggregation, using the :meth:`~Chart.transform_aggregate` method: .. altair-plot:: alt.Chart(cars).mark_bar().encode( y='Cylinders:O', x='mean_acc:Q' ).transform_aggregate( mean_acc='mean(Acceleration)', groupby=["Cylinders"] ) For a list of available aggregates, see :ref:`encoding-aggregates`. Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_aggregate` method is built on the :class:`~AggregateTransform` class, which has the following options: .. altair-object-table:: altair.AggregateTransform The :class:`~AggregatedFieldDef` objects have the following options: .. altair-object-table:: altair.AggregatedFieldDef altair-5.0.1/doc/user_guide/transform/bin.rst000066400000000000000000000050231443422213100212020ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-bin-transform: Bin ~~~ As with :ref:`user-guide-aggregate-transform`, there are two ways to apply a bin transform in Altair: within the encoding itself, or using a top-level bin transform. An common application of a bin transform is when creating a histogram: .. altair-plot:: import altair as alt from vega_datasets import data movies = data.movies.url alt.Chart(movies).mark_bar().encode( alt.X("IMDB_Rating:Q").bin(), y='count()', ) But a bin transform can be useful in other applications; for example, here we bin a continuous field to create a discrete color map: .. altair-plot:: import altair as alt from vega_datasets import data cars = data.cars.url alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color=alt.Color('Acceleration:Q').bin(maxbins=5) ) In the first case we use ``bin()`` without any arguments, which uses the default bin settings. In the second case, we exercise more fine-tuned control over the bin parameters by passing the ``maxbins`` argument. If you are using the same bins in multiple chart components, it can be useful to instead define the binning at the top level, using :meth:`~Chart.transform_bin` method. Here is the above histogram created using a top-level bin transform: .. altair-plot:: import altair as alt from vega_datasets import data movies = data.movies.url alt.Chart(movies).mark_bar().encode( x='binned_rating:O', y='count()', ).transform_bin( 'binned_rating', field='IMDB_Rating' ) And here is the transformed color scale using a top-level bin transform: .. altair-plot:: import altair as alt from vega_datasets import data cars = data.cars.url alt.Chart(cars).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='binned_acc:O' ).transform_bin( 'binned_acc', 'Acceleration', bin=alt.Bin(maxbins=5) ) The advantage of the top-level transform is that the same named field can be used in multiple places in the chart if desired. Note the slight difference in binning behavior between the encoding-based bins (which preserve the range of the bins) and the transform-based bins (which collapse each bin to a single representative value. Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_bin` method is built on the :class:`~BinTransform` class, which has the following options: .. altair-object-table:: altair.BinTransform altair-5.0.1/doc/user_guide/transform/calculate.rst000066400000000000000000000037071443422213100223760ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-calculate-transform: Calculate ~~~~~~~~~ The calculate transform allows the user to define new fields in the dataset which are calculated from other fields using an expression syntax. As a simple example, here we take data with a simple input sequence, and compute a some trigonometric quantities: .. altair-plot:: import altair as alt import pandas as pd data = pd.DataFrame({'t': range(101)}) alt.Chart(data).mark_line().encode( x='x:Q', y='y:Q', order='t:Q' ).transform_calculate( x='cos(datum.t * PI / 50)', y='sin(datum.t * PI / 25)' ) Each argument within ``transform_calculate`` is a `Vega expression`_ string, which is a well-defined set of javascript-style operations that can be used to calculate a new field from an existing one. To streamline building these Vega expressions in Python, Altair provides the :mod:`expr` module which provides constants and functions to allow these expressions to be constructed with Python syntax; for example: .. altair-plot:: alt.Chart(data).mark_line().encode( x='x:Q', y='y:Q', order='t:Q' ).transform_calculate( x=alt.expr.cos(alt.datum.t * alt.expr.PI / 50), y=alt.expr.sin(alt.datum.t * alt.expr.PI / 25) ) Altair expressions are designed to output valid Vega expressions. The benefit of using them is that proper syntax is ensured by the Python interpreter, and tab completion of the :mod:`~expr` submodule can be used to explore the available functions and constants. These expressions can also be used when constructing a :ref:`user-guide-filter-transform`, as we shall see next. Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_calculate` method is built on the :class:`~CalculateTransform` class, which has the following options: .. altair-object-table:: altair.CalculateTransform .. _Vega expression: https://vega.github.io/vega/docs/expressions/ altair-5.0.1/doc/user_guide/transform/density.rst000066400000000000000000000027461443422213100221220ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-density-transform: Density ~~~~~~~ The density transform performs one-dimensional `kernel density estimation `_ over input data and generates a new column of samples of the estimated densities. Here is a simple example, showing the distribution of IMDB ratings from the movies dataset: .. altair-plot:: import altair as alt from vega_datasets import data alt.Chart(data.movies.url).transform_density( 'IMDB_Rating', as_=['IMDB_Rating', 'density'], ).mark_area().encode( x="IMDB_Rating:Q", y='density:Q', ) The density can also be computed on a per-group basis, by specifying the ``groupby`` argument. Here we split the above density computation across movie genres: .. altair-plot:: import altair as alt from vega_datasets import data alt.Chart( data.movies.url, width=120, height=80 ).transform_filter( 'isValid(datum.Major_Genre)' ).transform_density( 'IMDB_Rating', groupby=['Major_Genre'], as_=['IMDB_Rating', 'density'], extent=[1, 10], ).mark_area().encode( x="IMDB_Rating:Q", y='density:Q', ).facet( 'Major_Genre:N', columns=4 ) Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_density` method is built on the :class:`~DensityTransform` class, which has the following options: .. altair-object-table:: altair.DensityTransform altair-5.0.1/doc/user_guide/transform/filter.rst000066400000000000000000000140361443422213100217230ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-filter-transform: Filter ~~~~~~ The filter transform removes objects from a data stream based on a provided filter expression, selection, or other filter predicate. A filter can be added at the top level of a chart using the :meth:`Chart.transform_filter` method. The argument to ``transform_filter`` can be one of a number of expressions and objects: 1. A `Vega expression`_ expressed as a string or built using the :mod:`~expr` module 2. A Field predicate, such as :class:`~FieldOneOfPredicate`, :class:`~FieldRangePredicate`, :class:`~FieldEqualPredicate`, :class:`~FieldLTPredicate`, :class:`~FieldGTPredicate`, :class:`~FieldLTEPredicate`, :class:`~FieldGTEPredicate`, 3. A Selection predicate or object created by :func:`selection` 4. A Logical operand that combines any of the above We'll show a brief example of each of these in the following sections Filter Expression ^^^^^^^^^^^^^^^^^ A filter expression uses the `Vega expression`_ language, either specified directly as a string, or built using the :mod:`~expr` module. This can be useful when, for example, selecting only a subset of data. For example: .. altair-plot:: import altair as alt from altair import datum from vega_datasets import data pop = data.population.url alt.Chart(pop).mark_area().encode( x='age:O', y='people:Q', ).transform_filter( (datum.year == 2000) & (datum.sex == 1) ) Notice that, like in the :ref:`user-guide-filter-transform`, data values are referenced via the name ``datum``. Field Predicates ^^^^^^^^^^^^^^^^ Field predicates overlap somewhat in function with expression predicates, but have the advantage that their contents are validated by the schema. Examples are: - :class:`~FieldEqualPredicate` evaluates whether a field is equal to a particular value - :class:`~FieldOneOfPredicate` evaluates whether a field is among a list of specified values. - :class:`~FieldRangePredicate` evaluates whether a continuous field is within a range of values. - :class:`~FieldLTPredicate` evaluates whether a continuous field is less than a given value - :class:`~FieldGTPredicate` evaluates whether a continuous field is greater than a given value - :class:`~FieldLTEPredicate` evaluates whether a continuous field is less than or equal to a given value - :class:`~FieldGTEPredicate` evaluates whether a continuous field is greater than or equal to a given value Here is an example of a :class:`~FieldEqualPredicate` used to select just the values from year 2000 as in the above chart: .. altair-plot:: import altair as alt from vega_datasets import data pop = data.population.url alt.Chart(pop).mark_line().encode( x='age:O', y='sum(people):Q', color='year:O' ).transform_filter( alt.FieldEqualPredicate(field='year', equal=2000) ) A :class:`~FieldOneOfPredicate` is similar, but allows selection of any number of specific values: .. altair-plot:: import altair as alt from vega_datasets import data pop = data.population.url alt.Chart(pop).mark_line().encode( x='age:O', y='sum(people):Q', color='year:O' ).transform_filter( alt.FieldOneOfPredicate(field='year', oneOf=[1900, 1950, 2000]) ) Finally, a :meth:`~FieldRangePredicate` allows selecting values within a particular continuous range: .. altair-plot:: import altair as alt from vega_datasets import data pop = data.population.url alt.Chart(pop).mark_line().encode( x='age:O', y='sum(people):Q', color='year:O' ).transform_filter( alt.FieldRangePredicate(field='year', range=[1960, 2000]) ) Selection Predicates ^^^^^^^^^^^^^^^^^^^^ Selection predicates can be used to filter data based on a selection. While these can be constructed directly using a :class:`~SelectionPredicate` class, in Altair it is often more convenient to construct them using the :func:`~selection` function. For example, this chart uses a multi-selection that allows the user to click or shift-click on the bars in the bottom chart to select the data to be shown in the top chart: .. altair-plot:: import altair as alt from vega_datasets import data pop = data.population.url selection = alt.selection_point(fields=['year']) top = alt.Chart().mark_line().encode( x='age:O', y='sum(people):Q', color='year:O' ).properties( width=600, height=200 ).transform_filter( selection ) bottom = alt.Chart().mark_bar().encode( x='year:O', y='sum(people):Q', color=alt.condition(selection, alt.value('steelblue'), alt.value('lightgray')) ).properties( width=600, height=100 ).add_params( selection ) alt.vconcat( top, bottom, data=pop ) Logical Operands ^^^^^^^^^^^^^^^^ At times it is useful to combine several types of predicates into a single selection. This can be accomplished using the various logical operand classes: - :class:`~LogicalOrPredicate` - :class:`~LogicalAndPredicate` - :class:`~LogicalNotPredicate` These are not yet part of the Altair interface (see `Issue 695 `_) but can be constructed explicitly; for example, here we plot US population distributions for all data *except* the years 1950-1960, by applying a ``LogicalNotPredicate`` schema to a ``FieldRangePredicate``: .. altair-plot:: import altair as alt from vega_datasets import data pop = data.population.url alt.Chart(pop).mark_line().encode( x='age:O', y='sum(people):Q', color='year:O' ).properties( width=600, height=200 ).transform_filter( {'not': alt.FieldRangePredicate(field='year', range=[1950, 1960])} ) Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_filter` method is built on the :class:`~FilterTransform` class, which has the following options: .. altair-object-table:: altair.FilterTransform .. _Vega expression: https://vega.github.io/vega/docs/expressions/ altair-5.0.1/doc/user_guide/transform/flatten.rst000066400000000000000000000033771443422213100221010ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-flatten-transform: Flatten ~~~~~~~ The flatten transform can be used to extract the contents of arrays from data entries. This will not generally be useful for well-structured data within pandas dataframes, but it can be useful for working with data from other sources. As an example, consider this dataset which uses a common convention in JSON data, a set of fields each containing a list of entries: .. altair-plot:: :output: none import numpy as np rand = np.random.RandomState(0) def generate_data(N): mean = rand.randn() std = rand.rand() return list(rand.normal(mean, std, N)) data = [ {'label': 'A', 'values': generate_data(20)}, {'label': 'B', 'values': generate_data(30)}, {'label': 'C', 'values': generate_data(40)}, {'label': 'D', 'values': generate_data(50)}, ] This kind of data structure does not work well in the context of dataframe representations, as we can see by loading this into pandas: .. altair-plot:: :output: repr import pandas as pd df = pd.DataFrame.from_records(data) df Alair's flatten transform allows you to extract the contents of these arrays into a column that can be referenced by an encoding: .. altair-plot:: import altair as alt alt.Chart(df).transform_flatten( ['values'] ).mark_tick().encode( x='values:Q', y='label:N', ) This can be particularly useful in cleaning up data specified via a JSON URL, without having to first load the data for manipulation in pandas. Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_flatten` method is built on the :class:`~FlattenTransform` class, which has the following options: .. altair-object-table:: altair.FlattenTransform altair-5.0.1/doc/user_guide/transform/fold.rst000066400000000000000000000026721443422213100213650ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-fold-transform: Fold ~~~~ The fold transform is, in short, a way to convert wide-form data to long-form data directly without any preprocessing (see :ref:`data-long-vs-wide` for more information). Fold transforms are the opposite of the :ref:`user-guide-pivot-transform`. So, for example, if your data consist of multiple columns that record parallel data for different categories, you can use the fold transform to encode based on those categories: .. altair-plot:: import numpy as np import pandas as pd import altair as alt rand = np.random.RandomState(0) data = pd.DataFrame({ 'date': pd.date_range('2019-01-01', freq='D', periods=30), 'A': rand.randn(30).cumsum(), 'B': rand.randn(30).cumsum(), 'C': rand.randn(30).cumsum(), }) alt.Chart(data).transform_fold( ['A', 'B', 'C'], ).mark_line().encode( x='date:T', y='value:Q', color='key:N' ) Notice here that the fold transform essentially stacks all the values from the specified columns into a single new field named ``"value"``, with the associated names in a field named ``"key"``. For an example of the fold transform in action, see :ref:`gallery_parallel_coordinates`. Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_fold` method is built on the :class:`~FoldTransform` class, which has the following options: .. altair-object-table:: altair.FoldTransform altair-5.0.1/doc/user_guide/transform/impute.rst000066400000000000000000000065411443422213100217430ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-impute-transform: Impute ~~~~~~ The impute transform allows you to fill-in missing entries in a dataset. As an example, consider the following data, which includes missing values that we filter-out of the long-form representation (see :ref:`data-long-vs-wide` for more on this): .. altair-plot:: :output: repr import numpy as np import pandas as pd data = pd.DataFrame({ 't': range(5), 'x': [2, np.nan, 3, 1, 3], 'y': [5, 7, 5, np.nan, 4] }).melt('t').dropna() data Notice the result: the ``x`` series has no entry at ``t=1``, and the ``y`` series has a missing entry at ``t=3``. If we use Altair to visualize this data directly, the line skips the missing entries: .. altair-plot:: import altair as alt raw = alt.Chart(data).mark_line(point=True).encode( x='t:Q', y='value:Q', color='variable:N' ) raw This is not always desirable, because (particularly for a line plot with no points) it can imply the existence of data that is not there. Impute via Encodings ^^^^^^^^^^^^^^^^^^^^ To address this, you can use the impute method of the encoding channel. For example, we can impute using a constant value (we'll show the raw chart lightly in the background for reference): .. altair-plot:: background = raw.encode(opacity=alt.value(0.2)) chart = alt.Chart(data).mark_line(point=True).encode( x='t:Q', y=alt.Y('value:Q').impute(value=0), color='variable:N' ) background + chart Or we can impute using any supported aggregate: .. altair-plot:: chart = alt.Chart(data).mark_line(point=True).encode( x='t:Q', y=alt.Y('value:Q').impute(method='mean'), color='variable:N' ) background + chart Impute via Transform ^^^^^^^^^^^^^^^^^^^^ Similar to the :ref:`user-guide-bin-transform` and :ref:`user-guide-aggregate-transform`, it is also possible to specify the impute transform outside the encoding as a transform. For example, here is the equivalent of the above two charts: .. altair-plot:: chart = alt.Chart(data).transform_impute( impute='value', key='t', value=0, groupby=['variable'] ).mark_line(point=True).encode( x='t:Q', y='value:Q', color='variable:N' ) background + chart .. altair-plot:: chart = alt.Chart(data).transform_impute( impute='value', key='t', method='mean', groupby=['variable'] ).mark_line(point=True).encode( x='t:Q', y='value:Q', color='variable:N' ) background + chart If you would like to use more localized imputed values, you can specify a ``frame`` parameter similar to the :ref:`user-guide-window-transform` that will control which values are used for the imputation. For example, here we impute missing values using the mean of the neighboring points on either side: .. altair-plot:: chart = alt.Chart(data).transform_impute( impute='value', key='t', method='mean', frame=[-1, 1], groupby=['variable'] ).mark_line(point=True).encode( x='t:Q', y='value:Q', color='variable:N' ) background + chart Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_impute` method is built on the :class:`~ImputeTransform` class, which has the following options: .. altair-object-table:: altair.ImputeTransform altair-5.0.1/doc/user_guide/transform/index.rst000066400000000000000000000103571443422213100215470ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-transformations: Data Transformations -------------------- It is often necessary to transform or filter data in the process of visualizing it. In Altair you can do this one of two ways: 1. Before the chart definition, using standard Pandas data transformations. 2. Within the chart definition, using Vega-Lite's data transformation tools. In most cases, we suggest that you use the first approach, because it is more straightforward to those who are familiar with data manipulation in Python, and because the Pandas package offers much more flexibility than Vega-Lite in available data manipulations. The second approach becomes useful when the data source is not a dataframe, but, for example, a URL pointer to a JSON or CSV file. It can also be useful in a compound chart where different views of the dataset require different transformations. This second approach -- specifying data transformations within the chart specification itself -- can be accomplished using the ``transform_*`` methods of top-level objects: ========================================= ========================================= ================================================================================ Transform Method Description ========================================= ========================================= ================================================================================ :ref:`user-guide-aggregate-transform` :meth:`~Chart.transform_aggregate` Create a new data column by aggregating an existing column. :ref:`user-guide-bin-transform` :meth:`~Chart.transform_bin` Create a new data column by binning an existing column. :ref:`user-guide-calculate-transform` :meth:`~Chart.transform_calculate` Create a new data column using an arithmetic calculation on an existing column. :ref:`user-guide-density-transform` :meth:`~Chart.transform_density` Create a new data column with the kernel density estimate of the input. :ref:`user-guide-filter-transform` :meth:`~Chart.transform_filter` Select a subset of data based on a condition. :ref:`user-guide-flatten-transform` :meth:`~Chart.transform_flatten` Flatten array data into columns. :ref:`user-guide-fold-transform` :meth:`~Chart.transform_fold` Convert wide-form data into long-form data (opposite of pivot). :ref:`user-guide-impute-transform` :meth:`~Chart.transform_impute` Impute missing data. :ref:`user-guide-joinaggregate-transform` :meth:`~Chart.transform_joinaggregate` Aggregate transform joined to original data. :ref:`user-guide-loess-transform` :meth:`~Chart.transform_loess` Create a new column with LOESS smoothing of data. :ref:`user-guide-lookup-transform` :meth:`~Chart.transform_lookup` One-sided join of two datasets based on a lookup key. :ref:`user-guide-pivot-transform` :meth:`~Chart.transform_pivot` Convert long-form data into wide-form data (opposite of fold). :ref:`user-guide-quantile-transform` :meth:`~Chart.transform_quantile` Compute empirical quantiles of a dataset. :ref:`user-guide-regression-transform` :meth:`~Chart.transform_regression` Fit a regression model to a dataset. :ref:`user-guide-sample-transform` :meth:`~Chart.transform_sample` Random sub-sample of the rows in the dataset. :ref:`user-guide-stack-transform` :meth:`~Chart.transform_stack` Compute stacked version of values. :ref:`user-guide-timeunit-transform` :meth:`~Chart.transform_timeunit` Discretize/group a date by a time unit (day, month, year, etc.) :ref:`user-guide-window-transform` :meth:`~Chart.transform_window` Compute a windowed aggregation ========================================= ========================================= ================================================================================ .. toctree:: :hidden: aggregate bin calculate density filter flatten fold impute joinaggregate lookup loess pivot quantile regression sample stack timeunit windowaltair-5.0.1/doc/user_guide/transform/joinaggregate.rst000066400000000000000000000042771443422213100232520ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-joinaggregate-transform: Join Aggregate ~~~~~~~~~~~~~~ The Join Aggregate transform acts in almost every way the same as an Aggregate transform, but the resulting aggregate is joined to the original dataset. To make this more clear, consider the following dataset: .. altair-plot:: :output: repr import pandas as pd import numpy as np rand = np.random.RandomState(0) df = pd.DataFrame({ 'label': rand.choice(['A', 'B', 'C'], 10), 'value': rand.randn(10), }) df Here is a pandas operation that is equivalent to Altair's Aggregate transform, using the mean as an example: .. altair-plot:: :output: repr mean = df.groupby('label').mean().reset_index() mean And here is an output that is equivalent to Altair's Join Aggregate: .. altair-plot:: :output: repr pd.merge(df, mean, on='label', suffixes=['', '_mean']) Notice that the join aggregate joins the aggregated value with the original dataframe, such that the aggregated values can be used in tandem with the original values if desired. Here is an example of how the join aggregate might be used: we compare the IMDB and Rotten Tomatoes movie ratings, normalized by their mean and standard deviation, which requires calculations on the joined data: .. altair-plot:: import altair as alt from vega_datasets import data alt.Chart(data.movies.url).transform_filter( 'datum.IMDB_Rating != null && datum.Rotten_Tomatoes_Rating != null' ).transform_joinaggregate( IMDB_mean='mean(IMDB_Rating)', IMDB_std='stdev(IMDB_Rating)', RT_mean='mean(Rotten_Tomatoes_Rating)', RT_std='stdev(Rotten_Tomatoes_Rating)' ).transform_calculate( IMDB_Deviation="(datum.IMDB_Rating - datum.IMDB_mean) / datum.IMDB_std", Rotten_Tomatoes_Deviation="(datum.Rotten_Tomatoes_Rating - datum.RT_mean) / datum.RT_std" ).mark_point().encode( x='IMDB_Deviation:Q', y="Rotten_Tomatoes_Deviation:Q" ) Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_joinaggregate` method is built on the :class:`~JoinAggregateTransform` class, which has the following options: .. altair-object-table:: altair.JoinAggregateTransform altair-5.0.1/doc/user_guide/transform/loess.rst000066400000000000000000000020031443422213100215520ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-loess-transform: LOESS ~~~~~ The LOESS transform (LOcally Estimated Scatterplot Smoothing) uses a locally-estimated regression to produce a trend line. LOESS performs a sequence of local weighted regressions over a sliding window of nearest-neighbor points. For standard parametric regression options, see the :ref:`user-guide-regression-transform`. Here is an example of using LOESS to smooth samples from a Gaussian random walk: .. altair-plot:: import altair as alt import pandas as pd import numpy as np np.random.seed(42) df = pd.DataFrame({ 'x': range(100), 'y': np.random.randn(100).cumsum() }) chart = alt.Chart(df).mark_point().encode( x='x', y='y' ) chart + chart.transform_loess('x', 'y').mark_line() Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_loess` method is built on the :class:`~LoessTransform` class, which has the following options: .. altair-object-table:: altair.LoessTransform altair-5.0.1/doc/user_guide/transform/lookup.rst000066400000000000000000000105601443422213100217450ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-lookup-transform: Lookup ~~~~~~ The Lookup transform extends a primary data source by looking up values from another data source; it is similar to a one-sided join. A lookup can be added at the top level of a chart using the :meth:`Chart.transform_lookup` method. By way of example, imagine you have two sources of data that you would like to combine and plot: one is a list of names of people along with their height and weight, and the other is some information about which groups they belong to. This example data is available in ``vega_datasets``: .. altair-plot:: :output: none from vega_datasets import data people = data.lookup_people() groups = data.lookup_groups() We know how to visualize each of these datasets separately; for example: .. altair-plot:: import altair as alt top = alt.Chart(people).mark_square(size=200).encode( x=alt.X('age:Q').scale(zero=False), y=alt.Y('height:Q').scale(zero=False), color='name:N', tooltip='name:N' ).properties( width=400, height=200 ) bottom = alt.Chart(groups).mark_rect().encode( x='person:N', y='group:O' ).properties( width=400, height=100 ) alt.vconcat(top, bottom) If we would like to plot features that reference both datasets (for example, the average age within each group), we need to combine the two datasets. This can be done either as a data preprocessing step, using tools available in Pandas, or as part of the visualization using a :class:`~LookupTransform` in Altair. Combining Datasets with pandas.merge ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pandas provides a wide range of tools for merging and joining datasets; see `Merge, Join, and Concatenate `_ for some detailed examples. For the above data, we can merge the data and create a combined chart as follows: .. altair-plot:: import pandas as pd merged = pd.merge(groups, people, how='left', left_on='person', right_on='name') alt.Chart(merged).mark_bar().encode( x='mean(age):Q', y='group:O' ) We specify a left join, meaning that for each entry of the "person" column in the groups, we seek the "name" column in people and add the entry to the data. From this, we can easily create a bar chart representing the mean age in each group. Combining Datasets with a Lookup Transform ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For some data sources (e.g. data available at a URL, or data that is streaming), it is desirable to have a means of joining data without having to download it for pre-processing in Pandas. This is where Altair's :meth:`~Chart.transform_lookup` comes in. To reproduce the above combined plot by combining datasets within the chart specification itself, we can do the following: .. altair-plot:: alt.Chart(groups).mark_bar().encode( x='mean(age):Q', y='group:O' ).transform_lookup( lookup='person', from_=alt.LookupData(data=people, key='name', fields=['age', 'height']) ) Here ``lookup`` names the field in the groups dataset on which we will match, and the ``from_`` argument specifies a :class:`~LookupData` structure where we supply the second dataset, the lookup key, and the fields we would like to extract. Example: Lookup Transforms for Geographical Visualization ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Lookup transforms are often particularly important for geographic visualization, where it is common to combine tabular datasets with datasets that specify geographic boundaries to be visualized; for example, here is a visualization of unemployment rates per county in the US: .. altair-plot:: import altair as alt from vega_datasets import data counties = alt.topo_feature(data.us_10m.url, 'counties') unemp_data = data.unemployment.url alt.Chart(counties).mark_geoshape().encode( color='rate:Q' ).transform_lookup( lookup='id', from_=alt.LookupData(unemp_data, 'id', ['rate']) ).properties( projection={'type': 'albersUsa'}, width=500, height=300 ) Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_lookup` method is built on the :class:`~LookupTransform` class, which has the following options: .. altair-object-table:: altair.LookupTransform altair-5.0.1/doc/user_guide/transform/pivot.rst000066400000000000000000000045571443422213100216060ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-pivot-transform: Pivot ~~~~~ The pivot transform is, in short, a way to convert long-form data to wide-form data directly without any preprocessing (see :ref:`data-long-vs-wide` for more information). Pivot transforms are useful for creating matrix or cross-tabulation data, acting as an inverse to the :ref:`user-guide-fold-transform`. Here is an example, using Olympic medals data: .. altair-plot:: import altair as alt import pandas as pd df = pd.DataFrame.from_records([ {"country": "Norway", "type": "gold", "count": 14}, {"country": "Norway", "type": "silver", "count": 14}, {"country": "Norway", "type": "bronze", "count": 11}, {"country": "Germany", "type": "gold", "count": 14}, {"country": "Germany", "type": "silver", "count": 10}, {"country": "Germany", "type": "bronze", "count": 7}, {"country": "Canada", "type": "gold", "count": 11}, {"country": "Canada", "type": "silver", "count": 8}, {"country": "Canada", "type": "bronze", "count": 10} ]) alt.Chart(df).transform_pivot( 'type', groupby=['country'], value='count' ).mark_bar().encode( x='gold:Q', y='country:N', ) The pivot transform, when combined with other elements of the Altair grammar, enables some very interesting chart types. For example, here we use pivot to create a single tooltip for values on multiple lines: .. altair-plot:: import altair as alt from vega_datasets import data source = data.stocks() base = alt.Chart(source).encode(x='date:T') columns = sorted(source.symbol.unique()) selection = alt.selection_point( fields=['date'], nearest=True, on='mouseover', empty=False, clear='mouseout' ) lines = base.mark_line().encode(y='price:Q', color='symbol:N') points = lines.mark_point().transform_filter(selection) rule = base.transform_pivot( 'symbol', value='price', groupby=['date'] ).mark_rule().encode( opacity=alt.condition(selection, alt.value(0.3), alt.value(0)), tooltip=[alt.Tooltip(c, type='quantitative') for c in columns] ).add_params(selection) lines + points + rule Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_pivot` method is built on the :class:`~PivotTransform` class, which has the following options: .. altair-object-table:: altair.PivotTransform altair-5.0.1/doc/user_guide/transform/quantile.rst000066400000000000000000000017561443422213100222650ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-quantile-transform: Quantile ~~~~~~~~ The quantile transform calculates empirical `quantile `_ values for input data. If a groupby parameter is provided, quantiles are estimated separately per group. Among other uses, the quantile transform is useful for creating `quantile-quantile (Q-Q) plots `_. Here is an example of a quantile plot of normally-distributed data: .. altair-plot:: import altair as alt import pandas as pd import numpy as np np.random.seed(42) df = pd.DataFrame({'x': np.random.randn(200)}) alt.Chart(df).transform_quantile( 'x', step=0.01 ).mark_point().encode( x='prob:Q', y='value:Q' ) Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_quantile` method is built on the :class:`~QuantileTransform` class, which has the following options: .. altair-object-table:: altair.QuantileTransform altair-5.0.1/doc/user_guide/transform/regression.rst000066400000000000000000000031051443422213100226110ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-regression-transform: Regression ~~~~~~~~~~ The regression transform fits two-dimensional regression models to smooth and predict data. This transform can fit multiple models for input data (one per group) and generates new data objects that represent points for summary trend lines. Alternatively, this transform can be used to generate a set of objects containing regression model parameters, one per group. This transform supports parametric models for the following functional forms: - linear (``linear``): *y = a + b * x* - logarithmic (``log``): *y = a + b * log(x)* - exponential (``exp``): * y = a * e^(b * x)* - power (``pow``): *y = a * x^b* - quadratic (``quad``): *y = a + b * x + c * x^2* - polynomial (``poly``): *y = a + b * x + … + k * x^(order)* All models are fit using ordinary least squares. For non-parametric locally weighted regression, see the :ref:`user-guide-loess-transform`. Here is an example of a simple linear regression plotted on top of data: .. altair-plot:: import altair as alt import pandas as pd import numpy as np np.random.seed(42) x = np.linspace(0, 10) y = x - 5 + np.random.randn(len(x)) df = pd.DataFrame({'x': x, 'y': y}) chart = alt.Chart(df).mark_point().encode( x='x', y='y' ) chart + chart.transform_regression('x', 'y').mark_line() Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_regression` method is built on the :class:`~RegressionTransform` class, which has the following options: .. altair-object-table:: altair.RegressionTransform altair-5.0.1/doc/user_guide/transform/sample.rst000066400000000000000000000017061443422213100217170ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-sample-transform: Sample ~~~~~~ The sample transform is one of the simpler of all Altair's data transforms; it takes a single parameter ``sample`` which specified a number of rows to randomly choose from the dataset. The resulting chart will be created using only this random subset of the data. For example, here we chart the full cars dataset alongside a sample of 100 rows: .. altair-plot:: import altair as alt from vega_datasets import data source = data.cars.url chart = alt.Chart(source).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N' ).properties( width=200, height=200 ) chart | chart.transform_sample(100) Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_sample` method is built on the :class:`~SampleTransform` class, which has the following options: .. altair-object-table:: altair.SampleTransform altair-5.0.1/doc/user_guide/transform/stack.rst000066400000000000000000000027721443422213100215470ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-stack-transform: Stack ~~~~~ The stack transform allows you to compute values associated with stacked versions of encodings. For example, consider this stacked bar chart: .. altair-plot:: import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( column='year:O', x='yield:Q', y='variety:N', color='site:N' ).properties(width=220) Implicitly, this data is being grouped and stacked, but what if you would like to access those stacked values directly? We can construct that same chart manually using the stack transform: .. altair-plot:: import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).transform_stack( stack='yield', as_=['yield_1', 'yield_2'], groupby=['year', 'variety'], sort=[alt.SortField('site', 'descending')] ).mark_bar().encode( column='year:O', x=alt.X('yield_1:Q').title('yield'), x2='yield_2:Q', y='variety:N', color='site:N', tooltip=['site', 'yield', 'variety'] ).properties(width=220) Notice that the bars are now explicitly drawn between values computed and specified within the x and x2 encodings. Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_stack` method is built on the :class:`~StackTransform` class, which has the following options: .. altair-object-table:: altair.StackTransform altair-5.0.1/doc/user_guide/transform/timeunit.rst000066400000000000000000000064541443422213100223010ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-timeunit-transform: TimeUnit ~~~~~~~~ TimeUnit transforms are used to discretize dates and times within Altair. As with the :ref:`user-guide-aggregate-transform` and :ref:`user-guide-bin-transform` discussed above, they can be defined either as part of the encoding, or as a top-level transform. These are the available time units: - ``"year"``, ``"yearquarter"``, ``"yearquartermonth"``, ``"yearmonth"``, ``"yearmonthdate"``, ``"yearmonthdatehours"``, ``"yearmonthdatehoursminutes"``, ``"yearmonthdatehoursminutesseconds"``. - ``"quarter"``, ``"quartermonth"`` - ``"month"``, ``"monthdate"`` - ``"date"`` (Day of month, i.e., 1 - 31) - ``"day"`` (Day of week, i.e., Monday - Friday) - ``"hours"``, ``"hoursminutes"``, ``"hoursminutesseconds"`` - ``"minutes"``, ``"minutesseconds"`` - ``"seconds"``, ``"secondsmilliseconds"`` - ``"milliseconds"`` TimeUnit Within Encoding ^^^^^^^^^^^^^^^^^^^^^^^^ Any temporal field definition can include a ``timeUnit`` argument to discretize the temporal data. For example, here we plot a dataset that consists of hourly temperature measurements in Seattle during the year 2010: .. altair-plot:: import altair as alt from vega_datasets import data temps = data.seattle_temps.url alt.Chart(temps).mark_line().encode( x='date:T', y='temp:Q' ) The plot is too busy due to the amount of data points squeezed into the short time; we can make it a bit cleaner by discretizing it, for example, by month and plotting only the mean monthly temperature: .. altair-plot:: alt.Chart(temps).mark_line().encode( x='month(date):T', y='mean(temp):Q' ) Notice that by default timeUnit output is a continuous quantity; if you would instead like it to be a categorical, you can specify the ordinal (``O``) or nominal (``N``) type. This can be useful when plotting a bar chart or other discrete chart type: .. altair-plot:: alt.Chart(temps).mark_bar().encode( x='month(date):O', y='mean(temp):Q' ) Multiple time units can be combined within a single plot to yield interesting views of your data; for example, here we extract both the month and the day to give a profile of Seattle temperatures through the year: .. altair-plot:: alt.Chart(temps).mark_rect().encode( alt.X('date(date):O').title('day'), alt.Y('month(date):O').title('month'), color='max(temp):Q' ).properties( title="2010 Daily High Temperatures in Seattle (F)" ) TimeUnit as a Transform ^^^^^^^^^^^^^^^^^^^^^^^ Other times it is convenient to specify a timeUnit as a top-level transform, particularly when the value may be reused. This can be done most conveniently using the :meth:`Chart.transform_timeunit` method. For example: .. altair-plot:: alt.Chart(temps).mark_line().encode( alt.X('month:T').axis(format='%b'), y='mean(temp):Q' ).transform_timeunit( month='month(date)' ) Notice that because the ``timeUnit`` is not part of the encoding channel here, it is often necessary to add an axis formatter to ensure appropriate axis labels. Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_timeunit` method is built on the :class:`~TimeUnitTransform` class, which has the following options: .. altair-object-table:: altair.TimeUnitTransform altair-5.0.1/doc/user_guide/transform/window.rst000066400000000000000000000155141443422213100217470ustar00rootroot00000000000000.. currentmodule:: altair .. _user-guide-window-transform: Window ~~~~~~ The window transform performs calculations over sorted groups of data objects. These calculations include ranking, lead/lag analysis, and aggregates such as cumulative sums and averages. Calculated values are written back to the input data stream, where they can be referenced by encodings. For example, consider the following cumulative frequency distribution: .. altair-plot:: import altair as alt from vega_datasets import data alt.Chart(data.movies.url).transform_window( sort=[{'field': 'IMDB_Rating'}], frame=[None, 0], cumulative_count='count(*)', ).mark_area().encode( x='IMDB_Rating:Q', y='cumulative_count:Q', ) First, we pass a sort field definition, which indicates how data objects should be sorted within the window. Here, movies should be sorted by their IMDB rating. Next, we pass the frame, which indicates how many data objects before and after the current data object should be included within the window. Here, all movies up to and including the current movie should be included. Finally, we pass a window field definition, which indicates how data objects should be aggregated within the window. Here, the number of movies should be counted. There are many aggregation functions built into Altair. As well as those given in :ref:`encoding-aggregates`, we can use the following within window field definitions: ============ ========= ========================================================================================================================================================================================================================================================================================================================= Aggregate Parameter Description ============ ========= ========================================================================================================================================================================================================================================================================================================================= row_number None Assigns each data object a consecutive row number, starting from 1. rank None Assigns a rank order value to each data object in a window, starting from 1. Peer values are assigned the same rank. Subsequent rank scores incorporate the number of prior values. For example, if the first two values tie for rank 1, the third value is assigned rank 3. dense_rank None Assigns dense rank order values to each data object in a window, starting from 1. Peer values are assigned the same rank. Subsequent rank scores do not incorporate the number of prior values. For example, if the first two values tie for rank 1, the third value is assigned rank 2. percent_rank None Assigns a percentage rank order value to each data object in a window. The percent is calculated as (rank - 1) / (group_size - 1). cume_dist None Assigns a cumulative distribution value between 0 and 1 to each data object in a window. ntile Number Assigns a quantile (e.g., percentile) value to each data object in a window. Accepts an integer parameter indicating the number of buckets to use (e.g., 100 for percentiles, 5 for quintiles). lag Number Assigns a value from the data object that precedes the current object by a specified number of positions. If no such object exists, assigns ``null``. Accepts an offset parameter (default ``1``) that indicates the number of positions. This operation must have a corresponding entry in the `fields` parameter array. lead Number Assigns a value from the data object that follows the current object by a specified number of positions. If no such object exists, assigns ``null``. Accepts an offset parameter (default ``1``) that indicates the number of positions. This operation must have a corresponding entry in the `fields` parameter array. first_value None Assigns a value from the first data object in the current sliding window frame. This operation must have a corresponding entry in the `fields` parameter array. last_value None Assigns a value from the last data object in the current sliding window frame. This operation must have a corresponding entry in the `fields` parameter array. nth_value Number Assigns a value from the nth data object in the current sliding window frame. If no such object exists, assigns ``null``. Requires a non-negative integer parameter that indicates the offset from the start of the window frame. This operation must have a corresponding entry in the `fields` parameter array. ============ ========= ========================================================================================================================================================================================================================================================================================================================= While an aggregate transform computes a single value that summarises all data objects, a window transform adds a new property to each data object. This new property is computed from the neighbouring data objects: that is, from the data objects delimited by the window field definition. For example, consider the following time series of stock prices: .. altair-plot:: import altair as alt from vega_datasets import data alt.Chart(data.stocks.url).mark_line().encode( x='date:T', y='price:Q', color='symbol:N', ) It's hard to see the overall pattern in the above example, because Google's stock price is much higher than the other stock prices. If we plot the `z-scores`_ of the stock prices, rather than the stock prices themselves, then the overall pattern becomes clearer: .. altair-plot:: import altair as alt from vega_datasets import data alt.Chart(data.stocks.url).transform_window( mean_price='mean(price)', stdev_price='stdev(price)', frame=[None, None], groupby=['symbol'], ).transform_calculate( z_score=(alt.datum.price - alt.datum.mean_price) / alt.datum.stdev_price, ).mark_line().encode( x='date:T', y='z_score:Q', color='symbol:N', ) By using two aggregation functions (``mean`` and ``stdev``) within the window transform, we are able to compute the z-scores within the calculate transform. For more information about the arguments to the window transform, see :class:`WindowTransform` and `the Vega-Lite documentation `_. Transform Options ^^^^^^^^^^^^^^^^^ The :meth:`~Chart.transform_window` method is built on the :class:`~WindowTransform` class, which has the following options: .. altair-object-table:: altair.WindowTransform .. _z-scores: https://en.wikipedia.org/w/index.php?title=Z-score altair-5.0.1/images/000077500000000000000000000000001443422213100142325ustar00rootroot00000000000000altair-5.0.1/images/cars.png000066400000000000000000001241671443422213100157030ustar00rootroot00000000000000‰PNG  IHDR›[p#LsRGB®Îé IDATx^ì}xœÅµöû•­ÚUﲊeÙ²Üänã‚ ½cˆP$!!Üç'áÞp© IH¸!ä&r¹Ä\°!cãwä^dÙ²z_õÕö¯üÏŒ²B]³²eKòÌóð`ïžýfæó×gæÌ9‚®ë:xãp8ŽÀ" p²Btù£9ŽG€"ÀɆ+G€#Àà 9œl†bÞG€#Ààp²á:Ààp8CŽ'›!‡˜wÀàp8œl¸p8ŽÀ#0dd³sçN,Z´¨c;vìÀÂ… ñÓŸþO=õrssñÎ;ï ;;{È'É;àp8 ‹À‘ÍÛo¿Mgv×]wuÌжmÛðä“O¢  ¯¿þ:ž}öYX,– ‹ï#Ààp†!#› CFÿÐCᥗ^ÂÚµk‘‘‘A=ǃ_|?ü0bbb†t’üáŽG€#pa²!DòôÓOãÁ¤ÛdA/‡Lµ3Ùt–!ÛlÄóéÜâââ°téÒ ‹ï#Àà0"™™É(yñ‰ Ùt‡1¸}F"Ïæ…^Àã?>¤«RTT„¡VÞûr¬8Vì°KŽ4½"ÿ`_µj^{íµŽIϽ{›5±±ßûÞ÷<';N•••t§é|_ Ù444à‰'žÀóÏ?O·ÈÈ—/_N1 å̆“Íè}™úšÙH3 |ì:ʱbÃ*H4D:H Á€«þ‡íéNjHȆL§s4ÚsÏ=GƒH %“ »bp#ͱbG€]’ëÕùǪ/b z%?ÿùÏñ“ŸüÅÅŨ­­ÅüùóqÛm·á‡?üa‡gÓÙÎ^yå•8rä=Îxå•W:<›Poˆ|ÿë_ÿšNt(ÉlÈȆ}‰ú–ädÃŽ"7 +vØ%¹^¬)¬^½š’Cçà©àç$Š—IIII‡Lçm´úúzzí„ÇÌ™3év\P¶;Ù¹·Þz ·Ür •ëìM±ÏœM’“ ?³aÓÜð0Cűb‡ŠcÕ +V²é¾Í<³ÉËËëBVŸ×›g¼ï8Ôç9œl8Ù0›N6ÌPqÊǪV¬ÛhœlBP²Dù6Ú@}õ='Ž;ì’\¯Î?VÁîÛdÝ·Æú"›P¶Ñ:G°qφ‡>3i;7 L0Q!ŽÇŠvÉs©Wý…>÷­Ö8‚iÁ àÛh¸gsa½¯^ÏåËÄû`_[ŽÇ*ºoÃõuÊ3Ï…,?³ág6ÌzÄɆ*î=±Cű +ÑP/„²<ó\Èp²ádìGœl˜¡â”*ŽUXdQN6œl˜õ—“ 3TÜ€²Cű «‘,ÊɆ“ ³þr²a†ŠPv¨8V!`5’E9Ùp²aÖ_N6ÌPqÊÇ*¬F²('N6ÌúËɆ*n@Ù¡âX…€ÕHåd3ŠÉFwÕA÷9!X¢!X¢ÎZO9Ù°CȱâX±#зdMc*NTÔ;©Ð˜X;RâìHŒ¶‹ÇŸ×gp²…d£5BÉ{zKe‡2‰©sa¸ä‘³R.n@ÙáãXq¬Øè]òË“UØœW Yi3Q¡æ6UóÇbÎÄä³íâ¼þž“Íh#Õ߇~{`´AwÖпK®†<ýÎA+7 ìÐq¬8Vìô”fûáRì>^‰®ÉE|TXB66lè(0Øqœëßq²ed£_õø:H“W@ž¼¢C_|kî¥6Ýö×Aë7 ìÐq¬8Vìt•Üs¢Ÿ.ÿ~I¿øÕßwãÒÜ4ÌŸ”ÂL6$uMFF.\ˆ )Ý{ï½´²òž={pë­·ÒªÊ$é'i=ô­J ­}ðÁøä“O¨ÇDjàÜu×] U™Éÿ7mÚ„¼'N6£Œl4G>ŸþBÌ8fÞG=œÏ&v< ËÛ+¦¦qÊŽÇŠcÅŽ@WɵŸŸ„£Ù‡nœÙï#^ûçÄEZqË¥{M÷m´`µäþÈæù矧Ïyä‘GðÌ3Ï ;;›n½‘FŠt#Äãv»)9yR‡S¼Ha·gŸ}‹¥ÇØ9ÙŒ2²â…ë³íÁ’L#Ñô¶:ºðòœ!]2Øw€o£…€'v°8V]±ú`ç)Ô6¶áÛ7ôO6úð¢m¸iᄳöli<öØc”H‚&„A’znÛ¶ )y@¼ÒéÏ^}õU¼öÚký÷çÝp²md@o­„ò埡5œ¡J DgBνb\׃FvsÐ.É;b+Ž;]%÷Tcë¦m´Ëff`VvRHdC„ i‰äá‡î B6ƒõlš/'›QH6ÁE×}­€·BDê@zÀô=7 L0qbf‡‰cÕ VänÍcBjtH´ 8‰H;UÞˆ®Ííq禷h´ÜÜ\º5¤·ØH›«¯¾ä̦»73Й͎;èÖYç3òìàßFëea¹e· +Ž;ì’\¯zbEîØ|¼÷ –LOë‘F"Ѷ*Ã5óÆ·»6Ä ê¼Æ¾º_IrÏf{6ƒQˆþ~Ã;¢+Ž;½K’»6ÿø,¢ "¬ýRg‹ËM×±riN¯wlζϾ~?"Ȇ 2Å@&²jÕªŽ¥þ\."Ë+u²«7n+vØ%¹^]X¬È›¢êfTý+]Mr¬™I‘=îÖ°òÂI©gÜ;œ?~¹˜˜˜gÍÉf@ˆ:ú5 ~t—Œí9Ò =ÃYzↇ¥vŽÇŠ‹CrÈȆ”&}úé§é!ñnHìuYYY—4 ÁC¦¾ ædî„}·ÀîßC+ß×åA$GÉ•jã”1ŽÇŠ‹CrÈÈæ§?ý)ÃŽE𢹅Jb¶Ÿ|òIżD¼B<„”º·•+WŽú•¨hp£Ùå‡$ ·c='s6ÕìƒõÔ»ôYjX"DO-@ÿÞ2ÿ?¡ÃÏI?ü!Ž@;™™™Š>²éGúÞ` Ž#èù<øàƒô¦joíbðlÖ}Q€£Eí—.ƒm\Jîº|JHJÛÛ¿¤ýŸ<½¹œ&ß$I8IBÎÀ®—é…OâU“gœu!=€A˜{ ýK„cuñaÅ>ãá'9$dÓyšäÜ&èÙ¬]»–~E.uþ¼·˜l"7ÚÉ&¯ öÂd”0&.V“…•ðød&GÒ¿e ©ñáÈÍJèW{z3<½åCë+w‹jrãÆ‚R» ÇŠcÅŽ@ß’zS ´Æ"èÁ Ú1ã FgÒ4T#­W²!à£Ñ‚—Œúòj.²ùh÷iì?UƒËgÅ‚)c¨î|¼·ÛráV#MEl½¥ï¬l½·À/B«>yöý2—(nàÀ›Tq Ëþ#äŒÜ€²¿Þ+Ž;½Kª…›¡þ{{`5– éîz@õCÎý:¤¬+ζ‹óúû!'›³™Íh÷lþoÛqz ø¶e9˜˜Ö®Lùø0¶(Ar¬ w,ŸL?#Ûl%5-¸n~VÔA|{3nA/†È¶xèžF@U D¤ÀxÙÓ€liy¸e‡‹cűbG §¤VsÏMyº= ý.ýÄÄ©ývCr˜]{íµ}UœÍCý-'› x©sëþbìKŒyÎ7!„wMK΢8Ü€² Ô.ñâX±#ÐURo© Ét¥ìk»” é,Eÿ!Y°þ£Qˆhßé­q²a\…ÑîÙžzc;M'ÞæñSTt]‡®Ý13Æ'ÒÏž®Á‡»NcæøD\¿`|¯è dÜȬÑLƒ@¨ÆeíWŒ÷ÁŽ"Çjtb¥žÚB&¦_eSîm¦¾uQ2"Á?‘ ‰ Ö!²¤Í-·Ü‚_|•••ô¢}0c3ù¾»,ù;)7ðá‡väXèêJ÷1qÏæz6­.~ûî>T78áõ«Ðúâš,›‘ŽŒÄHº^ŽVºFR‰÷(À Ïè4<ýÍŠ¯ùè\óÀžW¡·”ÃxÕÏû ÓOh’]ÃüïH6‚ €\1!ÿƒ³þýßÿ?ùÉOpÏ=÷ÐûÁÚ5sæÌé!KîI’/¢säêJ0c4ù3kãdsÉfËþblÉ+† Fƒ¢ÍN/HÖ׸È0Z)Ø Àª¬rܸ±"Å·ÑØ‘âXuÇJÙ÷?КKa¼ògý“Í'ÿ 12òÜo H6$‹Ü}|ê©ö²ð$UØÏþsüá)1$¡`9éî²d;ŽM÷*Ÿ$à‹µq²¹€dó›5{éYzB ðTÔ6¹PápÒψcDúܽfE÷æDÀªòܸ±#űºX©g¶A9º¦›ÿØo÷¾÷¿yêmÆ-ï"¼P?sæLšÅ…ÜeÌËËëBä:J_ž yXgRé|u…“M(ÑMöBé߬كƒ§k1oR2¸f:4MÇoßÝ‹‚òFLÍŒ°xRç©\Èyœü=~ÊçÁŽ&ÇjtbEÎWýÛžƒ˜<³G$ZpÆ4"­êŒËŸêqç¦s=›àez²í¬QsÇw 55>ú(ÞxãŽ3›`bd’饻ìã?Ž7rφ]åzJ^È6H6câ숰Òt5AÏfþ¤|oÅlæ©]Èy0’Aσ¤‰p¬F/VôŽÍÕ&¯è‘¼Ò ϼç¬îÚ,.$@ ¸ÆŽæà$ù6ÚÜF#g6›óŠi`9³!u+šœ^¢€kæŽÃ•sØó,qÃÃþp¬8Vì°Kžk½¢wmvýDÖö,ùº»Ð5|À;6œ“M'„F{ès0„;Ë’H=_@¥?ºãXLò@úÒñý¹VôÞ:æ}0/¿gÃǪ¬È­ö´Æb*%F…˜0¥ß»5!@^E¹gs=²ÒN·÷!¿´ž.üø1Ñ4}MçH4àDÀ‚R» ÇŠcÅŽ»äùÐ+öÑ ?IN6˜l‚*AH‡x46‹qPZr>÷Á¾4+Ž;‡$'›aB6g«nܸ±#ȱâX±#À.y>ôŠ}4ÃO’“ '›.Z¹¿ åu­Ðt1,ÉMïøþ|¼L¼v#Á±ºø°bŸñð“ädÃɦC+ßÝž%ígGÛS÷-¦åÆýæXq¬Øè[R©­…R]¥ªš ÉÉI“’!'ô_ßê\ô}®ŸÁɆ“ Õ©Ã…µø`ç)d$FÐBn$u°´Á’éiÔÃá”ýõãXq¬Øè]Ò{àÜŸ~ È2¤ˆ*¤¶´Šë²e0Ïœy¶]œ×ßs²ádCný®Ó8pº7,ß‘m:XÚ '=+—æp² áÕädÃǪ'V’b´®YcöDØoº©‹€óƒà/8‰ðÛnƒ!cl“ ÁgÁ/ƒI6IöæÎÙÈ÷$4ù<ØÈ÷«W¯ɇÖWeöÕýJ’“Í(%½±š³ð»!ÆŽ°ŒìÚÏOâX±£W² ÖÑáFýãXq¬Øè*©:hyë-XæÎ…eáÂ^ãÙ¹ž}ûq÷ÝââºÈôG6W_}5žxâ <ÿüó4ù&¹ØÌŸF’uÿNxÓM7ÑlÐçªq²…dÓ¹BgPQÄÄ)g=!¬½"h÷¶ýp)¶*ëu-X!”Pö׎cűbG «¤7ïK¸wîDô£?è÷/ÿÖ… ažÝ^d1Øú#Ræ3¹tï ø[Rݳ»w4ØùÇÉf”‘ ©ƒáßô]_1i[´š#е2—@žý`Ÿ:óÜ›_ôø®sin@Ù_7ŽÇŠ®’m~¥ÞÈûè÷Íyrll7ÜÀL6d»Œx/¤4)˜FZçm´`ÆhâÑœë*ŸœlFÙ¨EŸAÉ{bL —µ×®P‹·Cùòu)0^õ‹~˜x8uMnXŒ2-mйX7 ìæƒcűbG Ùlص®ßøF¿hùßÿ…۵׆D6Ý$˜‰'v©ÐÙˆ;îÙü ÑfÔÓ›¡\ iügÜCg©{šáÿðû€Á ÓŠ? ZgFVƒ‚á‡+Fé;È>óÞ%½‡½ýsD?úh¿j|ùeX—\ óô]äðÈ#à™gžAçsR׆´`}šàá?  ujJJJè÷Á`òœÎç;g;/îÙŒ2φÖÂØü{¤‰×SG-ßõø:ˆ±`XþŸƒÖn@Ù¡ãXq¬Øè*IîÖ´¾õ YY="Ñ‚’$"-PXˆð»ïîõÎMgÝ¿#5o{ì±^Ïr:G± v>çͳéF,7š››‹wÞy‡2o_m´g}>ÛÅëüûÎÆV*ß×ãñ†eÿ1.gÐÝrÊÇŠcÅŽ@OIrÇÆµe3¬ õˆH#‘hî;vù#ê®Íz6ÁŠqóçϧ‡M¤ܶmÛðä“O‚|×Ýë9'vuínÜ”CoC«Ë‡ÞV 12 òì „§°?°In@ÙáãXq¬Øè]’ܵq®{¤à•N…´ÖV@Óa_qs¯wlζϡüý‘M0^›D5ïæÙgŸÅÚµk;ÊŠ²îádþô¡7Í‘1f< òš9ì(³K†ºìOþJ’÷ÁŽÚHŊܹ ”–@©®¡“•“aHÏèq·†‰ '9ddŒpˆíð`º“Mçxï;vPRêÞV®\yáÐ…=›jöÁ\¶¢· ºh@ &®œ»GáLù”8çÌLöêºçt¶Ç!!Å@"6mÚÔ1;rE‚D=o‡{6g·ð$ÂLo«†`…`‰BQIÅ·¿Öù,GŒŸ­î$SçÂpÉ#h¤þë°ûÄø<\êŽÕðŠ}4ÃOrHȦó4;ŸÍð3›s£Ï~ÞAä‰äþLuÊHŸr íÀwì@ ):š0ê¾6ø?x˜~  [iOH˜Vü ýöõãXq¬Ø¸8$Ï+Ù¸nvvŠEîл4Ô#‰ŸHtwZåXÄÝò“ôuï%êþ)±ß–þ¤ãë q±Dªqʾv+Ž;‡ä“ÍÙÀȺ¢§{šàÿ°ý¢W‚÷jÜn7,S ×ÖOaHKƒiÒ¤v/çÄ ÊÊ`?bÍô3ãÕ¿ ‘iÁßÒÏnx™nÇõ׸e×fŽÇŠ‹C’“ͺÔÜöêÍ;i+9Ùr-ü¥XæÏ‡õÒ%TƒÝŸo‡gÏ'dÃ~ZýiæáI^QçlœlÎÍ‹ÏɆGŽ;V#Y’“Í"›þÎ]\ÞŒ‘wÀwò$lW_ Ó´ÜvÏæÈa´mÜSÎ$„-™ $ ·Tvèlwââdsn^gn@ÙqäX±c5’%9ÙŒ ²!ŠÖ#¢¬ö úÑjÉ€-ö:¸wï¡Ûhrr2ÕK¥ªª}-xY @w7B÷4B°%A°D2ë/7 ÌPñBsìPq¬BÀj$‹r²adC”dvVóÿ ­ît—‚! S<,Ö8´ì"ái&² Áh„¡AR÷Ô X¢i!5ÃâÇ¥³ÝɆžA÷ú ÅÆÒÔ¢Ý>¨gÄ >ŽÕŇûŒ‡Ÿ$'›H6D´Æ3ðoø%!,-- ¯Ð5è†DèªQrÂý)D«µ‹æ‰É3`ZùfÈÚØÙ¸µþc ÅÅ=žõ݇ϊp¸e_ŽÕŇûŒ‡Ÿ$'›J6Êþÿ…zf¤ôg}Uÿý3JNA6;`H…¼ úéÕÔ:H)3 O»¤T´’ÿ `¸â9È“nI#ƒÆ¤Ïh}çf L990¤§Ã_P_þ ˆ`¿9´çv7 ìK±ºø°bŸñð“äd3Bɦóýxp¼ô"D0Ç•AŽhÞ3¥øKzž#å|æORíó¾µzÃÈÓï†aÉCÒÈ qó> צ0Mš Ûõ×Ógø NÒ;>bX¢¾7p6‚¾:æ”}I8VVì3~’œlF(Ù¨ùB9úHã–C=ý š÷ù!Ê›è‡!s2=ÏQ wHóž‚ù²ûPUï„aý·`j8 ÷¤»‘xÍ„¤‘œlØáâDÀ±bGàâäd3BÉF«9ŠÀç¿nz~ã*‹ƒˆ-¼èkðíZ )p ¢Á1<u‘ÓPßЄÿ)ȺÛ"ïGMÜr|ÿÖ9̚ηј¡âVìPq¬BÀj$‹r²¡dC”ŽHS½E£Ó >ÇÔöÌîzz—F49a‰/ š¦C#eø,Ix'õ·hlõ`á”1¸lÖX&L€€r|ô–ršwM°'Bž¼¢ß¾¸GÀ´TˆcuñaÅ>ãá'ÉÉf“MPüë…«®Öøø« H•N䦱IPªŠªh‚!:S«±äàÍÆEˆ²›ño·°y7¡†>ûÖÜÛ«Æ›nûkŸo7 ìF‚cuñaÅ>ãá'ÉÉfÖPˆæÏ!Ìè-t[M0ZaXð(õ(œ»^ƒè®ƒjŠBÄÍ/A+ÛÍ ¬m™‡*û,<~ç&Í Å¸F=¾Ž&þb² „ÅÓ~IYiòŠ>=œPú`t/B¼vä8Và +öÑ ?IN6£€lˆZ ÍÐ@·Õ´¢Ï ÄN€ùž÷¡ä½×—oÂß\ Ÿ!Ž›þ‰ŒÆ-ðý‡|™¨Ë¼߸¦=µÍ@-ÃØó*´²Ý´µ”¹”>Z9ºjþzˆcfðàû½vJ·¯ïyìÈq¬†Vì£~’œlF Ù‚zì=ø·>K38Kã.CEé þ$*õdüÍô]\nÙIþ}¨2NÀį=ôÄ&Í Åð’#gIœl˜ ”P(ë1¨ø¹PH°õi@ÃL˜“ÍH'5ÝU‡²²2¤ç̆în€ï÷Ò46MR"N5›…ÓE HÎÁNe&§0)¬±ó¾Ž¨9w0«d(/“Zò”}ÿÃ·Ñ˜Ñ ]0”õýéí¿à}°#w>°bÍð“äd3ÉæDI=ÊëZ oF–k7l’’® ,*RΠШ‡ÿ†Òr¼ ébdÁ€€) ž¨m:"%3–} †1)(4Å¢¦² R駈—ša‹ŠGrf¤¬Ë»hlç—ɳs'ÔÆFú}°(!½þ4tÅG#ÏôÖJ1×½ñsc·qã}°¯ÕùÀŠ}4ÃO’“Í#›ÂÊ&ümË1D)UXÚ²šj”W´!.&IV…f 0\þ4\ÿü?l)-‡Eôbar"tg ÔFZ,hƒ ƨÄ„[@JlŽHÃ\uMíL‰µÃž½†yßéø<ø25üê…šL ±Ùg¸z|NRéèõ§ ë:}>Çïÿù0n¼öE;X±føIr²Ad£¨þøÏô~ÌŠð½HmÛJëT|àœAðÃI%@ñgÇ,DëŽf¼ë‰BMò8¬¼"ãe/~ù TySiS0ïºÅˆm®Ã©¶Àv¶T Ʊ qFÈBIá)LUöb\œƥõó”šj8m°™ŒÐýþ.Õ@=;?F à0Ì“Sa½ìZªå,‘g½½çã…å}°"ŽÕðŠ}4ÃO’“Í"›º&%›0³ßÙ©ñÔ?«;]p4¶à±+ã`ßÿ[èæd¸NFa—-ÃR`6ÊH¼˜øéûpé"vç^†ï?p%\M­8ùâïeÛ†ˆ¬dX–=oÜ ¼úÁ~L¯{s¢¡UØ  ™TsIéick+4— wÝÕQ Ôù—gà=\ãÄD|ç)*ËyÆÉæì 'vüF Vì3~’œlFÙ8šÝ”­F|7§8ñô¬«ðÆédÔ6´à3Z`-ß )û&´l*¤Ú¶5ûRœlVÖìÀ%G¶Á+‘øýä)cáp4ãø/_BvØVDŽOùú°¥â•÷ó0¡î=,ŽAsf@9,  ¼¼á[6Ã_Rë¢Eˆ¸§ýÒ¦ûÝ_Áµã L3æ!ü¾ÐÏH$‰H#aÏ$"µ£ÀçÁºâ<©‘-ÉÉf‘ ÙF{ùÝ}ôœeQ| fÖ¿V—Í º¦";%‚(ÁpéàÚ[D31“æMNCSYìåg`ŽŠDØÂ0M›Ï¡Ã(غ Q¶£0¤`;¾$4T"K? ¥~o.,WÞóŒ42)f÷n¸?ß9) aË—ÓçûOìEàäAºf™?›~¦7Ò œòÜoAÊXÌü–p#Í c‡jÔ`”‡('›D6D{öœ¨Ä'_QEšç\‡)îÏ`ÒÜ%&{4 Óàßè÷¾#‡áÞ½ZK Óݨ5•€¦²BX,š<*Ú"1QÞÜE‘ Ì IDAT9“bl°j‘ð8ÆÂºd),óæÑ6Éá@ó¯C ‡×ñ  Mh€Ç§ÐÏ ²„¸ÉK`¸$´rœlØmÇêâÊ}ÆÃOrÈÈfçÎX´hñsÏ=‡'Ÿ|«V­Âk¯½F?衇ðÒK/Áb±ôŠÌ /¼€Ç|HQ‰/le½§ä!5ÿe ìá‘Pu6#Ù¿jF£Çuà(/ŠßÉMžuTÉ"Àzû“¨³§àXA1,%›hhAxT â&ÌC Ñ׿O ˜L““ÑÜÔ ›Ç Ý烜2†Ö®!MŽ‹ÃGJ4<'· F© Ÿ5ÈcPlžŽ§îc÷jÈïFâzô¦ |ì¯-ÇŠ«‘,9$dCHåÅ_ÄÃ? «ÕЧŸ~>ø bccñÄOàùçŸGLLÌ€¸q²é¢î•:‹Êª|j=Ü»ö@â ¥N!c,Â.¿Zíq¶¿à ¥Í‡?jÅ—4L©cHïmë×Ãwâ8  ëÙ¹hùüpa->Øy ‰N“{-ªCIM –LOÃ’Üô×:(À 3Tœ˜Ù¡5X…0åa':$dÓy–„x‚dC>¿ýöÛqøða*²cÇ,\¸°OP8Ùô­/+uŠq9(>pö ë ×B0ÙhòK¨*äô Øæ'AÉû ÄôÒn‚ïèQ(%G!¸B޶Ãzß«ý*f ¤jCjjk2i2 ]ä×ï:§kpÂñ˜1>‘~·í@ v-GNz,V.ÍaV|N6ÌPÊל}ÍG²ä’Mp+-¸Fþ¾mÛ6º¥ÖÐÐÐÅË!ÄC¾ïÞV®\9’ñ²±›Ë·ÁR¼Þ1—›ºÒ'[!—Ÿ‚!ª‰ ¡x!QÂbí0FTBÎD ÚLÇ$úœÐ%|7=-½+ø õn4¹|ˆ 3aL¬F’ó¦—öùñZœ¬hÅ¥“ã1qL{žµ}§ëq¨¨ ã“íX6µ€xãŒv23Û¯ ðÖ!%›`w„DJJJp×]wuŒ ³Ç“ÝëÚpϦo•í¨ÔI ?›ìhü¬ ’®#lª Ÿg*4·µ¶š× 1Ì Á_]Õ  í ÐÚ\@ º`ƒqêû,¤ZÐJÇ߃ÞZæý^ˆZ¬‹–Às´¢R¨.¨N²Í@ƒApÃSÁ€®Z ( PÚ"ˆDÿðÿAŠŒ¤#!épHZœ”8;²R¢èŸ+NÄXï­ìýÀÿÝíùhÞ‘m4nSL àkKØ·ÐHßœlØ•cuñaÅ>ãá'9$dC¦Ù9-uF>F£åææâwÞA_^ ‘åž ›Âèmµ(m„ævC­: ɪ²‰Üx%ö(ÜÅ Œ­0ÅUA4‰ 2tC<G¼MýÄËi*œß¯Ëƒ,‰xàÚ\$FÛPÓØ†76F«³ ?¾ëRD‡÷Œt~ðûÃãW €Åh@x˜1?-šP¶5çÄÌŽÓhÂ*´Y/é!#›s1MN6ì(=ŠØSðæí‚RUÁ(CN›ƒá O¼µÑ0Ç–B2{[ D{t-Jy4Í‚°[þðÁ]q'jüðFdcÙ7žèÀkÿ<€âJ½m!ÈœÎÍ{è\Ÿlê’/Íwâee°.\K?A ÝgÈɆ}Í9VVì3~’œlFإξT(hx<›ÿŒÖµ[ X£!˜dž"h>‚¤Àu¢ ç4ºhjpA²ªÓÆB0Û¡é:Ý6#-lÜdÞô“Ž‹¤$ôùÉ›§C,+†RYEïÙHññPª*á;~–ùói¾´@q1\[6ÓÏäÔTد¿Æì‰LÚÏ (LTˆcuñaÅ>ãá'ÉÉf”‘æÈ‡ûÝçá)6Šð» ëŒÑȦ@s:‰*ð&B’Z ÙS'Aš~'¤Ô¹Øð´ƒ…Esb¯}­èIÚ¤ —7B­¯ï¢É‚$AWUØ®¾š^%[jZk+‡£#Ó@ø×VÂÀ©Ã (»‘àX]|X±ÏxøIr²edC†”…Ö+¡VæC×$ˆ¢‚A‡îqBWÛÓÉ肊?’PÉꃔ:Æ›^`i¨Üð\gvá€a1Z’—cñ´4„íÚ[EŒYãa;––ðìÙMÉGe³³á?s†h·Ó°kã¸,ª*!EE!âþ¨\Pv#Á±ºø°bŸñð“äd3ŒÉFÙÿ´Š/¡{š &Ï‚4n9Ä”Y½jQgÃC*d*_þʩԳ#Ó“­ýY ° zùîr¸ ¾°Œ»‘ãçÓ3™´üÿ†ÖPH36“Ìͤ•ý×!LUqï}4˜€´Ö¬¡Ûf¢Íµ¹ ÒRJ(†Ô4srvÅhyãu¸yÿ]ò©uŸ™giq2&õ>Çsõúp#ÍŽ$ÇjxaÅ>šá'ÉÉf˜’oÝCЊ··kŒ ¢ Áãµ/öJ8½µ2Êg¿ Vº¥¦Õ “gBŒËÂâQ|d7NÕù‘n¬‡GŠD£œ Y`zxì#ŒW=!"u@² »ìrè^/Z×­…h2Á¾â˜gϦ$ÓA6ß~¨#¼ºó«@HF9¸ZEM‰–4ò¤›!¦Î’7†PvX9Và +öÑ ?IN6Ãlü[žÉ}FH†lk –(èŠð9!¦Ì†ékoôФ¾ŒõròÞ€ÖT Ýq’†CËSWBÌ\­ê^ÛZ†z5 ·ÅC’è@›7€&§FƒˆqËï‡ØQ-X±ÏxøIr²fd£ìÿ y¯mˆióaZù&”oBÙÿ&ô–rÀ ËÃ{™="¨U„î¬`O„.zÕah•yhjlÄ«•³a7êøNJHê3ÎÔyP&Ç‚{ŸéB6ä…M‡ÿLa—ÐgRX­s#g8¤¬¹ïC2Oˉ ôÿ½µî EIvŸþbüD–þ䜿5¡åȨ'×CL»RÊ,¶x('ÖA«<yÚm&^ßëøBéc°ä}°#7Z°bŸñð“äd3ÌÈ&°ëwô_úzK`´A3°DCÍÿ°ý°?uLwü3Ùø?ú!tW·På¤9P‹?§yÒÞ¨›ƒXo!æbì‚ ÍbœŠ ‹;ýR/{º£¯¡xa•ãë _iò ºeGúHkÚõ̧ÔÛ‘s¿~ÎßšPæØñºÝhXð}ZŽ4â}*þ 1e Wq²9Ë e=ÛÕhéc°ó¿ãd3ÌÈF-Ü åÀjhõ€·µ=8@õAWdL÷o‚ÝÕK ÆÏqæ bÂ$ºõ&e]­êÔ@­ÜˆíŸ“’ÑJet· º&ÃHÇ™ØepjùHpÇu ŠÅñ°ÉÜeþ‘&ð¯ƒúÎ/¬gçNz†Ü±!U@CÉ@†zi¢PÜЪ’Vmuº`½íEà.ydH‚B1<Ê¡·¡žÚyÚít›4âiª…[![yÖýœlÎÒ’…²ƒíj´ô1Øù‡ßq²fdCôéatK%´æ2ÀÓè*-ãlúú;c¾ªÀI(°÷ÐJwu6£ÛfÍ¥ÃâA.xêÞ ›¡úí¼ÕÐn5•Ž\øv¤fì…&IøD¸™)13y!R¾­á äY÷Á°èÿQ] ¾° ¿z¡WÝeɃÖÛù‡ÞVÁÖJ,Ðôt†â% Åð¨¥;¡ì} 0X ’A&;=C#^¦<çAHc—p²9ËE e=ÛÕhéc°ó¿c"’µùî»ïî2Þ«®º äs–Š›ƒèÅšŽzr% h „èL¶GlunZí1¶ÿІèyH7 ÊÉ¡y‡ä¢¡¿Õ5•¦¡Ñu|Uvˆ¶èœR, ¥K`9@vò6H&?Œ1r}-|ïzs s¿ ù’G:È&©ºî;h4R*šdŠf̓Ü2#g1¤¸›@±l´º“²¯†”¶e•H›0F¦ U Õð½›Îã!wžäYßèsˆ¡ö1˜¹ò>ØQ-X±ÏxøIH6¤\) °iÓ&N6ƒ\¿¡Rtõôfz?ELœŠò1·‚nòöK({_… CÍ}F¶Ð”|õáÐcš üðšà6'ÁRW‰CA£wyDk4õ‚H¶h<0^ûkHã¯ê ›øãÇáË?ASÓ˜¦å¶{#Ÿo‡gÏ'dÃ~óÍ}¢Øó*-EÝù¢¨rt Ôüõ婇 «ÎƒLää’+Ùú£3i°Em0}„ª^¼vÄF Vì3~’ÌdCªköWÂy(¦v±z6¬XªEŸv”{.»’’MàÓçØ÷'æˆ S@¼ÜÐ(î0¨²†VÉ‚ãd$ ‰ÞbH¢‚¨A G% ÐIÑ4’˜éúßRÏŠ4òÂ^¬dú&A¹ÑbÜø<ØWþ|`Å>šá'9 Ù!“í²ŒŒ N6ƒ\¿¡RB­ö8Û_Œah²f!.3$tZ-ÙAïãt·…^ Õüfx5#|ªåÞ)HPÈðýh”,ÐdñF7Œ¢N‰‰4yÚ0^óÕù ™Çm£uŠFêÒºCµgë=…ª^|숬Øg<ü$$¾vö‹6EFl F„Øñ2z¯øü× %¢ƒë$ €x3$r¨®zhÚ¿Ê;‹2<~3ÚÜ1¨«ÏÉæDlôÔË6`Dz‚‘öHèÍ…€Ë!.¦^îHWS¼o⛛Ѷá#  i4Û34¢ÅŒèU?„Û/`½˜nûk‡÷ÄɆMç£WlOþJŠ÷ÁŽØùÀŠ}4ÃO’“Í0‹F#*Ò›A&áÇ$¸·FnÛןø Ñ6cûùÌÑ5Ðëò¡¨*Zü"…Vèà„­z\º-J v˜—â&ÃÄJÑŠH8åe¯ºaÀnˆr¤1SÛ#®2—›÷%ë×ÃjµÒ!ø‹Šh.4’ݙֵ‰Ž¦Dc¿ñ¦ §sè39ûèœç|¼°¼vCı^X±føIH6rÈã™M[$ú‰DAõÖ‚F!¡¦{Qìð!Ü_‰p¡ ŠhF«9›['!Y¯ÀcêÇ\‡[ø7½æ&âs5 å(ÐdlÃ_[1ë*4ÿñU´Õ×#jÎJ&εk¡µ:!'$ üÎ;á/( ¦)Sa»öÚA« 7nìÐq¬.>¬Øg<ü$™Éæ§?ý)žzê):ƒçž{$``¨ÛÅH6ýElIé ÏûNŸd“÷Ç«8¹RL2Ö×Úi9…ÅÒ>ȲD3÷$ À‡©†Ó¨ˆ\„<Ûõ¸½ê'ˆÓkðrà›Œ@2As‘Ú78M)¸qÁdŒÛ±‚ňÔ¬‚/?® Amm¥uj"¿ýÔÚšMŒˆ@ÔC½‘E_¸eA©]†cuñaÅ>ãá'ÉD6‰&8…óA8œlÚëÈÃû#›âcǾá#ZûFo*…`BI“!ºY™ýM,8áIB[“ÉÖcçÃ1î.Älû+Ô*|)OÁd{ ’ÄZ›ª°ß5 ‡ä)ˆŠ ÃM¥»ŽìÇa!Ú>xŸ“M?ï3'vcDZbÇj$KH6Á{޷!D§­^½š_êd\ùP^¦Án£•¾ñlõ3S!86@SL(=-¨Gxb94“ †tœöÆ!A-Ü1"¬ ¾K·åZÿïïxF#V$"ଇ±ÎÓþÈKš„°¨pÌ;¾ážVä\±¦±cѲú¯|“ ãпX(ïÇ`;-} vþÃáwœlFA€Q¤²_þa¢’Ê_TòirËúC** ð&Õ"Vh¡L&#ª“–¢9ç¤ÆG £ä(Ö‘£ª %"UW¼(Šƒ²¬iÈ’¼ð•”aBÅ ¤G™a‰†R^ÕéüW€€ Rts€@Š?ZŒŸ»yãX±c5’%$2¹Ál£íܹ‹-¢ØtÞr >+77ï¼ó²³³ûÄïbÜF ‚Áú”/záW¨VMð/\†¤ âv­ƒ÷ØI”z%x`‚é„Ï€RÀ Ý< G2ÛË/ßyùTmù ëŽ;`Ð5„ûÝ0AEat¬®ÜáÃ,®f¬ë`¬­¢©jHè3tº¢@´X¾ }VýP+ò ·ÕB O¡—CIJ–Æ  Jí2«‹+ö?I&²ñxãá'ÉD6g3lB·Ò‚ÛhÓÞt&!²•¶cÇùîmåʕìá8VOÿ÷y dIÀ­³³}3”Âb|aÏÀ6c,µVÜgØ…¨:?¼ª€ÏRâQäOÆ¥­âT¬·ŽƒªéHвÀ ‹¸tr<&މ@]‹7íES] ,c1kñŒ‰±B^·b~>´œ(+n¡ˆ§OCÚ½ zj"ìÒVè¢ÎY?€j‰ƒÔV û¡?@дÌù14Kÿ†#Æ|LV†:ûë8†£Ü’Mp„DJJJè_ƒ9Ö¸gsnÔ¡º¡ ÿ³þ Ì’‚ß½ þ‚“ô¾Ë1—ˆõ躗ºc‚Óƒ:ÉŠMQéÐ àÃðEÎÂôº¼°Y° ˜”‹1qátpÊÞݰç‚}ÜXdÎÈ£Î[IqÇ6šyîÜv¹ª*ÊÊ`™ž ©y-„ÈT¯|¾c‚þ Ao«ƒñŠç Deô;qîÙ°ëÇêâÊ}ÆÃOrH¶ÑˆWôÄOàù矧õnˆG$~fÓ»ä—Ö£ªÞ G‹qVÌÔ›`¨¯£Â†1c`š2¥×zý ~õ÷Ý47ÚÍK¦`Þ¸Xýê¿Pçh!‰›áö·ÁâóÑ8´3æ8·Ä"U¬ÆM¦­Øn_ˆ/­×#9Ö¯» –Ö3˜f8—…b5僄’|̈”/çk†l0A)*‚Ë£ªÀ 0Ûm4*-æÀ÷þwé8åéwBšp5‚™©Ijãÿ=àÀ è€up¬.>¬Øg<ü$‡Ì³éÖ9 €G£õT‚í‡K±ýPYÇÓŠö#¡©™I‘%‘~nÌžHÚ{kä÷ï:Ù‘·ÌêsabÙqL€4¶¸P„jC |Í.„›q§ô,lÆU˜’jC ¾WGžD*Êp²- ›[s XâaOƒÖÔÍçÇ%f'‚ZC=2« !« ŽÙnEܲ%t|Á{BÝÇÉZyó\Pÿ©SP›š ÅÆ@NH„h³uιèc W™÷1B_}ϱbÇj$KÙœ P.†G³ä€ßd”°xjâ«‹àÛº¥’†‰“pinjG´WØ•WÁ<}z¯ÐnÙugêÔ6¹•…”8;¥…C9ú!ôŠ-’'bužN·i†zd[8l˜uŽš9VîœaBÖä™xuKµÕ˜^…œ+ïÃé&»W¢¾©Ó•&Ì«/€uìX4'¥ãhqbëÊ1ê êºkéø´ò}Pò?€Þ\N„èq]’mö§gkxZV¯†R]Õ¥‹°å—Á<{6÷ñRžíz°tÉû`AiäË É6Ú¹‚åb ›ƒ§kðá®Ó—…».Ÿ‚¶Ö£!ï vQ5?¼m~G´—iòdØ®»¾Wxû{aIi·/€‚òˆ‚³Q† x'â1œ,­‡¦(XSŽGn[‚–Èiøïµ_ÂÒzߌÝðù÷Á—´€âáÂjÜè+éqË.…õÒ%x{Ë1ˆûvcž¯ñsgv_ ¼ †ä€dclgcxÚ6l€ïØQˆöpzHø(*¢=G~ó[435igÓã4x¬@ñõ©‘-ÊD6äÌåî»ïî2S–h´³…æb ›§j°~÷iL͌NJÅÙhûðC´>Š-éðdf㑳á;rm7”3 ¶n™lÈjw½­Ÿî gW,_Œ/…y¨t´bóþbh>~:~ÆÝð#Ô ñ”Xlî|+j¬sï…šz)^y?NUQ²Y`t!æÆëhYèu_ q_–µžAüìt|îÏ>ƒ¿ð4ÔÆF²†qãúÜì>™³!‚æ?ÿíÓ~ë­0ŽË¢nyë-(U•t\?N6¡½•g³¬=ñ>X‘Ùr’ /žvö ÜßËTTÝŒ·>9 ‹IÆ”±qH+? mß^”숗ܬ„Žh/ëÂE°,\8(²!?ú·—7¡¹Í‹ìÔh\6k,¶î/FAy#"'þ+ëˆ)3 ÅMÅï¶ÖÁÕÚŒ)au˜µì¬ÃÓ5hnmÃ|_ r›JaLOC“5'¬-õ˜j vùRf3\[·Ð1ÒÒh”iý9užÐÙžÆß¾ÝïGÔÃßë8§!‘y$B/lùr˜gÏád¢:ŸÍz°vÅû`EjdË1‘Í#<‚gžykÖ¬¡³%™Ö®]‹o}ë[C:û‹Á³!’­¨3•MX^±=L IÑ6 _mAÅüøñ>ñfya7ì)Äß·ïñŒ;¦à²Š 5”Ó’ÐyÊ$|Ɤ% 1N†¦z4IxÌVÛqwÕê!‘¦©:dY¤[s˜61yŸC÷úÐ G@@¼±¶VÌÑ6DÞ¾òÔ[ûÕ™Þæ¡{<ÐÚÚ ÅÅõûÛ–¿½ ¥¢„”ÙÙP[šáúd34g+"î¾§=Åß¶ éeѫ؋0ïãl¿l‚©jHÖgÒ‚ùÎø6û³¼LG‹êp¦ª >¿Š1ñvdUŸ†ÕÕJ;‘ãâúôh‚£`éƒÈn?TУŔÖ4#=1SÇÆaQŠ÷Ÿoƒ3Õd´Uf¢)`‚(h¡ã„} ÚÆàêDfg3Z=„G!³úŒJQ­ˆª E”`¼P,H>«€ðÙfÈáBG èÞì<µ¾m›6B©¬¤¢$¼Ú<-·O,ÈQëßÿÞã±Ý½*V¬ØW¸§$ïƒ=Ž;V#Yr@²!“#a̤¤¹7CîÏi¼ž û²÷—©íÏßî81aäœeÐêËàút3j["‘oN‡5e ægÆ íÐA´´z°!r"²'g¢*,õ-\-Ö#¹ä­‘0Ø I× …Ë0 nøDôº( éëÙ…è/ :ˆ•îó¡åí·@‡œûˆQQPí÷ŽúÛN ”Zç Y¨É%ë²e]k¸¯«fñy°"5z‚BØg<ü$$r/†¤` 7ÿƒ^ KÆæs1Õ‹eí\`u6†Çùë+¥ Æ%«`ZО¨îÑ;QÙfÀ®ˆñ¸ì²K0aé|T¼ù¿Ð‹J°ñ¤ŽEcdÂÌÜ/UB/*„'9M›>L2GÛIñ²NIì)"þæ kÞ Áiò-£3!Ži?Gé‹Ñú5íe n^A#ɼy_µm= ¿ã냆íl°bí”÷ÁŠÔè!‚ó±æì¨?É~É&…F¼˜åË—w ™÷lØó|(áÙôá|ñ* à„ùÖ—a˜Ðnü?¼Ž–6DÍ@V|fëMT–ÃíÓ°#, ÙãqÊ‹p«÷‡5@9vþœi8½ö#„«ÄGŠZœìhr*pI2²g•@¯ IDATVªKÄÈtÚOw/'8ÏÎpïÜÑËiøÕ ôwý_ ´2gƒÕ@ÏîNš¬òƒ‘ãó`Gm´`Å>ãá'Ù'ÙÏjÈI9к6„€^yåTVVö[fà\L•{6ì(†ò2(©ÇɲzzFDÊ $ä¯Åÿ1“!eÌæ(†'ï|N ûÄ ¨7E ~$¸!i*öÚÓqË8J«› ¨"£lˆ÷¶¢Æw~>Â"Ì*ÂЯ,Ãå—a5·"ir-É)çFè®z¨%_äi·CÊ\1unÇý”@I Z×¼ÓÅ‹ æ}ãžM»^„²æìšÔU’÷ÁŽÜùÀŠ}4ÃOrÀ ¤öÌÂ… é¹MlNž<Ùñç¾Êœ‹©r²ÅÃ…µ´4@MãǦ`In»ÇÐW#Dóîöü._ë'²j6âFS^Ççš_‚§<~Ÿ²nrö¢Ù€8ŸF›­&T—‹Z³úÜPuX´|e |GB ‡_Q#Qצ"«üZÂ"‘Ÿ6JÁp#ÌÝ :]§õq  4¨.IDÀ–Ý S’ÆeO ðåë¬Ñ€É½¾bâhŽˆ)³Pžt%Ò‚žLçɰÜ×ñŸ)„ÚØÑj9)© Cµ;á} ü^%8VìXdÉ~Ïlˆ'óÞ{ïu)ßL*lÞ~ûí¸õÖ[iÅÍ¡lܳéÝýÕøhO!2#höêêjÔ{$”Ô´P"éËÃ!¢I¦èïÞ4 q‘íä$­+çdbþ¤”ŽN][·Â»?–9saž7o}¼YŸoB²³ú´´¹á¬uÀïw#µÍɨ@÷›¨$›½Ð€h±CUÂ!Š~D.Ö€0ô¦’öª²™Vô”gÝeÿ_£•Ó~ÜA6d šÓ ¥¶ºË)!rbWâèŽPÓk„ÖÒÒåãîÑkܸ±¿µ«á…ûh†Ÿd¿d$–Çw9F m!‡â…}ÿ‹)ªÃ¢©©X>3ƒîß—4‹Øq´9é±X¹4§×Aþa]½sÇe“‘ZšOÓ»¼[©¢D1âŽçabZLÇï:{¢ÕŠcE5ˆr6!2à‚!"š× ¢!ðêzaŽÑh2‚K†ÚVH2O&4Ÿö^ÈaNèMÅÔ{" :ÅÄ©².G`ÇK"ÓP‘õÀ =÷öÏàÙ»RT ™™4ósGn´ûè¸:ëÑlÞû;±bÇj$KúÜ=]Íù¸Ì”{6}«ÖÚÏOâX±7,ã)Ù´¨VšÔsæøD\¿`|¯?þ`ç)sSÑ)$I´é"ªU•ýö´(¤~íæ.¿#Æš¤Ÿ!†ûTy=,âüm 24¿]€ðÁ )0D¡´(”lLÉF¨-µ0%àkÍ„æñ îßÿtÇv(Þ„ ÊârèšÞPŒ[޲¨KÛÏlÚÚÐôÊèXyf ƒîõд3$ýL÷Öü§?Amn‚íêk`š6~Ý‘íê«i.7Ò¸qc7Y«á…ûh†Ÿä€dÓß SÙÒÎçºq²éÑ` ²F*kÖ9êà‡™n£Ý´pÍ©ÖW{í6 ¢¼‚ÅBoå“¶ÜèÄäæRôVÆ@WhMMX½áKXÊK1³ò8Œ²’FF (ŒšAÔ¡k"‰ € i ûiR´ZÀ )"ñ¿ú5íK=³Êþ7» OLžyæ}(®i¡dCÎ]œï½91÷ÞGe½‡õi#ääDtK K¾oüÝï(uÎFˆÒ»?¬Ë–Ã2‡çF õådÃŽØùÀŠ}4ÃO’“MQQ—3‚¡X¢Þ”Ê“ðcr@Ÿ‹ñc¢Œ$ë>¶çÞü¢ã£`€)ëüµ%_m¡µº|øòd­Úæ ÐBhOî…«ÅmÜx¤ÎŸŒ©ÙP>ÿî=ïÀk“Ð6v*œr|Q³ ;Œ(G%¬f# U͈¸P˜21­õHn¨@$ü047‚„  ‚´‡ ’~fãØX_ 1~ [Ú=O#„˜,ˆ1íYš{Ï&¯ úæ°”³ç!~V."ÚšàÙ»‡zD3AŸ£Àû`“8VìXdIN6€l>=XŠ76ê¡7_¿l2®ßntYñpêšÜhmiÂô‰c1+û«t§Û‡—þ±>ª¦± N§‡ž³\á<ƒ îZ‹JÇUq€ý²…hØò$ìžVø f4™íøü°·˜ÐØ0¶c(q‚f‹­Þj§/„2u:ÒD¢?ø;”º:H?Üfg| ˆóùíqAu²Y‚dhdZÄ54ÌYÿ*!ØIÐðóœÉIÉ»òJÈ _y6)cÑ)B’”9 ¹åHuSR唴ΉL»ßËáÆU»ø–#;Rç«PÆ3Üd9Ùœg²!‘`«þ°mn?fg'anN2öåWüËœÜÆÿêk¥#½Ð|–üÒz$ÇØpª¼JmtUÁXÝ…%®TV #Õ~6¹B jÎåø¢Éƒ9µëaW]hòÎBmìt”{ñõÅô·ðû!żcíd¨¨„ÚÔÛ×VâͶ(´5¶àúÚCˆ*8è~Œ:ür8ÂI´€e¬ÖÅK Ïí™5<8R* å­Õí¹Ñ$™n÷‘óÒ:ŸÉU5á­ÍÇ:*Æ5V¢qÿaøËÊ ËôHÜÉɆ]Å8Và +öÑ ?IN6ç™lN”8ð‹·wÑyÿò¡åˆ°¢¾Åm|Ï=°c“"CÖ”ÞŒÂoÖì¡[g“3âpìT%Rª ! ¨LÌÄUÎ3Ð z=HÛ‘\¾”ç@þ·_ãÿ<€[ ~ ˆèâ8$.½ o:ÌÈ<ð)æhM0ê*­[CîßFî²(uµˆ”VÖ#Lñ"ÆÛ ’LS ÓQoÃÃ4ÌóÕ@pTC2{aL·CžxÍyÖ¹FOçyФš?¦õ|H#÷e¬K—šÖ!êûÎtT:%_/®&X|ûº©Âb»àÉ (»zq¬†Vì£~’œlÎ3Ù”5àg«wÐbi¿yø جFêåüð•Íðøüç=‹Ý)ü˜Uez3 ¿_—‡ÆVLƒ]‡‹1®²‚ÉŒÂøL,˜š†²Ow ¶ò ¦Ø¶aw¾1j,žùË爬; ÈbÌ™|¿ I‡v`‘Ö£$À2o>,‹C$êu\fÍï½ WC3H¥³€®ªÍ:NÍJÇvÛõø¶÷$´/>ƒ «cŒ&wL/˜ë¬·yhnwû=›^êÙt¯tJX[z G?ü=RÔbd&EAˆyÒÍ4iÜ€²jÇŠ©óƒU(ãn²gE6C=™ÑF¶ÑùíFêÅt¯˜I¼ßÿàêöBd!¶Þ èG»Ocÿ©zy³°¢J}=ô@€Þ¬Ÿº*Ò< ˜*‹¹ J\.6»rQTÓŠè@=bõfÔ¨ið죴aAkÆ£¢vù]<šËìoo£¢¦¥IÈò6 ¢¶”–7LG}b ƺ4(µ­€}éZHÍwâ­æ¼x*t¯tš`õùcÔ£3Úc¨—¨»Û·ß —>1q'›t+ÔõáÑ¢¼Á 6ò~3 ÙïÙühC1 ;|£‘lÈÿºé6ç÷Ж+fŽWµß µõöÂ:šÝ4;ùe½n ðÁ$è°‹*<ºˆKŒ.dLˆFlÅ_PêÅvÏT„ mX,@ŠË‹Î8lNDŒâÂU­…ð˜ÃÐ4&ËV}áa&:LòüÆ·ÿã½P$5² ¨ˆjk„‰” …×LtM‡Û††3!'å"Mˆ‡gÏ'dÃ~óÍÌD()†RS òÿ¼úN+fÔF%arÛd¸öá4²a÷&¤Æ Ëñ!ÔÒ]r®‡<õ6æ>B]ƒÎòÜ€²£Ç±bÇj$KH6ÁìÏ‹/î‘#­¿‰“ÄÁú7=ôÍMÚªU«hñ5Ò‚Ÿ[,–^5ZɆLöÀélÉ+†£Ù…¸È0\>{,½Œ9ØÖ× KˆàTE*êœhtzành¢‰;þ¢-Èv±ñ-Åðµ•ᨃ)r &È*ãæÀXQŠ<á$ñ ¦ú¨‹¡ç>†qYXµr.¼~×ïÄÔÂæqÂe¶Áa‰D”êÁÿgï=Àã,®õñw{“V½XÅ’{·1¶Á`L1Å`j †ä:‡@¹„„„üBháOîåBð…pM0 ¦8´›€ÁئcÜm¹KV—vW+mßý?ç¬?±ZíJ³*«]yæyò8Hó}3óÎùæÕÌœóž|[4~ÅxB¥«Q Žs¼ Þ,Vê„Z3Åž<&MFÆå— A,Í´¶/vM>ÁC/ DuŸä,A³>,Jzñfÿ+Ô…¡;÷×Bmôu”çä*Ž ÄJ«t®Ù+ÙD+(ƒíII€ž¡Œž”Ù“„:I°“?ú‹/îòóÞ€ÎdÓÛØý½è«(LUŠÉiq¸ñþ;ŸÂms`þ¨l|ì2¡Ü\åøÆF|®ÎÅs¡2¾[º¸\‡rOöÔØQe)§Œ‡^§wí»˜Öz–€®Ü"ìÑæ ¸ö ÊuP©ÕÐdg£#à„Æé€*¨ÊKá)Ɇÿøh;‚0ZÆ#ïâËùXNd¶§žB ¥™5ÛtcÇÀWu®Ï6áˆK…ƒ~ÌQo†zòU8–»ëwÅhû:\lÝ ÓŒ¯C;åkBm$Št}‘qÈ6ÂH¬úk éñü MôÐi—søðaÌž=›E<­µ 6pú‚xE’MïFDó¤oÖÜҌ±㺹ùF¿á±W?ƒÍéæÀO"*+|‡Ûƒ8ïÌIØPïãŸYCÓXjµu+&Œ%g• ¸ú¶oøokJ‘=ªZc·¼)z,Z÷…IU®ý^h2,(|àwؾòg°Ô8`®Ë†.;Úâ"Ø«÷@íuB•UŠŠZx6lO.iµe]¿”ÿ%ûÓ+PUuûÆ”b¡u23,ì…¶ŸîªÚ0"Ûˆü‹~uél¹¸õnV5$ˆƒ• ¬Ä{“z5{%›þv™ˆ†Ò( ØÖ®]Ëw?Ñ»"ª]/§)–¥;ºg»J¾(5|'ä]baö܇Ðáñãê3F"ß¾sY÷Üì÷èpî¼ñPggãÝmµPµ´@e·¡]­G»% gL+ÁyÓ‹¡Þ¿-mÆË†1°–¡¤z?Ù¹Bk4è°;¡owB 4e |‹¯EóÛ¿¥Å “"49ùå íà&hÇá8k1 &Åÿ£#r*§ÚU¯"d6ÃåU€Nø|оþj«›ñîè¹øúøã(lø€kjó ÝíGhìBd͸Bš‘D`PPÒc j#iúr!²‰â3L™Â‚›žíÛáÙµ µ¼Vq&{Ú+ÔÁÑtû޵ ÕoÆ …µîCvÃáp {ì\NâKÅ` ¾ådü•+ÛŸ©á‚•øˆS¯f¯d£8(—úô/¥ˆ¦B»•x—û” ‡X>2ñšrwC?£ô+V¬À}÷Ý÷’lâLÛk¯Á»o/LsçÂ|ö9LêtÄéÙëirxâõ°¤‹R(ZÒñ/Y¢¦) EžÆR]s/:7à¯Ç[;ái‡A´d@3u.½ät~ÅG<‰òÆÃª5Èì°Cð#h0Âàó@ «@«uZøCv ËcÁ1èáaõ´1–YKXñ™’§‰, ä PûâË—D.ää2NqKÆsÏÃ*›‰½ã"Ë… ˜eÿBöþ±¢#GÇwñdsúû©ŠŒC¶F@bÕ_KHç{%›HeçÍ›ÃiƒéîåÞ{ïÅã?3Sg¬<8Ï=÷®¾úêNo4‘œ8’lâ‘sõjx¶oCÆ é|ú`Km Ÿ+ž]ñž¦EšòÞЮ&'Ó‹Q‡§ÞÜ©µ?j §ž}êXÜtÅ,´>þ´ì;ˆv_Ð`²f £¤ù—^‚Ï Ç;ï¢õÀ!š0#öl†ÊÖŠ€Nœ¶f„T*N­Öª¡Õ9 µ6ÀTp*OtOh´PeÃpÕ8ìÉéUµª¦ï¬zŸßŸÕÞÊpÇóÊqé•g!Ëbèô¾³ZôY˜…‰žOáßþÔ¥3¡ü5=v%mŸ#Xó´Ó¯…fâeþµÊTR‰•8Vé\³W²‰ÞÙ(ƒíÍmy @‘dEÏÎp¾ýOÐ1𶤠Èñûz=F‹õÆ»Ÿ^‡Cµ6Œ+ËÅé“Jp¼É‰w…ßÄÝçW ÷Ã5P pŒ’‰áÝ»®O>FÐã¶°0œM³¡j‹ÎVðC«ÕBë÷Á¯ÕÁB«r!ÓÚˆ€WCn5t™¤My£ÉÿYƒPY Ñ‘?ÖŠéÐÍûiÌÁûA–Ó!e„3¦”b|Y“ËÇ»jk5á¦+N…VC_߯eab9õ;œ¨·ŠàAø·<ˤ›wÛ@˜k—wÈTR‰•8Vé\³W²¡Á U5I6=›VÛë¯ƒŽ”¨(GCôÿéQüᡷ8^&RuúWO®EMc¾WÂ)õ{¡5 M³fó®ÃýÙ&´>õTZ-ÌçÇÉÍ:Ö¾oC'=#ÑffpóÚÉSáØòtA'2FTÃïȆÖÜS¥‡½ÐB{ ø€ ƒq<ŧÃl§«6\ûl·!4´¶3Ù”¹[qõä\¨kŽ!‡7:qØRÀdS˜céòœëóì[íôë ™xi˜l¼{àß¾šñ ¡=e‰(TÂõä* •Ûíߘ‹cÂI×îv=_®«R‡Ù5;`˜< “'3ÙÐ=ŽýÙg¡ÉÌDáÿçútIßòÌ3ðÔÕ#¤ÑÀ˜aH-?¶úfè-È*¨¿-ZS3ÌS³ Êàá …‚@À hôh¼ø™Î#.Í„EÐÎøF—.“ÛöʧÞÀ¬#_ ¢8 j• ÁPGêìØ4j6ç–ç‰,#áÿt9 3qÞœÖÖVd›Ÿ ÚÓo„¦BÌ®÷Ùøª†$q´$VâX¥sM!²‘;›þMqªLþý#N1]Q”…)£ PÓèàÔ^7Ÿ;ã7þ“Ó2· hâDÝtïØý˜±Èûå/Nãüø`Û[…v‰õÓ²ZêÐa0#ØÞ­Ú…ìÌ㸠Ðg×BŸÝÌŸL4a1¨2 Q»è•¯Ž¸FL‡×s KÒ;\ÐýøqØüÜkPÙ[˜z FΙ£ŸmƒfçVøÌ™8ã¾_BgÐw›0ewC¿Pvƒµ«¡6R}ÎE-ZŽC©ä̹xoR¯f¯d#ïlú?iýù`oålž”·etIFäe`LIN·Nõ§ R¢þêÏ`o÷t¾·ÄÕŠqF?®kAÀဿºnµ&­Aúï†èJJ¡;Úò‘poÛ [m#<í.4ÀµZ³*€lgXSoÔB­¶Áå AS¹:•ø¡j·©Ö²gرòÅG\¾ö>Ôˆ-š”kªp†áSN]¨nF¦º*}&43—ÀÖæâ#® ­ ž†„¬3`¹èBh‹ŠáÙ¶ íë>à„jžÒ ˜)޵ºPhÑaÚgïÀès#ûú¥½E$VJ¼R$ÆD4™W\Ùë{z²ûþ·È7%ÛA)\g¸`%>âÔ«Ù+ÙP—‰X,XÐI6=käOv²ùÓk›Ñdïîu•ŸeÂÍWÍîõ@~Lñ´Ç:šš`ÎËãûý„ h}r9W:´FÊ*EK唵5@“‘‰šŠ h¨nÀ©3Çâ̹“ðÊÓobû±VL.2`‚ïcØ|Flwä#¨ ⻕ÛanÛ³à6±ƒ@PU†ŽCYÐO˜ˆÌ+¯ä±ÉØž^ÁÉÔÈ/:¨“¤k²oú;.ˆò>ò´3Í=ƒÈÓγg7 S§!cÑ¢>›ó@ÎG¼NÈ6ħg¸`%>âÔ«Ù+ÙÄâT†Ò“ g‡{2“Mk››¶(æGW΂٨C‡ÛÇ™ín~rõŽ‘¨òžž´ÇÚw성¼ ÖÅ‹¡)*f2Z°ÿm…ý9#qÖiã0kó;œ7çPå44í?ˆŒ²Ì]0½² vOWÏ4a²ïm¨‹§áo[GâPS.›jÁ Ï P©5hª¸#ÆÍ„ß®GÛkÿ€nôhX¿–,ò¨BÛ«¯B¥Ós|•ËÅÊT, À8{N¯f§,<äÔ@™@ÉÓκøÚp{¿yù©³²sãM½¾KAŸ!pÛù£ÿ½ÿ†dÚ`ö°ß-É&É™:E'T‰ô·šõ¼‹aue_´Û!‰˜ÿ¼f²3lNìHf&{é ÒëùÈ̶â)´oÝ cEUÑîv»ïèËQ•_«'åcÄ{¯ÁWWOÁÔuø9Ȳ(Ç‚Uv3Žj2pÃyÙ(jüÔgbÕÖ"쬱áªÓK0µýy¨2‹P=áGìñ¦á¥-)eÙÚÑÛ0ž2A‡ž={hnf=7ãÌ™È$­4ÒI6»wÃùÖ›¼S˸,Øé¯«ƒýÙg ÎÈ@ÎÍ?x[ì*ÉXxdâÓ3\°qêÕì•lzêr¤º@o:i}úɼ³¡àÅG_ÙÄ»˜é£ 1s\1¾Ø_‡í8Ž„âI"Ë@}L!¿¶ÿ{‚•”£©:a.,€~ì8hGŽ„ó«XåySæH–ž)s6"»­™ÓB»3³Q•SޱÓÇ#ÿÈ— G‡·3ÇatŽ•hòd`»®?žë@®k;4cÎÃÑœs:Ü_|ö¿Óeœú1c )*‚ë£øçt™O™>©XοÆY³z55+ê»í©?³§aÒ$¾£ñ:oÕ~èGAæ×¿Þë»äΦÏÉMÿ¡K«7H²IÑ YÑ'»kðÎg»Ô@9(é H9YWVÃÔ©X·í‚[¿@ážÏ¡Ójgý*±]ÛÄIÈürOg‚v;ïHéùO‡ JlŸ ƒÎŒdÔóõ+àþô¸ŽÁóÁB´iTB£9 “Ú…b½A“_r §@g4 xÆ)Ü*D*Þª*ÞÑÐ=‘aÂDØ_ø;ÿÎúÍoBW>mo¼ŽŽuëøg´C±œ· Ç°7zXÄÑ §FŠüe³ž—ù4†æ-’lR˜lÈ$(ó¶ªz4Ù;PV`åXRkŽ.‰~LÍÿó`·w<ŠÑìõE%«Ý†’æc°¸8wá鼨 …0ª¤„¥jè^>?ÜÛ·!ØjCÐÕjs>ô^PPÈúl…õ‡™4¼_nEàÈ„rëñi°yêZ¨á Y ÕªP૳Ÿ…,sò²3Ù1@¥ÑÀ³{W—~ªŒFö‚Ó ë7¾ e|ÄærAWRÂw8=©(DcEÇqäÚ°Û ÍÏggj§?%ÑùèK[² qÔ† Vâ#N½š’lRœlDM&‘):=µñþÆ=ØX먱e˜yÆ4nvÇÁ®³ãœSFâœ]ÜGé¸ÍNRÿ­­|CúhD¤.@Aš¦Ù³à;r*ƒÁ¦}PlÐY¡- •ý]²«°Í7“\*Œ4×#dhA•j öiÎÅ•#´ðíÛË.Öš‚‚®žb»v2Ñ‘&›qú &;M~|pŸõãÇÃ{¼¾™s Ÿ{2ÍV…Ž,‰`%ŠÉ¿/íÈqˆ£6\°qêÕ”ds’Mtz2Ë¿ÿåmì9T3Få`Ñ÷Ãík·fuèIùX|î¤.dC.Ï”W†Ü³f£cý‡¼Ûpþ9­©3­¼ÛÑæXw tY6-gÀrZ9<Õ«iE@G IDAT±©øÜ;ßj=S¦úâT¬xQû}ü´R͆XèÓtúéÝ<ÅÈÎkÐ ØÜ ¸;2› ¶æ kú©ØÞìEÖ¶MpëL¨*‡9ó¯ZЙ•”Æ5\9ñu¸`%>âÔ«)Éæ$$›èôd–/¯|ÛwÆ%ãrpæ·ÃdC o~´SGàê³'v%›¯µÌ«¾ç¿ÞF ¡¾cG9Г<ÄT:Ls¦CÝô/ ðÏFæ…3àÙý<ÖWë±Û;KluЛU0Ž<Šý ^Ô|·ÍÈú_o°“Ý¿D{ŠÕZa¬Ým›j¿ Cž|Žœyòß^«« £m–,xý1WüôÛÈ4…el†ËÂ#Ç!¾ ¬ÄGœz5ûE6ƒ=œ“Ù-Qlù˜¢ÓP[îª £U¡rædn¾ºÑÑã1Z¤×y§µ¯YÃÞ\t¬f¹ð"Nì¦ÉÉ‚÷­ŸÂwxü®\hJ§@Õü:µqÐ_·•† Ô„Êðe`>É‚ÿÐAÛœáàÍ(O±Ã¡Ï ×ºaMƒ¥Ñ—1€ ï›‹‘ë rVÑÜsæC[^Ž]«Þ†ÏåFþoÀ¸©c%Ù$hX‰ØU‚¯î¬.Ûè+réõœÙ<Íßþö7ÜtÓMøÞ÷¾‡mÛ¶aÆ Šƒ5dI6âÈ&úÁRà¢{óg¹ÝQ.ÐO„nLxAŽ,ww~ÌEºíõ×ÐñÁð¸‰ø›¿ª™hL6ŸmBÐí‚!ÞÔäæ`çÂo ¡5œV¹0ÇÌŽJ‰v¦äm­uͨ éᇠõúLÔd¦·`’Á‡‹ŽÎg3N5!xpBíí©(€5à °/ïr¨cP®×Àïnï’*<ż_~ ×æÏØaÀ•e‚ݿހ#<§Â˜› oK3ì®}ð9¬j2€ÜŒs§¢®Å‰]O?óу(¸ú*Œ=÷ ¹€&h\‰ÚU‚¯—óÑÀÒô™^ÉF ܼå–[X#­²²’w8´ëˆ—z °;q$Y(Nû;k8 Ò09|dæÙ½»×,ŸÁMMhYö|ÕÕhÒ˜àÔ‘¡ >ªô9Øl)E°¸KLD™­®Ï6A¯ûú’Lhç\Íèój«ƒï£G²×@wÖmP—ÌŒ¹ðÐŽ†âh¼»ws¤¿aútl[ù è­ðyKQ0õ47l…Ö¹MM£a ä¡1»ÞâRNO]ØT˜y×íÐäæÊÅMܤ$V)ˆU‚]J©ê½’MtŠGykÖ¬aÒY¶lL't©cT’lÄQM„lœÿ| ž]»øNÅ|ö9ÜHLJëàúäèÇO@æU±e_”6ÈuÚö׿ðÑÖëHØ J0Keãu½Y8uãgâ’s¦bvE6ìO¯€¶í¾ Uþ„õÔHFíß uûÐLù|¶BV#hvØ;ƒ:I§Ìþ÷¿Ã_SrµÖèÔi¨9ºƺ*¨ýÖS#ù¿Î g^9tÚQèhj?äÄj$é3rÑ…]’Ê%‚•ø t­)ÛGNb%ŽU:×ì•lhptgsÖYgD7{ì1ÜsÏ=¸÷Þ{15‘`J²7­D>ØXÞhžíÛ@?7LŸŒ‹/ŽÙ°ÒFÛ›oÂùÆëOólåÙ,÷}¡š×^b²÷ §âÐäÓqõùÓ0µ,›uÕÔ­ÿ„-hf" ª€*d‡6´ š ?¼í“T…ƒI•ÄfG G£þË*œ6ä¸ÛPèm©3³V›ÇOÈŸ.„`¦zŸOÆÌldßðCZša«:„Œü<èG–³ƒAdI+ñd#±:Ø)µÔW,†ósBd3TH²G>‘4–7Eà“ÆXÆ¢K;¥b¢[WÚ ¥ä¶ü!GuÙð¨µP[2PÞp®Т³À§7¢"ß}†…#þµ†èŒ{¡ÎÈ„&$‚Î&[p«àÃùa)šÉ“Q[[‡W··¢µµ !·*–wA§yëqº¯þ†Fnj5ÔyyÈùÁ œ¸­ãÃYq àwÿMvvÀ%‚•è ËwÇúõ44@“—›Á€ŠkÃJÒƒUcñæ|°Æ@ï•ãLtSçÝBd™f`ùò娪ªÂÒ¥KåÎFpSñc":¦Š,‘¹cb Mm6Þõ>2óA…:…^'´ ¨gyVA…´pêMШհX-°„ Ïª…*ЕÁ¿»®#&¨M&…V‡Û­¨ šQÐÞŠQî&´”¡±|õuø¦ç ²î {ÏiµP[³øž¥än”&û¦›XZ§§Rýék(Ö;²…ºhT9•œò ¯EÉ‹ù<íвgžÚ™‹§¯ïîé¹T´«¾ŒSŽ£/¨¥ß3½’Mô ‘Í–-[x¤=ÝÙ(GoT/Òsœ î¾ûn̘1+W®ì‘°äÎFÜ úòÁ’£€¿¦šÑ––ÁxÊ)=6¨´áÝ·­Ëÿw,¤iÆm~“ tAµAP¡IŸ|o;rƒn˜5!˜++ Ö{RYà¯oBÈåfM2Ò";jÌÁÓ¡ °È¾³:jÐl-ÀºÓ¯@›7ˆžc³ù} à~ÒN& …ÿ ¡¯¬DÞ¿êÌok0Áºí°¯þ-'h‹,”’Z]~š8à5íÏ?Ç÷Jäªm˜1þú:Ô½º „õ­oó]Õ`”¾Ìy¢ýmˆ#– ¬Ä{“z5{%›È4›7oæÌž=›ïlây£Ñ3wÞy'xàNM™=©SÁÚµkA)¦÷îÝ‹+Và¾ûî‹ëd ÉFÜ`’aèŸ~Êw6~Ê%£Ó#`o~@CÉT¨6gcGv N³„¡°ÞF7Œm­P©Õ0ƒÌ™3á¯=÷¶mÚl@(deáÓœ1ø·?¦€ßoü t/£ÒbCö°–àl×1Lk«§¢öûAjÕ¤‘æoh|>ÖQ+|èáø™:^xß¹íõ‡9ó¨KNAðøVöþ *ëè/î.N*2-Ë–q2·H¥è#ù 2„º‰´]'™sÞ—þ‰>#Ç!ŠTz×ë•l¢w6Êp‰³¡]ÎáÇ; gÞ¼y ÷>üðøùæ›™bI6âÆ•Ì–TŸmù _™æÍƒoç:x7!ÔAàˆ9G FàôÖ]ÈžT ÷—tØÛà×è¡/*@f~XµÚ_[ËhD6þÌ,Ôh,øD[ˆF§9bt†GœìÐçé5á›Ã(‘ïL6ºÊÊêÌ*x÷ïã{œ‚;ùvb•ý¼kî„Ó§Bî×ÿ•ÁŠÇßêÿ‡§ úEA•Q$ú‰šö¿þþ†zd^s ôcÆ"ØÞŽê¿þ–öv”‰ƒFIæœFÿ•wÊq &º©óî^Ɇº:‘TЊú¹­ZµŠw7 ÙW›r÷CŠT7º,^N ,Kê  ÞºÚ·ÿ èõN™mý€È†¢4C€[¥…!èT~¨ ZƒZ´T°™²€éSQLêý:=TÍMPïÞ •ßç„ÉØÚ¦A«Æˆ#°@ÕîDÐhB“֭σ™N+„*‚æÍ7€Œ xx#T6ByyÐ?ñ'Àé„ñµŽ,MG=¬› ! ŽÙ·#¤1@ðÀºùa¨=v8fÿsâd£ùä¨I¥Z«Ü.¨ˆ@½^Þyù/» ÁIáX&Y†?”eV–Ø‘M_Á£ã3ú«…ŽÍ¨ÐG’ÜÙôÙîÏ%ó¯CRvv¼ü2 pvË«ê#¸T¼K Ãý jTãiPkʆÚ`DÉìé(š>™cmHÀwä0( Ýk¼`ZwE~B8UÂGj~¾Y„aôhèFFëÿ=Á1>æ³Ïæÿ¹>þíï½×»7ZÀÇ:mí­õÈœtÔ£ÏAðà:´°þ6-T39C§~ü8N— ZèHÛ×®åGÈyÁï÷ÃPPµÕʪÕÞCá?z ÊÅ3jTÂmÄêK2ç\‹¾Ô“ãè jé÷L\²‰ÞÍD­·Ý 9Ë/Y²¤óQÚ¹È;›Á1’d~°äfö„|¾°™?€PÛ8êZ¡êòL‡Z}ÈÏi´Ë±Yr ÍËC¹ÊÍIÎHM¥@?Â_‹¾#ÍhPñ¥±­Z3ªÍy˜cñ¡¨µÙfäd„“™±ÁmG Ù†J•)Þ7‚~?#F¢åÚïu‘؉F;°o5=Õé àÚ€¯)UV)T–‚Îê™W^™áѸ6¬‡¦°æ³æã¸Ï‡‚ã5<˱A´Ó‰*‰¶ý|2ç|p¬6üV9ŽÁD7uÞ=(dC—ÿ×]w v*å¹çžcâ‘Þhƒ3ùÉþ`[{ î];;SïUÁ Áp!5Zµ&4j30ÃÛÈÞeÈ´Â<ª[+BöF„¼N¨t€iŒ®/Ûh×#RÃ¥ÒÁ­ÖÁfÌD^A¬Ey(¸|gmå!¸·îƒ&S… Ç`{ŸöP.ÅcçtöG…öÑm Ä˜ÓMwl·C]P†ŒË¯á6<Û¶±ø§~Ü8d~íjáÉr¼øÇ))©ªi>ÊuZÐ}N ¹†)S`:}.ïÌúÚ†$áéèV1ÙßGß{:|ŸÔc´þÂ&ÄLæÇ¤Ä•l6h²²Ð\׌ýí@«9 óö746—NÅ]!©Qyh¯é'N„aÒh¨W#è×Ãg+€÷pÇë¨^èFd£C]Mu5«QS0gÆe—s¬JàÀZx×ÿí{ŒPe"ûšóp|ãjtªBíèó‘uVøn/:»h,#½ê:Ö}À;jƒŠ26•ÁˆÜ[ož%û©iÎi0ž~:;Ä××£ýÝ#Ârá°œA¿Úd#<’lúÕ =ÙãΆÄ7NÒC‹,½£ D%Ùˆ£˜L²ñlßRÐÅ÷+ŸüvnÆ|×1L­ßDz2-¹#pؔÅ—‘è®"ãüiP7¾ÍA”ÎÝp}ø!ÇÈh3}Ș[ˆúó~ Ëã`O5ŠŸÉ¸ê*X¿¾þOÿž­áªÎƒ¶tËâ´íßßè2a™29?¾¥[vўȆâ…Ú^ûï6¨ *ÞUh{õUíÌþÁ  ¼‹ £E§Ó ³ZÍžiT Ó¦u¶¡àG÷]fòæ«®A ±Ó1èÊJ…ï’9çÂ@ô¡¢G@KÃG$Ùœ„™:ûj§Ê¢@ñ1íkV³ 3eÑ|qù*ì<ÖŠ |µ_»—]ÝY¹¨sú£S!Ëíà@LÚÙX/šŠÐ¡U§Á±®þã5¼ëјÐäeÀyåRd®ÿž;ø¢imI)àØ ÿ‘™Êòéàoj„/Ø­® ðeA¥Î欞fËÙEOWŒËΌ핦Œƒvf¶'—3Ô†:3$Ûls°àgÆ¢E A¥ìnè!EãÍ8s&Ü_|ÑÙ†vD1|"ÐÚÊ´JPmdCD|D€½¹H÷†ÐW¿.X‰8õjÊÍ@ SîlÄ‘LæÇDdŽ•+¡2šXärýþFl8â@i 3G`õ¹áU©Ñ¤5£Rí†%ägâÐUT@WœU븪B¸´y¼ªÒA—8A•*•ŠÑÈ3LeeÈ1 ÔzºùðjE(„¬ÎPÜþS‘SÕˆV‹O}{í\9o~pꉞ%sÎÅ-1ñšr‰c–ŽOH²‘;›˜vKºiÑÇ;Õ:}§ª­ãå—8´RiÈ‚aŸçSÛŽ¢Ðç„^j|P[,°.ùÚß{—ÝœC­u8=€*m–~›¡¹M«Â¯S©X°sÄŸÿŒæGÿØQ^È¥vh mèØ¯†ÆèƒiBëåXë?ß:°æ€ÿJÏž‹¯ŸÓUé9Ö®ƒäe((•ˆvX›z%3"mq÷ËöôŠn˜Ñ½ŒŠÂ…v6Eßüf瑘ÒFÐÙmi)Úþò4§N0Î<A» ê¬l¸¿ØÂ$“ýýëY¡§"iñ%w¸`%>âÔ«)L6ÉH ÜÙˆÌ@~LtÌãxåån;Î8£æ‡SDSá„k‡òî¤í­7±É0­ †f”x:0ÙÕãØ:µ£ûäT« +.ƶ…ÿÛ0©"‹Ï ¥Å‹q¼ðïD¬ßøf ]Ÿ~ÛÓOóQ—¶¼œÿÕƒ@]-<{÷ò®ËúëXJým?eŒò~~;¼{÷òøÚ…ÎçCÖ7¾ÉGuÞ}{ÃZpMͼcÑUŒ„íÉ'yüJ¡@þcÇ8Oîm?Kè-VÊê[¤8¨¸5}Us ìJ’M_Ð^Ï‘Mt¦ 9ØPH²Gx %΄<³²—þ 3ú²m:ëëQrËO ÎÎFëŸþ„ÛÅwä9F;!M~6Ì£m0ŒÊƒÇ7ÎÕïB?º™S`_Ûˆ€Ç •FÏ™4U ;:Ó´KÇí¿@Î;kàZ÷!B±—XÑïÏ@t¬ÿ¶åË™l4V+ÚÔz^äÖL¼.hŠ‹qä²%x{W=¦Ž*ÀÕg‡%g(Ý5¥½ŽŽ¡ ¥Å¦ôØJ¡]AËÿ.ãã,"ZÚÙPP&e… v´‡c‚²²8AIìÐ]ŒûóÍÜMv­Ò΋v.¤ãF©èþ…E#‹²›£Ý íœè.‹ÞË$ë÷ ‘„2çŠÛµ:Ó ËEv N¥û¡¬%ß7¤¨šeW’lú<ÃæAyg#º-‚¶ÿ{‚OŠz7LŸŠ ¡#§Ž`e·ÿ´¸9ßzÚâbd}ç»|ìÖ¾aå1ƒ6£¦òVø;ràm0Â4Ñ }¡ŽCðw˜ø¯v: ã½L(Ä’7ÊΆwa!tv;çÊ¡¿ôiC.ÂÚ#à«Ú^øid±àXùD|j.ŸÖcYw>ƒ Û/YÒ펖/½ÄÏEÆÀÐ[¯½–½É”Âq6¯¼ÂÇb4>€Žû舊ÒÒ´¶¬¬‹sD ¥AG¸ÏÔ/"jŸÇ Æ@„2v,LsÏ`r¦ÝgÏn&*ª3õ1û¦ {£¹>ýqƒSuzäÞv[Ÿ,I6âÐ%+ñÞ¤^Ma²‰îº êŸÌdá@¶áÞüY§¨dä(Û&OAåe—AñìR¼¹¨Ní?D¨½*–‘UÑ} ç¾xµ@[TÄGpä]F;.Ê¿tIÎÎh*öH£» ºÃˆ.š¢"¾ÓQ™Í8îô¡Mk„Õçâ;œgœ§ÉÚÅA€ž''ŽõxWEžb™eå0Ï?‹½ÅöiÂñ¦6´´¹1©ñòöm‡yd¼û÷‡ûéõ²€&åãɹé&gÏá1‘í¾š÷|ÕÕP›ÌPeYáÕëa2šø…ÈÊrá…«ô…޽ˆˆ‚ß_ÑŽ‰ˆ•ˆ)óŠ+¸ *Ô_~§ÉÔ ‡èÍ@§F72vï‹‘mˆ¯%é\S’ÜÙÄ´_º_àã±–fhró8¸ò¨ËÅ®ÏÞªýh[µ b¹è"&r°?õg„ ‚~„ º,;ŒS* Ê,zÔ|8^}/¬FGF>|‡ÃwäHX–ÆbáKmÚå)ÀÓhDÆ¥—±„Œí™gx'@–ÚÜ\¾_¡;%Zì)Å-Ø”šZí÷Õ“Æë®Ç¼ù3â~“tGrôÈŒš®³nÛ¬Ûz´³~­§Ý‚e…ж4!äö èlãc1ò*˽喘þÍ>È»:Þ£]X‡Ç½ÃÁwZD™—_Á°T!S"Y~¿ÛÅ8¨tz¨­™|ìEx¸·o “3©–"cáÅ]<Ô+85úœ*b]ɶ+‘>õ¥ÎpG_Æž*ÏH²‘;a[Œþ`éžÀ½e ïHhQ6Ÿ{n\W["—¶7^g’‰,DäÞL—ôtYOmWãK}ºË Nd!Ri¼ï^ÎÔ©?*=-Ì>öö‚V‹ü;-F»˜f» ---7ªV³¥mÆ”æ`ÉS¹"›Õ«?Å­«‘§ òŽ‚v+úÉ“áݵ‹I'û‡7òÝ“RèˆÎù¯Áµi…È"@©të|óMvt •ÊdFK»9*“&[f^u•°½ˆV,»êËÎ&¬†‚ÐD1MÅz’l$ÙÛå`- ä6Mw0´@‡B!¨I!zì8ŽÜ§c·ÈB÷'tCêìÁvÂe˜”(Z>–4ÿª¿ÄÎC¸üÌq˜9®˜ÉÆ0w¦"5"Gì¼wî IDAThmsóî†HÉdÐrÄ?yä5®ÁÇM~ŠK0eþ,Œ-ËÅ–}µøüã]¸ðÐÇ(«ÁGa J¤à«­ “MI ÇÖ(‰Ùº,êo½…öµïq‘…ÆœyõÕÐUT²p©Rh\”|ât”"2ñ4ÞâiÂÙÎé³q-Å[‘û·Ïóü³‡5Ù$Š•$᥃+J²‘d#l1"‹›ðËNT¤€ÐŽO>ác#ú‹¾¥¥æÃ‡x7C;r‹¦ÝåqoþÞƒ8…þò•æÞUƒo>ˆÓ&•àâÓÆð¯É“mù?6á¼]ïcb¡j³…SPL ¹1»u:dM˜Ó«ŽÜ¾íÿ;¼»wñ;!Âûå—,ûC®Ò…>ƤKGrt4I2¢dӓι³Çòª‹œÂخц烴ßÎ9ÖÿøD§¸×úƒaW‰A_°J´^æ$ÙH²6ñÁXš~÷;P~ÓìÙȾᇼëÈ}ï]tlØÀ14ºqãPpϽÜGÊ;CJtÜfš3§›{ŠÅ‘æ'ºÜ'‰¯ÇƒÅ‚©3ÇwKE Œ/6ã¥÷wcliþã„×Úûëxg4ÍYƒ³ÛÀ_WÏÎ D ”åÓ9j²l6V^ Õº{b/µ¦fÞ‘yvïbIúYá¿ãg)Gi¯QPkÎO~Óé_ÉèÄêWoó¡èÜѮёÆÀóÅöÜ‹öª£6bîl\.Öp£`Óhï@¥_t´Iпé˜q´'MÀXˆ}™ám˜V”d#ÉFØ´{[Ü„_Q±eÙ2x¾ÜƒŒK.AæU_c²)ؾ m¯½ÆGkäÉ•÷Ë_òäLÐòØ£|ÇA®ÒÑ^\D&ñ„'é^èÃÇÐÔ"äó¢ÂL×¹„<¬èxí«>ã>”d¢$?U5­hq¸øXî’QL´Ð ²¢Àº`ôûÙ}˜ŽÆèXÜÃYØ”]”ó =ò§i o7RL ²Éýém0ΞÝ#¤½Íyÿµ<þvBЕÔ*Öx£äoä=˜#ÁWw6#y‡E®ÏY~?䯻³QR8Dw62ŽÒñ'ÅŽÅòìÉ®"ÇÜÛ|ôåûNÏH²‘d#lσñ1uþ%]TÄGc‡&»=¼{½P[³8™yªÑ1-”¬ÂL»…(í¶xšbäJÜþÎöx‹õ°Ú¼·oÒõþˆ<Ò.=c,²3ŒL„ŠW÷À¸›š`ÌËã#5V¶&²)+CæÕ×°ÖÝ×Ð"FzrêÜ\»gÏŽËN‡¢?þI885Þö%A_¼ÑlO=ÅJ¢ÇšCqü$b»ñ¼Õªþ N²Š’lNb²¡…ž\†©qÆå—÷ë/é¾~;Mÿõ_,²Iw4|OCZ-Ìsçò}N·¿˜_‹Ž ë{Ô‹|ÆùϷ¹kbŽöäaí™Ô‘‡zcœ./Ê ­‘×5)íž<»v²šA€iÔ(>2£{'rå¦ûM~gü$ º!2êRT*˜N;S ôV¢ÐÎdvnÏWi þügÞ]‘cÐŒ¬ö@±>´{Š•Hœ ©íÉåìMGwkþÚã=fuÜ™¶·1÷õ÷"do‡fY° S«®§öEÚèkÿ‡ÃsƒN6÷ß?,X€yóæÁårá¶ÛnÃòåË»o¼Ë–-ƒ)†È ý^ÊÕˆ›X¢†Î <‰DFr‡Íÿÿ/n£‰¶!Ú{Z¤I ØÞŽ€ß­Ù Ë²w’Ê4-ºÂBvmö××Áñ‹15Åby£)GC1G'MŽI²}õLêøðCVJðCȽòJNÇ`ÿó“ðÓE¿JÕ)¨É ¿J–Á‰*¦ÓOO˜l¢ÓtÓ+igHGŽtŒF.ÕŠZƒ’\-÷?o› TTA€ê{”ÙzË8o1Î+j7‰Ôµ]’¢Ý3í¬M@E²¨·öDÛèí=Ãõ÷ƒF6‘Ä¢dö¤ÄkwÞy'xࡤk’lÄÍ.Co[õ*œkÖtz€Q+$;CG òQO¬’H¢=ïéˆ+–6½7Ñc›è£¡ÈÀÑXÇhýñL¢g)E%šË™3‡¥tÚ^~)œ.;ÆY³àÙ¹ Êã ɰqæL¾ÝPÒÂÇþ`Zhr° ;#ÊDúk¤Çi ví ï\5šÙ@Í(|øaá4ñæ“ÆÛpûíº:zÍ8oÞbe€µÑzƒa»Ñm'£ Ññ¦b½A#›­[·òŽå¥—^êÜÙD'aë-½´$q“IÄÐI4’!ÅŒð?? ׿Í|œ–÷‹ð…|2>¦žŽ¸bi£)Ç6±2‰öä–ÐÑPÙJE<“(ECã[oÁLùyêë9–†‚NucDzƒí|55|Cb›wþú©SÑöâ‹p¼þ»CÓ‘sõT”9'Åíö5«a˜<¥SYšvŽ—_fumÎ t"ãh—l ¿¼£‹Æ[_æœæ£åì5ãhOó™VÜâ«™È÷‘Ø›¿ªŒ6úÚ·TxnÐÈF\ä1ÚÆ±víZÜu×]ˆÞåñÐï£ËâÅ‹S§aÕݳÏ@U]À™ó8÷\›æƒ ùh#B••ðýÇ’¤W³þC¨BàŒ3<¡i¦&!Ê?âÿ¦ŸG•Ó íªW¢…üÊ«ø2’ši_ ªŽø¯¸¡‘ÌÈgÕÛ¶AÕV§åä xB &z°*JÕüÆëñÛ¸ú„zQ–V55AM:;¶CEirr8uT7àõA³i@ÿ_¥‚çÖŸB·ú_€Ç õ¡ƒáþ ”—ßw¾Ûë\¨„fÃzGFà¬ù\_}ä04ï¾ Uc‚e¥ ÂŽú­:tª`~qôÖxç|¨Õ±Û81ý™·ÞúJ¿§²ÄF ©dÙ:f»çž{°téRL˜AŒ.rg#n¶‰üU¸G­PÌýžyÙåqiC´ç=q%ª)6P^CÊQX¬l¥t¡N±<½+×ÇÁö׿r®šh¯:î¤ÔÑ:Gë“Þ¹{™/¼Á¦Fôä-§´AªÐ¶§þÜým÷.PQM^n7ϽDÇÑÓxE½¸âÕS2ÀF+c÷†q"¿ Ûn?m$2æT«›T²yþùçyüK–,©­X±÷ÝwŸt«HÔÐY"&ª •ƒ@¬#.Ê Kæ…ºœ ¯¡xÙJ£e^èž%ÐÐÀjÎäZMÿŸ¤]švÏ8…ƒ9)%‚’MœÖPˆÓDŠo!¯5ø}ü<ÅuùCmãF~®¹¥¹SþŸH›\¯Ù)€œôz¾ 2ÏŸ×ÇwkƒÆ¡-* |64@cÍb×lë׿ޥ®ˆ]‰ÎG¼zJؾ~äÑçúäcÖ¤ÓdZYîÀ¨.ÑXÑÏciîÅR O¤O"X%ò¾áV7©dé40cÆ ¬\¹2€–;qsë‹¡ÓÇW[•N ý˜1q”^ô¥ Ñtz?9ì(š>£[Ò´è÷$Ãk(2[)ÅùP`¤’K†úã|ë-VP Å×ðýHQ\~?ßÙ(¤é|ûm¾°§àMr /5" е‰.äú:q<ÈÙA~{gÚ„xwOt_ãÚôi8µO!+ròŸaÅ‚YW•q!5?üP·¶õcÆvÑÒ/Eç\t>bÕS2ÀŠÚJd=ºÿ²=½¢Û£„GäÜ(i%ÂU15÷HQ¡?„#ŠU_Æ9žt²éH’lÄÑK†¡Ë6¾šºxw¼´’˜IlÓì:>þˆ/ý S§ÁV^3ã¨rl¨ÎÍC m†Bì-FÁty¯ >%'"úÿy?¿½3§Mtpª‡4ÕÜÖ£¾d{£íÙ7ê¿é·¿…¯¦š T?q"“ùQ{ÙßýLg†ïÊR}ÎÛ^y…õò” °´³ã]ýe4À0qb—@^ò~$EmµÉÔMsŽ9 ç¾–d`Õ×¾¥Âs’lNâ ÎD 0Sº´áþüsVe¦eÏ$y÷–Ï9®…b}Z/¼(fÆQªÇYEƒAÐÂG%sÑ"8ß{/œ šÈàÝ‘Šgß>ö`+Zö¿Ü=œJ)·ƒ.7LgÌå¾PQެ( 4çÇ·t›êú[ÿ¤0@;%grÜGêÒÖkÃïIõùhýÓyç–õïB[\Ì}¦˜#×ÇŸ@m2²pa— °m«V±çÍ[ͽW^éÄ9ÑïB©Ÿ ¬úÚ·TxN’$a;LÆÇ”.mD»Sð©û³ML6tTÕzñ%_e¥z”™”$ü)Ö‰ çÂYý/¾¤û*æ3çÁ¼`Zþð'ƒ+zô1ɤÝJtp*¹8“3ƒå¼®Ï”€Íþì3ÌI9p¢KýÏÆ‹tº“ ¹ì“C7²Ù´‰“éY¯»®KX۳ϲë9³uÑÜ‹ÀYøƒˆª˜ ÛíkßRá9I6’l„í0Sº´HIªÍ¤@;Фo«¨ìÌ8éQ©¡FÊ ¼“Q«™PH˜“òùðnçâKà­®æhv]iòï¾»Ë1ZdVSº+ ¶9™ä(¨S“ŸºñVí‡~ôdF]úÓû[þ÷O$IKïc4ÚÕ¹·mívŒF„ÂÁ¬'vÉKGŽ$D)!¢½œ…?I6 A%ÉF’°Á¤ ô6 ‘qÐqeÞ$—pÚm&L„aê˜Îœ×ùúh‰rÚl,ê `°ZYE8ï—wp&R’Àñ75Á¿“ßC®†em23ûœP‹A[XȤDÇ@ä„@9nÜj5;!ðNhÞY1½Ñr~x#Ü_laÉê]€“3¥h}ôÃîÖ'‚Kéý†iÓûæ 0D)ˆTèîŒ2‰Ò½‰´>¤ÓFw`tœH%ÒA€v•±4÷ònÿ…tèíãéÇï%ÙH²6‘EZøeq*¦BìFüæ¼€uµšè.ÃráE?VÄ/ÉõÙ¬š=Í‚^/|ô'¿[š»-nLÚp6ÐèBYGéþ†=ÛJJò¸™(49¹Ðäæpì ýÞ3r$ æÌiÞ<öŒûÊÍÇÏ’£€qÖl8^y¹[” Áµñ#)°ôÄnŠâ{´#JPôûßwÖ™¡L1ÀdCéj"ÉÆÓgð‘";ÔT£¹¾…&0VTbi`ŠVýyº?+ÉF’° 'ãcê6Ø•ö©?³7WH¥âÚšÉ)H(”ލŠþj1VÀ‹§ñFi(ë(íD¢3Žäö kÔvÜ›61©Ð¢oœ:•wú @Zv¤$­²Xýï²è¥ë³MP4Åèý‘žqÞh¤vð`ø}§Ì„qö,ÎvêÞúExG ˆRpÓ®À³c;’“AÆ•WòÐDæ#Q­ºhƒi#ž‘Æ;F£»3ëµ×q* ÑqCø‡Rû8”ÏK²‘d#lýYDê6(× íhq'Yþü{ïC ©‘uÆHOŽv7ù¿¾“Å.#K<7ò £ Âhï'û /„|Ýüíý¬›æ«ªBÃÝwñ‚OZf9?ø´Å#8© ‘©*P]ûÓ+Xì³äÇ·ðD¤gõ‹vöçŸç»¥q±Ð{šî½‡ éÈ)ûÆaž6¥î–ó1 qÎiȽõV¡Eº¯Zu‘ØõgÎã9Ð¸É«Ž„O%Ùˆ~}ƒ[O’$a ëÏ¢ ÚÈP·A^fÊN‚é‚ÿïöÚ²?ý4<û÷ññTÁ]wA[VÞ•lNx™E{Š‘T yK&MîâýÔüÈïÃd£Ñ hÙ2h ‹ào¨Gým·…­‰¼ŸýœãAˆl‚6;ï°,—,â{…H²¡|5ηÞì"ÄIÞh­Oü‰ï‰ˆdhä™FciüͼƒŠG6¤:}ãMB‹ôPg¸ì‰l2.¾†éÓ…Æ!jŸ=ÕK†íD?‡ê’l$ÙÛ^2>¦¡nƒ½Ì^x!œ5“”šKK¡)*âc4º'¡ËåÂÿéyéeé)æÙ¶ƒU&swm4R…XÍ|Î9èX·¬—À8mô'ñŽˆî8HÑb†uñb„:\]ŽÑè™Hm´No´}{áÝ¿ŸÑ‘š·ùÜsÑñÁ¼Cã”ÔÁ`Ìc´È”Ô"ó!ªÏÐDÚˆ÷lOÇh‘)Âûӆ蒌6Dû’Šõ$ÙH²¶Ëd|L©Ð’¦¹Ï*%Žƒ@$xäÊíýDr5tG­FGt´srïÜÙ }å(¨s¾º¬¦ÝíD(¸“y±ƒ€Ï OE fÍæKoE™€^F*´ã r¤6èHÕ±# ;|òI·L¡¬Vðë;¡H¹‚äÌ8¥Ç,®¢ÚhÔ|döSòˆÓ••¢?™:iŽ(Y]—¹:á§8Ä:F‹Ö´£Z"éþ”dØnú7ÔÏJ²‘d#lƒÉø˜R¥ ò2£œ\†)Éí0L3O…鬳zÄ«Óû©¹¹‹Æ[¤6𮲦ÓçòqézÑ®ˆŽÚ(öÃráȼêkaù˜â’t”F‰ÑHÃ.žë3¹WÓnÌw  íï½×éF»3Ò_#7j:¤ :Ò£q¸7oFûûkÙA“›ËwAYK¾…H‘Ö@ FƒÞDZE´Ñâe?ío¦N"ï*øk޳Â52¥ûŽ,‘v­iGõˆh2¯¸²_„“ ÛþXS°¢$I6Âf™ŒI¶:"Ó1Ф¶¶®SM U$mˆPÈ“HŒH+Ú;+^+Ñé'ì-ÌCÓK×ÞŒ¨§ì§ÉÌÔ­i©#GšvÓÔ×’ ÛíkßRá9I6’l„í0“l#þtÄÓF£ã0úK>óª«ÐúÇÇÙE;2‚|鯕ædaËŒK.áÍGî{ïöšÅµ7#")ºÛ‰•a5™™:£5픣=:¥” 9'#zO¬ß'ÃvûÒ¯TyF’$a[LÆÇ$Ûˆ?ñ´Ñ(@‘v2—_Îé½£µÂ”c#Ë‹aŒ“Tiµ'²ÑUT"ÿ׿¶—ÈŠ=y­9ëëQrËOúu„Õ[§»¢ãÑXž{=éÈõönå÷ɰ]Ѿ¤b=I6’l„í2“lCì-Òãâl”c´hï,ºÏ »œÛÅ¢”D=•žŽÑÌsç"K Ki¼÷§B¦Î謦":r¢H2lW´/©XO’$a»LÆÇt²µáKPS,^ò4r Ï;KI… 2Ùä ÀºlÁ ‚äš­Õ²´ÎˆåOŠ<·N_3u*N´; 8ìaG Úâ"þW¤DÚU¤ç^䳑®Ò"“ ÛíK¿RåI6’l„m1ÓÉÔF_5Å:³š¶´tÑûR&2Ò;KeÐC7j4'-Dh” .èñ @*Ö&Ì\7M·è{©^¢™:wr"R=P ‰‰R‰NÓ¯/Ñv¥Èì6hóó9éI õ§$ÃvûÓ¿¡~V’$aLÆÇt2µ1”šbñ&=ÒãŽê\î˜G…F b¼9Weµeeð~ù%ï´˜Ha¡¤„ï¦4¹yÈþÁzme¸ØU¯Má ’l$Ù›çpù`SaC­)oÒc9!WãPÔ)ȸô2a{­o>”Ds$`J)«)]ÉnþŒãž”Ò¡£Ù?¼‘Öz*©0ç¢x ×z’l$ÙÛöpù`SaC­)Ö²QÜ«… F°b\²9‘™4ÙèÇçÔ’lANj’l$Ù›a*,ÒÂí¡bªŒc(5ÅúrŒ™qt æAyG¼ùPŽôb£ÑΆÄFå1Ú@ÎÄà¾K’$a K•EZ¸Ãq*¦Ê8Ñ‹5”ÁGooýÅ_y¾Óѡ݉â§À0uj·W+}‰t õjÊD…È[‘öÑdZ¡),„qæÌ.ï,¬"IF…ýP¼gÐÉæþûïÇ‚ 0ïD†<úï»ï¾3fÌÀÊ•+1aBW £H|ðAÜqGØ¥s°J2 D¶!>{'V"šbñÐL¬:‰ÀnCáØq.Åg²çš±îcˆ<¢‹Ò¥´ *£¡ÓõY‘ÉúrÑÅ0y gêìm÷4Pã¡÷ æ| d?‡ê]ƒF6.— ·Ýv–/_Ž 60Ùlܸk×®Å]wÝ…½{÷bÅŠ¸ï¾û`2™bŽ_’¸Y$ÃÐer>Ĉ_s =ÞÚ^y…åuôcDZÓR,-¸áb»ÿP½cÐÈfëÖ­L"/½ôRçÎæùçŸGee%‘ÑÃ?Œ›o¾yyy’lúiÃåc’ã7„tÅj =ÞZÿôGN—õïB[\ÌàÅÒ‚KW¬Ä­!õkÙ(CâÔ«™T²IÅ;›áb„râ—ÄJb%Ž€xÍdØ•xoR¯fRɆ†ŸjÞhÉ0Ù†¸áK¬$Vâˆ×.v%>âÔ«™t²I‚áb râ³.±’X‰# ^s¸Ø•øˆS¯fJ“ 9 œuÖYƒŠšlC^‰•ÄJñšÒ®Ä±Jçš)M6é ¬ì»D@" |…€$i ‰€D@"0èH²tˆe‰€D@"òdC²6×]w¶mÛ†ßþö·,u“…n»í6|ûÛßfÅ„tG,ÏÁtÍAdàpss3–,Y‚5kÖtÑæ#—|ånð¹çžã:©T¢Ç¡ØÖòåËÓf¤ò­o}‹aU¾ãÈq,\¸T‡ÔD†j>âÙ‡b Ô¯¿ýíoX¶lKl%âY›Jö4T}Ii²‰þÈhr,XÀ‹wªÅéòóÔSOí²è¥Ã8bÅDýêW¿Âÿ÷wª>¤ò8”…£®®®Sð5²¿Š6ß­·ÞÊ‹Æ<³ÙÜežRÁÆb#Rö‰ÆñöÛoã;ßùî¼óΔG´¢2‡fˆ‰Ü{#ùª¡G,ûP´•?²æÎËd³eË–„tSÁ–†º)M6ô¡ýéOÂí·ßÎIA’¦Ú_žÑ“¨|8ôs"lj'¦Ý8”EŒvg‘ ZºÌÇ¿ÿýoLš4 =öX'9FΓb[W\q/4N*‘ ùPœÔ~ô8FŽÉš‚øÙÏ~Ö¹³¡ºD:©:ŽH,Œ7mÚ„E‹±\UªÍGäÚC}§2ý‘Kß6ЪU«ÒyL[ê>¤Ù(ªÑC \¼ö#ÿŠ£E!Ù¤ú8h|ÊqÆ7ÞÈÍuttt#ÍTGôÎX™3ZHn¹åÜ{ï½ü£èEZùk;Ul,rD6D(óçÏç?º{£ÿMš©6…ÌI°’ŽÃɦ"Ɇv4ßÿþ÷ñÑGu!Íd#Ò>ˆ•O~~~§R}4ÙDצŠÝ¤Z?ÒŽlR}gy6M“My{èlýwÞI«CYhA£…™ÎÚ_|ñÅ´G,²QÆòøãóý@ä.w6Ô§h²‰Ô¤…1Þ"­(¬§Ê¢C‹öèÑ£;O&¢É†vÍC½ÓŒ¶È{Gúh‰(اÊ e?RšlÒùΆ&Uù‹(ïlˆl”¿(•þ}ôÑGÓâÎ&z‘¦¿P£/x©Ž2¶T½³‰5ŽÈ¹QÆDs“ªwOÑÎ2Ê‚=Ú™ åM,ûˆ\œ#O-äMâ´•ÒdCÃI'ï§hø£/ÓÉ«.ÒSˆÆEŽéæUëø‰v™JQŽiáÞh‰/¢ODz—)Ï(Ž3t$Hs2ÔÞhÑöNýTìCIîËÑA4ë°(Vù^Ê“Íp_ŽM" œ,H²9YfZŽS"  !’l†|Ù´D@" 8Yds²Ì´§D@" B$Ù !ø²i‰€D@"p² Éæd™i9N‰€D@"0„H²BðeÓ‰€DàdA@’ÍÉ2Ó)6N%~êü#Çï(qÔMEU7ź,»#ôI6ýO>Úw$Ùô;ù¤D d“޳6 ú,B6‘:sJ„9¥ ¨óC‡¡¾¾$ùNR-K—.å<5T"sÒ(©"®D´“ÆE€SQ¢#É•Ü+ÔÊeBÿ655qŽ¥k®¹†ÛŽüÝêÕ«»åmQÚ£gV®\ÙÙÖ0˜F9‰€0’l„¡’H¢È÷FKÈDÊšP½ßýîwøõ¯Í©&”d[ôïC=Ä ùæÍ›;Iþõë×ó±ÜÑ£G™ èØŽ ÉÓÄ"ƒ$rˆ$•_üâ˜={v·ç©¯¤©FB˜¥¥¥¬ðMä©ùEÉóH°‘ÚKÅÄl9§ò]žd#ícHèmgCÊÆ555÷7D?þññÌ3Ïà‰'žà>+w;ÑÚ[Ѻ[‘¤_Yü•ÝŒòü?ÿùOÎC¡äL"ò ~($G¤¢ì„è_Ž$yy"1’TvJJ›DhDBD6J{C¸lT"0ÄH²â 8Y›H²! £¿Þ{ï=¼ôÒK]HIÁZ!—DȆˆr˜¼õÖ[üšë¯¿¯¾ú*ïh”P䮈ÒÄkïds9î“I6'÷üÙè{#E‰9Þ1ZäÎ&Ö} í4è¨M9^£{ÚÑQÛÿßÞ¬Ã0ôÿÿZr(”¢=dƒ¼DÄ®/BXWœ®¿–Ñê 2•â4ô™™V2UŽë&¦ƒ¶fÙŽ•ͶÏÀÀl!q of£ßº¯ºÙU"T ‚(*˜ ƒ+:Øàx/ž£B^–Ê…HWT7~Ö†BÕVâA'ª˜"‹GºØ"ŒKÊØäz3¾çZ|¹åhÕ†<Ö"~B‰Ùƒ E&˜ ‰Y&™ ™™&š ©ÙæDšÕ¡•©l8§ÿqI¢—‰¤‘€þ)¨’I2Y¨“‡B)Ñj­E'ùí×ßzòùe”b:¦¦grº¦§o‚Š›ÁÇ£¥ ùIh «j(«þ¯ºŠ(¬³Êª£¢š*›£òz©¿új°Ã [±Ç««HªÆÚê³ÎF[+´ÓJ«h¢í-[S³Õv{-­ßÚš­¨Á’[¬¹Ú†Äm¸Ô²kí¸O‹m¦ñÒ;oº'­+/¸ûŠk/¿ÿú»i½ß‹oIúÜ®¼e”aÆ» C\0À Ü)Á ±DE,aãÁ%\ñ™–±… (oám¿$,ñÅŠñAKÑÀÍ ±È‰ 3Åg¦² 'ËÀ…»+Gœ´ËKÿl±ÌaóT‘óÇS ÐZ…<÷©ì@È´™D ÔEÙ,¿ 5Ðk?êÌoC$µUW-„7AAþ× ùÜvËgraô@(#mxÚMÿ­vÜl3î Ô]wJ¤wAqéÛ»ýô#Z?5p}–ߎ¾&D—‘Šà*#~¸Ò³3]»Óª§ÎP‘KNuEX®õOReNncŒQÀ[b…ün¾í¹ê½¢+v*B2·+ž¸îßW·ø1Ä»ï¿?Ðåßï×¹ÛS©PPk?™>ý×… ¶@&KYÂW®ñ°|äk\Ý6%ôÎw ØÙúöÖ¾§Då}ÊcÞœ—è­…zTàÏàºÅ-0w'4a¸B Dj¬ÚՄך¢4Ak8^®ä¹ÿ€nJþ£ë!Z@x.-TáE¸D6Ñ…©Ýì¦3~%"b²¬÷?.ö©ðß½F0† †8ÃÛF®˜• ¢W]ˆ¯GF1–±ŽfÌ#+2%pL X;Ø 9H9êqEihXdÇHÚ5ÒvÄ·È·[AU¹a®.eDJ¦èlCó‚#G IRJ’€Oœ$+™‘ÝPå?RA^îbÈØ0IàX£…Ä@¾ŒAnIÌbó˜ÈL¦`X™‘ o séÎÍW7õAM 2`¶WÊnž²{àg QéIf^ä•N‰ž4;¹Ê(Rcïä˜X8îhßþŒ¤÷Ä™Dr¶“‰åŒHº°…-t¡„|{%£d³N$.±C¨\*’p³ªY³'Ó&7óiÊ}ê3œ'?SÙ.Äà.pÁ b0@sèèB„œ%acC˜gí©=”-’£ÞôhGA:T‘†´Ÿ à ^ ½¥rï à3ç¸Ó%a›‚͈ «^tŒ\Ê*Ô Õ¬F-*RJR…”Á—=f È:¨º”ª`=’ÍpÁ®bõ‰eø©ZÛÊÖ€BѰª¬*¹ÀÔ¸Êàø,HT)"Ú”.y¤ÀS€P€M~¦r±_‰pSˆê4¯ý§ëOÅ®VµPÜþ‚ +ÄN²Mp‚Y¨‚› 8E·Ñ‚Tzc¼Þ1AI¸Ý„À€)þ•°ƒeíH¥[XØ&µN”-mm U£x¥ƒ»ÝÛwÃöIƸvLNM…@"ØLòL+Z×]ë¶»‡µïH»Ðظ¾ ¥ÙÍ\2ÞÞÈ)å­ŠñªƒÞÔÔ”o´Ú€Já²Vø¬–o†é;ßT¼µ¿C[)Y59ÜŸb°ìL\à´ëmïV7 ÝS·¾®Íoޝ{܈ð÷—C;)€b5ø8ɽÛî–ߨÆú­îŽïÛãÔN™!`8iJWúT‡ù`GN|'=;9Ê8Æ/þÓ Zr¡ \ «K‰Ôf4@«…|ò™¯,e5S™ÍÓœ3IÂ\¨7ö¹Ê¯åsš­c?O͂֡­ìèCšŠ†r¦÷\éHKºÍ(ÜX$Êè5ƒÚÔö4H&èNS³n®æt©ÿ|jZW¤ ^°‚¼ gU§‚ÕžµA–€ÕÒFTØ–®õ£7máfÄ 7Ø€´7pQúÚk©~šiý¼K;TÏÎÖp¸Íà—ëº0@)>½ø‹^W¦XŒz‰—ç—«ˆ–L©{6u•îÈ.@P1N°Ã58IY–‰™„©™©@dÓ1 [ðÓ¸\p—H•Q<¥€¹jÉ’h … ‰¯£R[0)… `¨™gޯ𝳋™y–°¹’³2˜™HUH²ˆš= à› )]vG™'3–‚þ¹¯i˜Ü FýX›¶7I3W\`“K…ÓÏXS`?àZ0dŸÑ#¸ˆpæÖ¨ŠßiœÞÙɹä×S0@ Š˜2P=`0Îøp:€ ª 0ÐŽÿ£r5ÐS%–ÞÈ™:¢ªœ*i •>>@7'³:PB0aÀPU``0 GP0PˆXÐ9ÀUø_À&0ú–ÇY€$ žà(žµ7v‰Maà^'¨i—€B?€Ž^€Dp1@êmÐR=`<КQÊ)j¢'J›Ýˆ¥D;î•ꦕA€þÀ·Q¦sµ¢4€eP9ÐY Z`=`6ÄÙ—%ª©Sš§zz§+:Fº==X…8•WWУ?ú¾ù4 JRpúƤ3žÂi›wÊ©¿z¢Vƒ7ð”X;_Ð:Ð<€n5€7à›3à‰RÐày5@ª Z—¬á*›ýxRGeŽÁ:G  ÚƒPap23@ô c ŸôéTÈYàHš#X@p™¹©þ‰°ã:›–1²d"H£i [@¯=°v'¸|1z§j¡ø˜ ÄVÆFjx›&‹œ:š4%Ý'þ¥Xˆ©P7 ‘:VØ:VÐÖŠakÖS9ÅmuZ vJ®åŠòÃxrBKÀÇä†ZP<À4Ð8ÐÛJ6p §‘VÀN`E`NàîUzQìå´lÛ¶n›ž*l$ÑD Á'˜‘MФÀ5ðŒÐ8•p©/$9§²ÆvZ Û©F›wÌ$KpñèŠ( d“‘ 8@€ûŒ(ð¹3yQD Z©š1`ðUÀÚ¸FY¹-y`±P6˜Œ°Ëeà:p% ‘ÒxŽì‰•(`®ƒ¥C`XªFq7`[«˸'[páþÉ™U€5P€00“8龄'ðf2€?0îõ1T˜Jš…CöºÕ[´ó·+®×S©~ ¸Ÿ º'š(£ > 'bÐX°k=8@aаB¸°²K´l¿-K¿Ê3ÀSyŽ-п'Àž1gí _J̺n; _p’6°­:𳋋¿Å™»¬®64(Cë˜)Ð!Ü2ÀqZ@SÀ»Xà]À[›8­7…= Xì²9 7ÌÃúd:ÁŸa2)õKr¹R)e€$œ+\BT|µWpHÚkf‚4<˜ÿ™ÅqÛÅh&=9þ/ „§‘eÀ497𨩦 *k,YÈÄ·(Jr¬Úhõ'‡ÁX\¿Tv½Ê¸W©R(ÕŸŠ@Kµ|\@pžYJÌÄHên̺ªŒH¸…=Á³£s½¦°f©ÇžÊÇWÆ_ÉúXX‰X7i[A°'UÂêf’>X÷tO¥üWrÌ5ìÉ[,Äœ¤i£9\Ð L@¿4¨¹'R0P ,|aÐV(ÄaĆà;¼æÍÁLα[Îv ‘ᆆ\[B\Nè†2Pð”Kõ·…,÷FÝ.½ˆ™šÇ ½ }þÁLÒÈ*—dåZ –fà”ºø”Åˉl0Kem÷™Ÿ –ü©É‰òÑ¢YÒâŒÇI½ Ü¸8‡RÓHV™üXq9Ê.€“(RðU_”t ÑS@öKÚé-—[P(¿&=Áw«j@ЗuçŒl”©'¨ÐüǣŒI¨ìÉÑoÅ]a´‰W¿1™•gUpQ¢q%ÝÖìÖ.wsmu6v·¢<ÄÄ.ð½UÉÃø$‰(‡«ƒÙD§‘8Pê'¿”½Ð‘6:çe¹Ù© Œ)Ñ,ð¹-€/€“VùÇsÕ?§½ˆEYÂIyKÝÜÔëkq±™þ]yÍ:»ý”½M¾ (R=i•¦œ­UxØžhŠ ¥Î}Ç’l¨bÙúã’ú–lÉq9Vy•Ú½ÕK5¤¤YÜ1{v6­60{|¹Þ+-Ûþ¸™•?ÀéÓ­=¥0-–õ]£ü0!‰á4PmêÆ`‹Í:{aYé-̰m½Uªlç½ÚÉrˆ¤y•/PÇ:Ÿ `\¤@<îF`¡ið9€‹جÛ(íæÌäÏå“8å Y:`ШÕþ0 ¾„ú?P+€FìB€8pæinÉ'‰ËÓ«Þ).ŒíMôs*ñàñâlÍ»Y ¡=ªR:®¦”0>`tsp_PäGÞƒIþæ“}ëM>g£séá‘:P‚Saú–*õI0]P`ã@£@P€‹0â¸Ø³·â¹Þæ¸irŽ´a®¶tßÅÖ‘`LÀ@¨pjT3?àæ àæ6ð^0àØã\àV<Ã*Îð'ÍLèj ëø ­ä1úî裂ÿþpv™þÀW@£A°°ï«…Žˆ2Û>ép>´ŸìR6H¹ßþîE!Ÿ8*=þw£•#Ðô¢ ïsŠÜW04æ ®¾ä éªfÙ ~>Q<îGLû¶Ä$EI°P ð=jô~3wƒ¶IP»Äò; Vp-*LRðÕ€Oö„_øl{m\Ù^`ÛHš>È› 'òðÀã&pï_I?€>€Àz¯oY8õ¦,ó%[óZ¬ÞÛx&“‹»õKž"›kV ˆ‘K¦i€òN¨v_'ÀêKô@Bð ­8ðñþPð:=G£u©ßóÞÞí¬”ø½N»VQƒç.›Å:…Ž×lJ0EB`7À–_Ô^ã‘C†þï°dð°šX«LÙ–9v¦K3© ¦’rPáB† 6„ñaDŠ'V¤x£Ä5vRäH’ ÇR%1%Npi1¦Ác† Qbņ…_²èðÂБ!E”˜Ù2cK… `X`À`‚0A(\ ’dˆ 6|bGP/2dlá’äOmƒÒœ‰0îÇ‘tEÚ ‰¤Þ¸}ýþlæL¾?.)šêÊ DRuÑqÅ!C1^¼€C¼ þBAF4DX¸Ñ%U‘ 7$ £ÆcG+:z\ÁÒ¶ \Âs}÷³p`âÅ)Q €åÌÁÂ+D\D1 Ž¿DžÌp‹ \’˜f0£ŒÍ £Ó˜ûe5×{èØñšãÆ÷a þÃÛyp—Ãgó/É?ã4î¤ä–‹©¹þž;Ȧ!’¸"žºøIµ…좜†hÀ³º°Ê‚*Š 2ðᇰpè‹¶Ø’/;ŒxС Å€ÒoAI0¢çÛ@!‡ThŒšˆ ‚1€ˆKÁ<¨ˆ›†À ÚÒ¡!»’h Ë.‹ˆÁ0ˆ€‚¦*x  Ö\3ƒ6èÀ† 4èþ †´´TèŒÝª°Œ0ÞÛóGø pÙÕB†;J(÷vxO` ꕇ,€5öÂk÷¸£€éu6aÀ"âQhÂÚl .ø \ØÂ p+°âŒj¨ ˆ!ˆÈ- v;(¡v{x!.ò“IØvs1 ‚'6¬bŒ– g…þ®¹¦Vâ u<š¢‹aæà ~ÈAƒ *bäÚ¬ÀàJh:ƒ‹_0kÞ„ ë´­›“¦íB}þh·CbÔ4Ž`)¹™Ó¹m¤šBJ",àV‹L0Ⴆ֜€‡ .Àá²F™]\xa .Þ"¨„„µ90t臥)$ ^øÂžrô=省ƒîû`ùDˆ¤ ¨–ìÞw6‘DçRõcÈMZ²œ¤6]80àà@U TáLÐèjzfi  &b0xAƒ ÉW[:ų zƒW´bE(Â&§„* !¶F"¥$ D+ÒYQ¦Ð…+AS8È—ÀÉÉÃÕ­Lp8Í L`ÛÌ"ÁaUð1¶±Bnt¨Ó™N|ÇÒbø¶8IƒH93#A)-!Ñaˆ”À”< |O•<Ðx!0ò8°G8…a —9ËbìeFV€¢Ôþ“ŠŸ “Šry¤À’‰°eR2aFŠûI»(¡(IÀÔ<É <ÀBÂpt4”!МhE‚ °$à@ ’Äšas 3ÈuBgÙ¨6o9f@§9¾0²Ð™M4%5PO}GTJÀÈ”„ƒ ›DÂâ~€4\¡0H‘~pðÀ¬²@:  € ¤Á ºP3x Œ$ÛAÌv%ø WÂêSÅMFRŒ¥X3š(…JS©ÈL—¼Ô€2VD E Â6S5€'­@ºð‚`æ&`Sàôƒ" ¡ Ü_©GÈà‰ráÜnôþ¥!¡MW @Ÿ*Ø&u©m*IÚ'I¥¡K£2M KÃP9ÀDºÀMàƒ œàn’UsÚŠ0Õ Á•$J—Ð!ý˜0N` «ÊN¨ˆ8«å½s³V€Œdð ý@dxŠ0Ë¥¥ÌhÞ±~ã\Ô57‹Q苈Ã䯍”Üm!p¾óÞ3Q­Æ:¡W­ì7Á–Xàa±,š04,éx¸Àñ–×Rdæ»’†ÈÛrƒ"`ZdC8ÂY¸ êQ«ÙÛ6&u©™Š’•`²’ÑJ€ -sþ¿:Æ={Ÿ”6­!<àBBi¡Ò pV˜²@:qpÒÔ1r^!œ Tc—÷ 2‡Á¬i—m[Ô¶¾83Þ=q7Ë’j·BÀÒE´Ž!°Öx¸;ÂFpÏ j4šŒp39@CRè@„¿‘´H^Êx :Ѐ 4@T^òà ©82„#f&d~7}Áýí4o½ãåƒÖܪÅn×MÀìf—BÚÕ¾v¶·Ýío‡{ÜåÎöÔÝîngÒ.s#”È› ‚¬p¹Ž5èÀ+qТݧ&øAÜ«p4A MЦƒÀà>˜{èE?zÒ—þÞô§G=Ü¿®0ÑdÜ\Ñן<ë_9è¾`… …²(KHLé+øTçAÂ*ðïj{€@eœ’®ø 3b ï{®†+èàE¹­mùÀ»r=Ô*ïzúÏ¿z±@0€«°ô1© @´BPÎñ/¤û 4†p›à¦¨€ €ŒXáŽÆƒ1H):jäñ€[i`ŒÁº†ƒºË€ºC-ÝA:¢òS¿nóºÄ8ôc?â€&uk‚h7’{?Vû"w¬’¨ õ*ì`‹*‚$P>³9Çà⹃?Ñ€ £ '„«`X tþ0°‹)8'î‚)ƒ«J‚)à‚³(ƒ*è43KAó3AL¹4÷kµüsª㿎Ч.À‚Þ£ˆ yȪà+ƒ+°£3­ ¸£Dd—0°ÀGäØRˆóx1Ð "²ƒ°Ô6nKÃ\?P\C7 KZŸ9DÃ6܈î󾢈.©ª §,H/èÀH< "°Ftž¨@ <"“è+ ±‚ª1·1ô4fLÅ:TÃOdCh$ÅÃÚ?G¢Ã†ð‚+h¹è’mâqªÇ§Tø‚·Òš8A`œžSa0CyQ4‚ ÈÈ s+‹\z±gþÄF€¤¦þÈj,S ¹âº½O‹ù·®B@Ì/àÈ’T0ƒð€ 0- ¸#`|D+Ĺ³°íÄ À€¨‚À€ ÈăÃÊG$(OTÅi Hƒ,8ùCE›¤F“¸*%h7ÿ˜‚ éÉ‚¨;¨°°‚Ð h““Á•SA*dÓ‰‚ ¸ò+ƒ.Ø‚=ˆ+p"Q”ÆPÄÉ‚ŒFt Ö1ˆØI…܈ÜÑÓ:†À|û®i+­@ÄØHVAI Ѐ¨$´Î:GlË© @B3ȹ Øl2!Ad‚ ,™™¹¬I‚%ˆºçÁþC+ˆ.`9õØ@VªÈ!"+ž(Ó^Ö5`0¶áß®ˆG!'Ú'¥Ý0¾I«5G+ ¢ + ‚%°—‹¡ ËøãVÌ­dc-•!:©È+€™/~Z &ã¤`6cùÛ2¾avÜbš <ö€5¹vQpJ œÊpè×ݾRrîòÚŸ¸ÅùBàŠ_Ê‚}jã1ã\ÞäKn½ÚƒàÀæQ´V¼ÎÈqª•UÒ°   0åh•^[>¸åi­àh/†à‚-@‹GÆèÄ’ö¨ádfMžäÖu?øûa×þßxØÓ?6³mtKŽÄS—bQÎÀ{çå   ؘ@Å,8¸§c08¢܈ 1ƒRuÕyÆ=d–çxæÞ‚l7²S·#¨–û“CÁðç_˜õ}\æaž©‰“1ÅÝšÓ&C4¹VŖ, °NÅ­Œ—‰ÉŽÍéuPñ¥d“¾êë½ä­ÚaðuÝ—Þ*%±½ ¶i„éµÂÐi  hš(L[j ¾dÃÙ°GV’T"*çƒá˜O³éƒª¨Óu®d7V]Å–ä­6 r;E‘“›†ÙªÚ;‚³C»ÔÓìÍæì¸Ã‚[äþçUvqè;jáê«H…Ž•RJÙµj‚*h8±‚¶«‚éy)x‚ʨ‚ÐSX±Œ§ªÎ&îâ6nÒslâ€Ítk4k|瓎dŽX[·ehf—5ñâ í¸ 0ð6‘@ü8É‚]¾e†˜\èƒ- DÄf3È"-S-ÉøÖjƶäþæïÞÐKbúˢƿ±Žî¬Î^¹€Î’Ú •ZAd³u­ç 49)I-Ä”±£€(s>"0£k ù^ˆÁë/¨‚îcP,Aqÿæéþïßê?Ed}®Á~&k«æŠ$¢1ý€–¤Ó[!þñÊîà‚Ç ™’†ˆÇØ‚xÙŠø û‚0øÑ×LÐS”†äŶñÆNnãÈdfxž š4}BB»Ã±ò"/¯îǘ`‘™ý{…¡›~N@¬²9ƒưîÄη^J_óŠX`rãнüñÄžŒ½BÎ phd££°ê˜)Ø:¥¤¸âp—ਢ(„APØPŠ£+©»½ŽðÖFŽñ3Ws4‡seoÝL?„$Ôšr¹Òt{€;÷«ôÈÀ1Ô‚+®9P Ðá?g%\é€,X1„á%ÐáQ9nôiþãÎ)IÐÖ¾^ödçwßw3ÆqhWWi÷tYP+ WYÙÈ9¹EÛœ d'ÅœåÂ|[)d:ꢨ5=©‚§{Óiˆ]æ©Üª0ƒŽO…7w}ÙÍõwéfv7'és øcÞï4—ñbwä‡` `m‚s¾œC¼p7–B @O…Ø×:«g½8ž °³CjEÕ`"í4ˆbvÆ’{œ6˜a"'jpÞ³‚+ ÍJs˜ïw'7“{3‚ÇÛRÅ·ÿÜð€4½Ü˜ò‚‡ýwB £w^Ö£ Rb—‰Š£Ù‚ñ…ûkÕ—Nâ<ç×Yˆ2¸¸G=Ë——yþ3/雸¶ï‹6GvÎ/}Ùr8øxI3¨H° § ¨HÅ1Zæ€åÙþ±”9ê‚#Ї `j‚!VÜøiˆÎE2ˆX÷T(Ñw4€ HµšixËïó}µ7ýÏ¿ô†8‰Èö/¸×jìœOþܮՔ©³HQ )çËýÑ”è Ù&;¬d±²CΜ<Á„ cF²Ã¤^înÚ›éo®Â‡8†€Ô#ÅS]å\ésÝLÓ긆&Ž4_¦êáÁƒ‰0L¼åСÆ÷zCxH9øƒ‹%Jãb¡C"C”d¨ƒ5i”J9lP5lÀÃA5¹wÞæ-Ø…G:ØPÝuÿ¥O’]QX<@'\tH•Xԉ˩¸¢OÇIõ"ŒÁUsZ¥Ôþ?5å…M5}(™H©t0 EÊ ƒ Ü0¤^"ˆ°^ 8Ì7%zd€Á•A ˜ÝЩL‘DWà…fxЩ”áfØPZ 6à°fn´åpƒ ÔpC›nžÓlõֈΑ¸(‹:Z”‹0Æ8£V5b•cO˜rèT¬YQF¡Y¼ôÅÐ0Ã[”ÑCW`±G}ðAfÑç `ÔP‚³‚ :™QEAJ qlð`C x°A˜0åðìžw}èff nÆ Šº•5eqfV$j#£è>º.»ìZZ•¦²¥{iA˜í€™_ÄdEE^X±ì Y|Ášþ”†%Ø`Åœ˜‘i*c«ÄE\ Ñ šÕÐCL;ðµ'¥ñ`…XXèX’ñ@Q¯‰ÕUA<Ú”›tŠÚ¬n»9ëLÜ»¼Í oº]¨•Å€­Úc€"Iá!La$ ”D “E¡ÄŒ‰™ìD#«õpA`ð°×šO#ƒžqm­Qf4uQ]\áCôÞ¬÷Î}ûÍTϾýìóÞiÄ–S%]1p¾ mgîCaHäÙæÑʱC¡¹Öc$ËCBeé6C¡‰VÃkV|áj`½\V8<$ÌUÙtCM8ó½ûß½ûnÕà‚ï ´SÛeQÒAúÂÚ^ì€þT´DE±yÔq†WÜpÁ@P`†fu&fÖÑ´AÊm¼©àÖÁy ¯;ñ¿ë¯àÀïÿð–rŽœçC9¬žïЬ EB²˜ÌeŽ_5ɲn¶+ ç5¸L®0$^m_=À 6œ¯h3iÈÝÂûñŽK¬9 Žö§Cßõo)ñËJiÈ”,"¢9]šå–¸ðKPHÖŠ0¦"$!zòA€°4(!Yb±P¯l0ÃT p={Á‚Ì*6™èÄ?6¹œ/¤±1úðn¬É倸Ã=꬇窡 ×”(én7øZ“¼ þL2/{ˆ±¨X½!©À0cÈÈ<â]ºÚ5ÆpMÃ\ë ½z5R;p²B…iáÄ&¡ ÑMT’£“ìe»ÚõÉórLȤ˜)4QÓ(óÏgªå¢¼¨? Í·Æ wå,ŸCð$}eAÐýé{ÃfÕ·„Ï’¥LJ­Ûpé™7E(1·ŒØ/!ãp*Ž0QZ ¦>5ªS­êU³ºÕ®~5¬W½$˜Ú±þ‹5®iiSk–Ö§®îš …&Ô³ ª¾›® j`ë@ØÄ¾±W]…[}`>ð^`ÛÀI¸A«{Íë$Ý@§´„6®M ìrÏ2cÆf¶³‹îyÓû¸šÞÙ—‚4ÁØÖrcë%K³Ü³¹M™ÂÒİᵱZÔÇËMEdAU5¥ø"èèR^!*îʇÕ?|áXu8Ãu3öÞGý2¨Ò\÷!d®ð¿AÌÊ¥äKá-ÊÃjµGò}ˆ¿®tèkIGäö+(ˆäÆ›ü˜å'œæf0 Uá:£Lè‘í JB2ˆ¸ ÌþÂ>uäòMK—ºè·¿®óL뤪A±Ï—b707r62rezÃѤ&xÁîÌ»².Iê¼âK´îì=máÀ j!xŽg6¶ñð1!.¨’–5¾ž6Ál âø¹»üærÏyËC¬û,·ñ?%©:ÁåÈ ÚIKò›qÃb>ñ€B4ã’p@cóÉÞ¤ŠÉÐ(´Or5 èI¤ÉæÜÓ¾öf.3˹‹{õ#e«dÔL@{-šäÀIYP·Ÿ·2~¶ ÌÊÓÄG(O-=Úîõ]QyŸ À™š(S*0Óþ‘_ùišíÝœS]àÞõD¢Y'Å_¢=`X8‰þ°MÔþÑD{}³\‹ëÄDh`ä†õ¥ˆE„® zà nà>WÎj !ïáÓXÁmÀ$\ÏÆy@ñù’ÈØñИ'LÉ L0”ôeõà>¿hÀ\!š< UY†²ßÞ[¶YŽá™­_òÔÀÆäÍUÉ\Èúä½Ö~…yÀñtgáŽchÝftŠ¥„Œ^”ÀèqÉÞõŪôI”XM0âe‘&ÜÂáùáœÞ¡’_MIi@N*ÔÖ²h ¹`V©HyœÕ’\³4ßûiK0fy‡†ŽOÝžP”þ'ŠQ*xAÛ= ¥õ€£1ã)nYšâò¢.•eå@8 2"ÝŠ±Æ!ß"éÅ|†^àÀ€KÔÀHÆ•!™óyWx!ÍÇ‚xL \Ëo¼`Y`O”¡ B$£fcbm£Ø=$E¶¢êÞÜ@µ•ÜIÍ›)„"z€*Г$DZì…K ´ÏÚœ…ûÄ ´ÅK”A^”@„Ìlõ¥TQÉ¡OÜ A ] B$*¦ßvcE²bîeä³xžG ¿¬ šð ¶Õ¤Jª’…¸¤¥•AÎT[<ŒÜdN–@ŽñäAŠâÙ`*\Q¥6¦"6áE~£*öþG|@{0ÕÅ„ñ ¨áÅéÅ ®•ôyMÕ@à£á$aÉLK4Ïó$d äÉa™CîK¿0$N¼%\¡\"¤7–æRÖågÀbfåDãEžÓŽK™Ñ…„"#ý¢Hœ(È!ã͇ìbd L±Pq¥ÈÑØDÞEÒyâ‘fh”D&Þ*Ö¥iZdR>Äô€‚£NH#á-ÓäÁ]w¨ |HÚaÁŒÓ"æŽGø E0Ë—ˆ„ó|›Cðxá“éEŽÑAØW”…Që…EsåsTtn&SRçi^§]ÎeR(_W=I¨ÅO Ü—ÐÒ‰%Å1¦¢èc…È"ƒþè/è&Òeˆ.(8Nç‰ EHÊ_*¨E´´XO,Ò‡øEdªÞœ½ä€.ULÒ†Õ¥¨ˆèh ¡u©‰iƒâÔ †–PªàŒ6!ÉR ÕX”Å¥ÑËWNÜŠ©‘(hª(’Vg‚n©O \)ä¿4dßɆþhP¤ÜÊ¡ËÇm!à)(—v騘Ú阆)Ef¢NÜˆà‹¸Å!êe,…Ùu§gô@ UX×ÝD‘Þéßxižrã‘Þe¦:¨2ÚD…žý$z*Oè•S ÞnÐD’ˆÝ^™DÍ‘)¥öŽ¥"¥ŸN*§¢¨¦RÙ.=’žÖ^ïŸ@f¸©þt*e­þέÚa®öi¯î)ƒö$ý$¦&…“º¨Sñ‹#ñK‘Jk³öÑ¥N¤®:gºŽk]R)Ye°²hãpkˆ½YXðËGŒŽº’k¹¶Ë³‚é´òê®®«ž¦œÃÅkPÌOW9n+()+‚òi¿öÍ¿2+¿²+­F+u çÀ … :Åà‘jA8ÈéàÑy®Ý²nêÄVE¾EÜݘkÊ&)ÆjlÆ,ê,R€ªí„bhÁ„Ýd£žÄêÙ}éʪ†)G§ÍåÅJìÍv¬ÔÚ¬ÕêìÅ6ÆñkPdßBRhÄNmÒ.E—ÁœtE@‡DŪlÍbmÕº­Ø¶-ÕöþĶÆYP¶åeÊìØ …ÙÆ¿…©•Z½ .á®á.â&®â.îàêš©aA¹½ZTE›ã.îÞ6JߢÚB-Û"ìÛÎíÕ‚.܆î¾úÄ_.¬O($CÂÞ^nOpš§Íܬ2Gë"mçf+µ’®Ü–îHØ s>Dkö'êÏ–(ͺnQ´ìËâ]L¬íñâ®Àò®îznÜzn¤ö@lx'‚õ„¨¢lØN/ò‡óæî薯螯ôšï¾†ˆóéˆ/ù¢oøòLíÖ/®ªïîæ/ÂOØh)üF¯þÎ/Ø/´â/õ‚/ºf-ôJÔϾ`nú 0ýÞ¯ü°/°?põb0’þ‘ŒÉ¼¯í>¯WÅø°W0Cpk°Æþ—€¦ð³ð‹/, Ëð ÏlüònmµÎ G0Ï0V”0ÁÂ0ë° ñãpÿð ñRq#q3ñïSñcqCqRH± ;±+qÿokq“q{q×pÔÆ0Ã1¯±q«1Žßî÷qñ_±oñ ç±bQ0ÿq²+r‡ð ²!÷Ëq![2$'±3272'O²PT2 _ò(gò²#'2*¯2(Ç," ²)r,§ñ&“ò"Ûr+ñ‹p*Ãò.G²'ã²*çróþ²þ&÷r0Ï2&ó)3v1³,C3-#s'Ws-[Ÿ3«­4/ó+'ó6—rëf3ív³5Só5ó-+38‹ó“ó9»³9Ç3+«s:‡s6‹r=Ãó<£ó#ó3;»r÷³> ³/4= ô?£u€uq./ï³AûsAô0c³oXÚ>sDo4EO´ W´Dƒr˜e@´G¿ñ:w´7s´IƒrÓb4à.ãÎ4M×´Mß4Nç´NÃZBž1¯Í…t9C´J5A¯tQ×2ÇœÓÎ.>#tKõAƒ4K£´3cÌ:40KµP¿sRËóQsuO;ÅSSõWuT{uZƒµE;sYŸþ4³uWËuUµ.×õC‡µZ[5TóµYÛµR¼5R¯5Zû5\çõT¶`‹õYë5]7vb6`#Åbïu\öe;6f#öd·1^oµesögófw6%Ïui“¶f¯vd36a³se?ök·vhƒ¶l¶]Çvj73mßvf÷6+‹³n³6j÷hó¶=»uq·qCör'717sO÷s7! wu;·uOónG³iSvvÏvxã¶k“wOK÷xÿvz‹öw‡òzÛvwsws‹÷@c÷vs³vç7}ëw{Ÿö}§ô~¸yûv^Û7«÷çó³wû÷‚Ã÷|x|ã·€7x1'x_þ#øƒ«6u[¸ƒW8ƒKx„k8ˆC¸»lV_ø†#7†ÿµˆw¸i7­ìj5‡¿w·øa›¸ÁÙf4zã¸d“ø‹÷RK×ߊ©Q@à.9“7¹“?9”G¹”O9•W¹•_9–g¹–o9—w¹—/9U|7ÇÕæªø ‰¦9š¯y;²›Ÿy›ÃyŒÇîÓš9 ³ù›ßyœëyž“5žË9 ó¹i£8Põžûù¡7…š'º3ºg÷¹¢ÿ¹‡k4¤7z¥?z #ú¥¶¤kz¦Gú¤çbŨWE©;Å©7Eª3Ū/E«+Å«'E¬#ŬE­ Å­E®‡:¯÷º¯ÿ:°»þ°;±óQ˜yZßL—r{ß:˜E²·ËOG rÀìHE˜?Ê´cu¿]ûT¬ H…§a˜Œ„¶‡‹¤ø4´³û´ç›Î¸ì¼û®s…ŒCÀü-¼?ÊqÀì—ÍœpØ´;J¿÷Û˜7`ôÀ³ÈŒGSwšÂÿ­püûÓB…uÝûì†úD@ôA¶·K§Eׯwüǯ ƒYÂs|¨•|£H|Al|P@AŒ¹£¸<¿i.,ïɧ‚̓ûP@¿É<Íû|½ ÅÈïÐܘknFk¼Ê7mÎÈ®ÈG}´ƒ<Ú ¼ï›:í?„ìb¼°3»ßü;´ï †‘4óK{Î/tÖ{{á/ǶÛ]·Ÿ»£x{rûŒ`ÿpDÖDÃ/o±¯?û·¿û¿?üÇ¿üÏ?ý׿ýß?þç¿þï?ÿ÷¿ÿ×*@4p$ФR¸aC‡!F”8‘bE‹1fÔ¸þ‘cGA†9’dI ",¹’eK—/aÆ”9“fÍŽ' Lc `@ ž@F8šê¥Pè!Ÿ©ZÃÓ)S Ú;–lY³gÑ~س'¦GRŽ1ð³ê€&Z <Ù4ëÏWÙ À€ÜG~Ž!¤)Ò´!G–<™2Fœ)ÑDPyd@^{•ʖqئE'-l ÂßÅŸN}[™vmÛ·q¯¼¬SsBΞUæìiä³ÂÙ§S§ê ¤@âÇ@_8;wuë×±ßÞíjÜÏZO±Ë¹ ߪ~#hW)Õ·ƒ f—?Ÿ~}˜Ûwöü‰YxþWƒKù ­?ªrâ.¾§Ü ˾lÐA›¨{P )¬p¬-ÌPà 9ìÐÃA QÄI,ÑÄQLQÅYlÑÅaŒQÆi¬ÑÆqÌQÇyìÑÇ RÈ!‰,ÒÈ#‘LRÉ%™lÒÉ'¡ŒRÊ)©¬ÒÊ+±ÌRË-¹ìÒË/Á SÌ1É,ÓL–ŠBÍ5ÙlÓÍ7áŒSÎ9é¬ÓÎ;ñÌSÏ=ùìÓÏ?¥RA -ÔÐCMTÑEmôÐ)V’‚6I+£t2K!ÂJG9íÔÓO…´$M##2SÓBu!U—ÕÕWa}4ÒIi¥ŒÕTnM2Ö]yíTT’r-+X²†ëÖb‹þìUÙeeµV[+Ùÿc+¾•˜ÍvÙ_Gš¶&oiW¦c[ÌlŒ˜´MwWnEwÜg%#—Es:©èÆ€€¢Œ²+3©ö“Ê®‡Ô-ØUvCr7&…abØ%yW¬Ö§1ìM_šPN=ä"ˆï-Ì!ƒIöuÖhQ¾TÚr#8wº{[n9 2–K ™›  ¶˜Àç JšQ„ArøaxK]y^™§£ª ŽeŽ`㎫Y"¡±F”èŒn©k–¾vV¢°” ó êsÓöW3ÂШ–®†²®›Ð­="X¤OUHz-²Ûn¼;Ò»[¾ƒøGÀ+¼nÂ92¼]ÄþSõÛC®ôsY,dz†|#ɦ-Å9L›­¹7ç\hÏ5½hÑÏ"}CÅ®5KõÕO†6åx-ï±´nšõŒ\çv³d×ÐôÌÉšàxaŸ‡^÷ˆŠ§lù‰›~¬ê ×ÞØÞŸ\AüñÉ/ßüóÑO_ýõÙo¿ ãwç}úL=£ÄØÂ~¬öùïßÿÿý÷¾¼yï{ó{÷ c?üNs6à!A÷ÁÏ€I«`Cè" nƒ`÷âgA• D×ÀÀÄÒA®p‚ aß.È &Ð(ùƒÛD*Fî‡äû`äh“b$y:ŒB>†˜>$‡#éáYøÃÏñ[/”þa «¡KtHÅž‚ <Åc™ãâRE5rPŠ­£b¸¬¸*™­9Á‰ˆ½Æà„‚`lŒ©xÚ’”#A/HHCÖ˜H¶‘xoœÉ/RÄ ™ŽNÈ"E*Ö”Þ%-[ T*ßülŠ4e)8ÂŽ|,:BVC=ÞÅi¸YBÊ’!§ô%ûIDG¾k• ¤YXCæEä?‰éì¥FjºäåB~™Mô3’Ã\XY¿û)SùG´™N¦R~ÅD^+'IäœQç=¹i‘cêÓ›/‘d… ☳ÜSù¬È>ÚÏ£‘ðC×Cþ ªMƒR¡U¨×ài!‡zq&•(;E¨ÊvÒoJÍæD3ÊJp¾“¡ú$ ³·R鹓¥"=à‡4™¿ÚÙÄy2%ÖE1êΊŠ%(.ˆm"¥.•©MuêS¡U©N•ªUµêU±šU­n•«]õêW³j‡ Õ&Šèy8*°®•­muë[áW¹ÎªbÅ`ý’ƒÄ‡Ö„®}õë_XÁÒÕ®W¤–PxÔkòu°uìc!Ù¸VŽÊ©µÊ"YÍn–³Õ,eÃù¡—fL#9[ÈÇ@s¶ÓÎÓ!žuíkaÛ¯‚W92-Ñ&24`ÌbàíCd\áW¸´%kvþn[µÝ’öeMlq¡]é:Ö¸¶•%nûƒ”å2Ž!ÓõîwÁÛÖêâ(¹¨eÈa>© žý,¼íuï{§:ÞÙ/cË}Ú!sD?²µðõïÛ+ßåÔ19m™ÈÙ_/˜ÁÃ0”a »öÁOšð…1üØ ;)Ãöp_7ܤ˜Äl 1“Jœb‡u¬TZñ‹a\×O©§4ý©OolÓK‰¤ôñd!™ÈE6ò‘‘œd%/™ÉMvò“¡eJ5žŒ•#ƒå,ÓFËér–¾œ–0ŸeÌdæreÊ\¥4—eÍci³›ÏL™7GiÎuOìrÛzÚ$ M8âïÌE3‹íÒ&Fo5™íën»&™y¹ín¦բЏ©m%j÷º,¨}ZÖmíš|%Ã7‚ÛÍ[CàØµõö]ÍY^!Í>ÌÁ5í¤} |,rAº ‚¤eø&·M8½ƒ\,ô·o’HòŒ·¤ ù˜É—þ%W‡\ä¸>ð°+ís]G{,;gÊÏSN“&tQ!;Ïu°i"µ…Üw¹Dǩϒ6ºh’ãQ?©›9ó–pZëçâºXb˜®(ÇÔ5I/R¸¢ÝL7šîu·ûÝñžw½ïï}÷ûßxÁžðdÂ\×+Â݇(en˜»áHzSxº‹ØéˆV$bV†`^óGªäÝy†˜)oÁ g št3—ôö>-] B•¶‘AHÌ\P†"Ÿèdõ 74NrzPv‡÷gà æ›^|ˆç­~ L@ÑJyøx‡.”WÌjV#m߇ øSHNC|· l9¨ÛM˘ïx§ €-ì±cäó™¥È÷æ){Ý~˜D¯ó›ž÷ä£ù¿/öò<,O Î*òTÃ7:CþT"òÖÂþîïK(ÏùêMù ðúЀ<”ãÿ4Gø–ï¬0'D!–Ã.ÌŠÿ°o'Lp_ £ù"ÐðHCÖjHî¾¢Æ/cz£)`Šô: ao.Æ`-P¨öªâö>£™tâ“xî™° ð ¡0 ¥p ©° ­ð ±0 µp ¹° ½0D!ùû,h <˜ &B€Oì;RáH$8²o 1 ÀÄÉ‘1ûöIc ’(fB†<ÂäH•*Ϥ !ùû,h‚S¥: À>b¢ì[ÈP Á‚ $yÂp¡Cˆû"JQØp Dˆª8i¸ïáGš<r䕘yDJJ## ˆ8ˆOŸ/L¡à$_V8‰€Ž$Ý7†‰˜’@`âR嘅RÄ@‚„#C309’fjR&h“!ùû,j«S ˜jŸÁƒûR‰!(¡ÁTI 0,ˆ0Õ3R&V @`Âôà‘1XÇtôˆÐL\&GÒ8txƱÀ!ùû,k ¨SíH°àÀT D˜PáA†Bt(†!B‡HŽHÙ¸Qá3RR…¼Xðˆ$QF’,h`Ì‹ 8¦BbjZ483É›û(EçE¡CgVqBðÒ¤¤<èôiP¥L«"%` «Æ'U# ˜iæˆÙ#LAè$%+‰‚D@€G Ža"f 0aJuLá“( š)ÉäHšˆûÎ,6!ùû,k·SíH° A)R *Ü—ªá‚ >d(¦a‡©’@I%EàÂTcÌ ”h0ÕHŽDThrŸ1QVB<²/ʘ$Òh@ N: ÚLòDfËlJù©3i€š6«8qhÒ)T¥OVµêÔ€Ô¦N0@öˆ”¢G’F`ÓÌ‘·GT %BÒ'H ©ð‰€vÃD̾#@`â¤à‘1¥ŒA‰$JA3Ž™I3qßÌ!ùû,l ªSíH°àÀT D˜PáA„R>L%¥¡A1:L…"C…cÌH©ø±àHŽ0ÔhÐÀ˜(+-î €æJ…ÄØ,If’'1:PÊNC‰Ò¬â„à‘¤J Hy2ð)Ô™K›ZMJÀ€×#S­F@ÓÌ‘³GR˜ %ÂÐ'HLV8‹D@€G Ža"fß 0iZuLÑ1(‘D!hÆ$“#i$î;ÓØ`@!ùû,l ¢SíH°àÀT D˜Pá@1¥8˜J*) Ž1#cF‚G 9Âp¢1QJ* 2%B“b\~È2É• H‘¹’e'*Ý'TçMB‡&5ðsß‘¤B ˜zDÊ“§" `iæˆ×##LAè$ TðŠD@€G Ža"Æ)˜89f ”1"‘D!h¦à&GÒLÜǤŠÂ€!ùû,l ¤SíH°àÀT D˜Pá@1¥8˜J*) Ž1#cF‚G 9Âp¢1QJ* 2%B“b\~È2É• H‘¹’e'*Ý'TçMB‡&5ðsß‘¤B ˜zDÊ“§" `iæˆ×##LAè$ TðŠD@€G ŽA‚d 01rÌ@)cD"‰BÐLÁ#Lޤ™¸ïŒaƒ!ùû,l ®SíH°àÀT D˜PáA„R±¡Á$PRIAèpŒ)94x$€1G.,i¤J’å%Á’M&ybgARj&¼³ É*NT)šÓä#R’Á ê¾ªª2„eÕ ˆ=ÂDÈ>–˜4säH@Θ &BÕ'HR)x¤Â‘(H(2IÁ1LÄì$€&NH޹9æ#HƒfH29’Æ"Á3™!ù û,k ªSíH°àÀT D˜Pá@1 L¡‰cÌHIµÑáH¢llhÐÀ˜#%¥BbZ"t¸‰)8%"³ˆÊ•Uœ,2d`€£ûŽî$2DIÒ£žÖt°ïÔ£ h=Â$ˆÕ¬4s¤ìƒV'ˆ‰pô ’†G*‰‚D@€G Ža"f 0qBðȘ†A†$h¦ ×#i$î;ÓØ`@!ùû,k ®SíH°àÀT D˜Pá@1 L¡‰cÌHIµÑáH¢llhÐÀ˜#RR: 2JD‡ĸD¸Ò@’'‹H4 Åe‘! °¬â$Éû„HÊRÊ! ˜ •jÓÁ¾#J…0ÀõXDÀÒÌ‘#B€ 0AL¡O4ÃDÌ> @`â„à‘1ï^D… ™°LޤQyæ¯Á€!ùû,c­þ÷ H° Áƒ*\Ȱ¡Ã‡Ó I±¢Å‹3jÜȱ£Ç CŠ,˜FŒ”“cFª\ɲ¥Ë—0c®TsRŒI))eêÜɳ§ÏŸ>ÏH#P(Q H“*]Ê4©)fžlJµªÕ«X¢JñéѬ`Ê+SÍÍ›hȪ]˶­Å’5ϸK·®Û4íêÝË·¯ß¿€ÇÊ L¸°a©+^ÌaªÄ3'ªiL¹²ÏÇE>=)E®åÏ Yr–‚0Ê“'Q,:a¤ k&PB˞ݳIƒcFuhR.Ü´´ƒ ˆ9É‘Ìû6‹É=tÌÏ § |u¸õë1yš9wõ1MžþpΩ°÷¾ßØÓcÇ|Ä’(ÍïÓç“Õ*¯9Y½ÿàìícÀðí“Ûv[5qÔ mÆÕÎ`î(ÅMdÈ„g 6”F†øY€ Fi\˜a†ËÝ•ˆ0úGâ>&ÄhP0Åx1ö¸ÞcGÐÀHaÄP'Õçt>6I{I$Gà%E}Nf)”RÅIî¦å˜–AåhºwPGæ›™)$Ô &˜ ©çaísÄ @¡FFäµç¢…AöçbD0äHœ§(£˜vDGD„d¦¤6DD0áKþf8—_©´ÆtDáäÞ{+1ÇY­À¾4ëLT¹’QÏáì²/1ëH¹qˆ³Ôb•†³—­@VëíR>XWɾøí¹@¥ØœInJ5Z»èÆ+“QE …P¬ÏÉ«ïNOå¤î¾+•¢g¤áUÀåëIÙ&ìpLc˜$pW ƒY¬ñK²ÖÄÆ _$QÄÜMð†¬²Éœ‰[r²îC“im%EÉ+çLÐYI8Íö dÅ:ë¬nZ)2A®‹ÌQ¤nÃE‡¼ÕWb0!ß8‰b$åH2mÆwfDÄð¸#(D!‡'©ŽÂÙ<ž¦M•ÔÄp7½Ñl «ÖG.ŸKÒeÅeóù/g&Þ‡¬=ÑÕ ƒ›>Ù™c>z1[.ˆYBWÎrŠG‚F¢no˜Pz}º2¡ˆ ÃÃÃÒðwË5#&;¶ê¼]+h¯nL¤ â:¶¶5ŒQae}ŠÚ„h‚í œ6^“ƒí?„®O³}DkêÎM<«A$£©Í˜6#DI ´(e(g&Â& ¦Edá¬3ð˜Ü•9ß=_lF‰õî¨lÕL¡BOâû3TvoQ3Ì&v¹Ìc ])ÖJ(±ƒ[†°ã´º¡ùšÔN s+ô>þ0nqÊÌ… 4›šÝIÞOo Ž#Õê\r»€O…ù5i‚᎟DnðÎQÀ =Í™Eåv5¯‹ƒ0Ä£qÑíçû£ôk®´rîí¯>˜¤`r~–A‹D¹I‰ºbeX‚©š UêPŒJR)/ ƃxLº£/Ô#NízØGB/ÚÚo™cN=V°ˆÞŒÓ:Ññ0æµEÌà{w s#<”O{ °‹ØS”7{MfÙe:˜_SD@—ã{õ8 §h6õY6D?¾$I…y÷„;Ñ!Çä9Ì:¥vR'%EÒ4àõ[(áPËq`'† 6A¦-WbXúKxT‘nE¥HŽôxþþR9¸36xZ&†‡!A.þÕˆq%ÓJÑ6̓ä„Nár\‡(Èayñ{x'£w–Lû1&Agc¡Cb††Há6h;’T×Â]8–ä÷ü3pSuбT+B_±âù¡.îåA2FÎQ„Å"Ps€(÷̱"{CŒ¡$ÈPáŠH²|Tmw!YeC8Dz!)cU»ç6+z£ŠÜø=Òä4…hØJ¦#IÓ‘uJrô…ERmbgA1ƒr4w”çÁ€¦G½$h’˜ô:x36]ÑŒ;bYL•WÙdgÀ‘¡E–xo ¡þ6ã„uÏwC!P RS'å;1+†ÚÄr¬¥’ A‡_ ±YæDj2ˆ"7ØWXFbÄ•MŠrš“~DIbš+tGJ6yRè˜aV‚jY(nX5Qi2—•X×&b7aé*‘³#Á£wìbM¦WÇ•nYK”AòñI7Q„7wÓÔ1­¡‘µ&:¶…x5c6ä2‡V˜ aQ×QkÒØûà;œØWF¥9‚%fdœù/%…ë¥BŠI;Bjâz„(A·æ†M0Œñ—”‡ UbCDwøšA†kÄOíµ"QðEHW’)ƒT‚ !êþäzïrtŠÊÉ}¸‡+é7£Ô„EhÛqRnDb¹ XÙ q˜ !žq%*–áˆB¦‰HæÄ—¯Ñ&Ð3c«u)#¶`"*®cPÌ¡Œ+!÷Œ[W‡ö{/Y:mÊI•¼èuÏ÷V6;Ä<:€[7>œ–$òf×b]E!+É©’jI‰|s<¤¥³¤q­gtCc0¹é7w/º¦÷(œ xxJ7Ïaš¬Æ.ƒdŽ<ùþs¸Q^eK7“”¡$m9z™sZŽJ7…o'T$8Žf*]Aƒ~˜]'˜§Ê©.“13vªZR0ªõc%ûƒ9òIâñŸVqµ.ÜéU dÿ"¢ƒ„ HÉ»ÚY‘Eœ8tPÒ4D¶9)z¥567Æ¥@™Z9.dz[K’¬·…gºšÎ£D)gNoØbÓñ—¦Õ;¼³ÒZªÜ¨HËœ ±Cœ1K%3®(~µÆ.È7|ºBöòsR´7“9j˜STE7+Z,¢h·INbycÆ#2Ù[c°›‡Z#q3JC‘#Zqä1ð¶uÚ9“MÔ:;’’«þaC&»±43DfÔ[^%:âtk4˜ˆ£Ù~â”-Ëw-sg°(¡<D2xta†Gy£B‚…×iH´áÅIr©^ÿÖþÅP*e„Oç 3y?˜D>(jµÐÂK›]Zy¡mÕ2Âl¹Ä‚(¤?0š7E;W¢¤hë/uEyå¶g›6ˆ¢&1gð^¦)¡…®}«ÌYAç‰xS—TìW´É(aÖoÍXe?K˜ë?ãYz˜Ç=ë#©4±V?w¦ßid¨ÖlÎÒ"¡ Èòx“uºd EŠ…Ó)N s⸠K¬ô¸ ãMcdÔK½”¡Uv¡?‹HìêŽpþÅ®y5£Ó·Žf'ɉ¨"ã7i7“ç\¼`ôJ³«f€CîI„‘ãzF¿E„µFVûD`cK½ê.ˆÕ¬í«•áq"8`<Ô¢ “aa48Ã8cõ ñLŲkšÖ™! —»Í£“Wk±š¤Á<þ¤ñ[™ÜhžD¾Þ W4ˆ>d¦$€¦ «4¼‘iÀ:Kö±%QQy£®;œÂ³*K¸¸?x9š‚­m¨#&ôtIüT ¡6g £ãcÁ{UFUøû¢43cŒx*îqô‰•ÞET§|Û œG¥CFR»·=î•úå’’ ¸¦5¹ª8qŸl’ˆþêõ¸ô’¡·jÓ~¤á;DA[øwäjq˜«µ7FqsVüš€4„5u(A0:ŽZì1SzZ„ˆ ÒAqD«Nžh›9ʰû}[XøKR¨'}P0ŸˆMþ&3Òµ¥Ÿˆþ«¿˜ì7С8Y(2¸¨“AšIZ‰ÊŽUo_z½Î邞±‹VâzM#¢è‡Nu[ºçD?£ cÍXkå´N¿Õ¦AÅ©ê› HB7ÙB£I"Áœy Àz%%C£Füte”…SÇz8€æÂ Õ‰ìb<¡“g;QSÅ’õ‘mN`±Z&1Ø{}‘J±ä™é†uT0ú1óaS2šgœ€*þG¡ YÊX·ëŠC8µ¥+=b(ª2ÃQ»ÊJ®’7mªÁ\+A#mFÜBNÚ!ö)£Ëh‰Š’ó±—6>Üü¨aeü¿{÷«¶Ôôf/>»ƒSÎR—7øSQ÷<ÕAORD1[!±½3‹ý´;YUw$„ ‰ÊÇ€Œ‡ÒêNhe«%ƒ¯N7åK."Yá¼IiJGM¸G…E7­³¸×JÕñ!É šê¯×H€6Mëêp̳_)¡™1š4Æ¥/ƒ[²d8Aõ5¡cFù”²ÔJÒuó¼¬É ‘Y\Ä9œ#›! Á#h¯5v+H]ð&&õ”Uè6œù €CÁc»þì–'óÀ«¬5›s>O':hu˜®fRÔÍõâRQuwÖ·OÔì·àºäÖ6]¹$z=HDDË‘!­Ê…J£iAª¡¨"0´más¨šY¨œÑƒD‰,“TF*My›ÛÛ¬«Z.‹ GA…§ §X—<¢ÍÜ*¹TÐ|}SfHŠÝRí+-)=2ܸcgq]ÍMªeÍ1‰kàsã?tTƒC8ÜÖP0Uó­9Ë1꺲p*4AXÍ(˜5vè\R¤#åJWÎF–Fbo˜Ïb€3’ˆzÑœˆY GùÁn0ÚRj0 Ïþœ®SeX­t­þ?t¶CM>¥›eå.sƒJ-Ù*r›¬4IÌšÊîÝ¡âÍ-â¦#§m€^ûáá¨ö,?{[ÔÌ…Ba„ªL …ȲM¤ÕT˜wU ŽQ鮤7—âxÏÓ.czÑ*ÓþýC`IËc¬vmæQ蓟ÈÙñ|C-)¼Ê_Ñ. áѼ"®XdNÌÁ7ìÃ;ÎÐk”Ǩ치â9qL=NE_ˆãadhC¿¨^Õ~ƒáw7ˆ”- ¡’yTY€BÛCsU™Õx §u'ókó™âžAžßõ3µ\Ï¿U”ãÓlônÏ$ªZL•Xù®ªïóåQ”ç·Uì7†þ§e>í÷S{~Ù8\º ‚I 9¯eLõ$ÑØŽ²Ð’ÝxÊÕ¿äçô‘›V5諨àûTôTtÓݺ!SOZj§{”ñ[šÑ¿ä19‘D ç*!ÂÌÌÑì¤Á!ü;eš‘ØzYN¯Ì rb¬†5„UTwOö$üK—Îg>­xzסÕ|~/9E³R7nd<;BæùÜÓó7¯AÆ÷îqïÆu!­ÐA=ήßs©•íxù O6X=¦ù1¦(¼j`žÓ‘ú§5E s#‘Œ”ÙºŽ9L‚ŧ>cDïy¢úM[¨‘PÀþ™ßÀ¿bÃï!~vïAîWO8¬ç_ÀíXFCz’±¤7•C·2Á.¥åÓ俚b®ù‘w0Œü`Ç?€0Ö‡ö_²¿UYö!fß@‚ D˜ð )iŠyeÌÀ1N˜H¡Øä‰BŽ=~RäH’%MžDiòŒ”‰c¤@Á¸râ>—b˜0óäæÅ3MœÐ”rfàÊ'3Ó04ø¤‰Ð}RšLIRLÇ™žÑ‰q_ÏŸQ½~VìX²"Ñ0“Ó‰'SÍ •bsçΩM0š‰[q*Ì1fjtWL“&oË*tÙ/C1z¥0±[WëaÊ•-_Æ re”'b ïþ›ÊPgVŒcB?ž{³ÉÍÆ£4nŠâŒ£^3ò±4„ëÆ}Òp¦Õx;è(ª¬ì朗ús‹F/¿Ó+Hc@ ré*‹´:c'‡Ò ²Ì—‚þÇŸ|ŒÌ'£h5¨²R}Ô`(K“¶|(¨0e´Q…κ ¦Õ¢0h¥ÆÚ‚²¸l0D–ðÒÈ/§d±²/—4B†núÌD–RdhE”Z\ÌPGoÅÕK1$Ü©Daóð;Ôv‚)>´;o1)8{Ì;¹vˆA_áŠÃ‚..¤4N³ö ¿ú²5WqÇ=ì 1˜v ¿œ0ì6AÓé©AMKË5ØbÊéµ—^kl¼Öh9MJ±±÷Ù0>JAZ2­æº±pÉ¥¸âF\ÐÚÄÎxÑÄÈì’÷#YN„5C¯6ìðN‹WfùËÄxœˆ!¦ºDÈ©™£R!,ñdbb’˜þÜ*®–‡&À4ášQ &Èæ‡ìb˜!A íYÌ;­,:k­1Kб·Z¼íà‚pÆ9äHÆPVáÐõϭ߆;$ß²]hU¤žäÖíƒBTM$øâ;° ¿Ä Ü ±R¶Ð¸g<¡¿åC(¿V¦°#¶Vs"ÝÞè=((–“ù#lÇ´qÓMG;.µ¿êº³n­Þ‹1¡O§½vŽvÞ9¬£Ò¼¤ÄÐÚ”*Û‡'>ÐA×]QÞÁú%ç…'>zÚS—bu¯Z§ùúM‹Joé¿ûñ×Ê/¹é*z˜¡Ï J z[?~¢ Fñ0ÉM*_euM+ +ãëü8@ÖåÅuð;ȉþ\96ÐQÙÝË8‚¼ì=ЂüˆEÜc‘&td5Pè‡A– –ÊIB+šÐ…¼¸ f·œU‰[|ak—81dé~¡u8ÄÚY)e8$b/h)¸xO‰OÄ Lr'BÑŠ Ü©ôwE.60o>ëb¿C3€QŒgDcÕ¸F6¶Ño„cå8G:ÖÑŽwÄcõ¸G>öÑd 9HBÒ‡Dd"¹HF6Ò‘„d$%9IJVÒ’—Äd&5¹INvÒ“Ÿe(E9JR–Ò”§De*U¹JV¶Ò•¯„e,e9KZÖÒ–·Äe.u¹K^öÒ—¿f0…þ9LbÓ˜ÇDf2•¹Lf6әτf4¥9MjVÓš×Äf6µ¹MnvÓ›ßg8Å9Nr–ÓœçDg:Õ¹Nv¶Óï„g<å9OzÖÓž÷Äg>õ¹O~öÓŸÿh@:P‚Ô EhBºP†6Ô¡…hD%:Qc¦Â¢ÅhF5ºQŽvÔ£iHE:R’–Ô¤'EiJUºR–¦âK-…iL7:—†©¦`º©—rŠb*P"™U¦4]TOg¤Ô1• N=ÌT¡Õ¨W])RmšÔ­rªe‘ê‚U²¢T«8å*Z½ ˆ5!e…+IÏúÒ´Òu­ ™©ï¦þÔ¡$®é\uZ×ÁÞõ#…à^Ú×§Ö±y ËWÿ#YÿP6:_µì>ÛXͦ”§íf#KØÂÚu¬!éhgkQÕ²v 8‚b«ê×ІV°4Ê,trëÌ¢–µœÍhg[ëÒׯv ‹Cm•ûÛÑvU­¦}«osÊÚàR£­õêr{Û¥’¶»†õÈH‡«Q©¨èà@´»]±ì–·Þ•Qo+#Z©R¨€ÑzËÝøÂ·©q/ID»¸me¬~ÿÊßÿ:·´ÑU¯FÁJS¸3Qp\ c¦Ã–‘/GË‚PµÀo„3LÖ èà v0OUŒ™þú Õ &H&°c÷ØÇ?r…0}ßÎIJHqfÝâU‡DÒ¡Vq¥Ëziôî£ дfG1¯¹u_¥ ìÓ~Õ¥.K«›[ìÔêVÖpõȰRcBG 1~0õÖ@­)²W ¨w¯'Bâ;þ×WÜ ÀSé¡’˜0ÀJLhû¼éetŒMk gw$·þ·©Ÿ=kÀ­É6¬ýíkGÜÚåî7Ô ñŒO\ÚÙø­1Nhu½·¦8¶û­ï¾š¼ RÍ÷Ê €î€Ïyà&8 Enª>œà /ªGPn”§‚Ý·x­½ñ>9a3ÿxº©zg„ÿûعú¶«q§øØðîµ£nšn6¸ôEíÜTÜ:Õ?zL£ýk¢;Üè3GzÇ•Ž_¦ÛÚéìŽz°Ç r¿ûêÉú¶îuÆÏáaDzÍÉ÷Öº«Ýì°1Ú÷.ǽ¥´î È-~m‘Ãþ\â{׸˜G„Àoü§*ÇïÒÑ;ì¼Ç<Å.‡¹¾ïÌù˹دKgJÞ_ ÞCõ<´/TMSûé8±¶QomqÓ„ÜÖöÆ×ŽðwŸ[³ôF×ÍÍoüVÝçb—¼_Mryãc±$®uÛñ}°ä5%Ê^ö–¿[h5¿¥<ªó«¿ C4?û³ôk¬f+ /«»„#»”»»þBÀ1S­¯`@‘€ÀÜ3Ô­ Ü´ ¬² d©ûóÀçÀ? 0Ê“ü< ¬À3ӿɾËrÁ¬‚Á¤@ AܺA•2Aä0!Ôµñš¬T5$LÂl¿³;Àô=B&¼.ì²þÁÄÂ,Ô‚£´éÊ@ÛÂ0CT8/«ØüÍÙ¼ÈÔZÍ !ùû,j "þ÷ 8f ÁƒϘY(e¡,„žC‰3#&¤(Æ"F+^Ì’`G$(‘4,ip¢˜}I²tÙq$Ë4cÞ”¸ÐLÃ;¦“ªhP¡RŠ=š*©ÒTL*-¸³éS1RšT•š*É‘&/YZjƈ)b¹1€äÈÙ’c‹ ` à[qSÍ¥Û6lļ{銉p€Ãt“<Á›tŸ\ć H! ±©ÀÇéVqRÙ ÞÌ‘¥,.8³Í%K ` µÛÑ÷F@×Ì‘ÛG€œ;AL„ÃOtßäÙ1¦I¤JÅ0½è龨SÂ\iëÅ}O˜Èì:ôbØ1jÈ.ív,Ô²·²|»¶)W¨¥Ü;ÆŒ«Ÿ LE8•¬4©/,Ì8±â?¹ðäÊ’cÎlu3gšž-wÆÌññæ}I h&¼/rß¿£)3>bI”Î% ``ÌmÓŽ÷íæm{2Áá¼Åü6n$€sâFŒ îÜLãÈŸ×>29»ö(ÑR{׎$‰xêXD w«Ã# àmÆ EŠÞ=ALçNè×]GD„ àccˆ%ÀÄ{â=URmHõØ\Jwl!ùû,!c‚þ÷ H° Áƒ*\Ȱ¡C„f"J”(ë¡Å‹3jÜȱ£Ç CŠéPŒÉ“'ÍT$ɲ¥Ë—0cÊœ)¥M•4sêÜɳ§ÏŸm¢Ä ´¨Ñ£H“þ,B„H$P¡J1£´ªÕ«X³*$"¤«"CWjK¶¬Y˜Fº6Uë4 ѳpãÊ«°ˆ"&‹4hàU‰Xº€ ÆÊ5‰I"|Ÿ:3¸±ãÇ=í1’DH°bŠa ¹³çÏ"-ïÝ‹$ïfШS«f8Eôh§H¯žM[µ]¯ ðíê·¶ïߎ‡ð2Äò×"nÿ_μ¬ð!SZóM©¼¹õëI… !nyuìàÃþ½Ýµ¼&ߊ_Ï>fk¯]¥|oO¿¾Æ“¥mNáú•IXû¸Ðø eRtB©'à‚hR~¨ ƒÒç FxSuvÞ…j8Ÿ‡$~X ˆ–¨"v ¢˜âŠ0.×¢‹#Æhãl3Ò(Æ„7öèYŽ(î³#‡>Ù.òhä’€!‰¢’LF —“"B)å•c!ÑTué¥J©`)æ\œ1”Ê™hŽ©fgh¶¹æ›ƒI!çœrÂiç\mž™iÜé§YyŽ!òýihVmŠ!'GHÑÄ¡*…fr¢1†G0AU¤œ…&ò¥r„H¡h§¨ò„¦•€þcšê¬4µ)¨®¾Ê(­¼Â”'F ¬bÑë±$¥yæÁ ««±ÈFëÑ™9F³Î ´Òv‹Q˜¥R¬³Ù"Q„·èj”ŠØ’k€H¤+ïEíº[ê¼ø2”«³ào£çæ+°A¹F0À«c¡°Â7<«H°MpëðÅGTpÄH@G\,ò>cø·Ï@ œïœI1©Gì²À„LÜì2eú,ôÐDmôÑH'­ôÒL7íôÓPG-õÔTWmõÕXg­õÖ\wíõ×`‡-öØd—möÙh§­öÚl·íöÛpÇ-÷Üt×m÷Ýxç­÷þÞ|÷í÷߀.øà„nøáˆ'®øâŒ7îøãG.ùä”Wnùå˜g®ùæœwîùç ‡.ú褗nú騧®úꬷîúë°Ç.ûì´×nûí¸ç®ûî¼÷îûïÀ/üðÄoüñÈ'¯üòÌ7ïüóÐG/ýôÔWoýõØg¯ýöÜwïý÷à‡/þøä—oþù觯þúì·ïþûðÇ/ÿüô×oÿýøç¯ÿþü÷ïÿÿ  HÀð€L ÈÀ:ðŒ 'HÁ Zð‚Ì 7ÈÁzðƒ ¡GHšð„(L¡ WȺð…0Œ¡ gHÃÚð†8Ì¡wÈÃúð‡þ@ ¢‡HÄ"ñˆHL¢—ÈÄ&:ñ‰PŒ¢§HÅ*ZñŠXÌ¢·ÈÅ.zñ‹` £ÇHÆ2šñŒhL£׈ à @ÐF’'j o €cç’:Ú1xx YLÒ„< rà2õ(Ç–ø1‘À[$É’G¤tʤ&7ÉÉNzò“  ¥(GIÊRšò”¨L¥*WÉJMVE°Œ¥,gIËZÚò–¸Ì¥.wÉË^úò—À ¦0‡IÌbSf Ëã$ù8­GBҘЌ¦4§IÍjZóšØì%2 Ì7RR$Î|f6ÇIÎršóœèD§^™ÎvºóðŒ§9×I²n¾q9þ££%"Ï~úóŸ h,é)I= AƒôxÈ‹„“Ÿ¨D'JÑc$‚T7Ë„†0ó[àÊD+JÒ’š´¤%@FUZŽz” (#™G!Ð/1äŸ]¨ |Št' ªP‡OzòŽ(ÀAºPoŽ!¦c˜é£"„}43 ÙÂ€Š™ÁM@%ªXÇJVjÒ³žÞt‚J ÒÑ AÕ£/¥À£f&†—6xã>|z„ øue ¬`Ë˳2¤­0€U±úRª.6«ûتBKÙÊRv›öü¦@ŽúF ,’ŒeÌ"q…T¡‘ãÃjÙÖºV¨˜eþäG1‚؇䵸Í-ESªX’ ¶Z«-ˆn‡KÜ~ÕUE$$…[Üæ:wž)èéG‚<÷ºØ­fJeKÝ:Z7»à ¯0 {ñš÷¼º¤gpÏ|Vò§™€LÈËúr„á’B8÷›È‡ÒSY"åouÛ¤_ïŠÄ¾$AðO¦Ç76œÕ%þJá [øÂΰ†7Ìá{øÃ ±ˆGLâ›øÄ(ö+JÉõ.UŸv«@ä¿ÄÆÁoµ&&ïÃÀ?¦–wiì]ãÀnJ{,ãàØ%OfÉð˜\A³ 1òj£,åÃ8ÈgZ²üP" YÀüsþ*ÄÌäåv„Ë$³H¦L€*GË9^­ŽåñÄœ|j$Åi4ÁQˆÑDŸˆV$f Ê„R šè¤ýiRØ8¨—”vz‚6ñÄG Š©§¨tÆL`ÚÄþE8ÊDª©¦’ŠLhza©’Òê©­h¸G€„¾Öj««2aDŒQD²¿ÚºO*c4ñ,´°RKi*²$KPl ­Èz‹(‰d.´HL«.¥‹¾ ­éÎ;©½ð"¡ï¾,ð½ùþ;'¿+¯Ár1p ñø2ÜpÀ í@tܱÅg:<Ü óyD¥"!@œ2—c QÅ> €³3ŸYÄ!†±ÝöÌå¬H=§TNG-õÔTWmõÕXg­õÖ\wíõ×`‡-öØd—möÙh§­öÚl·íöÛpÇ-÷Üt×m÷Ýxç­÷þÞ|÷í÷߀.øà„nøáˆ'®øâŒ7îøãG.ùä”Wnùå˜g®ùæœwîùç ‡.ú褗nú騧®úꬷîúë°Ç.ûì´×nûí¸ç®ûî¼÷îûïÀ/üðÄoüñÈ'¯üòÌ7ïüóÐG/ýôÔWoýõØg¯ýöÜwïý÷à‡/þøä—oþù觯þúì·ïþûðÇ/ÿüô×oÿýøç¯ÿþü÷ïÿÿ  HÀð€L ÈÀ:ðŒ 'HÁ Zð‚Ì 7ÈÁzðƒ ¡GHšð„(L¡ WȺð…0Œ¡ gHÃÚð†8Ì¡wÈÃúð‡þ@ ¢‡HÄ"ñˆHL¢—ÈÄ&:ñ‰PŒ¢§HÅ*ZñŠXÌ¢·ÈÅ.zñ‹` £ÇHÆ2šñŒhLcAŠ`§6ºñpŒ£çHÇ:ÚñŽxÌ£÷ÈÇ>úñ€ ¤ ËdòˆL¤"ÉÈF:ò‘Œ¤$'IÉJZò’˜Ì¤&7ÉIB^ËV LEN8IÊRšò”¨L¥*WÉÊI2”¡e+gIËZÚò–¸Äå+­”\úò—À ¦0oéÉ}à˜ §l2Ìf:ó™ÐŒæ!‹¹4™ZH¤ÉÍnzó›¤,æ`Y4D`™'§:×ÉÎvÚDˆ9—y”áœþˆ˜€4Áœ×T¦5ûy(ãÙ è1•YÏ}Üs 8TPÖŒÁ@ó˜=£ePl1džièCϹ ê¢$“H(dúsG˜€L'ð<äØGEº‰êÔ¢řǔ7†ÄS Êå@zL ŒŸ°(IŸJP€ÀhP¨2WÔ£î#©Jý$9bSèñr¬b¥HY¡‡M‚†”±,çJ×X"ï©9fAê*JrÊ5–€ååñ"0ÎTtì[\òW¸6ö±],ðžªM àà”d?)Ö¿v¶¯›í¨5 R€`•±|õkjçj ¬öµ°Mþ…bK[ºÎ¶¶¸åms[ÛÝò–¶¾ýíkƒ+ÜÕ·¸u+^¿ê„Ën$´ q-ra{Üé²ºÖ %v³+[î¦v»Ùox½›\Å Ö#ÐMÈ ÖËÞöº÷½ð¯|çK_ùŠ×º÷En~õK^Ûö׿ÿeWù‰ÌÄÞ¤¾N°‚ìÞý×Á¿…p„¬] WØÂ¢ì*QÌá{xæmˆq;bc¸»'.1m ´ÚäÃ0ޱ}OŒb «¸)¦ña»T ,(2²wLÝÙÆ:>ñ@–»P„¤¡²”al7l€ V”;2å.s˜ÈÃ=2’1 fãŠÙ¼çMHþC[zU4Ím2f›z/Û9ÁÔ¯¤ÏtGnÛ„ÓŸiFH=Ç'4DôÍ?CBØb¡¡¢h0È7=_AúÏ,ñôFD=ê?§âmÍfXµŒ2Tžû˜g¡y‘F Ó1)§wý^Rû ÔÀN‰¯32ì‹2Ë Á©PWëGç´¢$Ët¥íykMóúÚ u°…½m”Û""BiY'÷8™c ê PÏ–úS¡µu¦ ‚íkÜÝÖs¾CroŠ„{Ü ,÷E`m‘zóºßþÞ·ŸU‚ð‰4ü!‹2ÀKu:€ÓZ„à1ø®q…ÚãáxCþÊäjlzõ%ç´ÈGòO3\Û/§ø¡ š“”oz—‰¶ÈbËäbÇç«öùÎ?«»½èF':E6«Ý¥ØüÎ<mÐ+’ôœSݳQO+F»¬?—œYO/D<ÛuÎ ý­ ]ùGžnç°[ý!cºØ¯^v®]”rûDàêö©û\ y÷»BÐúWµ{DºfçìÜÂõÝ&>#¼ìûÝïXÁ/ÝZ’GºR¯w¸G^ñoˆÑ+¿w»‚Þð¡ÀLWÏúÖ»þõ°½ìgOûÚÛþö¸Ï½îwÏûÞûþ÷2ýqQ/X²{>­Ò-GxIS»Cžù¤>Aš/õç¯:ù¿ºò©þ¯õ‹pû¡g|ñAKS£$_ê=½c©~‡ÈÕµ]þûãoýPÂßôÞŸ+û-?vXÞñîXûwvU~ÃLj ¨€+±€'ဠØ€K!aH˜È¨!h'%ø'ˆ‚+‘‚Á‚á‚/¸‚j4ƒê2k9unÈÖîFs0…L0è„ÅbŘR>¨®fq”6„¡PGUr?¸®vƒ±ƒ1P'PX–FV¸Ž&Qñô1ÊÊV„ñ11POøl0oÞ4J(Týçw%c-8• ˜Ö~ñ1wøIjx…ç†7å†þph U†i(|QhJx†o¸÷„kÂgRUmkeÅfshIÐA˜4@³VKçf}X…Çô‰±x§öNãdNÙW e+ž˜ˆaˆiXŠ$µŒØS«ˆ‹@0‰»k½X•STˆ‰¸‰¥Qaˆƒ˜£xŒº¨}?5o©††p+†f diJOÝQ%ŽâKÖTXçØßÈjTˆ3ÑȃaMYHލƒG€†öØlø(˸eΨ‰Á‰ÉxˆqiŠu¸Ÿ%J-Žùf‚eŽó¤Zµµtµ‡/(| injµZX AUÇD…% †Òxþd¨’Ɇˆˆ‰)‰Âèy¢T„-JÅW‰Z}5Q’µ‡©˜ZFÙYMYŽu‡©@Xò¸8ˆ<™€ÈQÅQ9陕KaƒÛ8P9ÈV„SØAX„_(tI•®6‘‘%Yp5kzå–ýz«f—Åfè&—- €RŒz™…á„=4kY˜fˆoÉ%•—[E¹Ž.6™ÈE—·9cÅ‘tE—F¹™˜Ù™‹E”¯Eƒ¨™šª) @€Lý8)„‰ÕšÈÄŠ:i›™/Uh¯¹‰g¹dâöN[õT½y€›—CœÁ’­–MÖ„UG°n´æjYÈœð–4[eM9þ¥nÅf7H›ePq•LƒÔ9”Sh^õœè™M¨vî‰j@àž7çŸÊ‰*5õéc8£Lº‰'PkÎ)1µUúTÑ‚ @“ëY Áž±ö ñ9Ÿ/uœnf`ªû™†Øôl†6sZ…§Upž=uZÊf¿)9*j¡ ŠU÷Iq¥ø 1Zn¼IqŽX¢”v¢¢,z¤™6k/9 ¤ó™M¼É èy£Ê)Oz=ª 5 PnæOÄy¡zZ 1•S9Š9´ ?Æœ ¥e£LèPfOÁÉŸªL³6qÞ ¢èæžm1X…—Q8!ùû,!c£þ÷ H° Áƒ*\Ȱ¡Ã‡ ¥˜™HÑŒ,ˆ3jÜȱ£Ç CŠI²¤$bRª´X²¥Ë—0cÊœI³æI•*Ǥ©É³§ÏŸ@ƒ -x§1f†*]Ê´©S¥H¢JE’Dʘ§X³jÝÊ•aÑ”QšŒ¹Øµ¬Ù³hm¢TÙ¤ Ë´pãÊëð«(bÉÒÝË·¯Y»Ršˆ9ã·°áÃC튱š±ãÇM®Åifgä˘3×¼R¯æÏ 3+Fê9´éÓ…G¿Eͺµ\Õ¥]Ëž6íÛ¸›ÚÎÍ»wÏݾƒ o |¸ñã‹#_Î|¡òæÐ£ï{.½úqêÖ³ûÆ®½ûmîÞÃþ·þZ…Ë1±Å«g]tKŒ÷[À4^OõÍ*ïeháÒeKýÿ¦ÝÄE Z¤F2¨ f7¹W`J.(ad÷Å RxÑß„"V”ïŰEeth¢__m!C \Ìw⋯q–Òj0ÖˆÖTSIdãŽgI!#i<É• G¤dEY\á¢L6à bdqÔ7XaY“X* VÜàÃVø`E‰Y–” 0ôPeJ`†a曩ô#Nt¦$Å{]ˆÑÅ–RZÁ¦nÂ)hBgÈYç¡t‚há{3\á%˜b’9褚ҜˆâÄÅŠ.¸Ã l°A :T±$¥ƒZ:Z¦th! þ&˜ *Uˆqª¨ªŠ)«)¹·…7˜pBšGÔJ®”êÊk¦+ök .Ä€Da¤‡l™Ê.{¨ƒ1˜ÀÁ¢ÅV{í¤ÙjKçž1¸pB¬Zp‰DdŒ›ª¡æÖ¹i -˜Pú"aòÂYn½t¢) '$Œ-EU$òÈ$›!Eæµ8†ž/x,è­]Æ#¦4 Ë83TFº"Z¡ÅMä,4B®riE[­ô@\hqU8ÁP,½´NHqM0ñ„0[³d5µip-vÎd/&ÅÔf¤’F×k»úñ€ ¤ IÈBòˆL¤"ÉÈF:ò‘Œ¤$'IÉJZò’˜Ì¤&7ÉÉNzò“  ¥(GIÊRšò”¨L¥*WÉÊVºò•°Œ¥,gIËZÚò–¸Ì¥.wÉË^úò—À ¦0‡IÌbó˜ÈL¦2—ÉÌf:ó™ÐŒ¦4§IÍjZóšØÌ¦6·ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºóðŒ§<çIÏzÚóžøþ̧>÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8Í©NwúG)<î§@ ªP‡JÔ¢Õ§FMªR—ú85"•©Pêãž*Õª&Õ©VͪQ©ªÕ®65\õªVÃ*V«d¨_$kY¥ªÖµBu DM«[»ÚÖ¹*®_c]íºU¾š¯dÜ«_‡*ØÁ¯B•«a¡ZØÅæ5®^l¬c%»X¬:v©”5ì>ŒªØË&5³ƒÝlQ;ëÙ¢‚Ö¯–-­iUËY¤!þÀRYÖ®Ö¶hEˆlóªÅÓòÕ·vÈ€XÞf¸sE®[á [×µ‹Ê]ktË*\âd·´}Lv#ò‘ír„w ñî^Äkðv„¼1oBÐë’á@ ØmdØ;õn„¾±ïAð›þê7#ümÈ `A­àÀN°‚Ìà;øÁް„'Lá [øÂÎ0…¤á{øÃ ±ˆGLb“øÄ(N±ŠWÌb·Å0ޱŒgLãêàÆ7ãªCãûøÇ@Þ0Bnl4X'ÈHN²’g ×áqþ;‚l¼ä*[ùÊ®®ñ€@€# lÐÁ²˜ÇLf €/þ P€"S¹ÌpŽó’;\À ]ÎŽœ÷Ìg‹ö«ÏÕsŸMè D‚Àñ{»SèF;úÇ–²¢½óèJ[zÃnþ\àá\úÓ ^°kÑ „@ :Ô¨¾tuÕ<åS§úÕ¶1Ž[ýfXÛºÏ&¾µ®å<& Ç;žïB ‘N+„Øp-Œ»ûd»–&GPô'X“ à–°×êtźm¯v›®Ù>*š[ík7ÿDèN·º×Íîv»ûÝðŽ·¼çýngoV&öɲ=blî†ßÃm. iÝzüàO¸ÂE`ï~o$ßÌ>vÄ;Òp€@àûà2˜k²ðŽ{üãô®xLþ ÎïaOü» q¸F¤€f ¤ÜF>7ÈgNs‹&$?¯ÉKî‘›¿„wÑ–6µiRó¢ýà>wIÎ9²o÷<åø~ù¯àòŒ4ñ_êp,p l!G»ØÛ½t¥/¥ì-A{IÔ~«gc¸Ý>РŽî`»ÞÅÎö‘ô]$IàA2ø—c…@ {°S÷?Œdï7z៮”ÉSüì"q;âçn€ÃVñhˆÀÆ÷ùÒÏü¿?=«©™þ\ð®þ¾«?,AÊþú× ˜À±·}CR_ÜÉk>ãŽw²¯ql¼#Làø0½ò;®ß܇$õMå]SyÏú?ö KO¬s­þûNß»Æ-6õ¿?ù1 hŠobþ?'¿|ýY¾üîýÜS¿»´•>mïÞǾ^íã·á·sÁ[²÷H~#tÐ\ (zr'îWóWó†zþ'€Ï}¢Å}ü–8{qßçpÍ€(~(Z–ø‚õ¶_8€¶]úç¬Ç^ÆÅvþ‡ƒ·'ƒäEƒ¡}8UXƒH¸n¸]Чrè]7È5(^MXÙ÷}€%…¸}¼W…S˜…©Ð‚ ‘„dÈp@X„ZˆØÆiø—X¶G{h¥{[Èi(‡)wX@%†Q†I¨‡é…y•ˆNÁ‡Hè‡aˆþÝ'ˆBˆAˆ0Ȉ‰?8’È• ˆAq‰ˆžH‰XAÈ7ФXЦxЍ˜Šª¸Š¬ØŠ®øŠ°‹²8‹´X‹¶x‹ÇçxK‘‰JÁ‹Cá‹BŒA!Œ@AŒ?aŒ¿¸‹Ê؋˘ŒÌøÈØÑÈÂx€#(„Ε ¼˜[H_ÓXmÍØßHã8ÀØ„N˜ ‘zÛX‚#‘†ûPŽ2!1A51t=<á=Rˆ‡RU\Be<èH‡Ù˜]]EúxK‘MÂbÖˆŽùVwGY‚œE„ ~þYÖç†i‡it‘xU’ù\$‚ Y’‰*Ã÷þu?q2ùîUuÉTtÈ„Då^ØU’^µ“)æ·x<á^P“5Ó–Š·Ø@”Bá~ês²×‘qx’U˜;þ““€Ö…qU‘œWu?q•@Ñ?û0sNÁ–F¦–=1F@“K—C¡uAá?x—–Àæ0•À6Ù–©£”÷ˆ˜>Q”B1\—mÙ—?¡uhɰiq7zC—rév)Mðewi˜?Á”7ö˜‹5Aè'™Mñ”Q ™B¡:n V Añ”¶™;Ÿ¹›šš91½éh†c涘‰f–@¡›6™h¨9¦I˜CöÜ"ö¬Û·pQjéú5FØCãêÝË7aÏ\ËÄ;¶¯áÃq·–QEhaÄ#gõ:رä˘δœ¹³ç–›Û~MºchÂ¥S«®xšæãÕ°cïk-&¯ìÛªiÛÆÍû³î×½ƒKþ-¼8fâÆ“#F®¼ù^ÝiœKK{LÕéØÍ¶–y&»w­ÛÅþDÿNiãÐ2Ç—_¿3†–Ê!¹³ŸŸ“«–Ó×éë_Éph3hì'`JU¤µÙn&˜QO2üœ‚NäÓyœEh!E2ÜuàƒvHÐL]T† ‡$ $•1,%CTT•èâ‡b̘O÷øâ…bHáSX]øD‡7BxC ‚–A yÅ„^ùˆd’ ÞàW2Té^m@B) ”uùTä“ZîwÃ9V"jaZhà 1Ìp_…i&h -ÔÙB pÆ)  ,À … .´ÀšzØ =€C oºVh‚) *0´P¤£ ˆB .ÀpA Ah¦ô) t¾0„E`Jª~þ1 Ú‚ 1ø „¨X¾ºŸ|ºÀW᪮™…„mȆtįwƨ HhƒD K¬dg›l²Çn1« 3¸à XðÂLX{-dÙ‚tì¶§½tÄK/túi 0H` >Œº.»ÚÂ+REá” [ôô (Ì ƒ &`ÑÁKŒÁÄ¿Ø,°IQÁÂKMˆƒÐ@ƒ dÁ 6QDbq1ƈµ+Æ»K±ê0ü „siqC°ITÁAE?$!ÆDLEóa6ã /·rUŪGv?‘BLÁÄWT1FD¼<µaUo<é­=iÐîÝpÃþÝ`€Á±g_ÑÄÚm;ñv_qo¬³/…ÁŽYèíà ÔEY`ðñP#q8â \ÄÏ@q„\œg7ðÀºÞ,ñrÌ3®Wâr/QE#áD•UEGø`|ºW …í|ÁU E/}CcTo†Õg¿öÚ+”%óg¡‘óÑeoÝg¤aFúh˜a]*ð§þzQHa¿ó“'FüiàŸvRHür$†ÿa‡ ìh@é0¡zRh ¿6‡ ÷Üõ®gÁä`Ð ià €„y®ƒÂÁ Sa„}ÀbNð yÄ'Ô G@Hh„ΆþLhœ`îP GPÏc<)D¡ E4" =·DÙHA gƒZhÄJáUd¢ýöÅ}tq‡Q ãj˜03&DñŒÂÔ˜š&äpLé¨<€ä¼ÀÈÇÔD1ØáŠP#0²† ÀÄ:TÍ’$ ha&U3§íã@€˜°ÇQަIˆLHxQºr4­€P»[ªFj¾ ¦0‡IÌbó˜ÈL¦2—ÉÌf:ó™ÐŒ¦4§IÍjZóšØÌ¦6·ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºóðŒ§<çIÏzÚóžøþ̧>÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8Í©NwÊÓžúô§@ ªP‡JÔ¢õ¨HMªR—ÊÔ¦:õ©PªT§JÕªZõªXͪV·ÊÕ®zõ«` «XÇJÖ²šõ¬hM«Z×ÊÖ¶ºõ­p«\çJ׺Úõ®xÍ«^÷Ê×¾úõ¯€ ¬`KØÂö°ˆM¬bËØÆ:ö±¬d'KÙÊZö²˜Í¬f7ËÙÎzö³  ­hGKÚÒšö´¨M­jWËÚÖºöµ°þ­lgKÛÚÚö¶¸Í­nwËÛÞúö·À ®p‡K\àŽ€­¿1+h0À>”{•è€âCJ €†T4aˆ€N"Æè„¼Þå.s£«\ùa¥ ¸ €Š#ˆ÷(G€;’÷&Ö#òBÄo UæÛ_÷  @r—‹0ÖwÀÂvÑ€à¡ò&IhÂãáì ĹÆo‚!P]ÐwÃÌBxl” w¸¾÷½ÉˆSÝ#8¸ 01R"€"c)%H\ßýj—»7þ0NFŒâ žr¿4>Jû+ßð*è…ñQ6Œ.ÿwÊ`ò%â·ëÙÉZ†_™›e¢˜7+ÇMîU6ì]ðæXÇ`$²@°!ùû,\!”þ÷ H° Áƒ*âúöð¾OhQ$Gëë«v¿…óý—^€–G`ሠv .8]ƒ2a„ÄMH¡n^[†¢Æa‡Ÿ}âf"ŽxY‰&N†bŠ­Èâb.¾xXŒ2 VÄ>øÀÅNØÕÈ\/ù Z|ç£t&ÀàÃÀCh Þ 0p1ÅÄàRRfwB UH1„ZFÙ¥e0¤ƒDI.i“a¤qæd¸ÐB .haÐ pPà û)$ 2ˆa朋¡f ŒVAŒÒ` ]ìscŽ\H±¢e…tœN † ©R ¡… 5ð€…]Ä0ƒþVŒê>\rúÕž†ª«N2ÄÐ…WØ`7ÜÐ1hQ8Ð0C¨fÈiëVc„vßµØf‹„J¡CJÞÐÀ@ƒ 6h`‚ XÜwDIŒáÄ´[á Ò§»êÚj 2¬7lÐÁë¢ÊÁ°<„ôÄjE,1ÄôfeohùVÌE¯Ãöpƒ¤›€ÂÇ'˜`C ©FÄW1Vã[±®Uøpƒ % Ë &œ€Â. `Bb,,Äf 1„Ñ­ÜTË/¿|„”àº0ppÂC¢pBP0ª%­ôRg8t”Ch§­vÚGy…V\¡Å[pñÅxo!ÆþcL1ÅNc}ÆØX‰nfpw¬yw‘·Þh¨Ñ·J(1D„–† 2h¡… vâé .Ì Å@LˆF„p™VÞ\h!à x:‰!Å©¤ÒD놙A…@Up‘7îI!E½7ðŸ5aFï©8Aô›-O=c˜qö—=Á|ïRaÀõàKƄԗ¿cD‘þcM<Á{*î¿Dó/&Éä;Bh1|¯†ùß8@î…q‚ôØÀ@ òƒ `˜ =ü °‚¬Â¼4È—õ¥b!¥ðî…ƒIE>¨BˆÐ…wùêŒBà‡þG`!KøÁ À‚c(–ø¼!îE€Bè#810G¨€Œ€ ˜»b_ÆÀ„ïF(F¼ÁQû8‚·„ ¶ñ.ppiÝ0R`ÂIÈBòˆL¤"ÉÈF:ò‘Œ¤$'IÉJZò’˜Ì¤&7ÉÉNzò“  ¥(GIÊRšò”¨L¥*WÉÊVºò•°Œ¥,gIËZÚò–¸Ì¥.wÉË^úò—À ¦0‡IÌbó˜ÈL¦2—ÉÌf:ó™ÐŒ¦4§IÍjZóšØÌ¦6·ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºóðŒ§<çIÏzÚóžøþ̧>÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8Í©NwÊÓžúô§@ ªP‡JÔ¢õ¨HMªR—ÊÔ¦:õ©PªT§JÕªZõªXͪV·ZÎ!Hz` «XÇ:V–œŽ"'¤ÈY¡çU²ºõ­a5kEÒ:‘µ¯­pÍ+YåŠÖŠØµuxÕ«`©Ç׉ÐU"Í\`+ØÂJä°I,áËØ¼:6"]ˆdÇFÙʾõ² ɬB6«´Îzv¯+!íAD›ÕFÌ´§þ+hÂZ„¸–^°-Xg›Úä¶ÓÊ­n{Ç[„øÖ Àµ•p‡[ÜÕú|ËÕms rÜ‚$—SÑít RÝäA÷«Ãuëv ÒÝ\QÙ=íxR^œwNéõìzcø\ìÅ·²óEa}óÛÞ}¼7)çíï…àý¦v®þJÕZ–ו«ް„'Lá [øÂΰ†­‹`‹ÅÁˆmp‡ÜGVÄ}õpXL¬Ù؈àÅ0ޱŒgLc_aÄ!þ0ŽO ›ûøÇ3¾qŠI¼â·¸Ç@N²…lØo…Å£u±’§lc#GYÇCÎ1j¨Ìå3ù±NÖ ”[+å.Où˘ sþVÆlÛ2›9Éh­š±ÂæßºùÍ?Ž3mçL•:#÷Îx®±ž{Ëç©ø™ÃHtž­Lf,7YŧQ4mÜBKåÐÞM´¤iLiçBZÁŒn³¦7]å,ó¸È¦>ò–IÍéPÛÙÑ`þ´gXÝêT_Õ&r¤i]ê\k,˜6/ iÝiêZ:*Ávï°Y]lî»)Éö¯L¢=à Pž>5>'àjƒp[,ßÎöB r§™"æNIº÷Œî²¬›Ðíw· òn“ÔÛÓ¹÷Tôíìx‡…ßä­À72púú»Üó&HÁ—²ðþ.#·B"ŠŸ{"ßJÆÙñ {|"h0þ°qîîV"æ;”GB€¤$-×!mOžð#‚Gš4¤ç0€Ê3‚È| ¹ÉOHsƒØ\ @GR!õüîŒûüBËtâ.¤ úI€pßQà$G:χNÝ®'„åßÓúŠ®tˆ rßÖ72X] !ÿ{Ûãêu‚À=ëˆên‘´ïcíAC gÜÂ#¤è@è¹I$O–£¤ g=×›^$4áé¢×{ÉAÈ÷:߈¤`t=fžñ{µ¼@Lÿô#P€úFDú´†|õKˆÍ!_’Áwš7 ÎÅ>ó®ûõg×{ì‡Çßó×S#tcâk¿Z³¶÷ÀÍÓ7‚ó}˜ 錯|uû>òào>ë©ÐúØÕ^ʳ—¸„Usß“zd×®Â×ás#—~å·[º7½{{—xBý×ýgX¨ÌWgôsß×X—w3‡B(q?áÇÏ×|äç~3¸t„•‚O'wÇ·!(w Ø[²¸r"çr'Ár$‡Y¤çt7—s;×s¿SÛ§-Hƒñ‡‚ÖW[ëurP— !ùû,S •þ÷ H° Áƒ*\Èp Ç#JœH±¢Å‹3jÜ8ÑŒ)eqI²¤É“(Sª(¦%1fD®œI³¦Í›8 ¶ó2fΟ@ƒ ªÓ%L™D“*]ÊÔ"’§OA6JµêÔ=‘ZÝʵëJ¬G½ŠK#XŸeÓª][”gX¶pãz=«U®Ý»IéâÝ˨޾€«ü+¸°a„+^ 11ãÇ :ŽLyñäʘ_ÎÌyïæÎ ã~M:íèÒ¨çE›ºuY!«ëºž½UH·¬ië®*„nÙ»ƒ'íý[¸ñ¥B^f=Î|¨ïâÍ£ç<-½:IêÖ³gÄ®½;EîÞÃþ?/¾ð€ƒd€)]AÚpƒÀ©¹›UXñC¹É)EÑžžÌ !¨o*\DD¡†bÖ‚ ' °KúDžBà & ‚ i¡E X°OKC±¦™6ÖNþ°Æ*k¬/õP˜ + ¼¢pB¤1û >ˆQ„ SŒad«Tñê¬ÐÎúR1äªk¤½FÚB [Tñ /T (OÐÈl³ÏF«®QRȃ .´Ð )¤Ð‚¶1HÑ’0T0DH41Fç¢;ãºZ̃]Èú2Ä C ó¶ð ðÂDI¸ËEKIgÁB9{p´ ǃŠÃúpKVKñ¶2hƒ_pÁÄU€,2ÉM™ì–ºUh±EoKk¬U¸ë oAÁ0T}ñX L0ÐKáÑG_‡¶`x1Æbp±…Ø ‰íQ\ÄÍ…I˜FO=è‘þgSÈ5S%„FÚ\ì3x[8Ai ‘†`€FIœ=†Kl½Ïj qÆâ‹UÅ»2Ä­…  ADA©Ä{èdú [ÈPFEIQDªB${*fœÝícU·»[DùELaP*RT/G ÚG|nFÚwvù±aÀëág6~ù0FéWÆýgdß>ðÇÙN@ÅFHûÄ?ý-¦zM`BŒ€¶Ox\̃¢Ð)Á 4€ AÁ¨á R€‚1˜Á*8¡ƒ…ù`-¸¶O O@a`è?º°}&”a_ž ,„þ0€Cî…ƒ @о1áPDŸûrÁ ˆ! tB¦h˜#T Š@¶ÈEÁŒ{û &±Œ}1øö„1œŠp R& y< öÈBòˆL¤"ÉÈF:ò‘Œ¤$'IÉJZò’˜Ì¤&7ÉÉNzò“  ¥(GIÊRšò”¨L¥*WÉÊVºò•°Œ¥,gIËZÚò–¸Ì¥.wÉË^úò—À ¦0‡IÌbó˜ÈL¦2—ÉÌf:ó™ÐŒ¦4§IÍjZóšØÌ¦6·ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºóðŒ§<çIÏzÚóžøþ̧>÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8Í©NwÊÓžúô§@ ªP‡JÔ¢õ¨HMªR—ÊÔ¦:õ©PªT§JÕªZõªXͪV·Ê•"Èî«`•XÇ:Öƒô(g…DÒúT˜5B^%ëWÅ*W¹¾­qkDØÊ½„¯ÿ‰k]©7X²Þu(€Ý‡_’Ø„,v µ`ëJ×Â~õ°BIìcÙ¼bV@“•ke-;;ƒtÖ&š]«D6»Ó¾'´dþ-i? ”Ôîuµ´ålÇ*[Ëæ6'¶…ˆk ÂÚá¦g·a%íeM›”à2·Ìerçª\Ùý'ÎeˆqR\¸Z¶·…½îM²ËYèþÕ»…ï`Å‹ZÏÞ6"Ý•îw«[Úóâuzª…/{_;_úî—&äUÈvûßãö·º^I€2àø"hº²So]¬’#¤Á„c'a»F±î®y 2` X¹F‰…)ìàm˜°þýpfCü\ýÊX#+¾ñBZ”Û"<®-µ;bÈ’ÄÇ$.²@J¬â!—ׯ?ž1~ß äSs ¾m&–ëå,3sËëí2—¿ìåe‚yÂb3þ™Çlæ2+óÌ^³šÛÌæ7»9™p6lšÑ,g>ÓyÎH62”“|_µR¹­V6I —¬äÖÙÉn4“O²hGZÐ ž²ˆ/Íèæ`áÄËÝt•uü“Jc˜Ô»ùtzã\ãQG¹Çf°¤££êÁª7¿®&t¦ -jD£Z7µ¦,«‰ÌiK¸öõ«…lÑûɹƴ”yÝjeëú8ÍŽí³#]ìI+:Öžµ§AmÝC÷5Ñ%1µ¸™“mÞn[ÖÝn.¸YŒîÌ´;¹zîõ¹=Þyó[Ó×6ν©›ïjï{Ù¥ö7Â[oÌ <Âïw¼ lsï¸á•y8‡#Nï×DÝg7¹þëkð‹{À ¸É쑳ÞÑîô±òr‰ÇÜØØvy²®ò„œØ7÷6g4ã²Z\!Aö9µnmi \çGgøÉgò Óêú^yÏ›r§kÝë©zÓe>íŠgé߈ØyvxN ëy{±å>º›ý!vg{Ü“’wîJ¤ïh7à/øšdða-âoRø¿7zñ6i|D ¿ô½Dò¡üÝ¢ù™`ïç;Ü÷Ñy¿ ž$ŸgHé |z¢¤~!«W¼èûÐ${´Wîo¾{¡ôžæùý×"üŸßð)¾Ô ¢|®:¿&c p<î†z!c(@õûþZ_4`úÛ·@‘&@FM"`}’Äü0:`€e ¯ý©€ÿ@Ìg‰¤¿"ú—~ëWnŠå~†ó}=È~‰§W±ã}ó—=òGýGXŠå}¸ x~ÖW€Èw€Ô×V Hûp |ˆp<8;˜|hG¡FÓc]_%¸é÷ƒ-Ø|6X0(ƒGPZ×':hGbƒûÀ;Hƒ±£F©`GOà>i¤‚ƒ©€…© ¸XcýWƒ¡FR¸cËE„~‘ Gtè…@gvèV8z¨û‡‡4ǃþn5ˆƒ@àÑð}B¸Š#p<—È„jHDøh¸‚†ˆ 8;D¸8uèX³c‡,H…‘}á‚ Á‚IÈ}åæ}*Hˆ«åV‚Á‡ŸÈ‚£¸¸È X€Ü¥ˆ‹þGM†¡(Œ‹ø}Kh…›(؇+Ø‚÷ǃ¸“xü¬„@~Õhd8ã‡t`eà8Hn¥‚ÙS á‹~H Aˆ@ ;hŒŠÅ‡Ò7þM@åSûx~®8…8ŠØØxŠyˆzÕN¨¡‚z5rHŒÄˆ‚Ó†´8:’A…æ€ç7‹3h‘†‡ŒÏ¸—åˆ@h“á’3™|È…bxˆ›ÕåhR8Ž4™C)°“±”V8…Kó÷ƒFG‰$é†~#Xч¨~ì׎ ‰8;©‰±ƒ‹Ê_ÿ÷ÐS °Xéã·€¨h}uIH ~f‰e 8†!ùû,S!”þ÷ H° Áƒ*\Èp ™‡e5œH±¢Å‹3jÜȱ£GbB†4#ñ£É“(Sª\ɲå>‘#KºœI³¦Í›8 ÂC2§ÏŸ@ƒ -ˆ¤hQ)f†*]Ê´éÆ=JJÕ)T™U³jÝêò*ׯ`Ã>…U¬Ù³hu’Åš¶­Û¬^ßÊÛ4.Ý»xsÚÍË·ïʽ~ +²ìàÈ'N̸ñÀÅŽ#†,¹r_Ê–3ÓŬ¹s[ΞC‹-ºôVÒ¦SOE­ºõRÖ®c…-»öMÚ¶sw]«»·Pܾƒ.¼¸FâÆ“WD®¼ùBæÎ£„.½ú>£G“Zß~‘:÷æÞ¿þ'/¾8ùòÁÏ£ï­~}îöîk‡´’ÅÇ—4ñó¿ôq£¿/Úé‡^7ä€E^xq…€ëYqC!…á ¢ç „bHHa…âh ‚ r(žüù ˆßÍWß}(¦È[‹ÛÁcg2ΘY6V&ÆYdq…øå]DZ ÉápƒVXуa ÙÜ\V€!¥r7tÐÃ|Pn™\ØpÅOF)¦qlPä‘kúvC>øpÅpÆ©†bH!Æz “ŸAÚvHH1†¡¾ ú'£ìÁ„'¤ºí4)¥¶9 (¦²Ua…YXá'œÆDÞ`……–šþLZƒºªš’U„Ä_yÚÊÕ -Ë®ºÞ©¾†% ,¸ð‚ +ÁÚ¤’a°•ìO"!±ÓN\¼Ð¬3¼Ð‚ ;i»­[Ä n !…ZÄ"*‘Yv­Ogd{®HZ¸ÐB bh!.¹0™ “[tÑ…2ÀÁëÂ`½!!á)¨«ê»/NýfüoH;ƒ ÁœðN2¬ƒ0Ä@¿`³ þâø1C!‹¡ð¹\Ä p°,´«H\ø ÅRí‚ êº€ &\pÁ D Á³\ˆ¼#¨?Z»sM=ÿ¼m-‹;îÑ$dzÐB;. )´PC OWðÃþ1´ …È©ª*öØ3•=rHU@,Ãß0uà L¶¼…À2Èp &×0n [Tƒß_±äŸZQá9Rµîúë…ÁEHyF[4üŹoÁÅ^€q;î[ðj†_ta<ʧ‚úO‹fT`,*¼¿ûÞ{\ϼðÜk7ó§ïF`¤âüó`Q¹`ä^ù 0<;î 5XqDË3pá?A]ÐAºð…,è  bPûÀ²…–UN~ôƒAý`` ûø]š`+ø 8a’.+^XžP…BL >¨Â6Øbá ~“LxBÇœa þR8‚ö01i8ƒŒ°Œ¡ˆë;"b’¸Ä}`ˆP”"b~Ä@ YÔ¢` à§#X1WLÂŒ(Ƽ< i8BÎxE)±~é“äHÇ+VÁ xì Ç`4öÑRxB ÝÈ„&rŽhŒä™¸ÈÍHaŒ$ ÀÉ#$²’s¹$&Ñ\qGH%@y9N@ @ãVÉʼ¡ª@ `ÆZâe LÃ>Ž„€ €ô%]<)3‘ˆÊÜ A€€„#´*šwAB°ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºóðŒ§<çIÏzÚóžøþ̧>÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8Í©NwÊÓžúô§@ ªP‡JÔ¢õ¨HMªR—ÊÔ¦:õ©PªT§JÕªZõªXͪV·ÊÕ®zõ«` «XÇJÖ²šõ¬hM«Z×ÊÖ¶ºõ­p«\çJ׺Úõ®xÍ«^÷Ê×¾úõ¯€ ¬`KØÂö°ˆM¬bËØÆ:ö±¬d'KÙÊZö²˜Í¬f7ËÙÎzö³  ­hGKÚÒšö´¨M­jWËÚr.A}°þ­lgK[Úî¶ ‰¢ED¹ÝfÄ·áíA€{à ·!ÄuÌkkËÜæÎö¶³ÍmFx›ÜŠT— Ç-Èu'b\‹l1Ëu®x™ ]ÙJ#ÔíÈw÷‘]‚¬w!ݵngÂ;Þúƶ¼±=ïEÒË‘õ¶w ïUH|)àÀÐ×¾öÅ/lõ»[«·!ÿH2`îÎÁN…‚Ù8Üé:¸¿ðƒ a _8à ®-ƒ+ÂßøWÄ &±wOŒâñnø»H‹5òâ„äØ F.kìܯ˜";þmˆ}ȵOží‹÷F8Ï9Îѽrh7zØ—y®“ÝsQß\ç9ç¹Ëyó ïzä>w¶Íuƒô¤¯{éën:ÍENîcÝÛ•©úίr”;êź¶kntª‹]éeG3Ø>h¨ýëÁ»àNr+Ó=î?û݉^½óí¨yß­­ö©³]Ⱦ1<Ùÿó¹÷¸ë”—zÛs#y3c]ðŒW<â…n÷µòG¯úá /w•kþå^g}l:ïèÏ‹¾õ1:æ÷È¿}ò£Ç½Ó{Ó÷>õH_½éÍ^|Çsã™<Ë]C{ Ûþï‰Ç~ð‰þ¯ýÁ/_6Õâõ·œäè?öæ?¾ÛU|Ù/ÛòuF?ïÝßšð¿ÞùÌïþ÷¹îC¿ð¿çy€÷~®'}Y{ûg~Ëzøwÿ÷jãçwý·€òç} (T6˜€8}F×Û· 8 ‚hz&ˆzÛf)¨¸-x‚6ƒ*X#X€0Øx›—ƒ ‚Ñ‚$h4Ètª6„v7˜{a„Aȃ/èƒB¸/h„ øX‚UXƒÁ„·F…R…ÑK¨€!†:ˆzf؃ !†MxiH„æ÷†GØZt(æÅGzh† FrùEGͤ‡@0\è]þ G h`zhÈ"` t‡ z(ˆ©0Ô‡Ü5‰‡¸–‚8p‰ŠæcùňŽÈ‰ûЈ@Ñ£]­Š®øˆ¤¢Š …h‹«ˆ\ÎcD–XŠN„ÀA¶Ø[9¶`ŽhFcàDŸh^Ã(M¤"ˆ˜ Ùˆž˜[ÑÅC߸ŠXŽ©h壘©@LÎ#ˆÀ8L‚dÅx‹‚¨ŒM0cÀFᘋ¼`ÿXžHH¸M@oŠˆ¨>IÀAfÔ°‡ðŠ×X ÉŒý˜‘î…p™JzȉêèŒ9‰I4‰ôþE ©©‡ÂÈú(ŠÁH‰Öå‹e(ˆ ¹Áx“~¨@fÈâ(“ ¹ð¸ˆú8’h …XŽ †‹I [‹,¹HiˆJy]y¨‹Ýø•P©éŒX™”j ˆ@©¢ØõøŒ fF ”“¹>K)Œ‰Y`wÈF}YH9Žv•“˜]é’0)J)Žî¨>ʘޔВ[æ“Í„Œc€—!Ù–)™™ñˆŽQ‰’8ibØßx–v’шb˜™i‰˜Õ˜ŠÀ˜ üiI”¨™H Œ¥©‡–¨¨™¿…‹i™‡0”š¹—1iŽNùŠÌ˜—ѵ†z¸‡™)¸‰–I‰Ì¹‡¦8‹Ñ£˜®)Œ±%‹`F²H‹`qÖK(‹°!ùû,`! ”þ÷ H° Áƒ*\xÐŒY #JœH±¢Å‹3jÜȱã@1bzI²¤É“(Sz)R¥Ë—0cÊœ)%Dš8sêÜÉ )fx J´¨F›F“*]š)Ó§P£ºt*µªÕ«©bÝʵë>­^Ê] v¬Ù³:Ë¢]ËV¥Ú¶pãr|+·®]‰tïêÝK0/ß¿výÜV0áÃf #^ÜU1ãÇVCžüT2åËF-cÞÌS3çÏ4=ƒýR4éÓ'M£^½2äMÖ°C»ŽM[¦êÚ¸#ÞÎÍáîÞÀ?ÎNüèðâÈ+úš¼ùÄßÎiC{:õÕÖ¯ŸÎ®}4÷þƒß|åIËñ×oÜó}ô,7¼´wß¼Ë +óé#·_ÿûøùùœzì Ø\yçHœx Æ`ƒƒ=á_N¸—\töÆA [t!ƒ Ↄ 4„QÆ2pXbl4˜€ÃC\Ȱŋ±õ ãC"Þˆ#k'ÒàÅŠ2tñ#lÀ°J QC8¹ZB±ÄE ¡„•§!%1D`’D–@‰IDš£a©%—C,çhPJYšw‚–… WاCŸŸYqƒ`”‘…FŠ|W˜‘†8dá(f8Ü7††Zz)e6ØàCaôC£ŸN¶Ã}^äpEªþ—Ya…†^ÀzYbljëe éºëd,¥ñ+°Ç ‹– TT¡±&ÅÐ - ” `àEë%ÈlU-< ­ ' À‚ ` î ZTÁÐ ‘¸mG ‰D¼ôÖko¼\ĠżZ@Ûí („Û‚ 0œ ãºàB [€„|V€tšھÛÑñÎ{ïÆõ滯2Œëì³( Ðìo 1à*/ÄSì®Åaì0Ç8ƒÔE 3ø`E "/ÌBÉ'` AÀ0ÀðB·Zd<±WXá»Ó¬Ñ™TÖ\s=Æ×];ÔdoaöÙg[õ’SH[„‘u®`xÑ…¬V84³þÕ±P×gTF^€ÑÅÙdwQÅUa6R`ÈÅ¢±©b„©‘J*|GÅ¢ZpGáCÒ2h±…ñ¹¡KUc 0aD—«“­®nŸ'Eí¢k¡…I Q„LTÑÄsŸo*ñwÉžÊ A½]i|~DŒÅöq¥"ËGì¾öä·…†R!øbŒß>ZbDžþü$q?ZL`ä7ðIaÿ Kä®§¾8|UpB”ï5ðÂ&Ø­Å/Ä`9¸•T`Ìl  ÀÂ#h„WùÜõ€Ï GÈáØC©¤Âoþ(œ€"àÀ'ð°‡V Ê>ŽP#øD(‘ˆ•1 áQ( &HŠX)ÂþŽ0†ì!Á~`´Ê„#+^ çHÇ:ÚñŽxÌ£÷ÈÇ>úñ€ ¤ IÈBòˆL¤"ÉÈF:ò‘Œ¤$'IÉJZò’˜Ì¤&7ÉÉNzò“  ¥(GIÊRšò”¨L¥*WÉÊVºò•°Œ¥,gIËZÚò–¸Ì¥.wÉË^úò—À ¦0‡IÌbó˜ÈL¦2—ÉÌf:ó™ÐŒ¦4§IÍjZóšØÌ¦6·ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºóðŒ§<çIÏzÚóžøþ̧>÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8Í©No©€žúô§ @Èô$º‹ Õ"E•ÈQ5’Ôƒ,u$MÝÇSÔª u"QÈT‰J‘­"U!^ÝHTà ˜ªõª\5*F² ÖŽ°u dÍÈX'cÖŸ¢5"oeH\’W§ºµ­&™+dêêÓ»2¤¯€ýªVÿš½^D°!lO »Ä6v­]e,e=YÆH6¨~M«E{Ë„´•M,I:»˜Ïn–¯AþmAL W͆¶$¬¥ªd_‹ÚD¶ñ­TmûÛ“äö0®½íac‹ÙÅr¤¯À­@¢—äWº™}lvŪZ¨„ºp±îw±Ê\í:—»— ìx»[å¦V­æU*q׋[úz¶½×Åky+ßçv—³öm-~¬áž¶¹ýE/o<]ºøÀØ=oE„ ÞÙþ—¸f‹x!¬_øò7"tK`ä>¸¶Nð„·ËÔ û—Ã0YªUl/¸Æû]±„åêbW&2> YmìÞç˜"ž/Œ½»d]Êðs´|2‘A)å(?ÙÊ2Ä2”gYe._ÙËYó–eÙe2ÙÌaFóþ˜cYf6ŸÙÍi†óš;d“ Ù#¨%2x“üâó¸É*©sIîLgòzXÇ4Fp‘1Œ,HAÊyÕ³¢õªd×Шq4¤a{h$³øÏ~žtˆ£é6[¸Ó†Nt|ÝçצÔo>õh'½QïcÄêÅôi`gYWdÏŸ¦µ®[<ìÑðzÎÁ=rªA\iW_ÚÒ±9v¤•âôú¸Ä­þ1m¤ÍéY¯šÒÙÆ¶mMn÷–ÚVõ‡ó»Zq“ÆÜ¥E÷rw,lh3ÙÞ¬7ŽQ]íP§›Õãnô£Ml~ÿÜ×.¶¨¾és|ÞêF4À‰ïÕèÛ×Áî÷Ä—Íî{;;ÚuÁþ½½îZ7[ÛíVøg.>ò_×ÛßßøÂ_ò^·<ã7y¸U¾nr†å"–÷{#îik§¼â)4IÍ‘< ÝÈôþ¶»AýñnNàæ¹z·7¢õœK¤ëgØ©n±˜ fW ا¬´½#n÷Eâ>ôˆÐã¹û¤õN’µŸ„îlG;Þ¿þòƒð=æe'±àeâw“>냷{á¿ó>ì—ïÈ)î›#ž‡ºEB¯qƒ¾ä9½U¿ÓÖ«Àš@pŒ4hØG€ˆ¸¨"HÒ‡ÄÞ¹¯1èŽâÇ>}»ïýaÃ,ò# Îò‘þœe);öºç½ïåzeëï#©Èþö×z=ä§Â÷³ÝðOë# xÞü»_û„8?}eä7¥Å¦u:ûPFbçÇz¸|k€ûp:8aPÖeGKø§¬WYˆ08ä·{Èwèw{Øg|Ò×@ ‚c KW¶eȦIÐó}z7|‚O@ƒHTØy1È{?È~e†‚3˜{G(V˜>Ø ƒG~!ÃWœ'2h„ã'†‘ƒ 1(x€á3€·æ€–U|dd†xå€qÈV؃mH‚X…‡2T|³…ÈTÝg}æèׇoˆ¾§‚¨³H˜†c¨{¸GˆSˆ†qhó7‘¨|Á¥†1ð<˜ˆH‰yh@0~ž¨€Ü7[[F„FˆŠ†˜eXTŸhc@±×{x~c ‚¨‰AŒ ؉b(„ 1¨‚p¸Œ Ñ €‘؉½5fÒH`‹Ïˆ{˜Tbh€˜ŒXÈWxø†§óɇTŸ£‚m(‰±‡ÌhÐ}®ˆƒ6'|Äg|àõx~¿HÃ(~„sæƒß÷<áiŸ“}Ðg䇋°÷çÉ~·wk®¸!ùû,\!”þ÷ H° Áƒ*\ÈpŸ3²JœH±¢Å‹3jÜȱãE1b zI²¤É“(Sš)R¥Ë—0cÊœieDš8sêÜÉ“ $RÌôJ´¨ÑŒ6*]ÊôhÒ¦P£JuùtªÕ«X-VÍʵ«×}[¿ŠË4,Ù³hwšM˶-Õ7ÝÊ».Ý»x7®ÍË·ïÁ½~÷,¸0]†³E¬¸ñXÆŽ#s…,¹òTÊ–3—µ«¹sVÌžCó-º4MÒ¦S¿D­ºõIÖ®c{„-»6Rζs¿m©»÷kܾƒÏ.¼ømÞÆ“[üT¹óŠ´Ÿ.½7õ깯c¯­}{ìîÞ[þƒŸz<ùÒOˆŸ—îCýzç;Ü¿Où|çííßOž^ÿþâ, õŸs !à€Éä‚Êx ƒÂ9¡qZháC [t‘¡p\„èᇾäň$êR(¦hÛ[pÑ¢‹²ÁÃ`И›±„޶D?)HKi¤k?%¡Ä’± I$”®ñxÄT¶–AWx‘¥jVXÑ…—_š&†UY¦hR4‘暥a‘ŘpŠvà V„Qgh>øF\{fEû¤hgm†t¨fgŽqÆ¢™™©X’NêU¥–N&_¦aÊéN,´À yú)M.¸Ð‚¨,ÈÀ©þ–‚$²Öjëra«ZÄÆ«º0ê¨(œ€A.¼ð 2¼ÐB U€tÔ°Áµ5D»é©!+­»Öºá¸ZìÊ…Zlá쪣¶ìÀР?Xƒ 1èj… wvðÜÀC´þq›‘· ®[Ä CŒ2ä»+­]øêl¨/Tp ø°ê é⺅;Ø…t`CYÜŶWtÆCfEóÍuÁE…Á…8×lF^„‘„0ÂxÄG$cPC R˜C‡Ù,vñ,©Ä\Ò ÁE‰cAŠÆ™HÃÈŒULAEDëÆbˆ©Fþ]ˆ‰ßd¦"¸×1qñ°Œ[È0ã@RTxÄÊÂ`Á1ŒXEZÈ Å@<ÜpÅ5ppWè`…@‚NøK2´Þú ®Å3Øhãæ í‹ƒ5¨¼{d©¯þÒÛ2t9ûhÑ'ì Y…WÈéƒÁ;„ÄÅ—r¯º÷s™q„HDÑ=ùnIaÄ>Œq„ô³ïV*GìúêoÿYøˆÄп®ýï,îÈþ’ >P,RÈŸ@°?)ðbAÃ(ýí¯ Nð³â>z°‚OáWÄ`vƒ¡ »Ò„ºÈáüÞ7C¬0Á†þÀþÌw„"¡‡>lᤠ> HôÊ*`„# A(€£˜•1 !@BÀ'p1+EàÆ€>+ž+Pˆ€À¼7z… „²£÷ÈÇ>úñ€ ¤ IÈBòˆL¤"ÉÈF:ò‘Œ¤$'IÉJZò’˜Ì¤&7ÉÉNzò“  ¥(GIÊRšò”¨L¥*WÉÊVºò•°Œ¥,gIËZÚò–¸Ì¥.wÉË^úò—À ¦0‡IÌbó˜ÈL¦2—ÉÌf:ó™ÐŒ¦4§IÍjZóšØÌ¦6·ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºóðŒ§<çIÏzÚóžøþ̧>÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8Í©NwÊÓžúô§íK 2Ô‰H"E]ÈQ/’Ô‚,U#M}ªG(UËHA¨i*Cªº­"„«ñª@ÀZ‘¨–„ªž¹êúˆz²f•"níªR9bV’ QX=ˆXWƒìÕ©Ùk_RבÜU3já@þzÁ„±Œ ì\7RØ©F¯k}l[‘ WÉ*ır5h'rØÌ$V«,i;ËÔÉBµ±g½þ,bóê×ͮ֨žå+]akWÙš–¶lµÈh…ÇZ‹v·¢íXÓ \Í —³¸míg‘«ZÃúÖªÍ]¬mÃZܲº6#•íHi±›Yí>÷¶®suKYÞZw¹˜U,ê¶KØîB—½¯Mnoá;ÛòÎ÷¼Ü®q¿›[‚¨—!ã­Ìiß àú Ø»Óm¯~ß»óeÁz¥/‚í‹Þ¯R×ÀÊ­0sýKÜoøÁ÷õ°„«kYþþ–Ä©q‡·Z`÷‚×Æâ½®I¤c×v#ƒí±‹OœÞOøÆGÎñw¼ÞŽ€UÈIΰ‰C[déâÉ,V²ˆƒ¹‚.s—]^Á—wæ1ë²Ì`örþšÅ¼f3çÍdVsœÙu”©}íUƒ»0î>7‘M­l÷{Éì–M¾ùMk‚«Ûßô¸½³Íks\Þ·>¸Â±|oÁ <Þð&ðŒç-qq›ádžxºþ÷ðŽWÓ'÷ËÅ5.ëw¼äOy‚U³rUwã/çxÌCœò¾Ô|äRÖ÷Æ#¾óýz¼5?÷vÆm.òz]Ôí†6Ë·íò›ë|è2wz¡›œè gDÓ!:Ð[>v«=ààœ×7¢ök¤íd§ÜÍž¹7] vÿ÷>ò¾b¾“dî–Fß?¿¯½!†÷:C÷ƒ0ÞìGwäß~øŒ ž#‡òä1ø_þß7úæ1ø¿+þ"™GýÝ 2úʇžÙ¯oñÞ]²ÅGw¤öæÕîOßÝ7~!¾§û@‚qâçÖø@MþKÐ`ÀhÈnE" Fç¡xÒWH þ€¡#8µœPå; 8?û`>6¢bUúÞ>†Ì€ä1«ãú›oÿ}4a€~aõ~‚CG4p~L…}ç}ñwDÁ|û lÔ~äGê—?ˆÐúwá}зaä~₱}B}%xøX]óEU$xDÔ‡`'¸VGpD.È„rƒ„ƒB6hP*Xã÷з¨˜:ÿç|G}‘MpƒM@ÂSH€@p„à|#(<ØG…Á…@(c¢W^ˆ;¨~8€úG.؆ݤE€ý‡„Ü—…Á…þ…à‡?è÷„f¸X¨…5(ƒ¡‚>Xƒ0øˆ$F@€~k¸4èW”x Hƒ@Èih@Ìgz¸.؇h~GT‚Gh\‚ƒ~*hˆ˜˜¯Šéׄ¨‘xˆ]x†•È}xø‚Áˆ:(ø‰­h‚f(„щQ‚¨(ðnèŠ|h‡'H‚[䋀ߺˆˆàe@H‹Ü‡† qƒL8ã8*hŠáG›È|[Ô‚ÊÈŒdŒáŒa‰Àˆ‹%„]S‚l$MxŠ]Å‚#†.˜?¯XV9˜ñs‰¡~ö'óX}p}9ØcG™’bR¨.~ì'Šù§XŠx’$h}¨X“ªC‡ù…¹“9H˜€3i‹B¥‚ø£“h!ùû,k!Xƒþ÷ H° Áƒ*\hÆŒ¬…#JœHq 1+jÜÈQáÅŒCЬøñáÈ“("A"ÅLÊ—0KœyR&Í›mâÜ9Q'ÏŸ1šJô Ï¢H÷MJt)ÓŸNŸîŒ*õ&Õª3¯b}©u+Ê®^G‚ r,ÙœBÏâ4«–dÚ¶YßÂå*wî׺vÅâÍ[v/_´ ÿŠd+!áÂ#&¨x±À•-*™bcÇ—gF¼¹pgÁŸÿ†æ;:oi»§ç¦†»ºíÅ1h*kÓ£ìŠRšÀ¾M1÷ʼú<¡ïØÅ!n‘b;ùÂ1v;W(#†âÓ n‰Ñ{v\¸þ{ÿ^=Ìøì0´þ^°‚õì²äüô"B”Øw>?‰ËøÍ7Å~ÉU@|Úp‚ñùpÃWXñ€ûtqC `áEîsÃ6ØÐ…=¤à tpCd|¡C^±A Vˆ£²ÇËU|bˆÞpƒ^€â>U\áÃ=bØšZSžU%YW†•¥W[nÕåK1¼`™_y½pA .´Ð™sµ€ @5v‘HÜ©çZÄÀÅEÛi±'žRô` \ÐB .Àù‚B¼€< §E((…ÞЄ†<ÜÉ DgÜ™ç |ú h ‚îyĆþ&p/¸ð´^PÄD`®8à7lÚé§7„ªX©ŠªÛɰÕý¹ç=À@C&Ø ç0üéCàšqYX±A&fñ¢¨tFCf´„ï¾ør᯿]ðÛ]ˆWXñï[t1†`QVXqDKPL° ÜûÖ‹Ð…‘$hd„q]tÆÃf¤QEFqqS$DI~!dgì“Jh¤!òMGXQEUD­Ð| T-~C@$…*bU…X¬T -ŸhaDb0—ÊVMXŒÙ%AÄE$þG0…KÄi¤òvˆg<,…á‡(Å ãR±cLn8€²X@äš7žÝã FèìI±¶@Ÿ'ñåÙ5±ú>|.…æ€OÇ„µ@ûçU8‘JîÎíÞûï¶?ñì½ûÞ»UÌžóÍ`ÀõGH¡|ñ¼ÁŸó@ ¡;ïHAíMàQP€ôÙ±Ò>@€L°{&&#ˆ!rƒ‹ÞR¾"/DL [à&HÁ Zð‚Ì 7ÈÁzðƒ ¡GHšð„(L¡ WȺð…0Œ¡ gHÃÚð†8Ì¡wÈÃúð‡þ@ ¢‡HÄ"ñˆHL¢—ÈÄ&:ñ‰PŒ¢§HÅ*ZñŠXÌ¢·ÈÅ.zñ‹` £ÇHÆ2šñŒhL£×ÈÆ6ºñpŒ£çHÇ:ÚñŽxÌ£÷ÈÇ>úñ€ ¤ IÈBòˆL¤"ÉÈF:ò‘Œ¤$C¢€JZò’–TÂt0ÉÉJjÒ9ää'“JL޲8¥¼ä)ƒ“ÊLn²• X%o`ËW¶R–·¡%.e£K[¦r—•é%(a LÉ“”Äôe)‹é˜cNò™ÐŒ¦4§IÍjZó;"Ȧ6·ÉMn^A6Ý g8¿YqšS›ä”Ì9Ï™NǬӜí\Ì;ÅOÄÌsœà.¼§7ó©Otò³Ÿ"¨gaêÏr4 ÿì§@sP„”  ýKC#Ê—‰&TŸW!ùû,j!ùƒþ÷ H° Áƒ*\8ÐŒY #JœH±¢Å‹3jÜÈq¡1;ŠI²¤É“(+~ ™²¥Ë—0cž\ Q¦Í›8sÊD‚DŠ@ƒ :‘&Ñ£H“æ4ª´©Ó§"™BJµjB©V³j}Šu«×¯A»‚Kö¥Ø²hÓFYS­Û·ÏÂK÷ ÜºxéÞÍËWíÞ¾€Çþ LXëàˆ§NÌXéâÆ‡>ŽLy)Ûʘ›NÎ̹åæÎ K~MšãèÒ¨/žNÍZâêÖ°¯^ŽM»ãëÚ¸o㦭{7ìÞ¾Yóô¼¸ëÙÆ“#®4óæœŸCÇ,}:åêÖ!cÏÎx;wÄÞ¿þ/0ùò|ϣǫ~=Ý-Zº”q¿;†ý-]dЧ½%†Œ-a€!ûÁ¦E \€Tƶv`‚f,¸Eƒ¬qá€HajÆ€Ÿ~Vß|!²¶r%–¦Š)†¶"K-’a[1:Çb™µ‡£VEܸ#eDøø#dAôôÓ™!!$’‰éÈ$TN>éÔKJ]Àh%cÜ["Öà V¨fcRì`Åg2–¦h´™Ød‚)g_7xiçyYQ…–|¦We oEI¨e€ªHq*š×J:Z¤’²Çh¥“Š)X†nêR§ž¦j¨3iJjV£ø‘J®êêªþöźâ«bäYE&d€Á ¼ºpAI\QÁ\€ABQÜÀÁtÉC¯&zê«¶J««ði‘à¶7üz``î``B$ñƒ<0AXð|PÃüÖ@­\lÁ…O{’ŠíGÚn«ð«KÀ뀱ºzÐÁñ^ðƒCÑÀÆ 0@,»äâ`…>ÜpE3ă‡z0« ÇìêPpA?PðÀ>!´C;`´E„ü>Èp¡Ó\´¼é¬òdõÕXgµìƒ4ðÀ6Ø„?ø`DEDÃNP8x€OYØÀþ 0Ä0œxªAg8d†O†'®øâ†‘Df„Ñ…l1²F8QÅSˆ¡çSTá'UtqE`€áE_p‘¥C\pÁæàPhœÆîh¤aPJ$1†#ÅF A…KŒ1KD¿D_xa…é^|Q}iÄ>_±§B;JJ`<„ä‰s±W0áþ@ÁG¿OUdqÅ>Q…@\Èà¿ÿ\H…øÆg#!cJˆÜFâÿ ,b Ç"À$@A‚ôÊR1†Ãa0ƒY‘ÂŽ`$Dრ¤ ¢p„}` '` §â>#ì#%Œá iÈ„ ÄþbÐáRÃà0 OH…†Ø”"Þ08”B¤ C&¥†P|"«`„*ZQ(X €qø„~ñŠM0ŸÆ-:áŒCiB×xCàŽG‚á¨'Ì1ÇP á|Ê€ H!Pt‚9”#TàH@ 0IJe UX¢€0Á†ž ÊÜ"Âþ-•AAAŽÐCßÁò( ¹¥.wÉË^úò—À ¦0‡IÌbó˜ÈL¦2—ÉÌf:ó™ÐŒ¦4§IÍjZóšØÌ¦6·ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºóðŒ§<çIÏzÚóžøþ̧>÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8Í©NwÊÓžúô§@ ªP‡JÔ¢õ¨HMªR—ÊÔ¦:õ©PªT§JÕªZõªXͪV·ÊÕ®zõ«` «XÇJÖ²šõ¬hM«Z×ÊÖ¶ºõ­pÍŠæJ׺Úõ®w¥&^÷ÊWºêµ¯€µë_KØÁ°†=,_«X¼2¶±‚&dûúØÉε²–Åìd5 YÎ6Ö³ŠíaE[XÉZÖ±qMmsVÀÚÖºöµ¯Õ¢6aK[ÚÊöšµÍmkokMÝê–·ÕômnKMáÖ–¸Ó4®mµ©\Ø"WšÍ-s£»ÛéRwÏæuY›]hn»Ö¥nwŸùÝñ:³¼á®y›‰^Õ Sð¯|çK_úR³¾øÍo|ï«ßþΗ¿þ 0€Üß8¿>p}¬àÿN³Áúe0„á+á WÂnp†¼áw˜Àðƒ'¼àý!ùû,k!þoþ÷ H° Áƒ*\HÐŒ,†#JœH±¢Å‹3jÜHQŒCŽ CŠI²¤Iž\ɲ¥Ë—.SœI³¦Í›‘H1ƒ³§ÏŸ@'Ê J´¨Ñ›C*]Ê4dÒ¦P£JUøtªÕ«M«bÝÊõ§Ö®`þü*¶¬Y‘dϪ][1-Û·pºK7îܺxÕÞÍË7ìÞ¾€±þ L8êàˆ•NÌ8èâÆq>ŽLyæäʘY^ÎÌ™äæÎ 9~MúâèÒ¨%žNÍšêÇÖ°G®ŽM{ßìÚ°oãf­{7j<} Wýz¸q×*+7Ø{9ææÎ)Cy:uÆÖ¯#ή0÷þƒç+~<Þòæé¢OW‹—äìkÇØÒ%þn.\êÛ¯-Æ‹þý±õ÷€­á7 ©Íw ‚¥¹ƒ¥­!XNÈU… V\† aÈáT~h؆">Gb‰”‡"g!®¨Ø‰.bcŒÛÍH£w6ÞX9êÈc<è#a7ˆ!ä€y$’?öÈd\]8ùä[-NiS•VÒ„e–0y—y‰1Æ—`Ö%&™eÚ5fšç­É¦zf ùæZ[ÎYRvÊ&ež.É'…{þ b ‚Žèg¡Xñx(¢%u€AXðƒBl¤¡Œj” &TÁ4À€Á@…œ…Š¡*ª¶êê«þ°ªº„BˆÑÅ <ð`&\à@ ADEü€AWø€C8\Ñì YÄ C«2Ä`d¦Ñ*«±J!â– «´!Å ´ÛnTà‚DÐj,øþà6ÜÐûpƒ1Ì'ÖbÊ¥·«†ÛÄÃL0ñpRÀZ„½BXÁ½V`Á0øÀD½E$arM q„X0¡ò:‰±EÁ4o¡p–gÄiÆN:ë<ÆÏ@ùsÏ:3¡DKLA†ùyQ[T±„ÉmÆÏb=´at‘_q.úç¥aÆûœ1ô>hŒvAd„~[lqtJ¤¡ÆþÕc¤! ßc ‘öÏoï“F*¥‚8·.q!à \$Qı)¾8ã\).Æå˜_5F*I@ÁyçR‰;¦£“ž•G€D©«®XFìcÀ°ËÕMÔ€ë¹ë¾”O1bD!…ðJQl¼@¿#aDìÌ÷ŵd€ÁW”¸Ù þïU8A½÷4/þ>ãÿ.èÿÔ¶ë·ýóñߤøã³O€<‚ž¿›ì„ˆÀ~7†#E@Bmò0úM@ _Ê&Ø“&ìã8B €¼ÉÀ3œBœDP Gƒë^WÛHp þE`ÒÐÃˆHL¢—ÈÄ&:ñ‰PŒ¢§HÅ*ZñŠXÌ¢·ÈÅ.zñ‹` £ÇHÆ2šñŒhL£×ÈÆ6ºñpŒ£çHÇ:ÚñŽxÌ£÷ÈÇ>úñ€ ¤ IÈBòˆL¤"ÉÈF:ò‘Œ¤$'IÉJZò’˜Ì¤&7ÉÉNzò“  ¥(GIÊRšò”¨L¥*WÉÊVºò•°Œ¥,gIËZÚò–¸Ì¥.wÉË^úò—À ¦0‡IÌbó˜ÈL¦2—ÉÌf:ó™ÐŒ¦4§IÍjZóšØÌ¦6·ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºóðŒ§<çIÏzÚóžøƒÌ§>÷ùFøóŸC ã?*Ð*  s4èA :P„ÊQ¡#DJЄ4¢pœ¨EJQb´ŸíèB7Zч†”¤éGùÉÒø¬à¥0]c Ó™ÒT¦s¼)Nå¨S›ÒÔ§1jMszS¡¾Ô¨;cO‰úS¦Õ©CåiQ¡zTª® !ùû,Y!”þ÷ H° Áƒ*\Èp¡™‡²JœH±¢Å‹3jÜȱcF1 ÍDôH²¤É“(SªD RŒÈ•0cÊœI³f–/mêÜɳ§OƒH‚J1ó³¨Ñ£H7â™´©Ó§I—BJµêL©V³jÝú1$S®`ʈu¬Ù³UË¢]ËÖ¨Ú¶pãÒ|+·®]“tïêÝ{1/ß¿€oz L¸ðA¿†ëE¬¸q\ÆŽ#£…,¹rXÊ–3gŬ¹3TΞC#-º´OÒ¦SÛD­ºuLÖ®c³,»6OضskÄ­»wEÞ¾ƒ3.¼øaÚÆ“+E®¼¹Å H†:Ÿþ›9õë‚]~Åν ñþƒ¯-~|ìòæ[£OŸz=ûÒî߇Ž/¿3ýú™ï㯬ä-Öùg\ 1h' s2İEN&·E Z0Ø`qF¸Ý„¾%¸à…êF   ˆ!úÖ_‰ˆâ^*®xW‹.ÖcŒH£j3ÞÈVŽ:¢t=ºÆcc Iäe6i_’JæÇd“ü= edRH9ecUXyebMh¹eaF~9U˜b>Ef™M‰æhblÑ\¬)™º™ œŽ=(ƒel!Cœx&¦E \ˆÄ… [*(¡†"ª¨azòé' F§‚]È C¥`¶ù&¥œ¦f¨«yIꎦž:Yªªš5j«þ¯± +’$Îj׫¶¦t„PDåjW–µújQEQ„YdaÅEœ`AûAÁPàÀ\°Ï hà­ ÙI(,EEa®Ü nUP/TЀ`Ã8 „X„xÐ-»Ç;.Y !ÑRKL˜[Äøk…7X±0HE !„±M *CXƒ 0ð€V-å6PA<üP‹q(„#Êê-)|1‹‘…  !Æ7dq±>/ /Dý‚ (@OHKèC4` WˆAD6( R•‹{ð><'¼4ÐD}CÑbX‘ôÛK‹áƒþhQl ÀPEÃ*E×4[˜„øÀWXÈúR!†öµÅ ëK¸)Œá û¸Ïò)Há@B6X$1<Á0ÀŽ@°ŒÁq(L¡[È´A )Ãi˜(xð†þû@ “ðZr$H “hÄ©<¡ƒ,ˆCÅ&:årR¸¡·¸Ä"Z±)iHÃŽ€ r1…Up£’†TÑŒ €æh»*®ñ'Qƒ…¤p @$ŠpG£0 + ÀÄ!ž°IaBöq„ ! H@ `GHþD€ûØœRÈ€ jô$RŒP=a !œ¡*R‚ GHÃ,¡R¹]úò—À ¦0‡IÌbó˜ÈL¦2—ÉÌf:ó™ÐŒ¦4§IÍjZóšØÌ¦6·ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºóðŒ§<çIÏzÚóžøþ̧>÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8Í©NwÊÓžúô§@ ªP‡JÔ¢õ¨HMªR—ÊÔ¦:õ©PªT§JÕªZõªXͪV·ÊÕ®zõ«` «XÇJÖ²šõ¬hM«Z×ÊÖ¶ºõ­p«\çJW’*à®xÍ«^Pö-D ñëA›Á„°ñ+b´×Æâµ¯ YìD [ÉV„²û°,FÛ!Ç:² lB4;YƒÖ"œÅg Z…œV!˜=þ¬F0ûZФvB«ÝkkG+Z„ÔV¦õÈm›Û¼îÖ·½ìlƒÛ‘ᨸÆ%Hl+›\ænÖºq®€ ûXéF¶ºÔ-,v—›ÙÎr—¯Þ -jy+Þð&V ¿eÏyÑ;é$¾±/~B[áÂ×¼Ü=®r׋ÜöÞ׿åUíy<^‰è—¼nîàôº¼¾®{³;aÜ.ØÂì½lˆ1¼_à&ØÃ®ïw <` g˜Ã'&nŠb_Ù²¸Á¶ÅqoK žù2xÃ¥-°‹müÞ?÷Ã*V¯ˆ…Lbóx"„­1E,+e%ç÷"¿­ò‹Rå'«oÄ7î0Œ½Ü({„Ê9­þ–u â;ØÍ9fr˜Ü^2¿sxγžÍ©ç>ç™Ï~4 ÝçAúÏåî9|Âß[Æë=ï9<¯ âøÉ;~ß ¹üÜ¢yþ&¤ó°5<‚A‘½Ožó›§HåK_øÏ'\ô+ßé=_Ù›Øöª‡¼F$¿{Áç¾æ ±}•q¯{ÊCx ħ}òÅÓ¤“¿ô1?‘éóÝ–¯/ˆõþ}dûK]ǯ“1ˆ@ÃÒ¤¯ €¯×ö§ïè »é‚ ÁèÇBð—~ÒÅ÷HIù}†h8“4€[”†g8Iø—€\f€©ðˆZëãn´€‘ú— ©`Ygøgh€Ð*Hcý‡1˜>T_È=KbPƒûÍgjˆYA¨B=èmHÈ>0'G”48IFèjPXû@…G0MXo~úw…E(~ùµ@dx¤D…H ô…-è~È‚Q8hö7VèFThnwxyhEˆ~ >þˆðVHInX€O8]ŒÈ‡†˜>üµgÚ|x‘¨…?X……28Yxö…F؉h[qYH†¤ˆ‰ ‹‡lX‰=肊È~î§Š_ˆˆ8(ög†=¸g»8è‹!èƒÀø‚áÄø‰’Šûð¨ŒØ€h†„ö—Šø‹¦8‡ŒxÞÈW؆¶XD˜<*äŒÂØ‹ãø‡~uŒAˆß7Y‡cP“䋟ˆ[ˆYÿ8I¶´Œ˜†þ׈õ¸¡X‰ûÈŠ]˜€ùëiƒìCŽ ‡]Eœ˜…¿(¼8ƒïˆƒô—½¨Bgˆ“ˆI˜MPÕc†Y(“̈5Y=°¤<9¹,iqK8„X¸}“ˆ„Kø0” (“PHI8‰†i¸>©Ø”Tù“*x JçGz„ Aˆ8(Yî§€¨“- ‹–胠Fn –¨C™”È€ú·? ˜I‰€}y—Y[é—kyY¦f„%˜˜ q—VI!ùû,k!tƒþ÷ H° Áƒ*\ˆÐŒY #JœH±¢ÅƒbÄ<¼È±£Ç5BI²¤É„7ž\É$$RÌ´œI“bÊ‘5sê,xs§Ï= t¨Q“E*õ˜t©ÓŠMŸJeuªÕ*¯jÅŠs«WU¿Z +V*Ù²NÏ¢Uªv­Ñ¶n…Âës.]vïÖÌ«—¨È¾Où^)x0Ò¿†N r1c¦ˆÿt,ù"åÊ_ÆÄ\72罞?ûÍ*zt×Ò,/£V¨z5ÂÖ® ÂŽMp6m°¡o´}›7mß±»¾š8j㥑‹Vþ™9g瘡W–.™úc댱'Ön˜û`ï€5þËÔ]|_ózÑßUO—}\÷nᯕ6¥òåÅŒ·~þýu”Ñ~úG`i¡…"XQ 1lÑ…ƒmÁRhQF^d¨¡MbtøaEb8"EJxâD zqÚŠ(å#W3R%cµÝˆ#n¤íÈ“Ž;ÒW–b‰çãk@âHäWKzÕ¤SUHñc+^Áà 8d‘#• ‰DF`Š¡„BñCüp…7\fF_¾™Q ”)DÄ <`ÁD !DìðCØp…7ÜÀæ Y¼ÉåLg€ç›D)4Ú¨œ^‚*Ft–JÄ þðE¼„„þ`Ài§MøpEL q%aNÚÒ™S°ÄJ±ÄS|¡,ÄÛ,±S(‘„S˜‘QaHjUxñÅb„f]„±_°/î4HëF4Æ~hœñî»ò¾Kì»Tˆ .Ìš†@©¤‚ãVøpVQPÀbÔXEÁV \EAf¤’52Á-®5ñŸÂGÔ„G²Aڱힼg¨¼2Q¸,2¯ÜðÉàæ<óÉR\RÔ죿 4±ƒÀÓOÍt*G<½ÔQ#‘ŽWÀÕ`ÀØGHñDA{Á`†GÄDbÀbDðôþsû8ÉP@Ò8bóf™ÇM–·¼~r{ÈïÇW<ÿp}Â)Õ¤W/f úý ‰Ñ„gH’MŒ¡ŸA‘ N r„àFhÑh!G1ĠцɃi€xÑ1tQ¡‰¡¨"‹‰èÅŠ0*Ô¡k5.´…q9t_`?úä^CâUd]GÊ•ä[K²u^]ÑeAM¦U¥YWŽ•%X[ÎD„ T€¯AÄ`b`Qy‰D›pŠ‘„™E`áƒWÄÙæ›zfTšC!UXqÄH¡¨D4ðÃXzDLTzG 'Ž>±gŸ †Êg¨m6ÁDRDÁDáDþHAÄMÐÚ„¡”"aÅM º©”9á1 kì±È[l²ÇŽá¬CcH¡„F)¡„³Ø†ñDa|ñDØú7,°>’¹9[â>cÈ E»b(1…CÂzJ…ÝRÑß³f ±O*© ±g¶úg0'ªhžáßF±„GRŒq„H01eBRHaDŒÑÄÇI!†ûŒÄ(”1ËìfÌ>¦Á²@$'QÎÿ¦²sË$KsÌÍ3ÇGœtA5 ÅN ÍsÍXP…UÑ öRü ¥B×Á$›qèÛSŽ‘öbDPóÇ,÷þP@Ó£=b¤rÀáP¦²O榒1Ç/íñ>!']°¢—nú騧®úꬷîúë°Ç.ûì´×nûí¸ç®ûî¼÷îûïÀ/üðÄoüñÈ'¯üòÌ7ïüóÐG/ýôÔWoýõØg¯ýöÜwïý÷à‡/þøä—oþù觯þúì·ïþûðÇ/ÿüô×oÿýøç¯ÿþü÷ïÿÿ  HÀð€L ÈÀ:ðŒ 'HÁ Zð‚Ì 7ÈÁzðƒ ¡GHšð„(L¡ WÈBÖ)à…0< chº¾P†6Äá uHÃÒÙP <¼a s8ı‡¢ûaxD!úˆO4b‘´!ù"û,¶ §pþ÷ Ü7f Áƒ*\Ȱ¡Ã‡#JœÑŒŠ3jÜȱãD1=ŠI²dIMª\ɲå@”.cÊœI‰””4sêÜ s§ÏŸ-{J´£Ð¢H“B<ª´©SƒLŸJMuªU U¯jÍ™u«W—]¿Š5v¬YeϪ͘v­ÛˆmßÊew®ÝƒuïêÍ«×.ß¾rÿv+x°Ú†Í"N,v1c¯Žk,Ù*åÊR/cvªy³ÒΞ‘‚MÔ&NÒsG£þ©z5ϮÎMx6íöo+έ»1ïÞŸ,|¸åâÆ3#OÎy9óÏΟ‹Ž.hëê#¯cGK}»OíÞ7þ‚϶;yšãÏ4¯>fúöKÙÃgù~~ÃúöâÏŸp?ÿƒ¦ý‡•|Šä_ûX ‚2øŸƒüA˜Ÿb NRh±2xq¡LRÄÃ]È º´ÅˆdŒ±… \¤Ø’ 1ta2l!#K+naÑ‹1"rQb‡B®ôˆ™¤J[PxÆ“,iA •Yy–$ù¸%—^ &]bŽ©„ö¡9Ÿšð±Ùž›êÁyžœäÑ^€ffWfžxíÉçK~þ™` Úé¡Û!Š¢Õ1*£Ò*(‚Vj饘fªé¦œvêé§ †*ꨤ–jꩨ¦ªêª¬¶êê«ð°Æ*무Öjë­¸æªë®¼öêë¯À+ì°Äkì±È&«ì²Ì6ëì³ÐF+í´ÔVkíµØf«í¶Üvëí·à†+î¸ä–kî¹è¦«îºì¶ëî»ðÆ+ï¼ôÖkï½øæ«ï¾üöëï¿,ðÀlðÁ'¬ð 7ìðÃG,ñÄWlñÅg¬ñÆwìñÇ ‡,òÈ$—lòÉ(§¬òÊ,·ìòË0Ç,óÌ4×lóÍ8ç¬óÎ<÷ìóÏ@-ôÐD=Ö H'jÒJ£Ê4ÒK?5ÓS7}êÓ+T µÓRsMµ×V›ŠµÖYƒ½õÕ]£ýµÚa—Šu@!ù û,k!þƒþ÷ H° Áƒ*\ÈpŸY #JœH±¢Å‹3jÜÈ‘ ˜‡CŠI²¤É“?BDɲ¥Ë—0KªŒI³¦Í›4‘H1ƒ³§ÏŸ@'Î J´¨Ñ›C*]Ê4dÒ¦P£JUøtªÕ«M«bÝÊõ§Ö®`þü*¶¬Y‘dϪ][1-Û·pºK7îܺxÕÞÍË7ìÞ¾€±þ L8êàˆ•NÌ8èâÆq>ŽL9æäʘY^ÎÌ™äæÎ 9~MúâèÒ¨S‚LÍúäéÖ° ¾ŽM{ßìÚ°oãf­{7j<} W½r¸qª«+—|¹sÛÍŸ+ï-=2õê¯cO¬}{áîÞþƒßw<ù¼æÏ×M¯Þnôö¹ßÃç->jööõÖÏ?ÿ²þýç×~f`\ˆ `.h]ƒfa„ÜMHáw^(^†ö\‡r"^ ލ˜ˆ&ºW\ŠŒ•È"Q.¾TŒ2ú$+ÖX [x¡#b2lÑÅ…I!DöQpI–wd“€9$”|ñ8%•xUQEX¢‡b— æ&[4މR™fš„fšž}É&Tk¾é”›r.g݉gFzîi~Âh @}H(X}Jœ¢]%Ê(CŽ>Šœ˜’Â9h¥4Eúc .´€T`40dðÃdpAþp°ÅAšÖXE§-´ðB¨¢ZûX`A©~Â%˜PB 1ÄÀ¥˜f4ƒ 4hу ëƒ<ÄBüp”[B¹ÈÀcAµâ&Æ»H¼+ï¼ôÖ;o¼öÖû‚ Z¼‹m?ˆÁƒP Ä>ÜàC¹”ÐA¹>h±…‘ó^záfd¼SÆwìñÇo ²Ç[lFÆW`qÄbxaÅH$¡Ä]TqÅÍXÜ|Eaˆ1ÆÏ!C æPŸáÅ\€Ñ…XXÄVH-ÆSdáµþ<øÀ… I‘„g‡D…3Ì01<àÀC1Á„–”WåI1†L•G ·çL€F“~èûœ>ºêE±Þºb@{P²ÏžÄ·•ûìR¼ÞûM¿ëNøðÄó^ÐéR<¼M¹ ½ôT‘Äó4™.}ëàýÅc’öû pºGQÄúâ¿dúbDÐúÊ·èP€ö‹‰æ&†Tê@–H¡×ÛÇÄ ºô5ð%L2àxÁš`¬ƒ ¡GHšð„(L¡ WȺð…0Œ¡ gHÃÚð†8Ì¡wÈÃúð‡þ@ ¢‡HÄ"ñˆHL¢—ÈÄ&:ñ‰PŒ¢§HÅ*ZñŠXÌ¢·ÈÅ.zñ‹` £ÇHÆ2šñŒhL£×ÈÆ6ºñpŒ£çHÇ:ÚñŽxÌ£÷ÈÇ>úñ€ ¤ IÈBòˆL¤"ÉÈF:ò‘Œ¤$'IÉJZò’˜Ì¤&7ÉÉNzò“  ¥(GIÊRšò”¨L¥*WÉÊVºò•°Œ¥,gIËZÚò–¸Ì¥.wÉË^úò—À ¦0‡IÌbó˜ÈL¦2—ÉÌX*à™ÐŒæ3ƒ(Íj*€šÖŒ&6³9M ršÛäf8³9Nk–³šç”f:µéÍo^³ß\'8á)Nz’Óžæ£Ä':›ÉÏÐ¬àŸ è?ƒ(Ђ®€  (B:P 2  ehD:QƒV´ hFêЇ´£Ý(DA*Q’RÔ¤E)FûéC¸ô¥0©LgJÓÒô¦8½©MsÊSžî´§@éOƒJT—µ¨A=*R{ªÔ¥æ´©NÕ) £šÔ©R•©V½êS³ªU©ž°«[ý*X½j±’µ„f­)WÓúÒÎ!ùû,k‹]þ÷‰ˆd Áƒ,¨0á Vrø@È*2$Ë >t(ÑáC†#\ª4i"Å ”}0cÊŒyÆŒM)6sêÜÉS'Ξ9Çõ²…‹—.Z²ÀRŒQÚêaœ2ÁÄ$ŠH]Mb!ùû,µ‰Ö“ìH° A‚HŽ\XpŒ$ 1€D`DƒGÓd_ª‹3jL˜êãE#R¥äÉ”*“<9#%"J˜¥‘"†áMœ¸<(ç>Pž -Xæ>UœÐĈ3Xwî<Ã4e„Í)H4KQN!å$©Ò¤)I7d#IP@(Ý¿hö©ñ˜8ù› 1RŠ@ÜÇ“"’–tiJÙ½° ”«?ÄLÐ3Æ‚s!ùû,©x"þ3ìH° Á‚W.\hP $ #1€äHÄ…Fóã¢ÁŒÀðHЀ“¤Œ$i@€/H9ùRc‹gº¬I'ÃŒ'ƒjŒâó H¡/{þ¬ù’€§G¤-ù2ÂcŠÙZc€ M"¼lbÄã‘ ?0 @Ç‹b®ŒØw¡UV2üb` 1ŽˆÜ‘GÁ X>øÐ²E É}>d8Â¥J“&÷Y ã%̘Ï;w9cæó’Uªœ‘"F kÇ›H‘e¶”Õ°Ê–ÒÄ k4¯#6A3æÉ)c\‘’Ša”T©ÌØf†9š…Q¤˜I“†4èÌÍ0,drYLèÐ=’oe úæŸDa²Ê3è/²¸›9|†Nt†kâe¶§!ùû,£j"þìH° A‚G~\XP  10âA# Œh‘ ÆŒG8vü˜QŠÈˆ$Až\˜²äJƒ-3"yé1€Í›£ÐÜg€OŸ7 Ìdù@’1yÑh„ÅBµÈÅšD°ÙĈÅ#~`@" @¯ÇXù°ïG…˜ìlÛa`1çî QÐ’"R¸lé8ÐF–*3b(&¼Ï‡Œ2¶<&ÌŒ#3-wÌYŒçÇU,JQ¼E —Îb"ŠÙ¢¸uiÏ ­˜~¬åµçԳܮ"å¶ïÜb®ð¸C·oܯX¹qÃsÝlèáB†GÌðľ&J”XÌÛïÒOfè£`‡ôíï{0 !ùû, ^ þyìH° Á+ *$(fÌ•… 8bÁ" Œ9bq ÆŒH8Z, @Æ}")iÒ’Išlù$¡Â33iÐHN“ T±IÐçÏ–G>4:“€§GªðÜg4€Œb~±Àµh€ M"˜lbdá‘ ;P ¬Â1H’ˆ)$€&) )›UÌD” ¼qÑ 7ÄX¼á#‹7®(^xÊ1W_¦l³æÍ_yù2h‚jlX]£ŠéÓûntøÀaà ®_´²¡ƒ.Yndy=yŸ•5n4ér»xÁãÉ—7_È»Ã>àÂaæ0Ð6níû” l-FJÇÁ !ùû,Ÿ\º³ìH° Á#G *Ü'†Ì……8‚â@# ŒIcÆ#@ °/£Ž  J‚* f”òrF™+Â$Y0c•Gdi P›d0ÀôH•Š# Øg@Œ 0|@:¡I„’M.X¹²¡†•#~`8"`_UÄð¸‘eL’<,TØ'€ÉË"Lü3aA'bˆˆ¤ˆ1‹ &ˆÅG–+ Fx£ó ‹û®XQ!ùû,j Soþ÷ (e ÁƒLÈá†>LE1Õ˜Š3jÔxq£G?ЬxQ`ª3fÌHIɲ¥Ë—-Wœ9S&Í›,WÊÚw2¢ÏŸ@ƒúDbfgO¡H“*EHT–”1b–J ÉJžg¨jÝz°)V®`µz=¶lÒ±Yͪ Šv­Û¡E¿¾›°-Ý»í⽫wïܾ~ݬv0ᲆƒM¬x+ãÆTC–*y²ÒÊ–‘bÎÌ6.YÎb=§ÚèhÒS7£†¨z5ÃÖ®™ŠŽMy6í˶okέ»³S¨½5_ý®éâ¾å"7®|9kÞÎ_C.û8õ†°{g×½ývwÚßcþ‡w=~uyÔçI§½žsûÌï-ÇŸ<rýÆ÷çj…ëþ VDUZsáàר 7ØAdÓ)V…?qDjE=% eEÄfAÙp§A&„FaµYgBaDŒqáY¶Ä‰FìC#‹8º8™C (R`°›Áã@4Þ˜œ‚Š(£A4RñÔ%66D>ôÀ˜D©äOÿ%†8Ìèã˜`@gFtDŽ„­ÙfoÂ¥” ÁÀE6¶f˜c¾I€Œ!Å–ŰE…*¶æ3Á4Žñƒ ^`g”RyØšûÌ8ŒéDþU…Qj—– tD"!@ÀŠ&©•B6F?øðCÀ Í0i°aƒ@ˆ±¢…@Á°¡¦6†…AñÃC>A“¨e1­PiúÕî^ïâ/_x^7/]÷þU/uù¾Õ¯`ûF÷ïZMixv$,º ï3pa;÷°Y#ñr‡•ñb#G”I´*LTÐÚkX­‰Äš,·ìòË-¯ óšRHáD>¸0C4˜`‚X€QD?ÜpxPÂÔ`…\<ÁPH!ÆÈ¡¤N`CâLc” U[¤½…l_±OP‰!Å y!«Ùf­Æ(AcHåõØ ¡„†,i˜1†\p†`Ú…xÏÌráfœ1J– !ùû,V!”þ÷ H° A‚R*\˜p¡C„#îk(±¢Å‹3jÜȱãÆT ¥€I²¤I’"OªT™r¥Ë‘)=ÊœI³¦Í›a¾Ü™ªåH5hÒ¨áÙ“¨Ë˜8“*]Ê”©N£,Kš‘BUÊž>¡¢Ù´«×¯`V;6I‘"IȪ]ë„ “&n™@YK·®]µaóêÝ{ó)É"C)²Ó§«©ÒFSXëI¤|#KžüÐ/È%Ïf>»$*Ì–SÍ4v\2åÓ¨#SrŠÖ©¦ UB{ÊIØ Ï˜I“J™Ý;q“ ;µñã`WëþmfI’1¿• "ÅùæÌ¥`ý;tìà³þ‡O^ª)†ç駆jܨ¤2hjj¨¦*!§þ¶êj«¢&ë¬ûÕzÚ­¸ª§+e¼öŠÜ¯“+¬qÄJfì±!Â:b’2kb²‘-+ídÔòeíµ'¦2FŠUBËí¡ÎÂ(–kã¹è~˜í^۶뢺<²+/¨ô iï½¥æûä¾üªêo˜üª·ànY°Á´læÂ çêðšGìëÄpVlñ°שñÆÈv¬çÇ 7‹ði7ÈoÉ6½‹Q”r´2Ë3I³d—vt)¦fÐÌŸË(3É>wûí®Bk4sÑmÑÞlÑÒLy2eKhEÊQ]uFNWT…>daE7\Ñ5Ñ_Ï{õd<Ü ÷ 8Ü`EA³ÝöþWaKtƒVXƒ 6øpÃSë½wW}GtÌbĽà tÀuD^/ž“ÈJ=nßXqÅ5X!µB2paŸ’šSÖøC.WD~CU¨ýP 1tQ`ë§½îPÜraGt1÷å iÿ-çI]¶û0Æuƒ ÑKotÂÊÞgDÏ}EÎCϺø{ ß jŸ¿Oúb¬謹^òSˆ¸v>ô¥yyÞê¢õ¿äP'ºÛG h)è!Šk {¸” RÉœÀ‡x‚Q¸ AD8Â/qP)'üàæ·ÀºíhÆ1`@à‡þG ˜A6é…IÑa"0€ô¡@ˆbˆXC#z¥„9ßšô9{aᱸ#TtG@ ÆÑŠ`C"NÄ€µYT¸Â7ÂÑ?r¼É¬ ŒA}|¬ÿX2*¤)Š@*†‘IqdDš°…ŠˆQƒš¬É'JšŒ~63ÑÎ2…I¥”r&§„ß+U¶ÈV:d–‰¥øpÙ]J—C«¢-ûH¤ s˜5æF| cH2‰ù¶N& ø™fËŠ ,1ÀG äÓ¦G4I…XÄ/x*$…&B¬Wg{)@! NðÁ€„)\pNx‚œ#T”¢è€<|×p@XpBU¸°È¡¸ +f±D¾õ›}x‡;åaΖ „ë0Ç;bþ¸#*„aR“ºÏتÀ*X XÐBà¬à…wv'<Ú1˜ªå‡ŒA ²3}è-D7gèNºà…/„! –f³wÂp·.tAta0Â`Ÿ/£¡ ‡–v ´ÙÍ =Xˆx ô;جP…*°ºDd;"Òå.ÔÍn¾þµ—šp„*4AÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~ûû߸ÀNð‚üàO¸ÂÎð†;üá¸Ä'NñŠ[üâϸÆ7ÎñŽ{üã ¹ÈþGNò’›üä(O¹ÊWÎò–»üå0¹ÌgNóšÛüæ8ϹÎwÎóžûüç@ºÐ‡Nô¢ýèHOºÒ—Îô¦;ýéPºÔ§Nõª[ýêXϺַÎõ®{ýë`»ØÇNö²›ýìhO»Ú×Îö¶»ýíp»ÜçN÷ºÛýîxÏ»Þ÷Î÷¾ûýàOøÂþðˆO¼âÏøÆ;þñ¼ä'OùÊ[þò˜Ï¼æ7Ïùä8¤’99è-’ ˆŒS £×ré6Ñ7ÉôMC½´W¯ÔßÒõýë'2ûÏ·Þ ¶íyzâC{øÀÿ}A‚_ÝçÞøÊFþò•ï|ÿTÿ÷Ìg­ô¯þßüä?_öÅÏ~a·?ñdûæ?ÈðÓ¿Õ³_žä¿õ½¯RîÏÿýâŒ?ô‰ûú—_&î×{¬‡è·{ò·{ø§Mú‡ø}ûç 8M H}ÿç€HmH€ýG}HBøk8Óg‚¬6‚¤×X±÷€…¦‚üGx‚0¨z¾×‚%è(ˆ}î!úWô„9x~|Dè‚·é'ƒ¡Gƒ:xƒ÷÷ƒ_"CÈ£—„;¨~!~IØ„E¸‚PH‚R(†èr[P*—†$±†j˜rl8n؆pø†(‡\Q‡tx‡vxrx¨…÷‡s(‡zHˆ|¸‡þ~؇&'ˆ…˜‡‡hˆ ˆ„XÈ…„]ø„†“(†[…gX…e¨‘h…X‰ r„K(^ÈŠžÈ‚šhÂ'‹3Š…vW¸˜‹º¸‹¼˜‹=Ó~´ø„¶¨e½XŒÆè‹«8ŠÂHnÇØŒ½ø‹ ±~68Œ,æŒÖˆŒÀ¨„ÏGvÞ–؃-ÈÖå׎©¨ÈŒæèŒèh„êèƒã֎¡˜âFÍøŽ§ãÈŽúXŒü‚þ8…óiÓ¹‹I‰i† ÙØÁ8‹ÊˆmÉ‹)Šâhù¸‘ºØ‘Ò~)’$y‘ÝGެ%’#©&yþ.yW)‘µH…’X{ýHŠá˜‰Ù芹‰¯8†A©’=yIt‘±”©N •=ùTù“q•ÉhZY”Ñ•3(`9c¹N™“A•hù•U)e ˆeù”Y9”—m¹v‰•yÙ}{ig)jÉ]‰–oé—±¸}©})—l逋i‘xyéi–wi•[9—^©‰©™dؘ<™Å÷˜±>kYtšûHh™HŒé®)•°I—›X9¶Ù}‘›OÈ›÷› ‚°CÀkë0φˆ°;4.äB†X¹CÜþÓЩ†Î;c@ãIBmh‡Gpž«¹C@%r؆å phë©Ñ˜ž#!œÜ“õ¹9¥ƃÙ ÅIzóé©pŸïy¹Š‡„‘Üs MÕéˆ !йÈi„Qäe ðlÈÉà‰|zjÈ£çW¢×¹hc€+:¡<úlh£.š @À='z¢·T¢Õ)¢`¤ï‰¡ÊPœ 61JÚó£1¡#ÁÚy£‘\d¤G 9Ú¡A8F¥š¡C ¦ØO§v J¢JªEzŸÌ ŸŠø£M@Ñéšþ/:'ªœ:%Ö‰¡#JÏ6t:%û @hà¥MJz :ñA¥W¤©e ¥\ê¡1¢Ej¦+:¤AªÚ#¢eªŸÁŸZ§!jKj@'J£è)«¸éû¨Ñy§¿*«åYŸZФêg1Ú¨úªí§†G£®Úh’E Uà©ûÀ¬­JžÒŠ£«jªªez¦¬Zš!j¨@à¡æ:cP©vjø¯`¬(Š­7j®vJ¬ßJ¨Àêe‘º›íšŸ«'œê¨{jÓºæÉžä ¤’ºÇã­ ª©±¢½j©ñ#Š¡ÛŠ¦á:¬–Ê£xš«Ñ™ÐhZÚ¢yŸÈÇŸ1²íJˆ4+UE>º¢5º² 1¢*¯:¢Í*´©€ªOš°IÚ¥–ú±ëú³+àšÌ §"›œËÙœ\kPú­îI³!ˤ;TŸ^ú+!¶;¤óùŸ@ضÁš¨ ê§BêeæÙ§÷é´Aj¶¥7¢þ‰­Š 1ªµW»eæYŸ› ü:!ùû,V%þ÷ H° Áƒ£9FåIµªÕ•RRiÕªô ×&Rh*5³ukÖ­.E¾TS¶­Y·pã¾m{µ®Ý»PÛ~c”&b´îu{mH1iä–-¬¸1c­x#K¾û8•”'M23ñšjpÛʩҰm<—´âÊ“S«Öù8ÍåÌ™ažKØ´cÛ§Ý®ÞÍå㘠¡0‰b.hÜZ#GÝ»¹sŒÿª•>“sm䯱g§û¼»÷‡Ñþ§q ö¶öÏçÑsÿξ}øçN_š>óöø»?F3“ïL4AßVØ™nù%Ø\ag˜aFý9(á„JAá…^ha†j˜¡, †˜ƒ‰EfPLÅž,š¢ˆ0R¶UM±¨ ‹b¸ãŽW‘¸#Ž:ò(äN>Æä‹C&iS‘0©ä“XÍÈ£“PVi“"Riå–a¢–\†i‘—7¶ˆ¤˜hJDf‚`¦é&Bkæ×æ›t'~sÖIçíå©§›|®h柄úŸ„Ši¨wˆ&Ê¥Q#íˆÄ¤:Zç¢Ý5ji•˜>§é¦Ovêܧ &)js¤–*䩽¥ªêŽþ¬òæê«0ƺ۬´†hëj¸æšà®ªõê+~À¦&ì°ì;Ù±Èz§¬dÌ6ûܳ‘E+í‚Rþ8赦fkä¶Ü®êm“à† ë¸Y–kn­è~©îºº¶[fŽg¯V4~K¯½1R‹—µüFæï],#¾S¾kp²ò²©ðÂÎ6,çÃO+1žWŒ-ÂÚ{÷™ñÇ» lWÁ$ Ò‘Æ8)•¦1Çú)³Å4“ëñÍΙ\ÊtôtÕ%]]‘7p´5×yM7T±ÑØdw)uN7dacFl·‘ÙYcþËÕm÷ExK´÷Ü}Sý7koã”Å8îÃ÷D~NQàÝ`ùà…-y‰ßdØŒï]Qä›ç•óÉWÜàÃà?Dzéàun“©¯Î:ä†Ãåéu}~Cèƒo!xD9DlPC¬³CD¯oÐñq°Á <Ñ|ëE/=AÔ;dCYÜp„ö„¯ù÷8…Ð MtqCŽH¤=ñì“äþAðw±Ï@ú(â½üíÏ ãˆ`€£p!dß Â,$ þò'Ùåä‚ Ãp·>î.ùšÌÀB Žð~%4¡ñ<ˆ“ ,4€^èº Ê:þ+{ ]V€"ÞÐûD^³²d‚!¢$‚߃âA¤ˆÃºÐ!X”ž bÃÀh<Â!èC'VdŒ¹`ÀÀ1x,cèFýÑð& œ€"PD'†6ëcÔx‡—#Tà8‚P€CB$ŒÅƒ#AÆp„ ìÀ˜`Å+¶Q‘¦Cáj,ð`@ 8_)§Ç¼D¢²k´ ‚+\ày©B,·ƒ/Ôë–eË¥NF0ˆH7À4­PdžD“©ö˜g+€ÁšWR¦dR<+xœ¸dd{´i».x~T'{¨`9ð Vþ<“)OöÈÏr7ðÂ1÷9&qNæ U°BlIЂöó;ÿ´œ@ªlf„žØë>õIQè/Ú싺ùÍŽnFE‚ˆ ÇŒL¡DB.p ŒÐ&€Á~P„\8°BB@T„€4à‚ÚBN™Ó¤'MN¦:Õ"loo0 BýX4!aª °*€+¼à/H« 0ÀVl ? Â6ð¬ÕF-A ì*æí­~!åæ;¡úF¤! BJ§zÕÁm•ED‚Jà dÀ0Á Làƒ œà—­:àÖìà­XPé > ×¸–Zpþ¬R )ÍP“°b™ &LU ÅÑcÅ ÒÈV B€$>[&A³¸Â, åÕÓrÈ€BP‚ËÖÀ& Áâ„Ë"aZN¤Å-DtÛ€)ˆa SMBpY4\•AÇB*`´Á·€À€0] Ô D¡]?@Ô\@<à® Jß½I¡–éU¯CÛý~µ"™ïpï;YÊ Á¢}N ;àØ€ €§ÍËf€µkÝwià;Æ “ì[U§J„ùVGLP)X@ÙG¾À^.”! XÀ½½Ý:]s·$àìj'†Ÿ’°g@‚þW…`æ«þ˜ 0-±¼Êß`hò „)0!7ÎÀnLÔ ÌiÍ@ÜÊÜ<3¤AÙ‘Y×fÈ®4 Ex1„ÔÀ h~PÈ€n Ö|à­%øÀªíZ7:…Žˆ¤'Í"Àl{*•¯*ð€û apØìK aº6€¦ 4‚T&pëjM #Ž/¹žËàCîr¿dpæNB¦’# LÀ´ŠP%\Ø EP÷Z…*t¡ ^´ÐMwj¡ æ>ÌQÂ4˜á i I_ 2%La S@‚¾P†0x¡›FPwþ°n?wÓ M Fj…+x¡ \ØBPö±pƒàšá;óŸ- ø >èAt` A ÂLzz,tsRû¶lÏ·Tœï ˜Nš À#,µ çC¢"…¬3A Lè7ú}v«»ýíp»ÜçN÷ºÛýîxÏ»Þ÷Î÷¾ûýàOøÂþðˆO¼âÏøÆ;þñ¼ä'OùÊ[þò˜Ï¼æ7ÏùÎ{þó ½èGOúÒ›þô¨O½êWÏúÖ»þõ°½ìgOûÚÛþö¸Ï½îwÏûÞûþ÷À¾ð‡OüâÿøÈO¾ò—Ïüæ;ÿùоô§Oýê[ÿúØÏ¾ö·þÏýî{ÿûà¿øÇOþò›ÿüèO¿ú×Ïþö»ÿýð¿üçOÿúÛÿþøÏ¿þ÷Ïÿþûÿÿ€8€X€x€˜€ ¸€ Ø€ø€8Xx˜¸Øø ‚"8‚$X‚&x‚(˜‚*¸‚,Ø‚.ø‚0ƒ¶§4Xƒ6xƒ8hƒ‘ %Áƒv²>˜k98„D¨;؃à„ W„L¨ƒ„…*rP¨aMx…GHR¨„áv…M˜…±…Q…êå…L†Q˜„c¸„fH„hÈ…1…A†¸Õ†nø„HøƒkØ…v˜ƒo¸‡q‡Ö‡~ˆ‡Z¨†þA‡„Eˆ8ø‡‰ˆˆ¡ˆPň7舑‰TȆ”Hƒ–˜‰zøˆ|¸‰Fhˆaˆ‰s¨‰›Ø‰§ø‰—Š©HŠiÈŠž(„¢8Š!‰«ˆ€8ˆµ¨Š!† h¸1ŒaŒ!‡Œ¾¨‹·˜‡Ó“‹±èŒ­(Œ]RŒÐ8ʸÌ‹±Ñψ¿˜à،ȧ¿§Ž¾ÇŽ½çŽ¼»'ºG¹g¸‡·§¶Çµç´³'²G±g°‡¯‡âÑÑÛ‘ŒçøÙhŠãXв(‡XQØø‘“S‘ 'èh‰iyÀX™'0þ“29“4Y“6y“"°y8¹“<Ù“:Ù“@”1ù“BY”;I”F™”4‰”JÙ”9©yN•Où’RÙ”LY•By•X ”Z¹•<Ù•^‰“`–69–d¹”Py–\™–jù•lÙ–bù–pY–Ö’)’ÚH’xù'鹑Ӹ)ù+)}…¹’I‰É’%y ˜‚H‘9‡©˜ˆ9™‹‰•y™wɘz¹™Q™ù˜™YŽxIš)šÑ™¦9š¡˜°é—ÁXª)™»x›Çˆ‡éš{ºé™Ë(ºަYœ±)ˆÈI›šYV$œÜh³4œ¿¹E}‰›1„þ™Úi™2øt‚ ÑðM0ܸ‘èƒëAh`,4B癞e±ž1‡ì¹ñ ÐûPŸêù쉟 ImŸô‰žZ‘ñžÇȃò¹€ºƒ÷yŸqž êùŸêœP¡ûM êɃ¡î‰‹xDs &º0B*Žû¢Ù©DGh0£5ºŒñ‹*Z¤p?  Aê FšŸDÚ ăKJ£ÝI -ª£p@p> ä Zúhc:>š 6ŠŸQZ¤+Š\ú>ú?Jt£‰(žÑ£júG DŨ¡PJ¤þÚ£5Ú§vê¤O:¨O¸õù?ˆú§ÊŒZŠDñ¥©À¥â¦‘¦kÚ£pJ…£Z©¡ ‘¦xJªëé©ûŸÿ™ªj¤­ú¦ì9¦‡Z§=Ê’:ª,ʦa:@§~Ú  §-úM@ñ¥¯:£B*Ž  m§–Y…¦Js ‘ ú­¬:«1§º ¨³š¬¤„@ÀBþ‰åÊ«ºa­„É?ê®~j®“Š®ÑЬ/Ô¥ß*­å¹¤(òÚ¨jªMP W¥5š¥µz­A®™ªDÂ*£LúB9ª§‹§5ê°W ®Ùz‹©p§ä9@z¥Bš°{HªÐú¥Æ ¡1¦Ê ڞѪˆHÊB0èɲ-{¬rqªò™¤5›žçЬú¹±>¤:¯ ‹³ò ¢OK²L;³С;!ùû,c1„þ÷ H°àÀ1RJeŒÁ‚RJœq¢E‚/jÜȱ£Ç CŠ)1Uª4 ª1ɲ¥I).cÊŒ s¦Mš6IêÜɳ§ÏŸY¦A(ELš›/‘*MUs©Í¦1JJµ*I—iŽ.…êg×™\[ZK¶ìد.âeºÖ«L³pãÊ Ù6éW3cƘa©mßTs <°.Û®DÚ5|ø-áÇÏÖýóLÑ1gÄHÙKÙéßÈ Cû4ܹ%Â3&ŽiL:§è×°?¶6Y¦K—23O§–²º´Òϱƒ /9Ù$ÈelÙëÒÌåÌ›YŸ9¼ºuëÍpáÒL.`dþš?uªÕŒÏ»¶ÎvË3äãË¿9 ÷ñö¹ÌŸW/ùÍû €š!K{Bö^‚‰á 2(á\ NhЃhᆒ™t‡a!ˆ$U!‰"jXâŠ;bŠ,Æ8RJcˆ±"8"±™Œ<‚ä"‡0ö(äF?näHçáŠG&éda,}Xb“O:Y¤…TV‰ä•f©¥\Jèå—<†Éà˜dÆhf‚h¦¹âš¶é&‰p¶'çœÖÉÞxZ¨§u|ö)áŸÕ*h‚„gè¡í%*Ü¢Œ^%“Ži‰Žé¥ÂeÛ¦œÆæ)l †úÚ¨¯•jjh¨Š¦êªþ‘µÚ«°>&+h´Ö:Ø­‘åªk`¼Bæë¯rûذÄÂe,aÈ&[Ö²ƒ5ë¬U4ÚXbŽ:š1m£“NY©ŠÛ­`Ò†;Õ¸•k®‰Ý¢øíºâ¶ûâ»ð‡î\êÖËÓ½r嫯NüÆåï¿# ×ÀÓ%/ô& šÁf!ì°G—%ñÄULÖÅk¤ñXwlÑÇV…,²’©H鮃–ž ìÂF6ì2…0c)óÌq‘\•É8C¹¤·,ƒÛ³Y:KT…N<]ôCYX+RÒ=/mŽ5ÎRDuÕ!]=sÖ5ý`×7MØU!FcäµËh ¤Ù?pQ”Ûe›mUþÜûT1ƒdÀƒÚ½}2ß2Äp„GÀ ×Þqµ Áƒû Æ GìƒGØî¨7Ñ5ƒ ˜Àøm_$yÇ|Ç ø@š³ùD¯cÜRFm1û>¨Ž„íNK”ûÄ,¡¡®\û º¶»ž÷è禶|¯b!}ôÒ0¼FÇ;ÌÒÛ {…÷ßK¯z7x=öìž—þcVÜÀ~à‡?>îó£ßhX2Ðl FxÞó@8ðÔ`ÐØ¡”æûH`"0` E‚‹0‘+‚-*`¼7*D xMÀœFn` ¡°*|È*ðƒAþ(À6rLð†Ùû®®€A T¨ fxƒ#"±~ J ˆau.˜¡mxE,ª,2# ÈŽp±†-+ã¾T™l¡#b#£{’C¤p£c‚òG@Lº¬hÈ« ²=7è#)’óðÆ9%ºAõHI‘Œ¡ MØÀ; E’ehèäHÔð„P¢a”L Ñ ¬à ½¨R$gh‚Žr&Dj«¥¶n ’\î2e¾,¥˜dKb~D Nh‚^éËâu‰™Ãtfá@)J&DÁ]U°BÂ`mRÌ9yQf—º I+xG3'‘¹'ÐP aþК OÑó YðÁ¦bƒbP$M¸Á6“{ƳŸ©¶&Š-Xô¢E‚„PUà@(‚ð4 HÈ塄 x@èB`ƒÆ™@7àALðƒŒæè6¸Ç ƒ|¢É †–ê +Ñ ½çR‰ "¡$õ°€ ¬T@øA8J | øÀL;PƒjÀ?€Á ‚‰¦:ÈžMËçB‘šT».Õ÷D‚SLj!&lô°¨*p§:ÑN ‚/- |ଗý€J`‚°€.ÐBÄPW»vÁ¸‚nоöÕ¯ÊþÂ,I›¿/þiÕü ÷Võ½ ( r Õt`“T¸Á*ûÒO÷ÀØEb ,ð YèÂ0ð*TÁ«>3š¹›Û"ùAÛUsä+ßTÀ<¸èkÀ®°ìYMP‚´Öeà;hòÖ¸G—p”AÒ“ÆP»ÑK¯™ÍJU)[ÛúÝ€B°g 0Õ¨Á,àéœÀÀ-@Î `@ƒãRÁ©À¥ÇÀüØrƒ‹ ‘ÓÈ^ª=¯P§â4ÑÿõÀ€`!E@êU H-Hp f dUáûÍ«># oÀ‡‘4aE!„IÒP\! WÐA t€/Ø€(‚Êž„",!ƒÂ¸ý-(g M×èÉwŽ(a¨GýxGbà@ô†‡}A}°[YZ M©‚„#l‡wbðBÌKï‘[zõ$©Bz0Î}Ì Vo&Ÿ„ËDÙE®/‰þÏýî{ÿûà¿øÇOþò›ÿüèO¿ú×Ïþö»ÿýð¿üçOÿúÛÿþøÏ¿þ÷Ïÿþûÿÿ€8€X€x€˜€ ¸€ Ø€ø€8Xx˜¸Øø ‚"8‚$X‚&x‚(˜‚*¸‚,Ø‚.ø‚0ƒ28ƒ4Xƒ6xƒ8˜ƒ:¸ƒ<؃>øƒ@„B8„DX„Fx„H˜„J¸„LØ„Nø„P…R8…TX…Vx…X˜…Z¸…\Ø…^ø…`†b8†dX†fx†h˜†j¸†l؆nø†p‡r8‡tX‡vx‡x˜‡z¸‡|؇~ø‡€ˆ‚8ˆ„Xˆ†xˆþˆ˜ˆŠ¸ˆŒØˆŽøˆ‰’(  q¡NåW‰Hq‰{“‰ìlj7á‰8Šë'ŠëaŠ¥¡h‰…‰¬xŠ®øš8ª˜~¨H¯ø‰±¨~¹è´‹ÑЏ‹«8Œ²XŒÁÈ‹È苳x‹çç÷‹QaŒT½ˆ‹Ï˜ÑÈÓ¸ÍxÒh~Ôè¤(ŽÞHŽà8µÈG㸉ë(í¸/ïH~å(ÖØáÊ„U1AñY"1áaSÔb9ù:¡!‘A‘³é# ‰ƒ2P%ø‘æh‚"‰! ’$X’,q‚*i,‰’#Øþ’ ‚2ù’#y’7™’0)‚5I’;‚=‰“&©“9“?yŽÙé™” Ñ”1“éø •éc‘YII•)Œìøƒ"0–dY–fi–9g¹–k™–È–pI–nyq—siu —wYyÉ–{I}Ù–j˜h9˜„)—†y˜"𗨘ˆù–޹˜‰y˜Œ)‘)™é˜•—¹™Ø™“I˜{ÙYI$[I]i‹a•Ó‘N8Xi°¹”=1›¶I‘›ú8¼i¿)•µiš>ñ›­‰›^¹›`)ÆIÁ9ÍY‘?!œA·yØþÙ"±Sñœ²IœÃ‰šÈ©šÚ¹œûàR?a;Áž#óðI1"1Ÿ³öi"‘Ÿ“ØŸ_r CÏ.9”$1ÀOÎ@Оba Ñ º/´ Õ¡+¹@P¡ÊO  ϳ h`@ZwH1ñBÐ9'š¢+: =P¡$ cðAhM ¡<ÓðEcУ º ¤p¤>  û@¤©•"ʤá<*¥T¢+é’!¤!Bû ¥)„&q:€dj¦gº@àc`\J&Ñ{XÊlZ ½ç{Ú¤þ>¨N:£aŽ`:;Ú¡r £Oê#Q±¨1z£û@üĦUêH.A$ú¢—š©m:G© §q§€J _êž1J*§ ¡ƒ:¡–j¨>š¡Š*œH*¥¸š¤ó‹º£!¤˜ºÄšBJÊçñAÇš¬)¤©þˆªZ«Uѱ¨>³¬W:jj¨‘êO«jMÐAJ®ˆ:2Âj‘"­£ÊG,©©€¤òº©Ž¤¦‹*¥ÄŠ¤Ø ‹S¥ÿš«A¦çZ|$«\ ú*¨àº¤ø¹¥Ú¥^®¸ç°Hú¦ 0Wš¦ÎÚÚʱôj¯ÕZ¡ ªªË•ê1Ah²´ ŒÑ Jª«Kº¨a¦7[IûŸú 1ª¢¤º¤. £(J´=q¨.!ùû,^:{þ÷ H° ÁƒîKsÆL…#JœH±¢Å‹3jÜȱã@3RBJóУɓ(Sª\ɲ£”…!ÅŒ#eÌA‘!mî{É‘gËŸ@ƒ ½˜ª¨Ñ£H‹JIufdѦb’53fÌ£K¥jÕšukÑ¡`ÊíèUêRcЦ Yöh×¶[ßnK·®]„pݦZ+æLQóÊÍ‹t°Ô»ˆ‹%¬´èœRÒ6Ì8e¤Š3kfY9mQ4 ý2ö\)i¯›S«æÈÔŒë×°cK‰M[ mÚ³oëÖ{·kY«ƒ XSÌpŒHf_Î|Óæm‡N=õóê¥cßžø:÷ƒÚ¿þ‹_|f¼ÁðæÓÿô®½ú÷)Ù§w¿>ÙòðéÛßQ¾yýü8‘ã(à (ž6H‚ß1è ƒr'á„V¸Ý… ¨!vvÈ߇Յ(¢}$Rgâ‰ð¥ÝŠ,ªçbs0ÆhÞŒÌÕh£x8.§ãŽÜõxÜ@b'äpDIÝ‘Â%©dsLçä“ËE¹Ú”Tg¥jXfÜ–©ué¥j`n&昛•©Ù™h*V\}É™Ñf‹ø½Ç月©™ÙxÚ¥§b|öIן‰*(yõz(X„"¦è¢B5z×£%©]”VÚÒ¥ueªéJœÒåé§ñÕÙžÓ‘ ]¨cªªIþ¬Šåê«÷%Š*­Pš:ß­¸V©ë¼öªå¯+ì—Ä.hì±d&á²Ì¦é¬…ÐF›Y¬aÍj-EØ‚¥í¶u;Ô·àB$®Pä–›à´V«n]ç*d\Gé¾kP¼ q±½îÚ‹hf1ÌQ½þ„/B2l!C \ð›™QÄ0À QœçÉn¶H±p9\p*:)¶ÅfÀ Ã>óZ$²¿S(¡Y GpÄbä|ÑË„Ì™ÉpÄ>ŒqDÎ-OijºK$1ÅšFì€#]ÑÒå6ýtfT„KÄ~ß5¾íãy@x ØÿCµŸŽz©š…`DTâMœpµñÇŸ„»AGTðà G@?LŸjõ‘ª>îìsAP…ÅdO¾¥æ u Á †Í?`õócMý‚R‚ü@ `Øÿä@Î 0,QÐÂÎØ@ŒäÆV%© Kš “D¡Lƒ-‘SÉÔ39%iazFòÀND *4ÏHÐàB•P%†ãaa SÂAŠÇþ^XÜ7R¾çVðÂQr=•Tá J\"¬Zˆ$2|AŠØ{@…(bQ€õ©‚¼øE41%¸ËØ* ÇGHÍèÆà(a rœcëD&è1#gD »ðÇ=ÂçG d!-È“¡ [dEi",ኒäVW#Æ5fR!LAÚ„0„)ˆa C†¤¹ò•¯l v0)ì 6è‚,7PGîÀ?°Àº0ü Z¨€1æƒ.tá>ÈYn€ƒÜÀIü$E΀4&²–©Œ+aINWN“–4¹å ª0Íôò5ð€0(S ÈþÂ2eÐJYÞ YØÁ °àlà aÐæD¸™3)¤’J )]ÙÊr–Ó–7ÈB²p @Ó 5è€,|@ðÁ 0ð€ \Àx€YQ+XÓš>¸N“èI…„¡9+‚†:Ô$PÔ¢Hà MtzÓüà¦5à€ 0€dà°Â/àaGàªiZá U ‚5«³0`Aˆ>Ps¶„"¡F=*R›ÀW¾2¯GðANÉ*…+ø V((V/ Ò€ÞàwȘÀ„*4!$}m )ŠÁ MˆkBâØôæ6UAÙ U›Z¼¦*^°þBºD+ض ©ÍmUd’ÛZ¡ 2|àÑjći ÷A¢Á åÑÉ_ÌP†.€á ¶½‚mÃ`ݦö UÁÏ_¼`Ý0|¡ Y0îjÊЃ\³6@+Fª°$Öê•v°+”a#M°lhóKàøÀN°‚Ìà;øÁް„'Lá [øÂΰ†7Ìá{øÃ ±ˆGLâ›øÄ(N±ŠWÌâ»øÅ0ޱŒgLãÛøÆ8αŽwÌãûøÇ@²‡Lä"ùÈHN²’—Ìä&;ùÉP޲”§Lå*[ùÊXβ–·Ìå.{ùË`³˜ÇLæ2›ùÌhN³þš×Ìæ6»ùÍp޳œçLç:ÛùÎxγž÷Ìç>ûùÏ€´ MèBúЈN´¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúÖ¸Î5˜…PËÅŒ )®õ "ì?òº4ovBŠí‘Tû“Ç®L²lP=;“ÑfÌ´ûSm•8› Ìžc¶'ókj/ÛÚà†v¯·M”nÇçÚ’·`ÊÍís{Þ‹”7\ØÍHw3ß…Ôw[ø=I;ÝØ^7½Ûmïw#<Þ 7вnÖÜØÿй)ŽîT¼Œ/ Áþ¹epX]\!÷ÊÈTòfŸ\Üÿv½9~ï‡ç;æ¿IÇòñ/¦<. ïwÃÿmó€ã|ã:¯¹Ç…R\™£äãN'"É-ˆtâ¸|è_zP𾍛¤çÊØñR‘±G$ê/·xÑ}Ük½ä…ÆmÇ aàw_¿}Æu·;\è÷Ë$…ïm÷;fð^wÁýÅy7¼Ä ß÷¹3>ðŽ—qâ#ãÉß]ò…§<Œ-¿÷ÇǼ,€ÿ󗧺ÁºʯOý"f¿WÙ¯Ns‡k(\W‰×³øz‹Ä¾ ho9ÃkOôÛ{9ï‹ÿ1ò5®üåyùÁ×1ôŸï| OßúÕo>ò©þ¿}ìw_ûyç~ø½?~ð×]üç'úÍw|G¿'­ç=ËU?|‰ü>AÂW»ñù<°«¤ÿøç.1€B7˜s‘€&G ¸ø~ñ€«'€½WxvH{‘uxñ×!èq‚dG*¨ Ñ‚(08(ˆ(8)Xu1ƒˆ>¨ƒ2(¥ã+Q„²çHX‚±„BNXسs`¥kV8'€9Ï3¡uxP8CÓ¾Îñk^¨ f¸[8`²xiX`8†e8 ßvxkCó†Œ4ÏÇû@†lÈF1þ :f臀D†nèl`(ˆG©Ð€ƒ‡@û0`¡•…_¡0hŠ“8ˆ—‡—| ñîãbXà '²µ0p‘áZ$i>@ \WÐÈn`…KbÒ\FdÏJÀ)ìÀ§œÈÂjt„\¡o±ìW*3”R0—9_Z2`ƒ/ì2–¤Š‚ …:3˜štP„5<Ó Éœ €P ^S—5ºŒàÍo 옲ܜiÎlJE¸:1éαáÀ óþDd=¡ÒK+XÓœÙ'O¶†ê=S ;±ÈL„ê„ mh41r…,rEøA2tà$A40@ÀÀÀ0ÀXA WèÁ°P‚daXøU;'J‘­ùÔBBPjl€rÈrƒô­Ô'xÁ N°Q®õÒ§9°@¶¶,xA='߯Ê7$•o£t©~ð€ Ø‰B(‚̺0Í=àx`ƒpÀ4  ^R¼è jpÏ.ôr\»jp0ä  aÝÇÎJVºrVk·C„ìL“sÍNnðÊø 8þêÒw-ÀàØ@ ^!`¡ðAvºpÝR®èB\›ÙÍr¶¬ÏC?³3Úd B@-]W»ƒ*ص±­Abð‚ÚÆ·à zktUµ7ÈÞòf),×Í®Ð<{V+¸Ö.l BŠW¹ÒÕ•\³:ÐU Éà4ÀÁz {ØÄ7X°‚Ö`ð‚h@&èÂH™_ý¦6ºYðŠ}ð€ ºøºBó¯OI)(Ha 1ðvlÛ©VÕ–3¶o›p”à.8Ȭf‘ó›ã G!BÃæ6…Õg7bCª`ÉÝ`Y a°M°å*äF5aø‚þ¾pãšá VP®’7bʇ\æ iHÃfÊ)ªÁË€Þ Ð`Þ8N ª©£)eq¦ [¨‚ªé:Ï™CGP«†ãxéP G¨i§GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~ûû߸ÀNð‚üàO¸ÂÎð†;üá¸Ä'NñŠ[üâϸÆ7ÎñŽ{üã ¹ÈþGNò’›üä(O¹ÊWÎò–»üå0¹ÌgNóšÛüæ8ϹÎwÎóžûüç@ºÐ‡Nô¢ýèHOºÒ—Îô¦;ýéP?Œ¦NõªO½ÜVϺ°®õªs½ëW'7Ø©þu°—½ëg×zÚ³¾v«·ÝëbûÖã>ö·“îfÇ;Úõ®v¾³=ê€¿Õ Oø¨ Ý…'üáÏøÁ/ÞÜ7|º#ÿørS~ò¯<¹/øÌc>ñš7çïù΃þ󅽸GùÒ“þô¦O}àg Øþö¸Ï½îE€mïþ÷»ï}¶OüÛ ÛÅ/þñ¯|â/ßÚÍþó«ýßOŸÚÕ¾ï³,ŸûëO›ûÝß>ømï}ißøâù£}~ò§üë‡vûyÿ~îÇÿÙóÇB@!ùû,VDqþ÷ H° ÁƒîKƒ&,…#JœH±¢Å‹3jÜȱcÄ1RBŠ"Ð̘1f<ª\ɲ¥Ë—0_вÉ>!C¢‘"fàL4¥t³¨Ñ£H_¦ZÊ4UN¥01ÃsÌ™™8ÓD•¢µ)S)^Ê v¬Ø¤hÓª)v§¦b˜Ì<³䘙XáÅÁ]|acæÊ=•d8tq¹¢=Ü๟“Ûs=]Cc8šÅ ;x†:”!{gîsE`¤Ô(7T1e:H=üfõaE꫊à TLyìÓs&™ë±Úú÷|øœ ‡RÒZÅ äëpû° îщQEVƒÏð×ýuä>àHáªÇi&/hÁFÈ@#&p ü” V#ÜÈ Z-Ä ƒkû aB¨‘¼À’FÞS0”Ax*, 3Òáþ\:¡EÄÀ¼× 0‡kÙ!F<1Á%<âD®pøx8DbZ”x‘tá8 ò$ĉñy:ø‚ÕÂE‹l@ F؇ăE"9àÞ”¬¾5>ʆá ΀0á=Hˆ~H¡ êËž‹ò¢´i¦&8“@æxH$$î D0€+xAY˜dRžÐÆŠt qÜG i&OÞÑ I‚Šp„\¡ RTeQš€@Æ50 ËBJïIˆ„@„÷$¡KfRšàKFi¹Ï‚ü€Ô,BÆ0…¥•  ĹÌÀ =‚J]”`Q¶Ès0؇2Àc‚NP…\€ÞÈ…–*奄1B&P… NÂD0„$ˆá I0ŒÚÄÅæøŽ €@ªÁ€Œ`$ü“«ÍAê`Äp„ ìÃ@€˜@Òƒ\€šS† P¸ZP®k¹ ‚1 Fè«A®0„]ª”¥†m Aas‚\@ ?,.™P%P4³,Á¨FR‚Š '‘Cþí©{!!È%¶²-m-„ÛÆ-·QmR.Ûß·U›=” ªp·ã‚°.Žj^s«¦d³Q;(.ug‹ÌÊ )ÜîE–Ò]¾‰W"KQÃu¿ÞóR„)…ÓЇ[÷*D¸Hé€ nP_û"¿G©AëªÐÿ¾w·þñ#±P… TÔÀ´Bp€d¡ ¨P |_ #….x0‚h  e¸žÀá'7#6Ø€Œ#’… ¼2´‚°à§°àÇ(pABlP ¼Xq >ðLÉcmqBh¤*É“V¶rj ã Ü D@é•ßs…x€” Àþ*@¿ôàŒó~à O ]ð—oð‚hà0Á2p3 A¬¢•²@®’å17ú=V¸Açt|ƒ;WÙ“U¨Á 1\à8(¢*èGbÀòL|``ø€ò Ð|„*\ YTôAýhU7zww¦¢²ìÉ+xà`³BP!°u.€Á|…œì Yèžmpì ð &w¸3p `¡ 7¦® ÂëFû:Ë»¯"b‹! ðÀ„€…e[@IÂàLƒÔà/(ø öû ÜúèÀ±­l  VP]üÖ½ëG#Òãó–ôþ¦í:{g¡ 嘜 Ôà¨Aˆcœ ÕVÈÂÀs sÙ!Ø€zðÊ&L—ã÷ø»áÝæÔÛÞUX62‚XL6q `pdÄœ~pÀ,ÐldÀêx¥kÀÜþr¼Ýöù¼³yÛ» 26ø²ªr @œÈ0ï@ÎÜŸZ—?øÚ!Þdí"!p·òÒåîîywî8ÐÀJ tÇL¶AxÀ«wÀbgò*ð€  |ðXöÿxƒDþÒ”§¼'1~(àÀÛÀ€4@”àÏ0grðÕ|ë Hàä–ø~oñ¶—ãwɉö·þÏýî{ûTøƯ`…‘Ü åSzvþ#¡F(‚ü‘ &òKá R(B¢¼ÚW$¾‘;‘!~éW~ä‡~èñ–3~]àVf"QErhP~¢ó#·aþ§O€è‡X`îY>°c>Ð>°=>Aðð·^>ÐeÐ8²ÁU`ƒUBcRkq ø~I@Q;@d@4¨)d0&6±„P…R8…TX…Vx…X˜…Z¸…\Ø…^ø…`†b8†dX†fx†h˜†j¸†l؆nø†p‡r8‡tX‡vx‡x˜‡z¸‡|؇~ø‡€ˆ‚8ˆ„Xˆ†xˆþˆ˜ˆŠ¸ˆŒØˆŽøˆ‰’8‰”X‰–x‰˜˜‰š¸‰œØ‰žø‰ Š¢8ФXЦxЍ˜Šª¸Š¬ØŠ®øŠ°‹²8‹´X‹¶x‹¸˜‹º¸‹¼Ø‹¾ø‹ÀŒÂ8ŒÄXŒÆxŒÈ˜ŒÊ¸ŒÌØŒÎøŒÐÒ8ÔXÖxØ˜Ú¸ÜØÞøàŽâ8ŽäXŽæxŽè˜Žê¸ŽìØŽîøŽðò8ôXöxøH†­ñº¢·t_ñ‡a Wû¨ ý˜™±‘  \™  ‘ ùy*™[Y‘þˆ‘á赑²Õ‘ ù' ÙY"iQ&i‰þ-91 3 ,)‘é‘9’!¹’q“Úô’Q“!”F™Bù_$‰ZDIIIH™‘ÕÕ”™õ”4ù‘>©’U™“¹“'y”)i“@9K©JX ,ZI+TYViXiékùm\oi` “s‰“\é–^Y’yY”{9eu9^wÉUq•…I˜?Ù•A©“ûÈ“29– q–zù—N˜P9˜LÙ˜~ù˜.!™(¹•dé˜fI¢)–¤Y™e)–¹˜rÙ“lé™v‰™Ë‘š1•´i˜¶¹„9‘óïu˜þ÷›ÀéM1œ½IƒÆyœR!«¹¯ÉaÍéœ^Ê šRþXÖ ÂI™ÒI…ÜÙ ³©Q8žÝ‰à9¦žÖÉžÑ鞟Î)ŸçyšShŸÇ‰Ÿt¹œÈŸÀéŸ|©ŸÛ¹“äyæùŸè …ªžÙi é‰  ê‚ÑžâI¡Zžß9Ÿ™ª Z ®ù¡ù¢z¡ºŸZ¡ʘ ª¸)•à­žã1£°–Jy£ôÉš—9™Ñi£¦Y¢9Ê£4:¤Y⣸(Nꤞø¤PÚ‰R*Q*¥Wú¤Y:¥œX¥[j¥TŠ¥aª¥cÊ¥›è¥e ¦]*¦kJ¦mj¦šˆ¦oª¦F*˜²É ¥ù™z›Hº£Aš§µ£¼8þ}*„ª¤q¨z*ŠÊš•ñ¨0 ’ZIY©Q©Ši¨5z˜ žŸÚšª¡º™œZ¤ûPª‘Z¨¡ªjyª‹šªä1ª«Š¨᪓ʨ/A««›z«7Š«…Á«á«!¬<ЬÑW¿ ‰fªñ¬xúƒäÁ¬!­¯­7Š­ùØ­EÓM`S°À’°Lg2®E™œÉIê `î ¯f®Ba´ùšõzS¸NíJ®™1¯ Ëd¯ÑA®0¹°èJNu¯+±ÿ!Kq±øª± qúŠG@°hM piþ®‹:us„²û°²PÙ²òZ“G U"{²6a³.ƱNu>[³ÜZc€SüÊ;{0(«²šä²K ME ´çÚ¯TK" °µ?{´M!²©®bk´ á°#khàZ"PL;‹³ý:hO0·æ„VñJ±!²cÀ·M‹^Éy&+;¸@Q¸á®Ìäà°«¸©P·Û¬¡¸ŠÛ·7+œUÛ°°¹Œ»±,{²H@¸~‹´‘¯¤ÛºqKºrIµ˜»êz¤Û¶ç𳡮÷º»†‹³q›¸„Ë»a¶IËb°k{ëf{¹ºNþ»¸mk»]ëµ+¼ûzºå:°ºØ«_K¹5K³Ëºvû²!¾6»{·U;y»·‹Ûº»’k¯ä˹ãE^¹6¡ºìK½éú´× À[˱5 ðÚ€¿ ÜaçZ¯½¬k¾úJ¸·Y˲r· !ÁíÂÛ»#<1›´b@³Zû_Ú›!+‹Â³hÛ_+Á/œ²+›³˜ ºaà ì»,,U. ÃØÊ¯K¡¼O«ÄŒ»P<q›±ïÊÄî{ 0Û¹ :° û®Ã«½6ûÅÁOØ;Æåj‘ k¾~‹Æ4y¹F)ÆVŒ¼º²´þÊŹCÇ «¯ãZ³!ùû,TMhþ÷ H° Áƒl„I1 #JœH±¢Å‹3jÜȱ£G‹f2q"…ÉÇ“(Sª\ɲ¥Kƒf¤Œ)ÙDŠ˜’LÆìK… ĈRö‘)phPŠG_*]Ê´©ËTP£¦B#E Ã&OÆ 0ÿaäðag@ºxÀ ÿŽ€„‹!¼› ý*8—”@1€€ð¿ˆá0Ø‚ùD8›ûuÄ„(܇à@‰þ/€xd0@>‰„r! ŒPH¡‡ ±À ,@)€a9F<"Ô$£D&ê0`Ü` "E ¡bM%cCŽ|@F¾Æ(ä!€Š_ántiãF<°8ÎŒˆÜ B* >Ñp!E¤\ŠF ÜÀÐ! ÀÉ#@ ñ ` .ø@’¦C¢S4àƒ8 ÐàŠ„Z!"F(‚–€ÊTn12&hÁ0)DŒNðâD„pFôru¿¬Ë öq„ üG@  L‰¡S@Ã3¡Y¥€¨À„;òÈDâ§SÉ‘ o(þƒŽðw&dFñ”'S蹑{ ä/ßDf”6… qA7:;‡®$ {—E—2…$(A£} ’0†”%hBÜŰ“²d *æÞ¤æÒ¤a¤%miMU¢št§(™JN™Ô3©²FXȉëŠZ™£®0‚3™úˆ0X`U«êTÉí ]Ø*Weê'1X!¬bíˆUé"†%­imSWS €5®Yë\0€+¯|šk…|…0,° Ñ«\RWÅ:…±iU¬:ð(²bÕk :Àˆ /ÁìV×j…lÀ5(A B ¬D´L¥þÓfKÛÚÚö¶¸õÁdwÚ„à<ð ü€>Àqo` <๠p@| Û á GØ€²0[¤!6#g(‘xÅ‹„ñš÷¼è-Qy­° Ü ·¾ €ËW¹a!Úµ@ 8@º8È‚8ÐTÁè@ÄKÓïN$¼è-oz'|ÞòVÁl8ÀA >‚ù÷À@ 6ÀЀ¿mî,€˜­%d<4Âv«®0…wLÞå ÁÈÀ‡1pwà½( h`ˆ€¾ÀA¨ÀdX¹°3…pcªæØ¼æ1…-üÞhàè°þpßÚ 1 A < Äh ¸Ûý^ 8PSÒàw"Â+êàˆÐÇ@‚¢í“Ÿ0ôÍ I(‚°pÝYá X°ÂÀó…¾u¡ R˜4¼`†Ím&äj_G—PŸÃº"S݈NÊÐ…&Á“®´®±Ð…ý€A `vúƒ^wºÒ¨ƒ†% õÕÒ ƒx`…<D4¾€8«ƒÌÀ3;Ø0ÝØ@:°Á’÷"Ê#0a E/¡ýž, [Ïu‹3`Ú.ð Yà!‚;…+l ûhÂ¥{°œl]@HHÂ7éŸ/` @ B~,À/3}a‚þ*}Ê‚l‡£âã­)Îò–»üå0¹ÌgNóšÛüæ8ϹÎwÎóžûüç@ºÐ‡Nô¢ýèHOºÒ—Îô¦;ýéPºÔ§Nõª[ýêXϺַÎõ®{ýë`»ØÇNö²›ýìhO»Ú×Îö¶»ýíp»ÜçN÷ºÛýîxÏ»Þ÷Î÷¾ûýàOøÂþðˆO¼âÏøÆ;þñ¼ä'OùÊ[þò˜Ï¼æ7ÏùÎ{þó ½èGOúÒ›þô¨O½êWÏúÖ»þõ°½ìgOûÚÛþö¸Ï½îwÏûÞûþ÷À¾ð‡OüâÿøÈO¾ò—Ïüæ;ÿùоô§Oýê ?6ÑB þ“í³è Þ—÷³ïØßÇù 2~ðWÆý"ÈúÅOþ^¦þ·QþÙ>É¿üõ‡J÷Wígû7€ï7¸} HH ø1 È €ê÷L8˜Ø ø€þ·ö÷$˜Xú7‚X‚)‚&(€(ƒô×},‚.Hƒñ§ƒkÔ5ˆhmÒ‚CȃYäƒñ/ø}X„KH€Oˆ~3¸Jh„I(‚0…¨…õƒ„L„7È…ˆƒEx´ã…*hƒYH„kh†a~8…P¨†!„;(†L膪ƒ†c†kH†m¨zX:|((+ø‡þxhí7ˆùg…4TˆŠxˆuˆ…“ˆ%Ȉ˜‰o(‰sXz¸ˆ‚¸GQ…H¡7¤H‰¦(ó—ŠŽ‰t؉lX‰‰8†˜Ø„ûàŠ@±ŠŠ~ºøŠ8Tȉ¨ŠÇh‰ x‹€ôíÑa(Œ_(5ÈŒkäŒÏAˆŒ[¨Œ0hY„Ù˜ Ûˆ‹9ˆ‰pˆWâ˜åè‰NHW!µŽÏØŽ³(‹èX‹EíaÞØú‹àhDüøþˆ84T±qò‹ 9ŒiQùù‰÷ø€u‘¥ùsÈ"䑒ãH(y‡+)þÙ’äø’çŠêH“™’8W&Ù6É‚*I’ô“¯”¨VÙKFYHI‹1¹‰Q9NM ˆ8• ¨”S©†Á‹]éŽ9'Ó¸‹?hŒc –P)’ñ¸”¹È–¸ø•"—Ê—À(–e‹kÉiˆ–BÙ“ž'€yx€)‚˜†7˜…I˜‡i˜…‡˜‹©˜É˜„瘑 ™“)™ƒG™—i™™‰™‚§™É™Ÿ)™£è"#—)H—g‰—}h–{©—­©–Ó8”¨7 ¹¶I‘‘›Ri¼iˆñ›¯™¹šQœw)È™ŒÊ©Ëɹ‰šñ›Ò‰œpùœmœþ©ØÙ›ÑÀYàùšãåé›·©Ô‰›áYØŒãéŠçY…ç©›ÓùŽõ9†ù9Ñé멞í©ÃÉÛIœ²hû9ûiŸ *• ª($qGÒéOÙijŸ.T ‘¡Z: ÔôŽ!šŒ%j}(z>hM0qrdh °À8R!c@0ª4 @à=ªh°O 7£8GZ@ G;š¤AŠ€oAMÀ¤6JMrJŠh¤îr¤[Ú¤‰Õû;ºV£âçA¦m*MEtªUè@Pþô£;š þµ” G£qᣠ´µt¨ ‡§q‘¨  TKcÀ@+Ú¢=*©A¦G©û¨O*¦sšCµDMNš‹‰Jªn:A©—Š2,ú†Pº©ãÇC *ª,"~z§hpªŽº«ªš§¨ú¨Äº}4j£Ä꨾:Vz. ÐZF ,š Тc7ú•GШ!£œªµÔ ”*ãJ‡ ¨Û*NߪªêZ€VŠ¦Ùºï:ª¶*M/j£æŠ®î¤¦‰°ù¯i*æš {¯ùš¦ªZ@©­k ñ«¾j±‹®zIÀ8Ðú±ˆj™Š­ÚÊ@ë ¡6š®%[þ«>J1°ŒZ¤ûºxê®ÜŠª[€±ªGà°‹±óʯÀ8åz®3K«ò©Üç°9¬ÛW«Ü] ´¦qQF˨ ªqš±RÚµ³Ú§Q1²Q²ÙŠQÜš²Nʲˆê²±­U€´éZ³1¥TŠOK³c±m NK¬C›pH{¨2K±ì'µL‹¯9{°`*(LK¨À°0ƒ+¬Q1¤Àµë ‹§ÛµK¶z¡Îz­…ê¨l;§‘{¨¡+´¤(ªwªO²ª©¬š€š«9T¸á«™ ¬©:¹ë¦³:±±êµXº´.‹°¼ ±òZ¨±*¼IX«kJÌŠ§  ¼ š£Íjº8:­g{­o*£q¦_Š£:Š»­®@Ú¦Cê·K+´Ë¤©*»)«· T¾X*¦œz¦]¿’K¼û»ö+¬8R¯]û¢pº«îA£Œ*{*¡{¬¡Ê¦|š~¾!ùû,{Qn?þ÷ H° @&2ib°¡Ã‚fÆŒ1ó°¢Å‹3D(%¡)b4>”B²¤È“(QFY(fL„gÄH’r cbJ¡X³§ORÎt”²oŒ“%LÎŒò$äÈŠc‚ Œ:Æ ÑŸX¦ÚÊ•«”T.›2A‚¤È%Mž¤éÊ6Õ×¶\£žÙJµí[¸x»f%WVLYj$ŠY"FÌ%Sr楻8™3hÒœ1ƒ¦í˜Å˜÷Ü:yÌ’*\Bs #QÊ”ÂbªTé†t”Õ]˜™}s6˜/_À˜ ã%¶ÄßiÓÞ)¼¸qã²4s®˜%,Y|hȰ·äÕ½…Í$ß»\³÷šÙ·þg¥û½üÉðÊÁš_=÷TgØËè~<üùø ÖÇÚ=þý?õçß|ú$à€ìXIc8… |dí”^|ʧ`MVøÝ…)e¨¡f¢äá‡Y…xÒˆ$þd¢H(¦ØÓеèbJ0^ÕNΈÚaÕB-]§£Š<ŠBMDÑ’C–XäOO0áUc0Ñ$V5>%Q¿Yy¥OY:ÅB µÄЗ/>ù“ ‰fš=bõÄKbäø&JaV$ÅevÞ)RžÉè§C€>$è êСˆ¤¨AUÝרFDÒ}NZQ¥Å4‘¦qJDŒjúk°“¶Ê ƒ½!¬Jš¬~¹ú´ë³‰FÛÓ¥¼öJ…µ5½JmA7XgVª~;Ð 7ˆaäOJ1Km7„±nO ~j®@YÜÆ¼ î™í­;¤ËoJ9kî YŒËŸÁß^Á-† S;lÄÏNüo¬ßëëÃRœl‹èq¯7\¡p³¯Š®ºùBVðîÛ²Ž1Ä€úÜqÊþIÁÅ?ñ¯Î"óœÈI ‹Ò >œlàÈj!G(-’ÃN÷D¬|4dÁ#VgRÖŸ 7aÕH¤ƒ òÎ<# V±c¶þE2İ…»ZC ?Ø€GT}ZlQ†Ü.¾ð‚Ý †â ñÃ`@žâ &P¾ÏáTïÝ8î9‰7| zåR`~ 0¨žÕ±CòåôN:m_$y€—=ä0Ñûè¾Å!Äh¬þ! ,@Ì7OõEBa¯}fÏ·Â Úo?:´Dì%Rõï•/öÚÁ‡Q,©  †4XOC.€Á0*D`{NxÝ©æwÀ ½ìø Ž @‚›[5$- À@¨À„ „ª%ÅC †Ó$ ¤@ zCÜ0ðB3+þBÙ“K„”"äGÀ€(2JíÉ1iy hpž&b$ $YËœÐ"öJTX”‚à3'/ÞJT²`£p/TÝDˆ]l£3‚†à˜ñ‹sÌÈÖ56¢Hp«¢@ȶ3íƒYHSaTXdŒ ª`  ’Ô#%5eKf#{Ô0À²ú2&ûäEBé§.ä”›,Ö+WËFeLc·”c ‰KýV 2d„ªbÊ^î²h†Ì¥¹”ù-‹¡5`{¦4§IÍ©‰Á Lp| ÈÀBP-Ä@ XØp $l !ðA cP8áVþ8BSŠ " †Ýšf4«IPi‚ ›^Ûæ>ð `@œ!°9·p„+\ V@ àyƒÐÀ58Žà‘F áû§*12™âç80=ÎNÄÐ….|¡ FÀ§E­`Ñ+Ô” ­é‚Xó…‹U¨Z°Baº° F~ÂÉ1O’4@& ÄüBM»`„"§;-ƒM¿ †0õ ¹áé>È0ta4`à‚’&Äd††Ì«^÷Ê×¾úõ¯€ ¬`KØÂö°ˆM¬bËØÆ:ö±¬d'KÙÊZö²˜Í¬f7ËÙÎzö³  ­hGKÚÒšö´¨M­jWËÚÖºöµ°ò­lgKÛÚÚö¶¸Í­nwËÛÞúö·À ®p‡KÜâ÷¸ÈM®r—ËÜæ:÷¹Ð®t§KÝêZ÷ºØÍ®v·ËÝîz÷»à ¯xÇKÞòš÷¼èM¯z×ËÞöº÷½ð¯|çKßúÚ÷¾øÍïd—°•Ëò7þí¯eÿ`XÀ•%ð œ`SVÁ fðƒ¼_ K–p…5|a GÃúe-Ør½,vÄ])1W‹b¯À…Å$~ñ‰cܧXÆŠmñVTìáÂêØ-8NìyÌaÃ9Ȉ=rg|ã%ç˜Æl±±‹,d(›øÉMŽ2“§¬e,s¹+!ùû,TTaþ÷ H° Áƒ*,Ȅɇ#JœH±¢Å‹3jÜÈ‘ ”†Lœ<éH²¤É“(Sª,)+ ”–Pš4Éd¦“1 ¥(<#E̘3b¤˜1¨s¥Ñ£H“*M•ê̘ …šùRfÍŸN¥ô4Ô©¦fÆŒáêtì­h»ªýª¶­Û·JãÊk4­2gj½ÛP ' ¡h™Wʘ®_Ñj}¢X¨X®oپ얮å˘-Ê‚ÚSJ“³gH9ƒfæ“°3£ˆóW ,=rXéy†g“Ñ@…Röº»7ÓÌÀƒ ˜FkPžL¦rîÙ7Mi&£‹‹ö¡CÇ ,V§òÌtoɾ'þO^nš±h̤ÿ©ÞŒ™3aÅ¢Iu^lYaÅxcFL˜ÿ`ÅTêõ¦]xâáTÞ‚ š$‹Xb­ÆcVh–…b…ÑESý7!†¹'âˆîIEâ‰(’ØàŠ,bd×Mh 0̰…@R<ÑÄUôX…U45aEAV‘I DE¥´š{-F)åCLQE 1È0QÉ Å—ThåDQ4IÐP¶laÔ“CM)çœUI§BpÞ©g‹vîiPž~:žVO J’Bª(p}* è¢ÎÕ¨¡Fj)R“ Z饜¦”i ›v**IŸúꨨfTêž§¦ê*Eþ«êÙê«´.$ÆMA:k­¼´cgè*”½«Ð˜? K¬±ÌÉ.ºk³µ>{«²qRKíLÑ::l¶Ú2۰؆»m¹Jû­¹ÔÆzç´ì¢ê.ðÆ+ê¼sÖk/§øÊ©ï¾–ö;å¿/ª]°ê.[°«:¥ë­Â £j˜Ã”®1Ç!ü0¸‹Ú°ÆCÜ1§—;²¼kjñÉ÷¦ êÊ,ó벩0Ç)¡b@Š(Ç6C*°”÷,çÏQ-t”%'ÌóÑ~l2ÓŠN ² UÔ µŸRCÚ…ÕWë©ÛÔZÁu×t²¶ŸU‹LöD·hôÚåµÍâÛp'÷Št×-ÜþÝ æ­7£3³:öß|.ëà„¯È7ƒ~'nÙâ 6†ß™öÒ“7yyb«9ƒ›“×9æŸÇ]9——eè弑äªÅ:G«½ŽxìÁ;ѵg;î*é®Ñ 7ô~·™ðùpïH`Cž+øÙt]‘b ázE1lQ½õËŸ®TWðÐý>ßO”åøä?n~R&ˆA†’«µQøðÇO9ös1Á € äOúç?¥àL8&0Â> 0 (iѓȨîm~Hy@À#P"Uк°…2xðzÂqÁb1˜0)‚ ÂÇÈÀ…þ/¼ ó0‚Ò 74^B¶-¬f 2àB…£XÜ ¥pC3!DF\pOdp£)ÒeˆiÁ°€}€„HŒÅg†JÑŒÿޱ€h‘„Q0âB¸ƒpÁ >ôóœ+¾Ñ4À~‘9Úq‘r¹‚³‚5>20€(P ìÀ~p ËŒH3 5ˆe86„Á>Š„  ;`¦/¹É‘`Že8?'Ç Éð"ÛþdgBõJ¹X€ E@Âx0‘|êó >Ùdy|àƒŠô Aãe Q‚HÔ2­hª¥)¬S£¹(]LÈ@†”£-úÁGM:‘Î…¤¤c©‹PÊ¢ÔÉ´$.ÍäJoJ%šâm§˜€E3Ħ@’>ØR2(ƒ°p*§õ÷xj§MàFþ0È4ƒª„wZ§ ѵ´§¨ìÉ~i*ƒMh°Ž z¨‰Š§úˆ©©0’êI@°Qo‹ ¨íG«JO©p{úªvJ§•±F{J«û «º¨°‚  G «©p©™ÚS±zž0¨°¬{ꬕګÈkT¨Ôj­Øª©¤ «æª¨ÞjK†š—*®• «¾j¬Œº¦ÉJƒ:0ñzRÈô¤§û€¯û ¯ÏjQ•!«=ÕЦ… GÐ~É*ªº¨°A°ø ±|š¬ÛJ©tú±vÎj°ÃJ«oʱæ­Ñ(ƒ"[þ®U@y±) ²çz®Á¦3D°¶Ê«v ªáJ¬™$I¢d Þ¹R>‹¦«·¯éÓ&Ñ€,£µŒ2rÑli­e‹3\­žù«Öê˜1fŠ)"|ÈèºÇG—FͼyÅT^¸€Q3 +^¤*ÃEzë^îþî6nÜ1J–ü^Òy´çä¢Ç8ŸO?bªîbÌ<±¢åJìapÑ…_t±f$¨à‚ù-¸Dg™1EK,ha‚^¨á†™!K} †èQt!¦„g•¨¢sÛq±¢Ib áá‹4öW5ŽãŒ9ö˜Òa>f´ã‡A9FV4d’L^„d“- å”=I%BR^©%AVnIP–^jÙe˜û€I&”æqæ—2¹&”¡¥¢æ›e¶I'”OŒ¦™w鄞^òÙgqÎù¦ ƒÖ˜ ["š(Œjéè£+Þx礔–W—Ú™©›vÊã§9ZJ'¦¤Ò镨¦Ê¢œþ¢ºéj¥°žê鬴ºf«¸ž¶*•¼öjc­‡Þ*,ˆ¿Nì±T% å²ÌJål“ÐFû#±»k-s+Eç‰)n;ß´LV+nIä&iî¹Gb{æºìj”®‘ðƋѼEÖk¯Eø©ï¾õëã¿K$p\¥»d&¬°CçèðÃE\ãÄd1g<ÐÆ/vìñ> ¯(²ÇqMëÈQ¥‚ÞÊ,£” {0ÇlÒÌJÔl3I©L‘³­£îÜîÏÅ-´¼ ï©íÑ÷&èÒLóët£PGðÔ’VmµÁX³ªõÖ ëZß 0~ 6Ä]ŸvC%œqÉÕp…·¹M1Üi0Vþ:š}¶A+u¡â'ÄFàÚô÷sax¡bc‡„F2ÌÐ…Ø‹Wù…ã%j1àLžÑ`[pž¹Á›«È‚ ]€cQGxúDÛ™" «±Ïë¢[c‚ßn_*`¨Î»ì¿ óÅèEñÆ?„wF¼³à»@À[¤… [P_=B×cÄ» ÛsoÀ?PD:§ã#„Fù­À‚¾@üpá‚Ä «øD pÁÜ|XÐûØÿ^W… HÄn “IÚLƒ¸àœàýǃ(É( <ˆGÔ @çü@0® Àï@ØðUàCv„Âd…þ-lÎ È¿ àub°À,p@„‡>ü¡6ØÄ`®›€"À¿&X z'Œâ¨" îã8‚€´o",Øü¨xšçù ¨@C8úÍxôËJ¸ ˆÁ?С Å@bÄ)~@6?2Ò‡ŽÉc„÷ÇÛe²9q,Ø'™J€5¥Ü×)ëØÉÓ­Ò4©´×+ûËxÍ’/µd×-÷£11~»¤Š’1r‰ŽLzB„bÌ!3IMXf}Ø­;m&\Í æTœ„›4S Ú”Ê0‹ùÍpF¥—¿Ÿ9S²#4|“dϤWËÏ|Í3›õþ íÈ0MLæÓG7°pDÏtR‰7ÀÎ;éׄt”Gð‚îÄx½* ´!ØÀAj`ƒtà¡ùÀ@ð'b b˜h#å4”Žå¥0éXªpƒÈD ;°Vrppàˆ |ÐÈÀà@:°8µHØS70–+œ:ÛvªÈ‚Š A0íBª¸wvö³È­|€…* ”¦=€œ8ÀÐ@Z°èÚ|€x6àÀVeÒƒpÀ]¸²‚dÀ²2mÂ.ÅñdˆCøÅP‚ºÀßþ†ÁBÊN¬àˆá7úRj….pè,=<«@ä3 ïƒx]øBqr†éõ× ¸IÌ «4€¡Áüµ"<4œa h`±„S’‚¤ ]À¾0‘#X! V€ÞŒ‡Lä"ùÈHN²’—Ìä&;ùÉP޲”§Lå*[ùÊXβ–·Ìå.{ùË`³˜ÇLæ2›ùÌhN³þš×Ìæ6»ùÍp޳œçLç:ÛùÎxγž÷Ìç>ûùÏ€´ MèBúЈN´¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷kø[•\ô¿Þè\Ñ/ø¿žè„3Úá_¸ÂýÍpDCáxÆ#Nñ‰\ãùÇ9>rŒS< !ùû,„\ãHþ÷í{Ò„‰*\Ȱ¡Ã‡#JœH±¢Å‹3J|¤ã“1 ¥b2¦Ñ£H“*#eŒ)Tzäèaeš›cÎ0¹©ƒ\÷‘"&ëN3‹jT«´­Û· w¢IU%Ç :rd;¦Œ•,Wª¤Lx03OæfzfðÖÂK‰L¹2áÉ©àjÞŒ”cY/V¾„éråJh0VR§3¦uk)D]ËžM»¶íÛ¸esÞÍ;ãÕÖ©ÅtébÅ´êã]Ì(“¤ys)Ê£›q=}ŒôëÑ¡cßÎ]¹v3²zþ‹ñÌô7®(4aE ì-J±âöJňO¾¿„ƒáCC'¸× Ý à ›éÇߊ`„.9…j6a†YÈá‡Jm¢EŽhâK"ž(Q‰*¶ˆQŠ.:ÄbŒ4Fcqíw!Ž<2tcûÌ$?ö(ä<Éã‘HÖ¨$ŽL6ã`aH)P”Vª˜J\d‰e–&‚±E^ê&Ž]lQ¥•_žùaškJÙ¦›ŠI&›fÒéâ–]âù žZ¦g“s á“5j¨ˆÒ¨è¢ä5ã£J˜ÊeþY)‡’ºH馻uÚâ§ næX¦;–Já`K ª*†þ7‰¡D–H Ý«¦‚Ƭ~¦Š+£ið*gž¿þ'™«Å{)²Éö'ªŠ¤6{Ô³'F+mLÔšhíµ(.Û+·ãe;â¶à²$.ˆä–«Ñ¹¦«î‹Þ«é»p±Ë¡»ôV”ÊS0›ï[E$!,¡ÄþÛÖC(ÁóŒTCLé··qVþV|” ã$¾;4„À‡ŒíýRlò´ñ2ìëÊÝN,ïË0·do† × `ËHæ¬ó>7cè³ÎAS8tÍEGx4Ì‚‚!üóJRÜ`ÅþÕÚR­·NÝR:x!ÆÓú±¤E {}Ñ:„!sHè7¶F1È ÍjWd…þe¼M^ÜI”‘1xwÞ}ö äÁ G~1pq8â™æ„0İÔs[´E ]\Ž9DMCx„G¹äÍC¦ŸîPÒ[sqÄ>ˆqAXOÃiÛn#Ïã!±Åîp7hPQû¥aüE¸³$à » d€l€EúYw½EÙ¯Ôy÷û4„7d1ÑNûOQékäCFÀÿhôl°"²øÍ~ÁòÄ7#ü¯}|Þ (†Ÿ ðxd °  `/¸üeÄVà`ÿ@$ì "åC Q·ÀÞdà*l ÀÃ#Táþqõføb¤=04O ˆ¢0D©Q!F¼ˆöáÀ H!ÿ ù¬xÅPð2!ÁP?éɨ,=f€â&·ªZõªV¥*î²U«YU£(ÄBj`fÁ^•›<‚d¡J¥VÅ7²Dušg ê\÷šU¹]7èõ¬Z…®.(=t=iªZ…¡.èh§Uob+JT céŽf¯óåì‡;dƒDó….„Á Rͤ#†Ò|a´¥9þ­t\S–Öðóž ËHn)R†.¶´ÒLHqºÐ[ÓøÔ*f8C²Òš»^¶7¥I Ô£+tàº×=êsOÄ„*T ¹Âj€ƒªl÷¼èM¯z×ËÞöº÷½ð¯|çKßúÚ÷¾øÍ¯~÷Ëßþú÷¿°€LàøÀN°‚Ìà;øÁް„'Lá [øÂΰ†7Ìá{øÃ ±ˆGLâ›øÄ(N±ŠWÌâ»øÅ0ޱŒgLãÛøÆ8αŽwÌãûøÇ@²‡Lä"ùÈHN²’—Ìä&;ùÉP޲”§Lå*[ùÊXβ–·Ìå.{ùË`³˜ÇLæ2›ùÌhN³Óš×Ìæ6»ùÍp޳œçLç:ÛùÎxγž÷Ìç>ûùÏ€Nñ3ãAgFƆ.4¡½è'šÑ‡vt£aühIGšÒ“~q¥1}iMgÚÅ›öt§Aýé‡:Ш¦JÍâUºÅ®žq¬e<ëׯ·~q®]¼kX³zŽnõ¯Ul`;ÅÅ&ö±Qœld/;ÕÐn±¦Mm¼PÚÕžöµYœmm»¸ÛÖþv··½bp“[Åæw¶Ïâtc{Ýê®6»Qìnn;ÞÔž÷‰ë]î{¿[ÞøÖv@!ùû,„_ãEþ÷ (Æ 1c*\Ȱ¡Ã‡#JœH±¢Å‹3j¤EJ'R¤ˆˆfŒ(R6ª\ɲ¥Ë—0)l„Iš5sêì2d'<{†tÂäÉP& …*]Ê´©Ó”1£Jª2¤Íž7Ÿ4©ÂÕÉM1©Â¦Q %¬Ù3"÷#Ōٷo¥ÀKw®\³TóêÝÛ­M¶\¶pѲÅ 1KI–­3l›Ìc%‹+cêÆÕÌîݰ|C‹žš¦IÈ*\¬py¡Å‡ -bÄàÂxìâTc™0~{‡o::§ú,\3ñÑÈ“¯L5f­„]¸€ÙB‹u.e¼X÷rvÌî°eºþ|)SæK—03笴ò÷ð'2?&Ìs/a¼P¯øºõ.fŒ1†dä÷EæYVœgƃFØV„Vh¡B(K|v8Ð|ûÈ u2¼ðš 263ÌvÓôœÆhcr îà *¶à#Š/´ ‰utã>3Öxä’zåØP 0Äà“%Iå•R9‰eEVné%KZ~Q—b–yQ˜föõ˜’i¶ šnʸfœt6'dÖ©§]x¡çByþéæ2&¨œ4J§ 2tQ†¢HÎ i›…6i “~Yé—Jši™Œ:Úi¢Ÿ–I¨¡ŠbZ*–|ú™ª§þ«zygœªÆÊ$sfŒÊ¦­W¦"†«‡ÖÊë©ô©ë°[¦b°‚ ‹¬‡ÊVqì³TŽQDÓR»$×f«­KQT‡"D[ß9ÆIpú*©éƘÆE$ôî®ñr¨DJx›/‡©L±„¿ÿÂ7«›Î,ÚÁm&¬0_ §éðÃMZz/ÅñElæÄO¥q™wÕÇb†,2L$iòÉ.¥ìåÊ,¯tÆ€¹^3_&‰4F6ßœ†bÌl†iÁÂê³T&e6ÆÐP ïÑHKq®D7k4Ô0MsÕÂŒuE=!Dn×WýR€löK.oéõÚòYì4¾p·Ô6–o×ý¦þÜV?­·Ý|“í÷ßË®gÞ„3t÷•ˆ'>Ú5ÏíøJ9!„m¹çF>9F@ Íõáeo>QÒT7-h¡‹‘IS/ýù{#±´Eêª?„B½®Üs,iA{íåŒÏÆÀ»J³¼DÇÃIÄ®¿/o§áï%vÄ9lÔøæ‹G bpƒFl†õgb¯ 4a?œ‘Gi³_Qø1Ípƒû0À0°Œ|Ä;úÛŸû’Ó…ÿí#8‚ù.Ò((/×sW‡šà@@ ;°ELsA .„0ƒ(JÐ9`L(¾ä9,ÈŠàOsþ4Ì`Œ² p!'’> Qˆ²A ŒpÄ&‚UÀ«gBº¤ b¬€$FðùÞ伨„ñˆc  @Ç#<ácâ"ÙÈ’¼1 † XÀÀ#DÔè8>®äW âª$6Á\Òc©’‘¡?ÀÀ€ü “%|"'W2†#dÀ?¨P P„‘‰[¥J~ Hˆ!‚?(Á-5©?]nD‹¹À>TÒí‰1fKF0‚‹à’pÒTÎôLhf39² C7U¹ÀAÍfœAü&r¤P¨B“!!¤Žð<é½S!êþ´  {*®œmÒÂDæS4\ƒüyB€¦‰„è¤áACSÁT¦Ó¡fº‰úŠOŒ–é‡ýGK†õ…tåKˆÉ¾”îå ,µžKõ⃘.o¦y±‚M‡Sª Î¢©Ê¤°Ñ“öt* 5ªP½$Ó¢t©K*‚P„}T4¢]lÎs¶ÊÕ®zu«HàªP÷U1TÁ >ÈBen S$XáYàj:ÀËdX)W…ÀW¾¢N݇VËJØ®†µ°„}ëe|à¶vuÀp@W‘lµB ˆ@!$Á tê`[ØÃ’ö«Y¸‚*W(x%Xèþ€leÛƒ®v¶b@f‹Ð„üt´§ýªiƒËU&p…+7Ñ ¤…ÔW°ìV;Vs‰‹¨Nähs.ÄÝ e¨»€"ÞòŠ7@¢Ð’°„)(a I˜VMh/•Ô÷"é,zíU’¥mHrç@”„'a ”T¹•à;øÁް„'Lá [øÂΰ†7Ìá{øÃ ±ˆGLâ›øÄ(N±ŠWÌâ»øÅ0ޱŒgLãÛøÆ8αŽwÌãûøÇ@²‡Lä"ùÈHN²’—Ìä&;ùÉP޲”§Lå*[ùÊXβ–·Ìå.{ùË`³˜ÇLæ2›ùÌhN³Øš×Ìæ6»ùÍp޳œçLç:ÛùÎxγž÷Ìç>ûùÏ€´ MèBúЈN´¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéN{úÓ µ¨GMjF'Á=X>u*´¬jV£úÊ­Îr¬Sýj+ÏÖµ®ò­mk*ïZ×½žò¯}l)[ØÅŽò±KÍìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íá2t&ÊÞæ ¸¿ åpkfÜâ.7¹ŸlûÜêN7»×íävÓåÝîŽ7¼ç-ïzÓ»Éöž ¾ï­ïº!ùû,…bäBþ÷ (ðIƒLš@!Ȱ¡Ã‡#JœH±¢Å‹3jÜhQŠ”& ™ˆÉ$£”'O¢p\ɲ¥Ë—0]JI’$'"/Ž‚ÒcÌŸ@ƒ ꊘ("¥àéÊL&cÆ|”2P “4ÍHsæŒ)f$Re9–¨Ù³hJIe¥M&Uª$õr­V&eº`±âEê˜TaºÄ“ª°áÃk+^Ì8UâÃi#Kþ¹Ò&"1ÇÝÌlš1u=Vé¡£´+R΀ÑáÃJa;–M[ñcÓsëÞ¸V–3Q£ŠN\ÌY…9“æK0eòöUýÅx`/jÿ­Í};äÝàÃþGL|&¸pãQijdJðßfºtóÛËü1꣒‘O>|2`ù'à€jE ,â%¨ yû|G(ôÄ@QE$ÑDY5áTè6œ*hân 6E[hÁK$Q„žX}%Ú¨#Z)ŠUCìÈЈ9 i$P=y‘J6 S’NNÄd”Tjt†b qF•MÉå—}ÕÙbÄ&D^ž©æ@–)ua¼°fCiÎyæRH0ØIP~r‰§ž^p!C Šh”m:qF¡3,ªè¢NŠ)E\¸ )Ž”Þ‰%\P:i§M®UF¨›’H*˜R !Ÿ¨þœ®Êe:\a¬ªÊZ¥:؇k‘º6™ƒf„ñk°Tîàdà*°È ‰Æ¨yaCˆŽ­‰bTa…|8üm¬Û Efßú`D"X噯G©”áGP¨¶ñ‚§ÆLf¤áÅ 7Œ›k¿:š1¦>¼/¹Û…qZ=ñŽPÚÉïŹe<çÆKæñš ‡œÖÈj–lòY(Ÿ©òÊDµ æË0 %ó—4׌da[Z¬3x7s™óÏ05aF*= -Ñi™ôÃ3-Ù+õdyŽ–ÏWG–šÖVw–Ë…-öYueöÙD9±Òk³m3Ïq˽3Ü\Û=7þÞJë3ÝyûMà} TÐUmøDˆS©øâÍ7ÔKC¾’HÃQê.¼–ÃD5Øw¾RÖ[.zK_—Nùé.‘­ºŸ³>ÓuË~‘äO‡'†K±ÛÞ¸YXðÐRï²ÿNÁØrÔÅ>€‘»í?ÔU!®FW|ƒ_”=o„‹—ÅdTÅ 8|ë…·~oRøáù ÆìSþEVÜ ¿axуûï›\xnÐ#ìÃbØþn0¾áø€ÑƒxfÀá~9_öÒ/`‚‘žP®PÀ FÉÁøâ=VD„A¡ A pA‹èÅ_ÀŠ _(Áݤ¯‚þ&l¥±ާ‡º‘ r‹u0Êø*€*VÑ%éR¨$æfƒF@öaE€† bÔ¸ØENðF¨"@ØñÕ ÓÙÈ(þ¤ ê2cZBô¸F>öÑ‹XSW&P…˜QˆNÜ£#âǘØÀG¨À0p Èd#7ÉIHJæûÃ>à T¨Â#òÄÓuò'@ÐÀ>B01ÐðiÔâ*Yù˘<€ ¸: ÊÊm²™BA0ÂM^Ó•_’7‰MÅ  ˼fžv·(s¬|¤×DäÀ ¯þ‹gƒÀI%˜ ÛóôIr†gÀA¨ðt Oþ(†{=´•óSô7œþýï¢ÝMÖƒ,ð¥} énò·Â^tŸ=Óº`…0X“œüLÜ8ù¨RðŒè¥}ÞÇ胠öt7ÃHs긪½ô¨¹‘ÜcT¦6©BÈjÆ0…§ZõHLÈj†” …%x5¦\B‚ˆ0œ)Äè¬BSVÙjœ% ®B„0#%¼u©h% :à Ä8Ð_öáÐÒɪX—ÀΔ~õ%„ý- ÖÎv`WØa;Pƒ*PDªKÐ!`½RœÖº¶µHxþ­l[˃ØÀ<øä:Ð,Ü ¸Áª=l…8Xň „$'äkgKÝØRw¶¿åÀƒ„ÀÃÁ‚cw°)ìàUh­dçZè²rº×}­uãëZt@~Hðwo0+Œ·¼çMoq¤p! 5¡½7}oìŸ1¸@_°•|¬p,\¡ƒ¾puœÓ…EÅ èÑ| Téþ&®cPžVLa/\áÅ/îßÀÐÐä f(êÐ`†¸–x7Ìãb0,MØÌ~Ìä&;ùÉP޲”§Lå*[ùÊXβ–·Ìå.{ùË`³˜ÇLæ2›ùÌhN³þš×Ìæ6»ùÍp޳œçLç:ÛùÎxγž÷Ìç>ûùÏ€´ MèBúЈN´¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ÷Y¸ÀÂ8šÞõ¾w½S¡ï|7ßÜéwÀÿ½oׯà´A¸lÞ†3Æá‹¸b$þFüà÷·Å .ïŽ7Z ¹È=Eþè‘›<ä%?¹ÉS®r’;ºå#g9ÌE s˜×¼å7WyÎO¾ó•¿|æ ïyÌt¡»üã@§9Ñgnô!ùû,ˆfá>þ÷ Hp“ƒL *DcfÌ3шq"åŒÂ‹3jÜȱ£Ç CŠ9¡ÉŒNÄHY¹Rà)Pš<‘‚†¤Í›8sêÜ©SÊ&@›˜ÊDJ¡ˆ9Cñ¡–*£¬T)FF)9±òÜʵ«W…i&6"ᓘI’Q2Få™T©š…«r%P)cÒ¬L·/\3Uü L®”Â_+^¬îK»rÅŒIBÄH‘ËEùÆ›J¥3gͤÒ˹¯™-2bÈðR¸µàÄËž¸¯š3 ÇœIƒFIÉc¤ˆAÓ÷án†føâ>þYp.У—qM} bÚØ³ãtü9·C1i?þ‹Y¶;p‡»£Oßýs.aÈHñ¿½ýû÷¥àÿlU»ÿÿ9FRp1íD–V)ØRvbˆaFVh¡@^ÈS„jè¡vy•ÑŇ:qH!‰(&&E[p‘âM&¾(cW`h±Å3ŽcŽ<âÄ -öÒŽB Ò0@gdGD.édFH*ùdFMNi¥@?y¥BUnùä1ÜèeA]Ži$2hiæ>e®É£^hFBn¶é¦ŒdUŸÔ)á‰wò(—Zt!Å/¬ig (ÊõÜ`üЧ™‹2ú¡\c„ÑE“Rú§¥=2VXÑ阕‚z¡¨GÜ`…þ*ú©ª9BÅ U0h&HèG+`è`è©þZa;X1¢Ÿ+ãŠ]xAì¬Î¦(CÓ6[mŠʪí¶$vëé·àz(.ªÔ–kn*1 ¨ºžëe±ðÎ&ï–ôÖËØ½W櫯büZéï¿_<åÀweð“'Ìlízû®Ã³éÇn¶SÌ^»«±vGØ´¹¬·®Ðë_Ý0ûp.’7t‘»îñîÕXT!Gˆ1’ï•#ÿ‘ò]ùžÃ ¼HWèw¼õ„Ëý_ž±b\PCH‚û=Wb¨®~_ûGñç=ÿV¦SŸ@ … d€t#_FþÇ+O€ûÀß@ ô¯n ܉ j ûàƒ¨¤Ç‘ ¾-ƒ:©Án`Dðƒ<“¨À‹ 0'6ðAZøÂÀþL¨@ iXÂÜ'ØZèB@‚§ºˆ ×vÄ›|À`â>žHxñT!•ˆXÄ¿‘Î?x_ üp E‚‹ ‘)ž­Š6q€@Œ€ T!Ol7bǰá'G¨ÀFp ƒ¬#ËX>‰á "ØÇ*0AŒ„œdy €@ #Ìß@)ÉÅQr+é‰>@ \¡¬ÔÈ× òJžÑC åG²ÐW¾’”ÿñ]Æg=dúç Åc&òœ©ÛISwÔÌŽ5{éKó]Iv ãæî~9¥ôÀ˜”Ì&vº ¡£ˆ3yäd˜dxùN Åsjóþ¬§½)0á$MŸ•¬S$Ѐ®I ¥g=ÕI›$0á!eèl†` EôžK"BðrQ~N) Cà(@%*›% a %FD!LA¡ï$)c„ ‹Žt¥EÚhr:Š·^Êt'Bp€PÉ$Ê"þ4'Y°@*`ᱩ¨5tL„¦JÕªV VÍjV³Ðð6ðÀ8 Ø ¸Aªà¹gªV˜²@¼+T¯ UU=gW$@ÓwugAõZU¢d V¬`+È:V8YÐ v —* áHÈþÕd]%$Ä5 U…hA÷›ö¬g=ùÙp}ûàÜ=Þ±¢†*x¡ `°nw[†ðÒº ù§HpS“10DdjàÏ>dñÍ dS]PÖ²’†ÏP×JW ]äÞKßúÚ÷¾øÍ¯~÷Ëßþú÷¿°€LàøÀN°‚Ìà;øÁް„'Lá [øÂΰ†7Ìá{øÃ ±ˆGLâ›øÄ(N±ŠWÌâ»øÅ0ޱŒgLãÛøÆ8αŽwÌãûøÇ@²‡Lä"ùÈHN²’—Ìä&;ùÉP޲”§Lå*[ùÊXβ–·Ìå.{ùË`³˜ÇLæ2›ùÌhN³«š×Ìæ6»ùÍp޳œçLç:ÛùÎxγž÷Ìç>ûùÏ€´ MèBúЈN´¢ÍèF;úÑŽ´¤'MéJgY4©2¦5 NgúÇ›u§Eýi‡ÚÔ£Fu©{|jV§ÚÕ«æq«eýjZÇzdzÆu­-Ík%wÁ/>þu_‚ ì {×8>ö­“]l+›ØÃ6v³wüliGÛÙÓÖqµ±}mjg;ÇÛöv·µí—€!ùû,biLþ÷ H° Áƒ"dòDŒ4 #"LE¢Å‹3jÜȱ£Ç 5Ba¤É'RB L3F 1gTÊœI³¦Í›Óˆ‘ò$Ê4cš4‘"%ŠP3QŽ1c†(Ä?[Š)˜†¨”¡R vL‰³«×¯`?¦›ª©Õ¡jδ”’†,”VÇŒ=c•è²c[Ž©ª¯ß¿¥&¶°áÃ5ç>TK´äÙ'mDz53f)^ ÑüÝ©9«1ƒCã±éÓ¨-¦‚KTÌÐ'$«È®Z²hÑMͤ’Ò÷vhÒƒS Žšî“'D«±â#K“ÙV¸T¹»{l—+WºÜæ²e —ÖX©þû 0ñóè½"oBRL1;­\y/fL/aÊäMÕ¥¿ÿÐ\( eKi6Þ`r…–S1¥çàƒ‰AIMˆ|M\ß{f„á!SR€Ñ…`„áE`0¥"SapÑ…‡]p†­hã8æ–#S•!á@*4…LDQEG$‘>Ä 1ÈàA|{U©¥–õœaDFÀð 38å–]é,lÆù KCUa|G\q.¼ðBŸ-´ §›fÀ)ç¡ÄÑÙÄG|Æ¢j(¤”š6GÈðâ /Tjå•…z*jXh4¦Å [taÁ.Œ*é¨þ°v%JMhÑ‚w0XЩ¨¯Æê+MWÁ¦E \x¡Å 0¸ ꤿ6û‘P¸´±al!C ʪ³ÜzÔ{Rrá'¬½vknFM‘„D3ø@î²çÆkÑì1¡ÜgŒîí›òö›l$ER0ê.¯ðú«0AõbJCaø` £"a1Rø¸ðÂtE©ŽZ‘,ÂÚ2»q¿j\õ\ŽL2¿'/,…PÈæá±–³¿f¤Á… ÒùªóÎýŽ®Ð í/Y^TtÉJ+ŒWOõ¼xMQõ¶WÇ‹WƒïBݵ×d½/×cwûõÖ&§ÝìÚ9'í¶³p‡mõÜo—Í6þÞÜÖ}vÛ|ê÷Ëhëàžm¸¨ˆWªøâ•6NéãCš K”·Ø•‹ZUkEX`wáCÚÒI`ŠE—.*QK,yE.;.·ëˆî{X°×:®„Œñ;ðˆêz£޼–—›¡–ñÍ?¨äRn=Øç~ûöAÖ5ÆT±^Œ±ÆàC¯÷æw§d÷‡jï~zðúýüÖ§üø§?›üëŸpþ·¥ 5äÒýè¿õUùsà¨hÃ9¢'UÊÂ-ˆ@ ŠŠä i0ˆ,„d!áaHx“+Ü$>H¡ ÃB›ÜàNóÛ7ÃÓÔ°&þ6ˆá ;bÀzå‡4ÙÀ ²Q†F<¢Q³tàÐ@„ Eœ q&0A Ä@a#Eì¢M¾(3Õà8Â< ‘4ª‘&lTÉ™°`„}@ #°AFìxG™ä1$/8ú€8|!d!CrH`àè£@ … ˆ.’\œ¤!§xš0š@“ûhä0À:‰HR”©äG<ðô@#ŸðH&ä•°äˆ,=‚lÀØG.ui$Á•¡ ¦XHiš| F€2uÉÌ(5²H¹ó!'G:@Gd häŠ ü¿ü'@/">_¦†Žû@æª]6áŒ1_Æ&êÅxžæÀ"€ ²$éLê1Xûhh€)‰tA¢2M;bÄbh&£$Ø•’&5 â#+ÉPŸŠ¡†DÌ»ÊÕXFUN$kYá9—½­ªmÍY°àƒ/ ï­ñêaªpÃX¡ixf\G%± _ðÂ7ËÖT˜ÍSVè@nˆØ+P±},¥¨`ƒl€~íBž€Ùè5,|Ub²€….ôÀ¢¥•þÈiÁòƒôÀØ@þÛ€žuKÈj°dE½ço·tÉÚÀ­íAr12[°!¸Á¾ðÜuº]]®–‚€Ðf Ýý.x RݯáK¬‚¼àÄõ U¼Zrá ÿjûÞw°®â]hãßÿ:öiŸÑJà `’}F³ H{¿r¥Ï¨7Âû˜°WÜ´ ¤nYÀ²pZÎ!6 î‰Æ¹ÖõÂ†Û $+Ù!Î$ ÅõH\.à6·6ÈaFøº_y¸ _ƒ:Dt¶©nB‚›¦Lå)[áË‚ü:eÐ^¡ .äÁg®äB\Á[¾‚þ› {ƒ/'VºG>ÈÄ€ƒŒ•À•¥\å>») m\˜V i÷3RاÜWÜpÊ|ÝÁgÈÞ8d|H°x`1xÀæ µ¨Gj'gÁbY¢ùŽàh1á6S©yÀˆšÕ7¼€CKdÎx¶­;[è+ñÙÏ~.ó™·|'76 U´¤û|ì5+Ú󅳯2g1иÆU®6²«Üè%R¹ }ݯŸÅ}¥+ Û&ÈTÔ+Œ(GÛ‘¾™†ÄÎGbƒ¾P#|ïH ‰MQ¼;2†4$_¸Ä'NñŠ[üâϸÆ7ÎñŽ{üã ¹ÈþGNò’›üä(O¹ÊWÎò–»üå0¹ÌgNóšÛüæ8ϹÎwÎóžûüç@ºÐ‡Nô¢ýèHOºÒ—Îô¦;ýéPºÔ§Nõª[ýêXϺַÎõ®{ýë`»ØÇNö²›ýìhO»Ú×Îö¶»ýíp»ÜçN÷ºÛýîxÏ»Þ÷Î÷¾ûýàOøÂþðˆO¼âÏøÆ;þñ¼ä'OùÊ[þò˜Ï¼æ7ÏùÎ{þó ½èO„F"XE2NRŸTkœ*aýE\/¯Ò›žö‘½@p_ݳ÷õÔ½0ûe{Ó[Ä÷¼¾P•¯á›–ø·O>B¿úæÏÄ÷]u~@¡þûãgÿ&Ø'ˆô?þ߇¤üßWñäýW_¶Ì‡ÿù‡_ûè·ßü6A†ã¿üù?¿þ݇zé×{Ö'ú'aÚ{h|è~àW€ ( q€ª·€ì×€øG€ý7ô§~ö‡˜è³Ç%Ø-ë7÷7‚Ø(Øz1ƒæ²‚¾Ñ‚â÷~4èÁg‚ ‚˜{Èh€?H~x.6x8ˆ€¸>…[‘„5‚BX„/¨ãç¸Qx‚È‚"˜ƒO¸ƒH؃<¸}axƒcè„$(…g†ix„η…e¸{`A}w(ƒ×G‡Sø}è…aÁzvˆH„þ‡8‡p¸]è†"¶}¥AQChˆZˆˆ•Ȉ‡•‰Õ!‰ÁA‰Xˆ‰dh„h‡~('žX ˆ¡‡oh†\˜ŠŒH…‡²Š­h¢˜ˆ¦(‹(‡¨xŠˆ‚‹¹è¯˜‰3XŠÉÈ‹µˆ‰ÅxzMȉ±x‰–¸ˆ±‰‚øŒŸcqŒ£xÈø‹)¨‰¶¨ŠÐèàÈŒ_ŽÁè‹¡…¨%ÄèóH°˜…|Ø‹åØŒjH)õ¨Ž»è‚¤èˆí¸ïØlѸŽ)Žî8‹ÂHމŽÝ(Ó¸IËøæèŒÃ˜Ž 9ʸ‘©ˆðè' YŒ*iÉþŽ ŒùÜÈŠ.I’0ÉyãçÈ’"™“yù“þ¨@ ’·8”¹ø’i’F •=y”JINÙŠT9•IY• é•]I“þÇ•QH–Vy’<‰”ÙH‹kÉ”jª‘¨úL´úGp¡„ЧzzF Z:˜*¿Ú¡³9™ƒ9FP§û ¤@঴q*›šSg¤á¤BÊ­œú­·zªÀ¡ ÕªCz«çꇪ®Ï4®¶Z¦žªUÊ®HЬÔÚ—°ªô*ÛŠ©Àú¤õÚ£9Ôj£mʪóš¬MPr!ªQªÅ(ŠK€£ª¦Ä žÛ¡­õú®±?áHóZ«k«²‘¯+ë«À Rþú±°(»ë¬JJ{Äj«uZ¤¸4²+ÛpQúbJ¯+ã:§Jë£*A¯D˦nê®÷z²–j£•z§qê²5ê§T¨Z¥ŽÚµW‹ô¥!µÿi³eK¡Û°[˧¸D©J£Q«¥.ê®\ª£&J¥)ʤàš©€£%kµA¶8z¤$ú¤-[²1®t»ªHÊ^,H­Ѱ‡Ë¡!ùû,ƒnÜ6þ÷ H°à¾1R¤d¡Á‡#JœH±¢Å‹3jÜÈÑb†M¤|Ùä‰A3cƘ™ˆrŒ˜Ž0cÊœI³fÅ&#‚”‚°ÉË}  Uq¨( ™HYÙ‘(Ä3 ÚœJµªÆœLœ0©Â5 Ô&cR¥Z*E̘3b–Š]+ÖLÙ³iǤ¢†­Ý»xÇæ]‹Ð©Õ¿€ïÓ¹$É‘0bÄQ¥‰±<¥œ‹0ì]„“SUÖ»·3[)žS¥9#¸´iš ŨFÊ¥u—Ö\äR>c&X4f2Û¥m;îÉ–Cwîù´ñãGšM+†Ë/aÀlÙÆŒuž)Uf·ÎÝzví)ÝþvO¾üuóäe!_ÏÞ`C³K˜XA߇–. úÀbåÈ‘*á$`Ɖa†zí%¸Þ{b Á~/L´Á>ø0ÑQ g ‚ vhÚ{SzÓ†&¦Ø{cQ*„bŒ4ÖÄÄN¤”EԸь>ÉQC)1’‰ä’¥6Æ=2Y‘’RVù“IÀh¥DTn¹åNc0¥—uI¦”Y11E˜PžYænzÙOLd'AH,u§•,2Ñæžû˜ h,î¨åž‚ã©…Xă&ªhЉ‘ÄDqè’Nj¢~JQĘˆÂé)’S$“vzª‡þE$á©«¯*˜ê¬¦ÖêãX¸r¨+©Öj®¿Æl¯ÅÖxì°¾&kⲊÒê¬qš!;mŠ©Laíµ¦²Ä¶Ü*è-¸á¶—-¹å®m¤Ä¦‹œY~*­»U%E¼¥6Kïi"JÅ®¾û–&Äa °`ÌìÁÇ‘¾œ¶Ëp`öBç¼Ë”RglÕ¹ {\Ú¸!‹ OJxš'S&r´·•aÀñãä”G¤ºM&˜`Ä>ˆAä¡îôí5e Áî¼þCÁÓ^ûCÄÓdE» ÔûþR$|ÒÑÏÔÁÕï“<Ø_´=ÑÝËôá[/EùS:ÿ|AéÇ´û ÿøGÈ?ÒóóZÐ:tƒßþ ðü/€áIöt…#0ûÓßë8‘•Að"õƒI|pA À(üÏ&r¾Ÿ…°#èÀ#0€ä¡@ø€‘îì…þ)AŒ€ –År ‚<ΰ Ê[¢Í”g¢¤|ùKOIÁ Ã$fÛ€I¦,X¡—ÊÌ¥qiÉUJó4Y B"•¹kš,¼¤7KNnv“™^ò :9NÁ”Å–¾lg`¹Íh®ÓJþ„ç-å JB“˜üüË1“IL zêÈ4g@­âL0(ôžUÊçCUÃàÀªÉhF‘ ÑŽzô£W¨P%?Z…b¡ ÝhJÅP,ø@”á¼$ZVªQŽÒô¦ª±)NiªSšÚ1€3ÅiOwúÑ¡µ£FýèOçGôtg)Nêy¤U¨J5¦æ$È4ÆÕ¬zõ«` «XÇJÖ²šõ¬hM«Z×ÊÖ¶ºõ­p«\çJ׺Úõ®xÍ«^÷Ê×¾úõ¯€ ¬`KØÂö°ˆM¬bËØÆ:ö±¬d'KÙÊZö²˜Í¬f7ËÙÎzö³  ­hGKÚÒšö´¨M­jWËÚÖºöµ°þ­lgKÛÚÚö¶¸Í­nwËÛÞúö·À ®p‡KÜâ÷¸ÈM®r—ËÜæ:÷¹Ð®t§KÝêZ÷ºØÍ®v·ËÝîz÷»à ¯xÇKÞòš÷¼èM¯z×KÞ"ˆeµîM|ß«ÚøÎW¾õ¥ojí›_üîW¿¨åïý`ŸVÀ&ð lZ/XÁ f0{'¼$´ †/üÙ §Ãö¬‡;³a ƒØÄ"qgG¼—8Å(^±Š9Ë⼸˜Ä'~±ŒcLãSøÇÞ^L+ä»yÈ¥-²]Žlä$#™´Jf “—ìä&CùÉ£òZ¦,å*SùÊVÎ2–E«e ¶Ì-š!ùû,qkþ÷ H° ÁL˜<“æ Ã‡#JœH‘"“&N&Äè¤¢Ç CBÔØD£I&Rî“2FÊ-Çlùr_Ì•" JÉÒdÉ“LĤ*ÅŒ1ûΈ):´éP£H•2•â´ªÕ«D±^å‰((Ož L3´Š•W¬TY[«”·p³jë”*ݦ\úüy¥‡Z—©¼ÜС£Æ†>tôh{Õ̘1fšÚ½;wòݼû~2,FL%I’(A£¦‹0e¬\éRL0”‡Ž‰=wvlÌšwÍ»+V̄ޅL˜.]Ì(_μyÑæÐ£;—NÝŒ¬œP26ìˆ"EŠþPÀLÞcçÎÖs>))ÍË BÂÿ(Oây1éEjtY³¾ûçå×SB µôßÝ' H?=áØE (aA ^’fÛÑv¸O…ú™$ÆLDè¡„H¤ˆDQ9™ôI q"‚ †˜ÐˆN41ߌÿÕx¡F#6a… <ö …?^ôX\hQ¤>~$ÅFV±Å“P)‰›A6FDbI_”EÑDQKŠY™iØD#V¡æ˜Zæ4ÆRÊÍI'zH†äTzæÅ¦GJÞ j¨ uŠ”Ê™áâ\!JÑo-9)¥"Y:QKf¨qÆz8p’§½E¦gš ªþ-…F`üåêG°BÔ’^ gE·š×¨Ÿf…!FX+,Ÿ9-åa˜”²åzXLøpC_R[í°ÕJQ’7t ÞRdmA-9…YXÑÅ éÈ,H-=K`€ñ õÆ ®ºP”tæc,ð½ a{ãG(œàÀáæøXÀJ|к3±”†GX±£ÆqlÐRg<šVÄ$—L1Eâ~u§V°Ü²@*®hOKÈ¿Þ<É¥‘©†› ýáË¥¢df,¡ôÐLO¤èÔK3ìgS€*M4AWOýõ@a{]µDe =¶@iß¼ö>m·üvÜ$ÏÍ5ÖÙ=Tþ×jŸQS´â5~}‚4”¼ >w:x1-Ös_¡C|»í7D©X¡C•Ë}ùC©|Ñx†šÀ¨Ö†§rÅj†–ðA¥Ÿ;4Ôê]”þA<Ç~P`?>ç ``A³º¤úäiB7ßiñe¾yòdŒ+ô`‹Þ«¡t§Iø®.÷d«žx ÔPEgHa¾Dzè65¢HÌ?1ê¡B°ýÁñÛÉùH¨»ÊxŸŒ°@, $\NàÂÿ=iÈBŠ(á”HÎX”Î(|ã3Bfh)¼ÀI Ó` ¨'d þ‘á KxÐ}lãáœ, „&Npˆ4¤BÂþ§CG)QM)"B|HENÑ!z{”¡v †‹]¬ÂFê½pSE"ix„ €‰p»¢˜<@9ÎäªðE—UÁ/_°Â÷æ*!F`%À„ŒŒ k¹Á x¥íP’áš6ÁŽÌ ®€lkkŒ4Õ*ð A(À%rlË:YØ$,Åhª1$a” €ª€J‡ìÀ—U°Ž¶À0L7bÉ7ñLhIˆØ`6ØÀ€ƒW¦.–œêÈ@,PÇD„8Яpp+¢3X6þÀD<`…c!Oö$&Ö8Ð& ”-Û Ú4„’l7€Ó‚Î)ЩAT¢* ¨5– |g C%– P ‰IÉ"+z3T H˜BRÊÖ«ñéL‰<ØÐ{Þ, ß_¦PÓ‘, MÏŠ RâÍMl¢2 Ë/xApGH‚–0ÑOæ ^‚+C­†…\šx»ÁB«è§t  !M×EÙ*%)Üà­<ÕØ lpÕ´fýBX×ÚÕv¬i-(~¨zÎ.\oDÊÎVWoç(“u*Þ.+†Êk÷ ­ú'ÚÒš~§MmhI«Z΢§YÏu4¢ñ¬èï¹ÏUù³¡ç¼èAGºÐvФ3—0Kx šv0¨;ÝàQåÁ¦NªC]jV38Õ«&õ«]½`X‹šÖ ¶u«e]k\'X׳æu®}``/8 !ùû,bu@þ÷ H° Áƒ*Ü'Å`”1N DyòJš…3jÜȱ£Ç CŠIr(Rš4h†‰””LšD‘"æbÉ›8sêÜɳgÆ—Lb jF–&cÌDi"åÌ)c669Õ§Õ«X³’|r4¨J¯O›¨9#e²bR©]˶­”¶pãÂ}+·n*­xóê% õIPŠCŸ4a2¶¬”³4íºUÌ8ÝÆl÷JžL¡™Tbœ0Õ,æ˜3K›>ÙqéºOW^ÍZïç1K—È^"¥Ší*µoÓLS:õi¶¾!·N¼çK4/Å(—BÅK˜0_¼|™Æ‹šT²Ê„)s½.éßp¿þÿ.N¾üH14•,£\Œmô]®tyî¥ 2\òç'c¦¿ÿRü'à€ö`þ'‹y 6¨PYP‘lMaÅM á`Bí‰QÔ† n8TEL±?¤¨aˆ!á"²(#yP@Ñ„I,1£B~¸ã•11HTäA=.xä’yU[~L”d”T^UpáEUî3e—`ætÔ>[”U•_†©æyXÊ`Æ™T¦¹æœ¥T… óu)'|f¤R1lÁ…žíùØç¡ ‰1F.h1(š…*‰è¤¥2ƒ \„A¨r†Rêé@©¼àglꡤŸ~ª e” )§þ¨¦:)M=¸°¦v*+¢/Uƒ£¹ÆºkŸi !]tì°ž¦ò®Æ)³“¾ùÒYËRÛgOpfF´Qî©m˜)9ñTfd;îš~A•†]x¡îºaE ‚ÎKo—*©ä ™ê»o•0Áp†;íÀjBÁ„Ád$̤¸ GI+!pÅL2áD0h±¢´°r &WO\qÄÈ —lr•g˜Q…Uhüê©/w™ŠVˆa3É8çåKXôlb•/˜®ÐL¾9óšÞ¬+Ó;¾ô„+oL5‹ŠŽÑIh½uˆ©Ñ@G=õØ,¦Ò@S€;ñÂlïèvŸ‰]wþƒ© Ñ@‰zïm^*S!aà‚“§ÖJüÜrЉ“½–ÜKRùàk-­¶°—c®ˆwÎ[”i¹èýù™c¡£.Ù¹/áøÜ.»^œ4q‰:J͹핦€û|qO¡‰WN÷ñ¬¡5Ƽ·}VOQ…ìÖ_ÕPPÅSÝ{ßîb讨æ¯m~^Â;›Tùï딼ZN±O;äõï%={ykγœùõo2O9—‘,§ : eTw™ÒI¦=!QÎRÆ  R†t TNØ>¢ÍŒáwÄ ¨$„bØ Sèرg8/šÞ <ò¢$@…†þ{Y!kÚsa‡iD½q5í1B 0À$.'}MÌË+Å}Lñ…QbM²ˆ—-R¦‹^4€0 ‘öÐ dÔŠ'ƒÆ4‚#nŒ`U8¹ŠÁ™bp€ÇöpV™ã•È8Ò‘Fd£BpÀ,(Ê&ˆô‰"÷ÅìärƒRú@™ìÉ&õ¢œ.HÑ‹¡œ¢ B…*ìàM°`*K²Ê¼(ç Rô¤# `€bA @ˆr|pƒ/Ìp—"a èXèƒ`F`S¤€ðƒ(S Ì ¡¹i†pRœ€"àH'r!YèA)HNô/UþnlðT¡ 7ø@ªÐ¡7Þø 9q¬b*yÄCžq-oà%7yŒO.H”;4e+“ÙÌ%.sþw?ðä@Æ4þ²tÉ;棹Êw–2‰}0á _xÄ ó>b& %èиC4¢ - 0tgö)&sÄÒ—¥´¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~ûû߸ÀNð‚üàO¸ÂÎð†;üá¸Ä'NñŠ[üâϸÆ7ÎñŽ{üã ¹Bþ €qÞ… UÑ$BRŽÐŸ´\*¼üËYDr“dæå\¹*]Ž““kçZ•9jà›«Pç<ñùƒ^þó˜×iè%/:Ê~ •çåˆÕ9¢t˜ÿˆèûèúÖ³^õ»–éX¦Ð¿õ°ý*]ÈØUšö«{}$q¯;ÍÛ.vªdî ÉûßÑ~v»wðå{ßáŽôœžìM7¼Ö¡nó©3¾ìI×»Úï¾ùÃS^êr÷ûÛÿY—^òœ—‘âG¯rÌ“~é=Ÿ<ê#¿£Õ[>‘·§æéžúÀ¯½ö|g½ÙŸùÇ;ö—=ð+úËÿõ A¼a•ŸüÞ‡ÈöÍǽëuþoüÙÃ￟ö"}Þ?Ÿû§‡üî~Õÿö­?¿é·ïýèŸúîg>ùEÿùËüÖtO÷rå€ÙÂ{Ýg€ëÇuígwX}(€ÿ§~ö§€Èg~ø·+иÓ×ÅW÷w˜~Õ—è+‚±xÚG‚õ—€)ø}5¸€•Á‚-‚8|4X‚¹7ƒôç{8:肃(‚'8„7„DèyÄq„<¸Î÷ƒB؃ ˆƒ¸9È‚H¸JèƒL„N¨\È~EØTø‚Xˆoø„e… ¸‚‡‚¬Ñ†I‡fÈd…r(ƒ¸†y†Uè‡þW8‡[ˆhˆ‡t€“¡‡bȇõ7n˜†q‡g¨†Rh„†x‰Z}¡Ø‡”h‡^Ø…(’¨c8‚¥ˆŠŒøˆŽ˜‰ø…†¬øŠh…¢¸‰´˜Š¦Hˆ+x‹‡h‰ƒ(ˆÈ‹¤è‹Á؉lø‰{¨ˆMˆˆ½ˆŒœX‡‡¨‚¶øÅÈ£˜…ʎҘؘ«¨]º(‹á‡&¸ŒÖЧ‰Ð8‰ãX‰Ȍ׈‰ÍDhŒ¨èäHègƒâøÇÈ¿)÷Èì‹¿¨°XŽä&y‘™‘;ÐmÙ‘¹‘Üæ‘" ’Û&’I’Úf’‰’Ù¦’É‘.yþ‘,‰m1)“0Y“3ym5i‘9im;)=Ym?”Ô6”7“D9mF’;™”Ò¶”%Ù”èèK¨ÑøŽI‹¨•Y ŒøSqrPF1–‰8fYeé‘–Éxn ‹bI–—®X— ¹–©vÙ•Ñ—û(u€y—ƒ¹—ûP˜o‰˜ù¸Š јSù`™%H™»x˜y‰—†Ù–îÈ™xr„•ˆ¡y—1šÓX¦y™‘š‹™¬é˜"›)µh`G µ €iMuk±°»Ù›6œlÑG°ÑœaGû œ0œÂþ ÒùX'‘!Mp„ÚùœÑ™„jQÈi1žp4Àûp· Õù`ëžë¹MÎbc°Mõ©ö9œz:À‰œhQG  ×Iž¿ÙìIœ°0¡Ã Ÿ\מ칟waŸÍ‰*œZ¡¤õ^"Š¡ëYšÌéœ/4žb (Ú1:¢$ªt´ ¡°c-ÊKmR@0œAºCZ¤™a×`›Í)¡Ä ¡ ž1º¤Aj¥ê›aù¢ZŠ@ŸMZŸÛôB_ÊŠî £>G¦ :CZ £Pj´iDû°¤!¦!Ê9J¡þ~  CÕ£ÀÙ°Ûä¥)z¡ùI»ùBŽÚvØ <šŸ,z¦ Z©b¸£p¡©ïi¡1 uºþù¡JkaªÒé§DZ É¹œ÷)«NjV†œMªª¦Xxyºž°*œiz¥€¦‘ª¥âIžœz¬kʦ¬Ú ˜JqÚ½j§¹I­nJ­ØÊž º =g ŠŸz¨Þv9j¬§Oº®Áù§ª*¡Jª!º¬"Ê¢íڡ•Ò£»Z­£ GE4Nš£;š©qñœ·Ù›Å™¬ÞI­±Ç‰¨ØÙœÛ™ž :­>gŸ3º±–z©»®ÍZžËyžôù©ŒA\ä¹›·Ùœ ‹Ÿ Ûžë!ùû,b{:þ÷ H° Áƒ*Ü÷„ “& #JœH±¢Å‹3jÜȱãD‡ ­ø°r$‹Y²`ñ‘f_/\¼´L(¥cM8sêÜɳçÁ1Oš8¬råJ$;:ìøò%‹Ž.edH•Z&•Õ«W¥`Ýʵ«U­^ÃnõI¶¬Ù³ ÅH#†m1fÆt¹ÒÅj˜.`ºpér7fØ1b_,Xì¾1h+^ìÑᓵa¾PQkåŠ)RªXÑ˳.=‹Mº´éÓ¨K›a̺µk„Q¤´EæF×:Åè–" ·ïßh¥8yeÊ” À=êVÛ;¹óç¥4ÅH%Ð7.ç½»÷ˆÅþœ!RdJ’ï·7GÏýÃ3h†$s³}Dõöógg"%Íùôé—Önë hàkM¬õ_PœwàAø=(!cô5ÑD×MhP„vXVB9DEÄa‰(æ$ÝZbtÑ…(ž˜âŒ9Ña›!W¢Œ4öh‘gXµÙ1èã‘%›.^P$sHF9‘YÈdŠgž[ŽÑ„kÕça–€öhHüqg¢Z:”`N„ƒŸxBÚ£CQüW…>`ªi”1FE$A¨£i%þ\ç«­ö(]B…£´Ö:£PàZ„S<ê+Q4¡„D°õC¯Ç–˜¬CȷĬ;†-ŠŽ!DªØzˆÄ¸R¬¶-ŠÓš±Ü¥Ù¦y®‡Reáæ¶û绚"p‹¯†+²ÕVÐþ{ „1†,`Pm*̰½™:¬ßŠj ܰÅö!oOœ¹1ÇíA™^øKò©ô`…ȯ,¡OÕ{¨¶2 XÆS}Æœ³ið`¯>ÿ¬ŸV ¬²Ñì=&E`„Z4Óè­Ÿ¾†Nˆ(ÕÏ­h•¾PLÍuwb4Ag*X‹=6t£‘öÍî®Ý]‚ B¤¶ÜΕ;Æþ6w¸5Þ¾•ËçÒ€'·Öà#þÚá©ô ÷½Š·gã(VÑ=tQqä¯F¹‡UÜ ú WÀÌ9pu–ØéThæE§'—:poj„à Uè†rʱÿ6ûobœ¸‹AÊD÷îšgl%‡Ä0däƒ 6hÐAs)ïÛᄅ% RWô„”½¦kÏ÷¸é¶ Ð_„… ;XÑD:¯>kì¿¶Üb C,âƒâ•‹úÛßbúçšå<ÏGàAE®@:)P! >H ÃÀÖ8Ð0€Ž@‘.ˆ®~^è7x–²æƒûa)BAÒyD,\ ‗™#!†þ!”¦T….Ta&9TŒUʾ×ÔÐ?ˆ I¸Ñ‘®oI4K* 4àÜÄ‹" …g%ä° ®à,fÑ'ipY]sƒ,Œ„Èc!8D„Ø >ÊæøFžì¬ nlMñðC=êÑUè£A6Ð.dXpQ!ÏrHÇᦂ>¢ CI„’”ô@¬ÐMš%hCKÎ oBQÀ¸<ÂL)J† ÀÂçJž¤Â ]üMˆ·V¸ŽpÁ ¶U#oµOr°V/ô®‹PE’&À°1ÛK5¤B¡±‰ Sµ¦–rAö"aQŠÆ…„µ\Ö"7Z,eãöÙˆv²²BJ RZ·ŠVBàþ€ ØÕÖJ$¯ìñÁºY…¬Ú6"¸EÏôº)…Õö· îw²`ƒì ¸:8.r¢\ïèv8èíS¦›Ü×>è¨Á삸ÛÝÁv5¯tÉKêv'®#ém˜À^ƒ¸7;Y ZÊ€¶’÷¾ÐɯöÛßúÀϯQ¢F_ÁÏɯ ×kà:9p9ΰ†5Œ„ {øÃÞ°x°ÛèzXµ¬uðAL¦›øÅ†±Œ],ãCNÅá›ÌP®ûøÇ@²{,ä" ™ÈFNòŽoŒc‚ †#OÞH”52å&[ùÊXβ–·Ìå.{ùË`³˜ÇLæ2›ùÌhN³þš×Ìæ6»ùÍp޳œçLç:ÛùÎxγž÷Ìç>ûùÏ€´ MèBúЈN´¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Ímœ¡0XHÖRq–¬=Ø'榮NÒ\°{!ävηÁmq¸Ü‰7OÞÝÞu»ÕÝÑ7pæMo{÷Û,츿»ð‰(<#ün·¼é]ï}ŒßêîIÄòðlüàÑ øsþnƒŸáùF÷m¾r›ˆ|â79Ç1n_•3\EÿvùE:þ’Fæûà9ËA¾“ #?yÈw>rŠÇûâ(Ïø¾[Ž“¤Ï\ç9:k|. kàR/:ÕÁîp²ç<9\Œ×i~oëo‡øËÑîôµG½æn¿yÕÏîñ¹¼îo{YnsxÝðp§ÈדvÞ腼ØõnvÊkÄê÷MãÃb÷Á§<ïˆß»â+_v˜üñJ' áA¯Å–ï—÷ûo6ï•ΫþóS·üÒK¿{ØkðP÷|Ø?üÞëé²ÿ}ÌQuáã=÷¡'}ë¥ü¾]1Á¿}ñEþýÄw_îL_¸ë+?þØoŸú’÷þôÕ_}ª]"Ù|湿~ú·¿"˜O=ÿÞoÚô³~þè€àÇ{Ö—u¸ÑuðçЧ€¨Æ÷}È~®€ý'€8w~ìÇ€wh€¯A ÿç€ö‡{ ¨ø—|:äxH‚“÷|/X'h¯'­‚ !‚ Hƒæ=È‘Íwƒü‚(X‚CH|0˜˜‚6¸,h„<È~åç„>¸÷Wƒ8E˜ƒGH}U¨…2È„Kˆ……œ—€Sø€IxWØ@hBx}Ø…4ñ…T(&H†xØ‚¾·‚iè‡tè|}„q‡þY8%*¨Dvˆ:ƒkhˆ‰h…‡(†£W‡Qè…‘øƒ›H‰‡„ŸX€Ȉ™x‡ˆˆ.ȇƒ8ƒ©è‰g¸>x¨„a¨ˆ“h‰oˆ‹•¨E±h³è†…(‡»èŠch†Ã脵ˆ…ÉHŒ«¨„Á(ˆ¹h‹[(zË(‰­ŠÕ¨‰ÑÈŒ{hŒ·¨jVÀ¤Žc1jänæ(ŽéXŽ¢vŽW1ŽêØŽñjîXqòÈŽô8 VÙØhüø¸úøiÿ¸Žèxé‹m¨}׆zØ„ÖxŒR8½1 éyб‘eˆYŒ’ߨ$ùŠq’ÙA’,¹'™þ‘“©4Ùq“"Ù’)“<á’gA“/Ù“%:Y“q”8É“±’Š”f!”)‘¡”I”™”AECTÔñ•ÏHbŠa9ŠQ–(I–ÝÖ–¥5Щp@91v ×ÑðXè4—c¡—ñ—¹‰i7A÷Ž“iT™û@˜VoèȘp)—Á™žy` náv«)Gp˜‰š™› b`c@ša™80—"Õ›¥9™ÈÙhˆG›¹‰äæšÀYÅé›ÐÙþðXïHå'œ@BÙ cpœ¸Ù•9Õéd0—IBÚYž›ŸÙšéy@ЙA˜‡9ž5cU@V᜕¹@¥Ùš ÚšýØ`†‰ºœžÓë9Š¡Êœ§žZI ¡$ô¡çôXG—èé aRpœb*ú˜÷Ù^”9Fû@š j¡$„¡Ú— Jn z 9›¾y¤j‘#ÊM@‘ Zš"*¢RÖžU:¤2JJz™'ê¥H`£ûà£âè¢ á›Ez#fê£õYŸg>Š™Ú)¤?*£MP€Á ñ šèt—À€ ꜡«r¦ð¡O¦¦‰Å§Â¹¢C ¢‰ª¥›(ú¦™¦‰ºlº™|zœÊšñ¦¡! §y—Z©ˆ1›ÒIn¼É¦º¾yÇiŸÊižÎé™wš¥azwúžþy J¸9žòùž!ꘚl «Á¦ô§”Épþ žwÙŸwÉ¢µªÑú­Yú’œI›‘¹˜”©ù©ŸµéžÍ ¡µHŸš©šÖz­ë ­¥É­j¯¦ªvÙ:¬š!ùû,b{:þ÷ H° Áƒ*4(å “'ûœ0ie¡Å‹3jÜȱ£Ç CŠLÈJ&QJF‰ò¤â>YgÌœQ3²¦Í›8sêܹ‘II)L¤4‘ræŒ)fÐHYº4 ϧP£JšŠÏ¡Q„J8Fʘ£bÌ€M¸5dYg©ª]Ë–cJŸYOm8Ñ')V2ÝË·¯ß¿€ûmK¸°a‚]‡Žq¢uÌW¯RĤš|4ÍäË—¥`Þ̹ódÍžCsÖ|¸´é©Q¬ÞEÉäŠV¤¨Yj&Õ™¥¡A‹ÞJ7ïÜ©N ~ó$c(OÄL9b¥¹0©Ð˜™>Mè1¿/—Ùnù2öì׃þOž£Ð(gºNabÅK$ÌÃLw옺}êHïë§î¥‹ÿ.òM—ß~î7àt²”§à‚¥!EVg8E b„„†•`…'!Q³MAa‡ ]˜¡’¨¢af˜ô„ê5±"B&bˆâŒ8¶å“1a9T£†A9•OHŠ1F?9äFFÉ’P`8ÆCNfH¤”\âtc¤¡¤OYž˜b—h†ôåRc.Q¦g¦)'GHú¤dY¼¹åœ|jô¥cð`ƒžPöi¨E_eµT6ä@hœ‡FjPWa*Á(JªéA·-ÅDl馤 ÝQzå“–zèdþ²¤a©”¬ºº©’x-J«–…Újhv†䨾öùàU axà Ä;§ƒWu¥¤;4ëlši8dgdc eíµ]æU§’ÞY+¸iëSK±*¯­¢[$’Œ=‘I(!Ä»fÊ‹æ^P,aDKŒAľæÂëo—G›ÄÀc˜QÂAž»°”o1‘D|eÅ9"!²ÈH]ÜeWQ,!ÄJHqð·&…RQÁUTÀ/œ1séc*XØpE»öÛs”M4†ÞƒNtnÆãª3hR©ˆ zìRZ{Ö·ã.»W»Ãî;yÕf{êׯÔdÇ'ltòãEìïÈCofÍW¬°õãaO½óQs?œ÷‹oùQú°½ù§¡o¤ Y<Ï~iÓwÏ_XýAúpƒáÓßõ¦'¥Ü  `[øò)y ? Ùë8@æ}¯0ZIjpƒ*xå‚\ ÿƒ€äY¸Áš–Ñ 8ð$TaGxÂhâ¶Œ°-þq»h"¡™š1²šjáÿ(ÉmÀ˜UB4`ð ŠIþ$çG§¤Á¡“**BÐμ“›MBˆ 6Ð QŸ;É¢ZÄÀ!ü1Å  ÀQœÙ² 6è€Â&ˆêD¢T),šÍ À˜b°À, S„xÀ!° ŸiR› t*BØ×' …l³ ÄDˆ–•…­ü³§ù©Tܵ#P A0‚P€„γÿËTA"U€ÁB @ €*$!Ø@üÄ:V”u*@ êÆÐÆ#@`!q«ëÚ‘»JÅ Q©@-bÿp°„݈a×]Ä<ØÀÜÙ¨¢1HW°A 6xƒu6$“%ÏBm€ÇŽó´…ýþ,ŽnðM!Vand€íGRKœ+l@WÂmû¨[»ÊvFRØüªàX§ÅÍo‰c1ÞÀ60mt5ÂÀ§Eé˜õApǰ]ÉGŠžZ^‚L—8°Ù¢ÈÛ^Œ¼w8bŒ_d [ß„ÜW8YplXÐÐß‹ü÷4Hfü’R`‹Ø4H°Â Là+äÁ¥y ÃòZ ÇáÇ_Ćá€\S¢$#Å… pÓÀTaÆ„IpªÐÈzÇm‰ðŽ{œÏ™-¯ñAÖkãŸwFù=I‹›ìÞ'¯(¾`)±‹¼‹QyÞÓ’˜ÇLf1 ©ÌhÎÐþ™ÓŒf/y Xc³œ×,ç1Ó¹ÎZró›÷g<—ùÎ~6s ÿ¼¾=sÅ(ƒ3 ý¼h<ëùÍX+¤ís I¨Ò–6Ð}Šìaú‚ÄÓµGDmèR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~ûû߸ÀNð‚üàO¸ÂÎð†;üá¸Ä'NñŠ[üâϸÆ7ÎñŽ{üã ¹ÈþGNò’›üäÅVf [´D!.÷éEbNVîŽD<¡ùZT~–Dç7ÁyË_n û#F—nG’.žOæèaºH„þs¢/DêJÏÖBu¤—Æé]7ðСö}l]²3ºÍ§¾ô¯¯<ªcJÙÏ®‘²ÇýækGmÛv¨›‹0·ºBèÎõ¼×Üò¬?|ÎÿôÓÿ½ñ´(!rLTÁƒ]d0¡Anè!aL\qƒL6pð¡@ž¨¢ONøpÃ^ØPC '¦¸â91áÃWXÑ4~h#ŽDþäCYXáÁ"ÔhEF™QXl€…¨8¤”\†äâ_Ô`¢“åuifE!¾H†ƒZ>yæ› 1qÄ Vxqã–pÂi†v®ˆgžg¢Å4µY& ˆîÃDDhœq§›‰æÙ]j8ê'¤‘Ú{–*_¦fF±ÞH«=z(¨\–´Òl¡té©þ¨FY’Hø¹ª"¸â+—EIAk©¯~ºk”¢ÎÚ˜©ÂK¤¯A¬§P*«"«#1ŒÈF+í‡+•VÙn‹#Q¬jfÅኻ"µN˜a…a¤«®– IQEX«í¼ú E’Õ°´üÞÈ[ixqC™lÁ|Æ^\A„ï q‚§a†X\œñÆ'<aXDÃBbJò„B±%†8üò†R¨±š<è{3ÎM©®Ï?K8×ÇòàÑ}Ú¬ôÒA7=òÓP7FÒTï7×kXgM_Teôص×ïEÕE’c“^TW M´Ú_§b…ÛNÃ]v*eÐ=µþÝwƒqeÚ|gg¶Øo~žÙYt¸áÉ­„…Þ⊄®ŒžJÅŠ«ˆ9d†Æ•›6†*âpÃétºàMuaʼn6lp!=Æ»ºv£g®\ ŒEu÷}ûtQ}aÅÕÓù G6Ø üÌçϺñÈ+wC¼k„Åé=l¯ôÄ?ît¨k¨‘¨¨:ø¥Íå…ÔÈ=áúGl$¿U<Ë~iQ8µütäYÍàSýío4MES:•òÁGÀ€Fâc«ކ iH…„×Àê!øßEºp\a), ÓÐø™†°ÁOÅz“¦~È…(@ÙZU·~( 8°ÁJ×õADa<ûZ‘¿&H P é` ;Ã"(°ŒaìXí*$_EV²q,€67Q¶`6³”âÚ~Ö"š­ ´Ø½d«§EþHj鳨 :þ„ml 2ÛùÔöÕ±ÂïvÛØÐ>¨FD«ZuKÜš÷Ap+0Ù\Ù>÷AxµA>W]†\°.ë.Az[Ÿ?U—¼ô1osÑ;õ—½ïqïn7IžúÚ÷¾ÕQ(~÷»_ýò÷¿äño}Íñ'Ip¬`3¿Þ¥’à×7Âp†|_UNx „"Žˆq#âÓ†Ä&1q>Ì“Å^ÄÅYlAYLãÛøÆ8αŽwÌãûøÇ@²‡Lä"ùÈHN²’—Ìä&;ùÉP޲”§Lå*[ùÊXβ–·Ìå.{ùË`³˜ÇLæ2›ùÌhN³þš×Ìæ6»ùÍp޳œçLç:ÛùÎxγž÷Ìç>ûùÏ€´ MèBúЈN´¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëJ.7)H*4‰]³’"¾>éE‚]U;,·ŽK® rì_¤Ùž´…:là¤ÚdIvW–=iïäÚÌ ¸{ýmlsÛÜbÑ6WνoË“ÜÎ^ˆ»ÇŠnjgdÞ9Q÷VØo›ÂÛ—À.·EúÝXcFßSá7¯Ÿ-{àÖ6x¶q]ìpƒñßátx´+Bð…Œ[ã馸½þ»½pƒt#7¹À9ñ{\ä~ÕuÉeo…œ¼à÷·Ë'®ìŠ“üâ ¯yBnîñzÇ|çcAøW|.¢Ó;è—öÊA>r£ûDé‚az»gnñ¨Ë{êR×9Fœž¬;Eá@W¹Ð1.vª}ì/ïyÕ·žvš{Ýæ`ÿzÛsžt˜kÜdÏ,Û®÷»ýïï»ÜßÞt®ÿüîCÏ;Þ÷Îržo[ëGíà yÂþép·üº1ïøÆ¯ê-¯<»UíÌ£ÜîaI¹ë_vÎ^ë¬oùì5ßõØo>õ“·}ðqÿù±¸`ésß}ÑQ/yµ ¿ó«/¾XŽŸõä—žîÿ=ãkþO|î†úg'}Ý{ß|ØS~øsÏ=aÀ/ñCSûÑw>ð½¿}õ†ý®üõ•Ï[éƒÞÿ…•x†h÷~ÌçyÐ7·—~èè~¾w€Ïg~ñ¼z£ñ€Ö7~—qô×}€H7€È·}üG{òW~äWø|G‚Õg‚û×pÈ€ hƒ ¸~%ˆx+˜}¦çƒ)È‚Aȃƒá·8„ Ø‚õ‡~MXƒÆ·ƒ.x‚²‚OH È„‚7‚ß'…¯×ƒ¨„ç'†DØ€8W„]ƒeˆ}H†YØch…ghud¡2È?†Nø†Xè‚öW‡^¸…pxzl¨‚ƒÈ‡þ`ø…h¨ƒj8…3Hˆ}x…ZxthoT¨ú‡‡…ˆ€r(‚¨ˆ•xt—ˆ‚yHƒnˆƒ‰¸†9¨Šføg9ð}öŠ~‹°Èg²èwxv‹‹—gºxy{Ö‹£÷‹µ(Œ³h‹Ã¨gÀ¸oÆXŒÄˆ‹w–Œ ·ŒÎhgЈuVøŒÇH{£X…©ø|ݘr‘ÈŠ7(‰å¸i@`PŠb±ŽßîxŠ{Èô…qŸHúèŽÝx÷ø¡ì˜)q¨ YÑ IÙ yˆöŠ )€‘›9)‘­Èù‰úHCY¦ô‘<ñþ’ù29‰Q“ç˜8¹Š±“µö“Ý5Щp{ÙøcPM°H [gÑFÎDi”‚±”=™”¡•EÙmáç• 4$–Oi•d¹kYg–ð”B¹•ûÀ–n©k¢ñ‚q{IN•x¹h™—°P'S—ív‰h`E9°˜OÙ—»‡€@@?nÙ i™'™” ˜ˆéLgוíW õcšž9Œy˜û¦šî&”cy˜±9›¦¤š¡ùù˜V9˜°ù™1PЛš<@P—M°œ©Ðœ¢ùMþ€•͉™¡—«™ÞéÝ™™Mž×iIõSžOÎtJY›©žaRÀ˜œbŸP™Žçy„¿)c`¦¹thPž¶Ò”±œûΩ€I—ʜ阞Ñð”ÐÙu‰žèÙ¸é¡õc¡ žz‡ ¢HàŸšp¶¹(J N‰œñ˜ú–í׊–ž¹ 'ªŸMPh1aöéZ¹G€Ô¹™õ9{H •À )º¢Sb¤)Ÿ'jžS:¢Ñ¢_úžñ  2:¥4J¤FêM0  o¹£=z”ny—{t¥_Ф»6™(º¤ a¡¤ù§w*ú(Чù™"ª¥UiœhYœ¨)šbÊ¢¯™¨²ù”‡ù›’ʨ¢{:—¼É—b:*A ©­¹GÄ™”r¹žÊªššl9˜†¹•~) ‘Om–…«­z™ª–yy•¦Yº¦MªX©—&‹¥zšµ:–!ùû,b‚3þ÷ H° Áƒ*\Ȱ¡Ã3fФqH±¢Å‹3jÜȱ£Gb¤ˆ”bæ£É“(Sª\ÉRã)bÆ„”2±¥Í›8sæ<³o )cöué¡cÇ…R†D#0$Ï}I9FÕIµªU“"¡8!éE‡Ž,^¯¤K–¬”²hÑŠL3ö¥™±gÓÊ —îÕ»xólÒ$ Ì*V®X,ÌÜ1eÓœ™ š4f̰M…˜®å²•åêÝÌ9§”$E”t±‚¥4–,¥¹p‰Ì:2I3d¼t™ ¦5ë1¸s³~m»·ïÖ¼ËêL¼øG1D†TñÂRJ'Q¦¶CÝÌpãØ³Wt2„ˆæÚ/þRc=¼ùóŸtÿ}ÃñåÝËÇÎDJ‘"Ëç3„]¿ÿ¼L0ÑGTÑ…ÿ!Ä_‚ ZõD€VÜ  ´`…Ú4 <Ü`EdØTuý‰h¢IVqÒ`â…'ÆØQ€H¬8[ /’(ãŽEÁvÑAŽä•Èã‘ñÅZQ‘ñ!)%Cކ^à("ŒSvyЀ?Á$†Péå™=¦íeÈ%špŽ‘[aœø&œg¾5–vêˆ'ž“¡QEŸEþ‰'Mc$Af™†Â ÅEÑ¡Q6ÚåRñ!¥fZz¤€B±¨ˆH”J’§^é¨nú‰ª”þˆŠÊé«SJ` Ê(­HB)uGmé*¯2šÁ—gŒù°…»£±OŒu†0[©³&JÑD\g4aƒµb[aLøFHஸ2:ÁD"QÁºìž8-RLÄ®õŠh†W(¡Ä¬ýŠXÀ\0†d`aźһp…R6˜Â3(Æ=dÁ*†wf¬_DmʯÈÿÌCm‚<,Êú܃_` ó|ið Ã|ž|ó|]Øàp>ÿŒžVìP Hl4zi@qÄNˆqÄN?mž±V„6Fhé²ÝŠ­³Ežxåz=(àl7„˜a©ø–ĹåPD'Æ6ˆŽa/Ÿ¾Y#É„…ºÞ;à²ë¥mÔá7†7ø@yïzѦ¾ËæžÃñÈãuL© ‘ÍÏPOE¯×ôˆY‚¹_¼éÞç…†Hc0‘DèuƒG>ð%gú›Í”¨è{G¿ÛG÷ð'½1œÁ ÈŸ"–m1<ÔK*Ò ΄dnaˈ$†šDð*hI 6½Œ©¸Bü02 ¢ïƒT‘…H†#È@/T°A6 2¬Ð"þ7¨‚ C«LO ©è‚¬à½¨h78‚Ž=Н ûð`sb’Œ%0ÁÂËnÐÀ¨àe‹:9"[Ìp,è…ÀA Œ°Lñ¹ÁÜGªèÏmÒ¹ .€B>V³býîWH¼d ]ÜÌû2À> ÄHdC‚8ÄpUR%©…b8³øíÀ“}ô£ÖÈ*4á c0å)QB–â…B¸À Œ€<²˜SüaBÄà£,î'½äŒ„À$GÀ˜×4ÀÞ2&t]Ï´I47S„¿ð±˜èD&”i,ÜÀWè‚ÒÂÉ’qšp<ÄþŒe`€~ˆQÑ °†0ž+±g^Ƴb ðã(` X!eô0™ƒ •Ò²`ψ9'D ˜LØ×Bt¨Qæw=‰BñÒ³}¡Ep€€!>è€|P04¦2 iq¬¶F ! C6ÐxM”HýÈL7óºaa %Vòx3WYMj*|¹™&¤H(gCB€#d¥U­l-N°è|+—wýHíjš¡Ì(ÕªREÔ°Å$±Ùjƒë*¼²‘l‚8`ƒwz°˜ÅˆfýÃ$\á Uˆdh32Zýø` LØþÇgǺڊ´v>î´BlÁà…0ÖÖ¶‹­P„|@\,•ð·À]«‰lÐY¢‚¹ ¹­~8€…Ô^º‘î|:@&»b—"Ú•O t Øïf7¸ÚÀ6ªKó$¼îáî>¼ëÞ…À=Ôcëk_ôVvOØ/rßó„lÀ)°yŒà}(8< FðƒµáO8;Ôá^ƒra숂6H‡³±&P2Äïõoƒ´UJ¤—㉱ŒeŒ„ÛøÆã©1ŽwcËX[¸loƒ“Åc¹È>>ò“ãfjÑÅÈo~œ)[¹‹WÎ2–ƒ›B9#×sGÂL4 !¯_N³þš×Ìæ6»ùÍp޳œçLç:ÛùÎxγž÷Ìç>ûùÏ€´ MèBúЈN´¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûܯR€º„”¤¢ ‰D%Bâ­Õ†Ð[#ï¦È½-’o‹ìû$ëf·»á}“~\Þ ù÷E žðÈbDá x»yyð–0| g­½eZ‘Œ3ä⿉ÄþNgvÞ y½õíð‹˜|##§xÉ Žr„/äåïxËý“˜s|æâ¬9Éoþs‡àœÃïùº'^t}áB—9ÑMò„<]ÄIùÒ‡îtšäê9o8ÕuÞ‘ªOÝ&>;Ð-õ¦[Ýíg?¹Ë•®n¦«ã^7Øí«ò÷ßYGûÖ¥Þõ =åb_¹ÑýÎr­×ëPÉ{Å ÿö»ÿ]ãsw¼À)¿÷ì¶Ýò•WüåEø–¤]ô‘7¼Þ÷²“#fG Ý7ïöÎG÷ó¨Ç=àûÒ³äôŒ·}ØWosÙƒ¾õ˜ç¹æíŽzáóýðÅ=ã‘OúÌ þñœ—üÚ)þ¯ûäêÖ_¹óo¿}ÖOîÐϽñ{þ–ø…g{ú¹?×ó^îÊ¯Ê ö¿.°ïªw~ÇWÿ÷}üæ{9ÁýW€©'ÄyÝW}‹gùG ¸þ‡ñWO¨ÒGh[ˆ˜»€(€ê÷€Ó·~8ú§€&è} ȸ‚*x‚!^#x%È€ï‡t7Ø‚,‚‰÷‚;¸?èAxUׄ{(H„èyí—€2„Ú7…èW„ H}àW:±„:XƒæW~è…Hø4ƒ˜…L¸…Ø…9؆.X†n8†pX†PøbUh‡jˆ‡wˆ`hdHƒþ}ø^(ƒ¸†‹ø|bˆ…ü7ƒŒh†ÑÇ…iX‡•Xˆ"x…$¸‡ˆ(‡–ˆƒhèˆCx€žèƒ X‰‰˜`ˆŠœ†IH~‘ø‰“¨…XŠT¨‹„xŠX‹ªx‹q˜‹˜HŠÅ(ˆl¸‰³˜‡ÈŠ¢Ø‡R8‡_؈²xî§ ׊QøŠ¿H½ˆ°øz⇚¸‚иŒV˜‡ßȯ&îè:·æŽ"¶&ôXkö¨kù˜kûˆkýï¨Éé ó( I i ‰÷HkÿXY}Ú¨ˆ¼˜†çx„F‹é(ŽÐöS~¸@ŽQ’Ä(( ±’»h.‰þŒ “á¸$i’A“,y“—Ø’tè“Ù:Ƀ#9”Ñg”iˆ”¦˜“? “/‰J ‰®È“ÐTQ”TIN™‰@9 q•†u6…“A[=9–gYf¹”jù”±–D‰nrùedðHG v @f‘5˜°{Ù—ŠE9M0hÄÀWø†˜‰Ùo‹ ƒ¹™¢•oeQ™ÑÀ—™™›)™˜hš!šL1™Xt™bïö™· @@0¼iS™©VeA›ƒœG0œ™¥šˆ©—@05ƒé𓙉I˜€Eþ û@àÕœ¹ù™€I›p+Ù©™Üéž•žAžŽ H¢)¿‰Ù ˜Q¸š”™ŸÎ9QX4ÀÄÙƒIÿ e§©Ðp—å„ùœÕ©Ÿø‰ û€ ŠúÌÙž‰@àšÊGÀ›€Ä¡~™`û ¢øI˜#:š UÐ4ûøÉ /Z—S”¡ÿù›ª—Ù™œ>Ú@ {Ì©šM@XÄ›zžz›”¹—€ô¤¨y^R*žùF¢ šœVÚ_ýf/*ê9À6Ê›9*¦kJg*Z¢Az™µ§ Фü™›M`£^š¢Ú™lúnfª®(:¡+ªj¡d:šrz¢ú§ëé™Iz›{!£ÝYA±§£á¨‰Ù˜È©œT—D:ªÙ©¹iŸ„J¤ê¹pIÚ©F Ñ9– \YÊžÝ9š¬Šž®z«‘z×9¦»j¢å)`5¬:¦ŠùH}Y˜‡ºœJ¤Òz˜±z™ÇzšHÚ¦ýF›©™²Ù™yÊšÅÚŒišã ¦—a®¹—Tžàj›Ì«û!ùû,b„1þ÷ H° Áƒ*\˜,†#JœH±¢Å‹3jÜÈqß1R¤ŒéH²¤É“(SªÌh&$H‘+cÊœI³æJ&MœˆqÒäI“&L¢Œ<(… š4AšIƒ$E5FµIµªÕPšDʤ«W&!S‰›JŠØ4/¥˜!+MÈ·cÆšeK·.Û¹vÅ^ÝËw/È—_½*¥kVÍË—kÙ¢•"&®Ü¼ïFNÕ·²å˜oÿ~å*E‹.`Èšu+&Mª–b&?V oÞ˰cw„+²+P UdĈ!C—Ç-—u™8kÉ‘e+_NQ Ï·_‡áÒ¥L—-[¼”I7™3g¿þw|¼.ù×ÌÓ«GÈ50“Æ]¸„‰}‹|µfÆèß?ÆŒÿÿˆ_€à€øÐz Ê…{]5¦E IñÓOSY&ƆŠaÆ‚ †X™…6V…VQŲuÈá‡"ƸO^qåÄ{c0!ƒŒ¹¸!Œ<IS{8uucc` ¹‚¨ä“'’O2 $”X–t£”`‰áC•>^™å˜ñàWXtÑ… `º(&™p^”ÕGR\±AVlÐ’V:矽”dÑ…5𦟀6ÊOtuƒV„ÑŠºÉ¨£œäVO dÁ:^ùpÃcTq©íFÌ©¼ƒ1Æ/pŒ¯ÇâÊ„7t†Ü,Êp6ÖoW>lýMÒL&„]}ñ2Ï©ú,¤¼M˘gTKt¾F‡ØjO@1†KNŒá´É=Gýd«PH“ oØWÉëHÓAzpƒ&6± '#âˆÜÓ$É;¸Áªp…ð€1t“¢Mr(˜Œ© MÌ‚²ðÄ®‰q/9 Œ~špFtÀ‰°ÃøÆ™XÈXO¸‘~,v•ND zÂpÐ €ño}T P à’&è ¤J/R…ܱ‰¨Á!I”ÜÈ ”„œ~nÀ«ð`“i"Ô˜Ü`”¥´ŠÜf@ôÙ¯*YÈÂZY‘Ü U`ã™K«Hé’{ÙÀ<@©}a"7°A ¤i+p)ͤʈ咦ñEOà@Ä€„kF$›ÒÜ€­Ð+x! eþ'M^ž±lÍ*6(Õ0Õ+BĘ[ì¢-[æôœú\ cÐÀ–Vå;¸B›p á Éæ2A²tÓ hèxÑ•˜†.¥ ¥°Q#ÀbÀÀBÐ8Ršæ±I]ÐÁìZª’ºÄÔ&ÈÌB. ›Þô:UÈ+“zƒ ,ìCYЈº£ò¥‰À„P„}à¦Rˆ*B²à„Àxå¾ @”«'ñê^€X a F0kž*‚„l ˆE¬XfO,˜¯E…)_ÄP²nÈ8k`€*¨• 7@¬U=ðV*L²]•¬_’ "\V³™}þê rØÐˈõ&Q«½^EpBhm0;ØÍ¶³ (im‹ÆÜº‘·&ñ­.Å`„à’µ¸Å=«Žð‚ –7pë¨ð\è’DºUb¯’`§—ˆïªÐÝ|@øApE󖽚üß>ÖéÔ à¦b¸À~€ÄùÅ/Ëëߨ&Wè‘M' … ¶ Ÿ%~;°ƒè—>¯j-ÃÑ áH@ Rƒ„@¿ÞôÁù»ÛKä-@þHlöVV !`‚ rßÁÀ¯I-èc ¯Ø2E`‚@0ÁÞA :pã¼ò ¤¬òþB.<#‚üà0¤@LaHÓ iVsBØl™üò Žð[C…Çê9#|^Ê|cüÚ1 Ë:4F­žà·n•ò,‡*éŠP:=<À/6p_äÀ †êôE>ž2“¿<°¤UmV3'Ðù A †­Òz"¶^Î",x Wø‚ªðk‰[9¢íÁaCpÌ0áXC³!òìØ@8P¬Â7Ð…þa_Ø6Cº ›o‡{ÜUèOЭî5_YFˆí@ÈLm€A Øfi½ÂnØ„6±!0æ\—=p…6< 5|€…!Ý ßó½8³Œ|ãì¸Çþ òpØ|pä)ùeNŽò}¨¼2È â:ûÛò”ƒÊ¹Î½Iõ¦ äpaG»Ùã>÷²ËÝE\ "iîö‚ÀÝëw¯»‹/ø¯¾ínÿûØ¿xÆþð[GùG(HùÊ[þò˜Ï¼æ1Oå¾û=AÁ½èýƒ Ñ¨ô¦Ï³ç2”Œ´#¯_½ìgOûÚÛþö¸Ï½îwÏûÞûþ÷À¾ð‡OüâÿøÈO¾ò—Ïüæ;ÿùоô§Oýê[ÿúØÏ¾ö·þÏýî{ÿûà¿øÇOþò›ÿüèO¿ú×Ïþö»ÿýð¿üçOÿúÛÿþøÏ¿þ÷Ïÿþûÿÿ€8€X€x€˜€ ¸€ Ø€ø€8Xx˜¸Øø ‚"8‚$X‚&x‚(˜‚*¸‚,8>E0Ä!”aRTÒUQƒ1ƒ‡ÔçœNÖi$JÙ&}z‰Ø€ûp† bhŒá1Q衉.Z$Š4öC¤’òaaàéf¦2:Åbz (¨2Ž! E4aª¢þ¨j(Å´új¬2’DbŠk‰pQ„£¾þª¡RTa·+âUp¡f³Îf(…Zh!g±Õ6Ø3XÑ+–§vÛà¡/ˆK­¹ ¦!à ڮË.€cpƒ[È;¯}%m±úîŸk2l1.°y lU1°Âä©!… Ztñ0ÄÙIEZÌ-ÆäŔœ +ÈØÅdEŒ2vQ4aÛšü²zR¸ŠEË6ß2W\ñ…Œb"A¦Ïë1a…`\Œôh1UaÏúô}‡aæôÕƒEáèl[sV¥]4í²Ø†1ÑgØh+…˜ÔCŸÝvZ‡}HµˆtÎMØþCdLͶÞF•äƒ5W].àj•¤ƒßr#ŽTI8°ü·ã,•tƒäSþRI;`NZUÑÓáš'5=0\MÝpCbœ\ºQ©,m6iÕuÄ7X‘Æìi¥RF…V]l$FP‘¼p%"$Í„@e $ƒ,œˆ _˜’ކ†þQà 9\Èyøf Õ1`‹ˆCÿމMŒ A*ÀˆU†E”8‘0Õ"¿6ÞÏ Yb7rÆÁ¤Ñ€]  ÀÇ$)ð rœ#Fèâ¡äm @p@1\À#BŠv4AfÑ;Ôˆ' …´± ITÈÈhI:jq4ãÙÇ*ðA(€&ÂR–2#uD¥Xh @L@ßB:À›ÆÜÒ#¹,Ì: ˆá~G8C<€ý°ï˜I&aÀÔÌ#XA éÀ¬‰MŽh³4V°AE:ÀŸ\³œ9g€:`r—§þÑ|PÕÜŸ˜ŒÑ šWÉVDžê‚Ô¼?ƒ4Ÿ²jB‹ºçЇÔD9›hE-ÚÐY¡E^xçF‚Ðþ Ô TéHRRûø ýÃJÒÒúTG13]HMãsS•æt ;UOî|úÓ}5==-*BŽJóõ§LÕN sÕìLu¦UÅÎUWšÕélu¤]]ÎW7V匵¢e¥Þ•jTˆfè¬MkiàúOB¦²D”|*Uå’Æê ¡¯€ ,`ÿ*ØÂÖ°†½"[XÂ"±Ž}la#+YºVô ­¬a)«Ù4r¶²Š],e‚Cã˜Y§M-jU+Ù-ö 2íþHl92Û×Úö¶¸Í­nwËÛÞúö·À ®p‡KÜâ÷¸ÈM®r—ËÜæ:÷¹Ð®t§KÝêZ÷ºØÍ®v·ËÝîz÷»à ¯xÇKÞòš÷¼èM¯z×ËÞöº÷½ð¯|çKßúÚ÷¾øÍ¯~÷Ëßþú÷¿°€LàøÀN°‚Ìà;øÁް„'Lá [øÂΰ†7Ìá{øÃ ±ˆGLâ›øÄ(N±ŠWÌâ»øÅ0ޱŒgLãÛøÆ8αŽwÌãûøÇâíŠh,d³y/Cp‘ ¼ä#ke,LF²‘•,å(?9Én2•¯<å,WÙÉY2˜ÿ‚åkÙË\¶r=˜Ë à3ùÍpæQY¶ :ÅÎ\Á³`Ð\g>ßÙÏyôžÍL=kÅÐlîïœ}hF'š¿‹&tXŠ€!ùû,TŽ'þ÷ H° Áƒ*\Ȱ¡C‡hÌœIó°¢Å‹3jÜȱ£Çû¤H"RÊ™’Ñ” c¡˜’RZ ”Ò‘&È›8sêÜÉÓ È&N¤ˆA#TJªT`t(Õqå¨ÓTc„Žy)%ÍQ£O³jÝš +ׯN{ŠK¶ìC4cž<‘2•­Q3J{dá¡ÃJÖ—gŽâ½ ¶¯S¯~·šL¸pÏTfØŽ‘¨fLª2]¼…ü%«3²ŽFDsÔq`°ž?k8ưéÓ¨ÒLÃúòå˜`º€qÝ¥‹k3S¦ˆ³xÌâ×·ƒ œ¸qáRd¥^ÎÇ/"¡äzú éäNNd'o’ä“X‚t¢\pã•Y†¹Ñ†2¸°Å-)¢‚b¶™‘}UÄÐÂ\¸À‚‹`º©gCöYQæn/Üiãš{úPŸ2Ä0F0Zbž†FJœ3ÌÉE ŽZI¨¤œç fn‘©¦Þ±Ùi§Rĸ t~¹é©þ§¦J…[„!F j– +¬O4á… [øVE®ïízês¿;ưx¾jl¤Ï5Á%oZ;ⳆÖÇD>pÑ…«ºbkhª1â—à+îž©2E1ÀpD&XkêºaFñœ\ÄpA_Ð`/¾z¾¼$ñE ܦ,R¨U… òÒÛ°ÃbF§U|{1ÆaŠÑ«UØúÃÇ c9c^i˜Ê);9áT½U "¤1ãè„vSŽÁÌ9çØëZB‰qÐA¿O@1ÆG7nÒCB(Ò`t€4Õ%B8•`x°5× ÚGô¢ŒM6†%†b»¸$“k爢ËXk-µþºu»è.ÓUˆFº×öMâ„PTQ²„ßk8ÛR„!ƒ`— öãB‘l]¤½wá˜WÈa“[aE—‡¾ß„RhQÃ]Xþ¹ãª˜XVl€…dzמáIQðàA¼:µï ’”»Xx¡·ñ|#¿à„Vø°ƒ]Ä =èÒÿ÷\WXÆÏ?êl÷~ƒTˆaCêè3—ª<ÔàEï÷€/MÅq?sQàŽ·¿ùD¥7S àŠxP4î0S'˜D¬…,ä’•×$€Ö9/nD“Y°B¾yšpž¦‘þØG. R À€@Ý9£Ý „^Ð= cOÓø`Éå¤d6á ¹‚9†0k¡—tÊ Å9#Tà<@‚P€“-Ä%|I|ðQ’¥¡©Ã2°"!X¦Bi™ÒÔ¦!=ÊH™ƒR a e<A ¢Q\¡ `àAM‘z‘Ч|0cþr…MnÀWH(WË‚Sð|Às ±Ao° Ès­lõêŠiWZA x½©^G×XA ;¸ÂR ÎÁ‚¨ F4,bËX±´µArí«a[Yž\–A™¥«]çÙÙ|–AX0+ZÕZÚœœ–AUåþ¨Š×Z¼vA05¡ÊÀ”ÚÞä¶"ªL{ëÛ7@U½jRh[\@E¨B›k[Çšï ã$-u;òÜÿxs» éî~¾ Þƒˆw>ä-oAÎ+Ÿôªw ì{ß»øŠg¾ïµoxð«^ý‚‡¿àæ)WÔÌ&Ñ·"þm€Á›`[6ñÀ ip{F á‹´;Hp†7ÌáÝd¸Ã Þð‡CLâèUØ"gÀp‰K<âw¸Å.Þ°O¼{8Æ †1ŽU¼c‘Æ ÉOqŽCäĹÈ:²qf d†”†#OÞH”52å&[ùÊXβ–·Ìå.{ùË`³˜ÇLæ2›ùÌhN³þš×Ìæ6»ùÍp޳œçLç:ÛùÎxγž÷Ìç>ûùÏ€´ MèBúЈN´¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;›ÓE8 CRA›eÄÚ ¡öA°}nDÛ ñvE° nÕ|„BåîvY¢nóVûÜi÷Là=톈û!ä¶È½Í ßìÛ#ì®÷@þÍ‚ DÞ!¡w„í­ðõê»áç÷X¾ð7¼!7¸Böñw{$ßㆸ¿þ×-íŠÏûãñFˆÆÂq†£ÜÝ!ùÁGNŠgÛã5Iù¶/ŽŽ[\æyÎû­‘•gÄæáÅ9G4žqžÃ<ÜN¯ïÃîs¨×¼ä7ÿùÐÒt '½àQ¯zAŒ¾s¢_›äU/7Ù}¢s¶{½ç.ßzÐñ-ò¢£]à'—;ÆUõ¹o<ìS—»ØY~w“'üí†W:ÍÏwÄK]èKÿöâÅ‚t¸k=òn¼Þ-oõÍÏò“¼æ)õ¯çó\ýäMÿwÇ‹þô¡_=O*ÿôÃ{>ën¿½ß ïúÀ£þñg¿zÚo÷Ì—]÷’{ïA_üÏâ¥ç¼í¿÷ãS_ú¼¿ýë§þûæ“~ø——=ãaï}ÖgÿúΗøï·?öÂãžûåÇ{û¿{ÕÇ?ýq_¿øÑŸúðßÿýÄ÷|h˜|tGuû—€ÕG~(ˆ-§|Úç{Ý×€8!~áwk7ý—{üWÖ·Û·JÇ~ØØwØçhbG‚.|êW()+H-È€7è€øƒ0HÞg‚3ˆ‚h€ð÷‚æw„DX{ç'‚O(„4¨n=‘ƒ_±ƒÈ…NÈ|\‚ÿ7‚uÇ„…\¡…؃KH…M†/(†øH¨‚J(ƒ>؆kh…ãçRè…Th„wX†Ihvy8…xh‡þ…HˆQ††è{€˜ˆa†‚Q‡,ø…XƒjÈü‡‚§€!‰£A‰J¥ :£“XÝih¤Eº¥š×I¢úI¢aR ¥` c4:mÜ™¢ê¤¢M@þa£=7© ¢Eš§J qô¹wºŒzp‚QrM׉¦ƒZ¨CšZ©GÀžK*oÚ©“Z©Á¨‰6š MŠ¥Ú} 3*ª(ª›Múd©¢›¾ZŸí¤©jÚÉ£GAFh`¨AZž€ „º0 ƒ‚1£‹ª§¼é¥ á¢BZ¬¼ª¤Y*£` ‚­ÓZ©Š­*Ѝw¦ßé­1@®® §`Zr´*·ª®õ ¡RÚo¿Y¤Ãj§xŠ­¦¤@ ¬¼ù§ÇêdP溜gxŸø‰êꨢéJ£ÚŸÚÖ©Ù ²†ZžØŠ«²Š¡‘°2Zc°½±¡ž«°*¬øŠpûÚ¨Ì ¨;sˤ]Ê§ÚæªJ£3Z ñ b`¬Íz¨+¤jú©1£:ú³–z²à*j Qº£1ªXk¥"®£.Jµ_J®bš dŠQË¡O˦{*ua{¥¤Ú°üy Ò žâɵÛzž: ,êŸWê¡ Iìy·y ®¥ hÊ›ŠkŸ¿I¡›¯ ©ºzú²Š[¡Û±W´ˆ ¢’G¢#šn!ùû,L&þ÷ H° Áƒ*\Ȱ¡Ã„iИIó°¢Å‹3jÜȱ£Ç ž‘BRʘ(Sª\ɲ¥ËiHŠCòäË›8sê܉³¤˜˜ûÐH#p$Q‚$)î#ÅŒ@)¡r”ʳªÕ«!ϘA“*•”Tfšvõúuì’$I–Œ9C“«×±pãÊ•[v®Ý¹_±êÝË×!ܯB¦2:¶È"FŒ ‰2tlÝ»»>Ž|7oߢûþM%ËŒçÏiº¢Q¢dÌ1Sĸí:†2åÖ®!·ÎL»öÎ1f6cÚôg3SÒzƒ–Ì樂“+_–¹óåÈ˲M½úÇ’’ ¤i=!’™àÍþLïN¾|C¶b¿ž1ïüLñìãËßδµúù¿»‡¿¿õ&P˜ôÖzþí£_x㨠fL0En÷xà{ .h¡^ >‘þM(†hUƒM”tš‚H¤¨bS"¶ÈSM¸a‡î}X¡‹8²D¢g ˆb æ($J ÙÄHÚIäC6¹Q‘PÂx‡ýy¤“XbD¢P2aÚû1™å˜ 5È”PœÁ”’a’éæC]n‰ÆP°‰à›x.'ma…æ)(BqŠÑ„i>jã Œ$EO4(E‘ž¨è•âIMMÐ$ÅiEc›™Zߤ›~åÔ¥b–:&þN4Ñ ML%9ê® ¥R8•øYÙj®XÆ)+n`âJ¬›½’£¬fˆq«Ë¾É‰QDág„1í­VKæµL8¬L á-µâŽ9©³L@+-»ífi¬—6»h½c>+SŒÀÎ',¿d¶µa²áŒ%MÃù°|+Œ¥O”䥨U.)1–²uÆh=@ïÆB~ C‘„#“Œã²2aDE8¡AË.·3EÐ,ÆÍúbšsˆ‘6hDÊSü€óÐÆœ)+!„„*¦È"Ó.~ÊÊB¤ÕÀÒXè¯Q0I(ð¾a[sÙ &!k Ýö|žr™!þŸë=ìÝì15S‘5õ1©€û'xÑñFqÚ·‡+›8~b@±s‘bŒqØ“›·'ŸùFžpçó}žyÝ“^Þ…‚ñXð`a¨.—0Béø°™Å 7Xá…eØÎÞ¶Ml»å¼+ÙÅ ;T!F:da¼ypFï4ZqCž¡×{¾çéÎßàÇfèÐCù潫<Ÿ«ÒØð=xñ…Â_žâÑxC•`ÍÄ{Áó‚õü÷¿šxeNxžÐ…/ÔÝQ“z$rƒ´1Êœ@À‚3©Cî…•×q$b'´N*v†¾«|`/ÔX ­c(“J þ8¼Š |9ŒÀp‡¶A•W̆`Å<Øö‘¨‹‰´™”}ºåD!Ö `¼¬¨C,ÖF…Cù!Ь²Ô€øÐ,rE3^Æ+˜ ]U:pƒÜ G0À\èY! ](Ãí¨™ÊêCzÑÀ¬Àâ"&O`$fÂ"À 1 >%\a“ ùNn,„! ÑåeêóÀ5 ñP¥LRAi ùŽ÷ÀçA—|yWÅL2$`å>ÐÀ8` •GãBR„…!"a(üƒ&_H9BSZE™à¦7‹i€*À²=³þ¬å-s©N½…K,¼Š¬ tà’Å,&8ïy\A W¨Á°Ð`¬ŸUé o¦|O Y@hBéiÏ„xô{6˜hE‰Ñlæa:AeðÄ0LVŽ´•V§AP™RXá–:Ø\K­òR½ á í&1¿I8õUÐ)AŽ*Ñõyf|C%ªn`š$xo4ÝG"0LŠhµ2óc…”2S|ïË*OŠúÄ*Ò4xB¾YR…ø€ˆ¨%ít€¹Îu«záAŽP`à@X¦“FT¥8(žauB×¾ˆÁ ¯³@ €¾"Í‚[»ƒ‹nÖ%åþ˪81ò 5¤0KŸîó™¯ÅIl÷â‚<¶UXˆ ªà=«šAvÁ.bm3à*—¹5àݗ w>»Í'h·ëv—%ß•OŠÔà M8/l§ëŸ“Š¡ X wäÛ’ôÆG¦…‚jø;߃I©ë{ÂOÜ_úVi™bP°R¬ÿ²ç¯7xèPÐ@á•XØ<ö¥K;ì‘—‡Ï8Lb”˜˜ÇŠÒ…üòŒ<=ê\ßzÕ½~u«Û|èi7:AºŽu>‹ÝåTÇ{AЮsµ·ÝïYHÓéó¸cäëS ûUÆ^¦ÃÜ |/H×#ow¶;]ð·HÐ5‚øýîK/»ÞEdÀGÙí§¯»ÔáÞw¹„ð–‡=K_ú¼‡ò··ríÂwÙC=ó@7<ëïyâW…ökÊã÷žûÂ'ßô«W½ð¥ÿzÎÏì•· òÿþNùÖ3Ÿì ¡<ì{ù·k~úÁ¯~â³üÑw_òÎo<ôÍÏû¿×Ÿþ凿ëïzß«dû—g{à÷}ò~»7ÞgyÑ—~úwx×·~þ—˜zÊgvñw€¸÷|€¨‡~ ø€¨~ÅÇ~<1À÷{˜¨{È‚ø H}üÇ€$‚ˆ&ˆï‚¤×‚=x‚ø‚÷·€ùǃç'‚ÖWƒ:‘ƒD¸ƒH(€öG€ˆ€ ƒU(ƒJ(}7˜[L(|N8ƒ(¸‚(„.„T˜…ó×…c¨†ådžñ… †J¸ƒ“‡gˆCè{ø„4‡ˆ!‡UH‡g×|þ*8…Aˆ†‹ƒè‡E(ˆŸw;G‡Hhˆe…?¨‡yH„}x…(†Zˆ}‚¸v–h|˜˜ˆ>…œ˜‰|èŠø‰n¸‡’(ŠKe±Ž—‚R¸Š¨‰ªè‰ixƒ›·³èv¥Èg¸Øº¸‰>h‡iа(h„¡xŒ‹X‹Øxg®‘‹Ë׆茌ȊÂ8ÖØˆÔ¸†Ih‹oø±ñp¸‹£GŽãø‹ŠXŽˆŽç(‹lØyI˜Œï輎dH­hé(ŽûhŽÄøŠ¨¹Œñ¨fH÷H‘ ‰‘Ûh‘±¨ŽÆ‘î(‘͈ŒúXŽÁè½˜ÕØYwÙþÌø©¸’I’©‘¾7xÃ÷’€’%&’4ù8™âwùˆìؒ븑“”!îG”öè‹©’U‰’ Ù“9I‹¤’29‘Eé&©”Ii“g™’]éŠ.¹–±–Q9“Y“i”xhŽn™•>™’m¹”È—‘!•ÍG—c™“Gi…Xi ™–^ € ‚Ž„©—9–ãŒ[y„,ù“_”a9’”©‰e‰—{I~X˜—›Ø—NÉ‚P˜r9•Y˜ìx˜²È˜ ¹˜›™ÙùšƒI•Wy¡i›Á‰š<©™{y†@É›B9—À9šj™˜¦‰”~É—M¹œ°5œÏþ7™Åé´Õ9œ¸™œˆý¥Ü÷œˆˆ–Ò‰™Ñ¹š©šæ¹qïhpõYp÷Ipù9pû)pýpÿ púoêoÚoÊo ºo ªo šoŠozojo Z‰¼Y—d¹žz™7™™4ˆš‰8Ÿ+—y¡q¢±)*ZÑ¢J0*œ1£¹ù¢‰‰0*ˆ*šŒh£Í¤>(¤õ¸DJœGŠšGzƒ@ʆ;Ú=*\,Š¢Sº¢FÊ¡)z—5êž8J£:š£ñ¤¥”X¥ y¥V𤩦nɦ¾·¤Ѥ!¦A¦Þ%æ5yê¢û°§1ê§Z¹X\*¨þ^z~ʆæ%ˆÀå£%Ú¨î6M\Ñen&°ðL” –ep¡Œh`0ñÕ©Ÿjs̪1ªœZ©\w1ªô„¨:w¡Êª±W›º·   eªªŒ7 ±W¦«¾©Ê_:7ñL7w«Üx˜„hñ€*y«š @K‘IܺÞ ªÆÚG©p°­Ýú­ë®±WG ¯èº§ri¯1`úš®ÆZ¯ÒºXä:0çJ°õªŒ Ñ@ ¯7wÈÚ¬Ój©‘ªGá {ð:CÀ±Ç*—©@±›u' ‘:þ'ñú®äÚx»zÅaíúe3É® ¯}³Ìš«Þª°6ë³>´ë«eµ©DÛ³G+G[ ¬L˳7 ±ÒбA´D ³ö:³Ñà«cKî:²°»­Ö„«I;F@­]»F«² ë²\¯2;®Ú´z ª¬:ª¦ê·© ®PÛ-;´V[¸ ´;§·[³}ú©Y{°¾)´å*¹Û±¬ÏÔ´èúµZ;ØjfµX;ºfk²cPmûzh+¨)ô²èг-ÛµW{g>׺MpsV[·ªš³'š ¼O{±â:¬Hð»Á»Á¶ÌÛ¹¬ê›_ÐÖ[´ë¯ »/Û®ÈÛu@ ±P³ïšº°G«`¡ä¶`+·´k¼ÌÚ²K¯n+MP°± k¿ÛãšBæ:¯€«½ÞZ¿†‹¼1®¾²ýû´ë›z›¿bðÀÂ;² !¶­Ê­©à­ ¬ °¢b©œË«°"›´xÊg(k¿°{·3«Ü8{¥© B2l·áJ«½j­*»®‘®>Œ«ÿûsê{ª9ü°ºÄÅôªžµÇ«¹‹ª• ¶ÛKª=<µ Ä A«7ü³ÓjwC,!ùû,X“þ÷ H° Áƒ*\Ȱ¡Ãƒf¤<sæ¡Å‹3jÜȱ£Ç Ž‘"¥‰I)!Sª\ɲ¥K—jÄ”)¥â>4$ÍŒl"Å A”rú²¨Ñ£H ¦JCS šTP#Šz&'T¨R®jÝÊ•kÖ®`Á~Ýš´¬Ù³¯¦I³µê¨e´Ä!à ְx·ŽÍ‹wïU´€ ˆ73hÎpY̸Lª·|óBŽv²ÖÁ˜3Õzæ°1a¼| 3ÆË–*PÄxá&¢ç×°cÇî)»vmÚµeiÞÍ»#çƒ7Ž\x¤wB1ÈŘÑm¼¹s…¿Ÿ[L®œ¹ôëÏ£cgH}ùöï¼Iþ’#üB$è‘ô4Ï~°öö»[‡Oéûúûäãß_ô~}ýü¨’e6ñ €zDÒUîw`‚f4d â÷`„:$•]x!à…†˜PDVè`…‡‚(âŠ?UáCeø”brÞ±hcART¡Ã©TøóÝh#`è€E)ä’û˜¡ƒ^€‘$r52ic*^˜ˆ¢JZÉâPu¸¥ƒ]z)¢c\¡å”Õ™¹¢UàÀCåÍHenB8†8d¤mæ™áž8ðè#}* šbœ<€›U*š I]˜Ø¤xJÊŸPñÅ¥€Fª©€P¥á¨\Þ9j‚`‚qCþV`º*‚$ñp±†šé¬ðEQ…VPñ…¬¼îÇGøpy8èZ,~Ç"» bŒáƒ³ÏÂׄÇV±A]ˆ±¶Ù²gR´XàPE6[.xMD{,T<`zêÉø®yQ0/²UŒia™û~'¯7œHlÁàË'1.̰ÁXÜÀ£½©:ñwOÄz$ÆdªºñwR q”¨†¬ñÈ×9l©Ä,?wl*;à sÌÍ1áD %Àz3ν5‘Å>%X1l€8ôPÅÊ@'ûpð'7T]…¨Mó&Ƶ5L½WÜÐÖYkÖD¬\!ðUÜ€Ùecv,=çj`Û<À·þ`L¼•Å)ã—ůê½7`2qê5~U÷ÀôáƒMH•€Š yfnMîîå‚Éæâ?Zι`O¼…E‡lŽÆèrK¡C>Û)…r¬&…pàÈ‹1Ñêµ&5ÜúYW|„\INØž5èp¼Y6ÜàÑòMÐî&ìP '|ˆ<æÏš+&Rz€!xB,Ù 4„ŠBþ¬&:c‰Má ä8á€@“;+bö™uBò (B dq§ ;¢‡–Åü€Lx„ƒ*¤—Ut!GùÙÆÁ´k ¸Â¬Ç˜ hZ©#ûÙ4Á!YÀcÂh¨S–†Ð<ËB€µ»¢rÄ£Ø!\¸Tè…µ9õ!P½Ž­–¦Ñ\U#Y•Î(}`\5õ«iá)}ª`«ªY!|h½HX¥ó+d! ÀŒkBæúœDéu¯jEÁþʾ:ǯ„5ˆa›ƒØÄ*%°ði¬c ÙöHv²‹5Îe›ÙÞl–°â™¡€ðµžÉ.¤³Z¬i[Yö|–°¨ÝþÍkÿ[ÍÌV¯µÍÌmãš[Ìì­½ ¥jWûØ– VtÄ-Hp;7Üäîƒ3Ô‰®t“ƒ„éZ÷ºÕ½®v©“Ýé¾’¸gØît»+^ì–w»äîwWÞóR×½æ…ïx¯»^ÓvÆ6·Á¯~=ƒ›ýÚ¦¿±É«s5¢¿Ž˜#°‚Ìà;øÁް„'Lá [øÂΰ†7Ìá{øÃ ±ˆGLâ›øÄ(N±ŠWÌâ»øÅ0ޱŒgLãÛøÆ8αŽwÌãûøÇ@²‡Lä"ùÈHN²’—Ìä&;ùÉP޲”§Lå*[ùÊXβ–·Ìå.{ùË`³˜ÇLæ2›ùÌhN³°š×Ìæ6»ùÍp޳œçLç:ÛùÎxγž÷Œ¡«øeË~&‹–}™AߥÐY&ô_ Š?3Úщ>ô¢#ÝhASÒXV4T-éM?ÚÒ™ît*8]iD‡ºÔ“>5¦¯¬éQóùհ޵¬gMëZÛúָε®wÍë^ûú×À¶°#‚b‡¹Ø"8¶±ÁŒle'›ÙËþr³¡ýliGÛËÓ¶vµ±}í.g›ÛÛöv·¹ümq‡›ÜÅ!ùû,G–"þ÷ H° Áƒ*\Ȱ¡Ã‡¥¤H±¢Å‹3jÜȱ£Çgž4‘"æÌÇ“(Sª\ɲ¥G4R¤Œ1#2JL)höIÙ¸ScO—@ƒ ú1•ÑTaºd¹bE “$E’L#3•”£X³jÕzu«W¯]¿K¶¬Ð£eº¨U+†É%pgžI5æ«Ý¬uïêÍ+֬߿€3íâŒ0]¬,3fJ’%cfJ1C¹²å˘+OÎÌ™óæÎ•eMºô@£f¬x)$‰“'F¢þ4]PŒm1fDÓÞÍ;èÍ*ª I’$v‘Þ‘(G2¹óçE>ˆ¤È!ÐÞÆ­;»÷ïÑþvŸp{nòèÓ<:f‰úÚ·Ï¿ŸïÝh¸ôµÇïž¿ÿn£c$ÁDæñçߣ…ãåW ‚6†2aEƒô=á†cÝpU¸‡$ú…h¤ap«98b‰0®Óû¤’†Á¹h›|1öI5ÂF‹¾èã‘ÝHÒ„Rœ†ˆ;ˆä”™á¡CBÉ•\^d…3åXd”]–ùU¿Yñ…–<šéæAR@1ÙcP!æ|¾©'APX†Ö¥?W4¼~ ¬^7ä~ƒÇŽÞ±Œy[‘êIÁà WT«¦ï¿3±]U Oî7dáÃR^ôμw9Ú„ôóUaCºgáEÛ×Ä»:O|zRlÐVàpNé{'¼ü‹a;YÐHjà 0`èËŸwÐ$†4¤0AFl0>n Ï`ÞGˆ ¹æW¿€M$ƒõIEj88„À AEl0ÂòuA{(tNšXH¡cþ?!D4Ðø`>¨¶røœ &"(AB 2\"7Ø@õª¯'1:hááP:àAÁGàC® »Ýáð‹½ÁÊŸþÁ2 ‚¶$†á„pDVLò(†Šw €®€‡˜ç¤Í ãÁ| w€"©p‡˜‡F‘ŒãQéba0‚@ H$ð !Ÿ ¥(BJ³Ò˜vi*4r!”%o&IÇ2Š “»d%™,ìÀ=ø …YbúeÆDæ.yY4³àì@Â@MIŽò‰!Ø@6IVnS‘G¸¢AnÎXaæþ+§i¬iVA UP¥&YIôUèdAlü“‹]@‚>IÃϲøsÝG"0EŠe°ÀÒšN*ˆ “èDSQ²R 0B&@…h² #EˆIh?1®”(-Kªr„ üG S„Ð0œãü)KÏY1$A¢?¨@Pf"¤°¾;©%¨~)€ö„1 ñòLnðϦ™u(h5‹R$X`¨ ñÁîlv×±ä50Qh‚C²0X»ö¬T¥OèJØÇž%²ó¹VfY bö=0älg/KËÏ–%‡ýNÐNë’ÔêO[¬mþ‰k³³ÚØ®d¶Ð©­mS‚Ûçèv·'é­s~ \9Ä-.On2¡þ¬9ÊUÉq{“Üè æ³ê©®u/2]Þhw»éîn¾ ^ˆˆ—6ä-ox°›žôª—!ç5{ß«øV¶ôå.{Ñ3ßüľ¤é¯ àÑxÀ§Ù/yŒàr;†0"Lá ßfÂÎðv0¬aÇ6ø!gÐð…E,b“¸Â&¶°‡?Ü3€Æ2Ÿy1hb,cÏÔxÅ,î(3²cŒô8Ç@²‡Lä"ùÈHN²’—Ìä&;ùÉP޲”§Lå*[ùÊXβ–·Ìå.{ùË`³˜ÇLæ2›ùÌhN³þš×Ìæ6»ùÍp޳œçLç:ÛùÎxγž÷Ìç>ûùÏ€´ MèBúЈN´¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬i£€Z§B ³ŒArM‘[#„× ñµB€a7„Øë±²]^3#=yöPj­_#»×»Öõ¯-"í‚\Û ÝÞöBÂ=ìbgÄÙ‰¶_¨míŽ<ûÛãN¼ÿËyŸ"ð&·¼Í mp§[ ún »qíîlc$Üö&p½Í‹oø*›ßA7GÔm–ë¤àÞÖ6œ¸½ð‡œ ù~8Bþ>¾o…Oàë¶5Á9ònÛ IxïfC\äÉ>ùÌ+®ò‹³üå7ÌËíqš×çþî·Éy‚ržW{åiùÁK>tªÛè7ȲÏt¦ï¼,—ùÑ3>u¡3<ægˆÃm>r®/Ý'M{ÏÅžuŸs×êEG{ÞÕ^sŠl]é9÷:ÉUvŒƒÜåd¯ºÙ×Î÷±û½îX;Ü¿N–Âÿ<ñAßxx;žÈÓ›íŸx×'?ø”X>ê@ßüâ;¯wÖ7òœ=èï­óÒ£äô‘úÝWõÖ÷þõmŸýÛ…Oy·Ûþ$¸G¼Ý3Ÿz×ã½ïÇf¼çi/úá+ýøI~Ùþ¿ýæÿþùŽ>ôµ{àÿú)úò•O÷À{_ú¼/¿æÃOþàWßýÆO»/ÏýÝÏ?íà'ï‡æG}H'yùû‡z˜§zÿ·whâGˆ€g}Õ‡}(±€¹7€ß×€Î{#Xççyhp)8rX‘wízñ‚ƒÌ'‚0H‚7h‚+(q‚\-Èz2h}Aˆp5ø€ãç{%8²w˜>x.hx4Ø} øFHD‡…8h€Ä·‚Üç…àöƒ½„8„¨…ýg…Y(€"¸ƒKè„Ñ…@(…PW„S¨xWȆi˜„[8ƒõׄ"†&†kG†\¸~þxx‡hƒox†~ˆ„€h~‚ès“˜`r8†tˆˆ‹¸‡!˜‡|Ȉ'؇¡(‰ÿ6Šúu‰…˜‰fŠ9èŠØ÷w÷wˆph\„Xs†h«è‰h¨ˆð‰¯¸†ø‡´ˆOhQȾ˜ˆu¬XŒ°x€‘¨„x•p·8v¹x~»èŠŸ¸Œ½è±¨‡Ï7‰g‹¨ˆ‹ª¨|àø‹Þ8â(ÐH¦Hs×è`é¨ëH…œ¨†UHŽÍØŠ¢è†ÃÈv÷˜ÉÈ€ýèŒ y„ï8ñH| ‰æXÜ–ü¶ºg‡é )Ñ(‘I‘¥x|ùŸØ~'s¥r"Yþ$Y{Ç8xÈ~ìØ‘îøß‘LŒ8X‘2Ùk'ùy)ɓɒ¢’¼8’žHðX÷¸{Gù‘;©‰WÙŽ=‰”N9€P9‘|7•AW•Z™”i•Ù”Cé•n•a9kaLrYF™6—sY—viLx™—´—|é—y ˜v)˜w‰i|I—†y˜”˜‡I˜ré˜zɘ)™I™ƒi™…yiй˜ªG–:Ù‰eɆ.9}i_Ù•'–pI{‰­ÉñšÌH²é‘Q›[y›©›¥ù¯©š0‡›ü(œ››giœ ©ÄÙ›¼É” !œ“ø›,%Ëé€ÈéQáþ¸Ú ”Ñkù»™ÞéÒ©€Ôéš9ÉÇ™žÉ™à‰}Õ¹‚ÐÙçù¾¦P IT멟ÅYþ™• Î‰ – A “¨PÀ¹šiGЩ€ðcãÖ‚©ÐIx”G°KX³¡Pˆ  XM0ð…ŒÙ.z¢†^q!ºK”£#Š·V”aSÐI+Ú¢vÑE*¢4Z>JEFÁ£ù8ˆq80)s.êq? PZêVc ¡Šµ¡>ú£û°¥`¦ûº‰ŒDÕ¢kʦ6Š_jZ:dꦖ¦j þ)30ú¦ Ê¡ƒ‡@à¨@СPZ¢š¨pú§ VltÊrGaR¨ X†Š?æÅ¡WÓ¥qú_Œ*\:ªûp5õ¥ƒºn%£ûª¶Ú¦ƒH«›:Bjh€ªªª§sn: «²ê«ƒ§«©JmJ©­ðBFô«rÊ­oz] ª¢¦DùƒÂJ¬wlÎj¡)ʬ]ʪÎ*fÊ¥¹:®?£­Jnª¥èÚ§z¬ .êVOÀ«ôН{Ф©Z¯ta«Ôú'z©€ªÙš2Þ*§QŠÕ£®Š¥«°ê¥_š 2zkýºª6Нò ä¢Û­»I @²¼ ¯*k p²Ø(¨+º*¡H`°‘аµºÚ ´:ªÛc ±o K²ÛJn0š£{¿Ú§ô*²ØEk«a ¨³ê³}êVb`©(k¬ KT.ʵ1ë§äz©c›¨ÿš¯8ê¢MPi«XpJ§M«hp¨YZ#\ª· A¦wŠ©=«©ñ²åj¦z¡5*²—(¢±ª¹/J© r¡C*³,·›ôº¤);²Ò–£]Ê£0Û¤ !¤D:ºµ ¤²Kªº‘£ÂÚ¡":¥»¹kj!ùû,D™ þ÷ H° Áƒ*\Ȱ¡Ã‡ј#E Ä‹3jÜȱ£Ç Cz…+7.ݺ`óêÝû “&ÇŒ™’Ä%Å)SL*^̸±ãÇ!›áK¹²e>ÑœƒDÈebB‹1#ë³éÓ¨IFq"˜ ê¢G—~M»öW4RV ®i»alÒ½ƒ )+óæ'/‡/ü=[¹óç ÅJ#f3`è™cßÎ} XÀÓÇþ\ï>P;ùóΣ6Ó:ùyóèãÛÎ ^ðø÷¢ËßÿZÊ“éÕ‰ÇwðñgàeNL§ÙN¸G^F¸—I»É¡„zE¬Mfa~Íe("WA¹Ta|ލ¢H¸IF*`€áÃ(‚¸â7U$F*:XáÅ 7Ôš~8é‘Rt¡ƒaXу²)eGQ@áà UŒŇC†8å—EÁ„6di—Q‚©¦CM4q]€šD®içAMü5VtñÁPÖyç IÁPHQÅd¨—„z(böÜ襩šb<1i¥Gàð覑æyh•2QŨ¤Jz¨þg°G)«­Ú9i›ö ¸¦µ9é¡•¢Jk¯jNEªvÇ+±7J‘g`c0¡+vË2«¢§ÒFÑñ!á-Rxhí”g8ªDÑ&K ã~‰mƒœÑ i»7šËÄRL±_µôbîH@1ƾìö[$EK,Q)Á]l$M$\iò:Œ£¡÷"ÑÖPÃZ,"¶OQ„I ѱÇNú„EŒ‘„g™Œr†“&QÄtÇÜðÌ#ºD©q2Ïþ•DÁž©sšD§ÜDÉi!AÄÐMËÇ„§N Ai&/-hÕû!&°. íõ¼`£'Ë¡·GuÚä¡q(´Ò¾ 7whà`þÝgßm záºÝ·ßûEK6Åñ½3áñ¡¥AÉwC‹3N^ þtœºÛÝÀCå–s77Yj¨~:tϪ‘Š,¦£×A™L¯Î`š3È9vlà’ê¶;¬E_5 |ðéVü´¼Ó¾<óÁ7rÇû^;õÊIGzì”…àò©£Í½m~Õç2·•‰ BG6Øp¸âž¯œõ Z&B<8ª‘ç ³_pH-è}EØÀ.ð¿.4aWðè Tç ŸÙŸöA†d¤ ]è@²`…耂à /ã>ùàGÀˆl0»8q ]@apT˜ApÀþ0Àvp1ÜÐ ¤¹ÁuزP9b^ˆ)‡ˆa@¢,”˜&ÚÆ‰ú”6E)ˆÊŠ8¨!n˜C/ÒŒí AÈ8Å#Tq!WèBÐ+”à„n|ãSV¨?x@i™bÈðÀ|´BjHA:…• An€È} Ž1\H>ø¨% Q%OÇð‰`g ðÉ)>Áƒ Ùc®@”€«då ?‚W!û˜e-§ˆ„;dšä¥,|ù¤`~¦•|Ñ$'‘©Là›GXdBFYÊS–À Ö¼æ0/3Ém ˜¢,ð ,pœþ  KpÆtZ›{ñEŽ9*D€–Mp&B® +Ü ^è¢?/P½„fG¨ €—Ë‘ÙD)SѼ$ÇeHûA &€4!ü)WJº—"PJ Ÿ3ŒðÒìˆT¦z¡©^œ@ á>ðÍO ¡V&~‰)SsâTçHuª7©êý–ŠÕ™®Ón]¥êWÖ¯hu8W-ëGÎ*œ´ªµ#l­WßšÕ±â'€tÅI\{ãÖ¼b„B»ÛηèçWžìÕ6}-,D[›Ä*Ö!Œ¥cËȾf²”UˆeQƒÙÌ"d³§é¬g ZÓˆv´)ígN‹Z¨ö2¬mþík-[Ôζ2µím)“[Ïî–/½Íìo÷\ʪ06È’ËÜæŠf¹Înl +ÝâŽö Ñ¥nu«Ýí2·»Õ^k5«µ˜÷¼á:¯z×ËÞ³´÷½ìM/|×k¾ñâd`Á/Gôkßþú÷¿°€LàøÀN°‚Ìà;øÁް„'Lá [øÂΰ†7Ìá{øÃ ±ˆGLâ›øÄ(N±ŠWÌâ»øÅ0ޱŒgLãÛøÆ8αŽwÌãûøÇ@²‡Lä"ùÈHN²’—Ìä&;ùÉP޲”§Lå*[ùÊXβ–·Ìå.{ùË`³˜ÇLæ2›ùÌhN³þš×Ìæ6»ùÍp¶Éž2TÄ€±³AðÜ=+„ÏñsC]gàYІ´Mf‚è¼ÌÙ)…¡!{I'¤Ñ{ΦbéM3äÐGR´H]™GûYЖ®l¥=âiIk"N´fCMÚEGš/¦¾5LD‚éT‹úϯ~H¬3j™ü$¤¦L®]{g^¯®Ÿ¶C†ý×…´zÚÇþH²qMgf»:$½fu´ó ëÅÊúÒ´.ȵ«ímnCºÝ»÷³9²n_ïcÝÍ~¾çýÙt§ÖÖðÖ˲ïo„Ú ±÷¾ enb[ÛߺÖvÄÝm‚ÛàüÖH½¥=膳ÝÆ®õ¨þ'–£ÚÙ™F8°É-l_¤Ø4ÉvºŽ“|­ç8B^n}Ÿ»ß!W7À-îîSß\Ü)§÷¸/Bsj¿üáAÿ÷ÈNñw=ÞOúF6ÎrlûÜ᳎:ÉcNõ’WüäòֺƗîr¶«ì '»Ð§~uŸýè*Ç{×9­ó„·½åa—»ÔC²í½Øüâ8W{ßßçžSî@üØ7Rx»[íY/ˆ½ç¾r¦;¾ñ—ùÇìÉwåð O»æ‘Þù¿·þí£ýÓENxÓïó‰_}Þw¾x¾=ö´§¼ì…_öÊàžõz÷¼ß÷îv¨Ÿóć¾Ä‹Ÿ‘-X}íˆG~ö{þŸ|çwü÷³{ôI?ýºsÄú4?öæ#ß}ðóžùËüóÉzêŸ;ý AÞ×/zþÛ?õÊçuò~ì÷s¶÷qø—ú§tÛÇ€ºç€¾€ß7€Ÿ7|€'}3 ¸uíXÇñçzzów€Íg~A—€±Ø€¹×$8‚î‚°G€(€ô—‚x}çø-(ƒHƒÞWÁg€ÿ‡€;¨‚ºæƒ9èƒ\''(~Føƒ6˜„áÇ„Á‚=ˆr‚¯7„axWh…U¨…ó††Á…{ç„$…SX„DH†U˜ƒe¨†ˆ‡Á†è†e‡sXƒqxƒBh†h‡þѧ‡ûÀ‡.ç‡(ˆc(ˆX€‡Hˆ'¨ˆ¨ˆŒ~Žxpø~R8‰–˜s†ˆƒ&èxšÈƒmè…0(‰ŒçŠ…H…•x„õ‡‚r—Šhx|»ç‚AŠ"è‹°8ˆ¶x†C‡‹ûÇŠ Н(‡‘H‰¦H‹Ï‡‰XgŒ؉¤¨Œ_8ƒÍ8ŠËˆ…s(•w~ªØ‡ÈȋܗŒÌ‹Î8ëHŒt'ÈgÝ(†ÙHÀ˜ŽÂ¨†ºާhV­¨¨Žt(ŠÐÈŽÜXˆüèq¶ Ùù‘9‘Y‘y‘™‘Yºø‰æèõƒÂXŠ9Œ³h’p)hPŽûÐ’»hþ0‰1“"ù6y5©“ 1“* 9“A ’1”ÁxFÙŽ ‘” ™C)>ù,é’LiQ•J)“¿¸”øˆ“<É•MÙS©zE©}HyŽZ© Q•jø”6•¨!h¡”y1—`˜v©–‘—]©|I’ ñ—Ò8—?¹‘†‰dt– pNqPК¶N0K1Ô o’YqG0K›¹Ž ™•egQ/´˜©™Ö™ãX—³14šu–˜×%›ª™™p1›½9š[ÁPA™²™ G°™MhÀt@©0 ™‹þ Y¦Ù™û€ðÙ¹œªéœÖ݉uh¢É——wš 1AžÍùœèi›q@°cžî¹˜§ùšc°œ©pŸÅTO‘M  @CàÉ_Ðùn“OÏÉŸŠžÑžM¡ßžš¥Ÿ·)˹˜ÿ¹¡ý©nRáZ­–›€":¡÷67ê–¹¢Eéy¢¡©@¤6š£úF©¤p*¡î™Ý– ”y>JžÜ9 ~ö™›Y£QÚš&ʃ÷©¢ŠQ¡Y›Ýi¡ž¢û0^ ™ù顸¹™Ö™¡Ú›0z£ ¸¢EʘlФ…œŒÙ¤ã¦ù¤#J¡Ü™§þYy¥-Ê™úY—Ó)¡$úY8z¢<:K€eÊ¡–ª¦ ±œ5J¢qšž Á¤íÉ©jqù™üéFZ©zcÀ¨ŠÉjNʘöižÐ‰¦MPøÅ¦ÛÙgiê¡Ö90“Êœ¼Z¢9J ‹)¬yi›/ŠÇúž»ŠŸÅjªÉœ€Ú9¨Íª‚JꪠBªÕÚ¦„zoª˜ð˜y¦¥¹  ¼ ©qZŸ³4£¢ù®¤yžz꬗y¯¤5¥ç ›¬Ù¯ð £ùú¹9°š©¦»6Ú˜þº 1£!ùû,L™ þ÷ H° Áƒ*\Ȱ¡Ã‡Ÿ0i2æ Ä‹3jÜȱ£Ç Cnĉ”“cDª\ɲ¥Ë—0AB‰òDŒ)Q¤ÄÜɳ§ÏŸ@¦‘ÒÊI1PšH±¨³cSŽOƒJJõ'Ñ’NJ–Tz²«×¯`ÊK¶,تhÓªM8FŠÑ1fÄœ,³E†Œ-eRéÝË·¯ßTRþ xðൈWÍ ®™Ç]¸HæÒåŒáËc.kN•ys_Å Có̉&™1pÃP&#%r˜Ç°cËž}“¶íÛRnë~,K´ïß ÛŠ‘e¸C1ÈÍô6μyöRÒwž¹ÍåÔ³k'}ºö‚Ö•þßœ¨âäÁ'Çž¾½b¢P¤»žýüûU¡ËŸ_¿ÿü'¡ÇßzÿÔIf¤â]zýè ORt¶ y >h¡K*x_…v(ÒO iä“ IñDLüP…U8 å–9!ÑU´èž‰\nIåVˆ±!e¶)ÑWd©e›\–T…]¬ù"\6ÑDf„¡gŽ|B)†Ÿjø™…êEQûÉf£F–$…€’îIi‘%=i{Œnz"QMD ꤢÆ(EIc¨9þ°æ–ꎇ6‘è DÎÚaQ ¡¨v¡êê`§Šyª¦Â^¸êRgœ…¨&û V¡1sJ;íVbXûl‰Ñj[ •T2ál¶â8«fàš®…=²j,ƒá¾{ߺ%›)¡öú‡$g\‹n¿óõè§ÞLp{nIÔ„¾Çò»ð|H¢æîÄþN4âÅßEI·*Üñx«2a*½ÈŽœÞ~ž ­MíªìžyŸ¢ —Ìî•$††.î3Îä…Ï™î3/ÐÙ WóË#=ÞPÝéi´Óã©q’Ëàªù3ÕÔA·tÖ7s]N þš]x[‹ÍiDGœ«Ú¾)m6uÁ ÔX/Z¯þÝ¡]Õê}°"!+߯y=7O7|T7ákexøNðà‘ 8ø 1ã 1tÛS…ÐAžIlÔÁèV¼9b{=Sà ÆhäCdQÆé¿¥žl€Mb„œ Y€»oº£eE¼nÀG/ñÆZòUÙÐ b€bXpÑìµßn=hØSåÁ>\aßÃQô—~>úz©S7hoÄ>€¤=‡ÐoX0Ýý¦’¾©d¡ 7èÀÿ@ÁçÅ®!:ÀÁ² ¨¢.iéBn°ÿ ~h‚dá ü ç¤R…ÖÀ„¬ ôÒÂÆp-þ ”Ьà? ±‚M’B2¸Aóý-A Ê ú'AÂïˆTýBp>±'QÊ|,ü/€ð#€Ö¦ „6èÁi¾E¢Å:Ý£`ð½1Á€TâA¬Ó4:2ÐŽU±Î>¼7)D€‚MÀ¡Bðx4Cú$Œ?AŽ@ŽPèA(€$«CHKr†AqÕ>^·ƒ+¡@•xˆœBšò'˜”Jl8 ˆÁHØáBÂSÉ[Æ$—AqA~€„"|kÉI›1w‚ÌÅ4á!„”æ4aRÍæ(R›ÛtI7™óÍp‚‘Ç ›9‰N”=fÔlçË.Ï•ŒÓ8‹þ«§Gî œ|ês#]ùÛ«bÎ~„Ÿ¿ñ§A3‚Pß(t¡i¨h чH44­hC. šŒjt!UŒG?š&f¤$=ˆIƒÒ”d¥ki©KSµÈt¦û¨iZn:SÞqo8uˆO Ô 2d¨Tá©K‘:¥¦”©Rq*IS‡Gë ¡ªXÍ*V¯ªÕ®nÕ«^¥§Q!r†¬r¬`=+Z»ªÖµJÕ¥eýª[Ù:WºÖõ­)mÖlr³›¾òµ¯ºù+`eãűf¤˜AìCkØÆ:ö±¬d'KÙÊZö²˜Í¬f7ËÙÎzö³  ­hGKÚÒšö´¨M­jWËÚÖºöµ°ì­lgKÛÚÚö¶¸Í­nwËÛÞúö·À ®p‡KÜâ÷¸ÈM®r—ËÜæ:÷¹Ð®t§KÝêZ÷ºØÍ®v·ËÝîz÷»à ¯xÇKÞòš÷¼èM¯z×ËÞöº÷½ð¯|çKßúÚ÷¾øÍ¯~ŸzójÆ¿ý-ï`òØÀïœ`ñ.ØÁ ïƒ%aðNØÂþî…5œaïnx¿ ±ˆGLâ›øÄ(N±ŠWÌâ»øÅ0ޱŒgLãw}a/î½±^rŒãöê8<Þ±{ÌÞÈCr‘‰¼^#'ÉKVr“™¬^'GÊS–r•÷!ùû,CŸ#þ÷ H° Áƒ*\Ȱ¡Ã‡ÅH™(%ŒŒ‹1.–XP ǃ?ŠI²¤É“(Sª,˜ÆÌ˜3iÊpñbFŒ.eRéÜɳ§Ï1>ƒ Ý t¨QǬ\Ê´©Ó§'wžéÂ%Œ™Š\º˜Ùʵ«×¯R¾ŠË5,Ù³^¡ª]˶mÊ4RÝ®Cw«Ü»xóÊ…«óŒÞ¦tÅØýK¸°a‘jâFxðâÇóMå72ÉÆf,kÞ|wreÎ1ƒMš©âÒ E£^ÍšãéÖUÞM›àëÚeãÞ½ú6nݼƒs.JYø>àÆ“/žØ×8råÐÿJ“æ3ïçѳ˕¤ɘ1!¯þ×ͬ½ü]1LÒ‹ÃDLpìæã3MßD öá—Ïjz):±Å{ûõgàRé•~ FÞžÔÝR|ÇD~µÁᆠ5!Eõ'œ†–˜Ð5aÝn$šè"Až9Wà‹4&„†oÎXãŽ%Ö܈:ò($Ž´µ(ä†7þØ cGîHa…î ‡Ä”a5)dŒ@:h%f9›‘[öÇ\qY2¦‰u¹¢~Zžù"x@­™c›nšxF…d.ù`JtFžâÑÉç†O%g‘Az`Êb¢Šö—¦£lšipR:§¥—Ê—é¡_BÚ©y“‚ ˜£7¦©­¡šjpþıʚ«¯î†¥žµò÷§’â]á]äß®šyà Vx¡ƒ–çc±³]qƒ_„‘…Á6›—°eqC¿^{…¶Ûòº[Ôl 5ŒK.tܶÆC6XÆ dñ®r·ò†®º^Ôлû ×ï¹ßþÚƒ\¼¬IK­µØ:lÄ«‰ql²ËZ|±¹úù ¬ÇC{ª¨$Ãvp¥{¦\epS"Q¥Ë¼aŒ­8k¦si<÷ÙÏjÍLÒ ( ]ÑPIkRVªtkL?åà W$AR>H=uo ëå­H1×^½tØyI¼Ï9ˆuÚjVµSWl°ÁþÁGHÓ]7gw7µÁ €cðÀÑInYáLm`ÅŠ/î7D!tÍ©ä›Q¾²R!ÐâRñP8úÐlãEºéû°8ª7®¿þ˜è+YŽ9A‹S±ƒîQî;dÀ«txâÄkîCHóàkÃËOû]yCoû÷PÅß ‰ÑA¾Vtwö>o/—ÄŠ×þ½í}“мõÞÛìko²\WÃ\äà€Gh‚ý ‚/l¥8^ÿ˜ç>·Œ-sÀâÄ„Zà ià;€ƒ Rðn_&ЄØ® hþì…¯˜ðwl‹Æöq„ üþH@ ðƒ†˜ÏY°‚:pC¢P‡¹9ÂÜ„€ ,ß 8Ä `¯‰iÞRŠ`º a À]î"±j]Ë `4ŒWâ‚ EÀBƶ•.0,Ž…™£S¢Ð„‡ðÑ ~ü" å"Hô‘bÙZd^Y!KYÌ’¤^(Iºˆ,’šÄ '¥¹PBe”z š)OB™§f7[å]P™Uʲ$´Ä‹-o9’\ž§”¼\‰/å²Ë`º&‡;¦1Q2L·s™i¦• M\"hÔ¬f/¯IšgjS!Òd‹7¿‰pÎ%›ä„ˆ9‹†ÎtF“›£§;mOÐÈsžþY'Tî‰O}>…Ÿóô§SêN¦øg=9CÐtJ%0b@D'JÑŠšÑ¢­¨D3ÊÑÞ%T$(* ZFz’¢Å,&%ËG¡¢“´´$/%ILWJÓšÚô¦8Í©NwÊÓžúô§@ ªP‡JÔ¢õ¨HMªR—ÊÔ¦:õ©PªT§JÕªZõªXͪV·ÊÕ®zõ«` «XÇJÖ²šõ¬hM«Z×ÊÖ¶ºõ­p«\çJ׺Úõ®xÍ«^÷Ê×¾úõ¯€ ¬`KØÂö°ˆM¬bËØÆ:ö±¬d'KÙÊZö²˜Í¬f7ËÙÎzö³  ­háÚ“ä'+Ñã>PkÕ„µqþm9!"Û…À!µ&H–ÛÜZs'¦µMjrÛ‘ø¶¸ñ­p¢\â.¤¹àÜ­0Ý”6¶ËM‰j‘\ÛŠ¤ºÜMHu³‹Û¨HW%½}ÊuiK^V:·µ ïsÕÉ^‘ÈW ã}oGxK]õò¤»ù®Aî›Üø~—¾Ìíå|MÂÝüz¿þ.vBà ¿Ö½¶pC*L¯vÁ¸rœ{\å ׯW¦q‘#Œ¿™Éaþñœ¡\f‹×¼yVóžmÌe{¹!N^1¡§,hH[ùÐ 13ûËè>»¹Éú%‰˜£\i†xXȈޯ¢µœ%[Ð(‰t éüj2×Ú̘q¢ÑËé$·ÙѰ¤[=f)ÙÖš¶o²3½fÞþºÌ6u¨[êBÓ:׸./³UÍëf óÙ·ŽöŽkb“úÚÚÆ3¶/Ýa=³º)§ ö‡‡-gJ£{ÙÆ^·²Ë½io/ÎÔð“Í=h‚ßÚÐé–1¾îï’È€Üå†xÂaðIÛàÒÞ·¾ÕÝîU/åá±x±þ)>òi[ÛâwÎ÷Ä5Þñn/Z% ¸Ä—=ó†‹|ãOyµ¹Ípž÷ûå)‰ù°kîsaÏå¿7¿í|æT·|Ľ:Ñ>õ¨“üÜÇzΛÎò…3½èÛ~÷@„~ôyk×äZ¿ùÊ‘Žó?ÝVOµØBvQ«ÝÞf·»Ò×¾õ¶+Í^×øÜ÷Q÷Šç}àW/øÝ—Îv¾»=®Ž’»êþ&ú¨v¿7Þæ_»Ü?>õxƒ:ëe¯÷ÅU¯ó¿;]ò=¼å£ŒyÊ]ïŸ÷<˜“ŽlÆgö—–}érÍ?žë½Gþï}pà žô!O¼á¥¿zêëžø¶O¾è#þúç¯dö~>ýî§ßùÖ£ž÷*|ú»¯náGÿöˆ‡ÉÏ_}ú›¿ïÎ_ö'ï~™ËÛø{§~Øwj÷·sƒGb07|Ö7h˜{ŽW~û×|ì‡ký7tÿö‡~¬§|vË7Á·¦wvÈyòÇ‚Ç'‚Éw€ù§%ø~Ø÷€)˜qÚgƒøW´ƒ<¨YhÀCïÀ儎0…Ô@afÿˆëXNHá„îøŽª³f³e ÉeÀûÀþ¨Nõ˜ 99SÆ:á†1 ©ÐA©p"¹ZhÈh¤þ“娥õ’qfÃ’5¹ :Bá’æŽÿØA<ôÄu“Èe”IÁ8=Ùj°‘¨¥“oc`Ž> Žô$”‰@à•;‰”#Q“©ð“4‰•„aR`Ž©@69X:Ù @pJÉ, —ñH•™‘÷…F¹nY•ñøZ>á’1‘ðh—x ŽG\ é—<ôñ5™mYH‡‰™öÕ™ ™û—™,™=é–©“B9|ƒ¤©’ÿ…“÷húˆšb™‘¯y昚ŸÉ8й˜hØ“4›š9`R‰ZÀÿˆFO—GùN\Éœ¤œù‘©ë€z‰œ§Y6Y˜(±šo)sÙâ9›zùš¶i#™œ\Ù›±‘Ÿ)ôDœòÅ’ê©Ì‰‡YŽH €¹™Õùa¹“Ÿ‰yLcže ÿd Y*aœôÈ’*n ™gYHì™cF‰Fb€•?¢RÉ©i”©‰¼I®i–N‰–I¹¡:MP%ZH'ú‘ ŠV‰—a¹M1™’@ð¡ñ”Zù=Ù‘™ö¹“ó” :!˜Ë٩ّP9ŸÕøŽHI’™OI‘Yd‚ š^j]H±p#d*ô(!ùû,@žâòþ÷ H° Áƒ*\Ȱ¡Ã‡ž#¦É˜43jÜȱ£Ç C¤(E )b0Š\ɲ¥Ë—0 Ž9cFÊE(c¤˜á(åcO?c Jt_ªœfRé´™ª©Ó§P£J‰JµªÓ©V³> Z´«WGÇ 9ŠÆÌX­VÇ E«vmÖ1_ãÊÕ˜jâŠwÍèÝË·¯ßš Ö)¸0_²æ*^|°éÄ&Išpe¼Q 1fSÞ<×é)H&sŽh³æÑ¨‰zNݱtfÖ°a®ŽÑõiÚ¸?ÎÎíÐ6ïßwWè{¸q‡Â,®¼ycÇÎ2N=9õéÔ›§Q=û@ìÞþG‘r¦nxàÏÿnÒ$Šš3êÓ«ÏmÆI4ðÏËŸ[J3ù…·°¥áßz7 ©EÁ^ f· ƒ£µ—T„×]ö…¼ÍÔ€ÞÆ!mÖE7ሔ•è܉(.¦bs,¶ØÙ‡ †(#‰M‘Ÿ7²V’Mb¨‡Ä:õÛ‹ÊÅhdWH§ä’ªÑ(!PrÖ¤qOVÓ•Ãe©åK\çå—-9µ“~T’©XSax±£ij2– V¸‰&œq®Ù…a¼¹ažr¥’…:Þù' _¥²gŸ†ŠˆhQ©„Q§ŸŽ>:TSe|A©¥‰J™!žœBꩉi†*ÔUêùàÇšºe*þI(¡Þ 6Ü`E¥®–©DK¨wE>XQF®—‘„hѩڟĞj,\çѺŒ6 殽žÇ6d†µ²Á*k´6lEµà²ÔÔU¤ê̦[¦VhzÞ¯Á+¯ºWÄ­¶âºopýyžª¬ <0]{Ú "¨ ë–ʤF Òc|{Þ¡i±Gaþ6æÇ…ÌÛÈ$ drn(§¼2n-“üòK·VjÊÍì’eXØ 1Î9*×>G1¬sK6Ø ?Tv3ÐÈ ×8a€Ñ%mñÒ,Ý€ƒ FÔ¤MM5C`¯´ƒ FìS6G¤ýóÚ µ-ÒþÛqP¶u?äuÄz‡$vÜ•ÍuojãPá mâr`@G7Dk­:¾ä5Myâ[g¾Ptp«çî‘YŒ.wÙ˜3´m·è²®ë6#î÷e÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8Í©NwÊÓžúô§@ ªP‡JÔ¢õ¨HMªR—ÊÔ¦:õ©PªT§JÕªZõªXͪV·ÊÕ®¦Œ N±)X›"Ö°Öt¬©(+YÏjVš¢U­ieëZÝÚÖ™¾U®q¥ë\íZW™ÞU¯yåë^ýÚט!ùû,C¢þ÷ H° Áƒ*\Ȱ¡Ã‡ J1³o¢”1 JÉhp#GCŠI²¤É“¥ˆcÆLšT0cÊœIs Í›8cÚÌÉSæÎT(ƒ J´¨Á1RTŽÓ²©Ó§P[NŒJ•êÔªX›^m)˨ׯ`ÃLsf̱_W²ìж­Û·aš…;T­¶tóêÕ+÷ì^“vñþLØhßÂ##^̸äáÆCžL™áãÊ %cÞ¼ù2焚?‹fìytÁЦSï-­zêÖ°Û²Vý:¶m¯/Sͽ]û¶o¡ff§îý»xI1ÂM7ÎüãÎݶ—7Ÿþ07ôØÒ©k_˜|töíà þvý=¼ù}ã?—?f˜0¾×³×ž Ì.ñWÞÏß`[ÀÇ›~‚õÇ_R4àZ(E1l!†oHT8Qƒþ`~ bÈ_*\ÜÇá~Î瞀ÑX¢‰ºù•b‡+š—gòÅÛŒ›Õhckj¤ql:îhšI‰Ô‚$ 9ÝEÙ„G@ª¨ätHÙÄÔ“/&9¥qbHF‹M@‘e[úFä>š%vR–YI)µf”0ºùÛ]²%ZÚé[*>ºÈf~Þ†#fA:™c°4"™Šª§‘EÜvC›‘¶F¤e™‘„¶Ýp¡™šV¥njVÛ >ZªhþHꜭmÐjŸ¯Š¶i£jî©Z=¸š+gI=a¤¯¿f!ì°˜…±c4aé­2[Ù¦Òªš¨ËZ Ù©Wй*¦Þ6+…¬W"«¹åR¶+KÚÇn»“M-’ÕÒ˘N4a$…*¨/f‡V–èÀnLÙÁ£¥ðd 7ÖÃE,ñW7fñņµø—ÇÍËñ`åà $&òÈ«y¼× 7H³,TÉ(*3T³Í(á|ÒƒGpÄ>¯ 4\B›T…F`ÀHG¦ôÒ »¬×ÓFì#µÌVw‹µlZçõ`×H-…ý<öHM—4j×}ÍöCn¿RþÜ$ÁL÷>`@=7t©ØzƒÅ÷H'ÿ]·ÑU3Ôµ‰óU6]WÜà¸×R7qDCà€xåÿ—š×öê ñùB\Š+éb-.RãisN€¼!Åë uÀÃè´e{H~§ÁR‹aЫüÕÅuüGs0¤ÝÄæ )K|õA_×JGTpÄH@à'tƒã“oÒõ­4FIˆQ€·(kvö+ þ"³"0Á5HCëÖçVÕ/p3]œ • EPB¸…@ ÞLƒ‰‚´ r¸š°|¦£ õ^H’¾%o4 ×uÁB$Nþ.äCëíÐ;3,"Glè*=G$OŸ&¶Å‰J´"Z°XD-Š…‹>ôbXÀ˜C1‚…Œ44cZ¦HE‡¨Ñ+h|áGα(uÄà‰’Çî±.ll£e¢¨ž@ R!Jí—È ,’|DÉ#«É“L²x•Œ!‰L†¬‡œ<ˆ'IrIÚõE-¨L¥*S‰„Uºò•­|¥,QËM†ò fÉŠ.·¢K«ô2+¼Ì×-!‚‘‘S$Ç I2‡ÉÌf:ó™ÐŒ¦4§IÍjZóšØÌ¦6·ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºóðŒ§<çIÏzÚóžøà̧>÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8 ÉbÓ¤§<…©O"Ô ¾t¨@ýiQ•zT£º©K%jS™úT§¶ªS•jU©zU«²«9 «XÇJÖ²šõ¬hM«Z×ÊÖ¶ºõ­p«\çJ׺Þó&.ÅkKõÊR¾®Ô¯*lJ‹Rž԰&ElIKRÆŽÔ±"…lH% RÊ~Ô²½I@!ùû,Ó£Soþ›H‘rfŸÁƒ*”¢°¡Ãƒ JD1!“1hRiÜȱ£ÇTc>Šr¤IŽ%9î{’挙0`¾„!c¦¦Í›8ÍHÉɳ§Í>ƒÞzS–”T'*]Ê´éD1fŒ¦"ã´ªÕ« ¡Jƒµ«W¦ZrýJ¶,°©Ì˜]KmR¶p­ºK×éܺxŸF=ú6¯ß„wÿ 6xð߆ó"N\w1㸎³,Ù,åÊm£rãsã¨1¶pñLWk 0eHCŽ:£¥j¸ZeˆûÚ,’1¼¤®m´hÞ½eUñÒxf©aŒ«ü+ZµÍ»j…"«oôªZ/B¿.7j“1c¸þ_ {&¼xì{Ó˜?ßtzyöv½ƒ‡ß>*“}Öé?œŽTÿR´ëù·Ÿwh(`VöAÛÊò„2xÖ^JèPXZ¨VN@¨á† n÷!a F¨¡Všh!†*JÈâˆÊÒa‹ f7Œ$ÊÒÄl8t›NýõxÙC X¤Gê—$}KúµÁ>>,¤jWÔðƒÎí5%iYØ xÕd^7àp„G„‰Õ˜xÝàƒ Ækj™_e7dgœW|0žµÝƒû€T¨ŸTwJvÅ „@œG€€£µ‰Ñ¡j@,ÊWmWˆÁi¡ĉÄõaúšø¦&”ªQ°ú ¯e+B©ªj«^®R¹k¯`ìRü:`°¤Í¶gÄ9FûµÀŽúš³L@E©:qê­Ìz6ÛqPÀ¸J±Ù˜c$‘„E€L(›í–æâËÄlHŒaÀªþJ$oiû8AWHQ¹Ú7[MtWî‡ _×qta)Øã>?V ãÇÍ¡¬œÊãŠ#ËÀÁ̛̙º|²Í#Ò¼-Îó|¢Ï+í¢Ð5M¤ÑF"¤ÒJ2ͤÓð鬚[³UmõÕXœõÖ\#Áõ×W{mõ^ø %Qfû„vÚ<­UY42÷R&!ùû,¤c þ÷ H° Áƒ*\Ȱ¡Ã‡#JœøPÊŠ¥`¨q㾎CŠI²¤É“(SL“*U™0`ÊœiI³¦Í–cnêÜI3'ÏŸ5}ÚTI´¨Ñ£H“nL¦‹S§dÌHJµª”ªX³b½ªµëÖ¬²”ŠK¶¬Y‰f¼œE)ÆLصpãÊK²ŒZº!Û¾ÅË·¯_¼vÿRÔ+¸°áÃEÏÜEÜ0ãÇ#;d*Y¡ãʘ3?NuF³ÁËžC‹žËy´@ЦS«FZÚ4êÕ°c—l=úµìÛ¸%Òm;·ïßw‡î ¼8pញ_~¹fåÌ£«vžºôë¡©c¶Ž½{e핹þ{8Ê© ‡»%Ï3&Ç4Iÿ|}ûûŒe‰a2&™&OÔf~ –†M°t†óñ6`âó!FI !WEè!]²HÁŸ>샃{}¨âYfÀ7Æ7Ü `Š+Ö8–yax±ƒkÚè£R9¦&ÞDŽ4©$JŠ Ùã’PޤeHFi%IR ÑY•Wv¹‘E[Îèå˜qž“I’©fBhœÉåšp&d¦hÆiçA`Öyçž§¥&Š|š }É=èšh(¡ç¡qš1†¢o2º¦S@*¦¤pŠ!†¥€bº¦™S,êé˜g¸yé¨cŠ1Ɵꥉ*”þPœÁj¡®¾ª$«Šjk”ªÒé®PÎédÀ^Yª¯[,¯¦:¸¬•yò¨ì³J:aaj^Dí’àI6ä¶vÙ·àÞ'.dä–ËÞ¹¥«îxì2æî»Ýŋؼô^goQXˆ„o¾ÑíKÔäe(À ¬Ò¼Ñ¿§pJVÁƒŒAñq³šÕ+!dlÆ´C81[GpÄKÔÉ%‡ÛqYX±b€€ÃDÇŒÛÉ'Ý`s\B.ì3¼3“•…Í@B¹ˆ0)$,!\Nˆ”ªð,,<Š _X–e†4‹ ‹‚Ã*e‡þDé¡Yã@tApˆ›)b»ŽˆDÄQ%Bl"Qž˜’(J1%Td ¯(˜,žÄŠ\4‰MÆ0’dŒ%)£E‚F’¨qiãHÞGŒÈÑ_[¬#i”(¯<ê1.œÑ” IÈBŠ †L¤"©ÈF’‘ƒôãár¯X’+–¼d&3‰Iªðo’¦ÑÖFD‰R‚ò”¨L¥*WÉÊVºò•°Œ¥,gIËZÚò–¸Ì¥.wÉË^úò—À ¦0‡IÌbó˜ÈL¦2—ÉÌf:ó™ÐŒ¦4§IÍjZóšØÌ¦6·ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºóðŒ§<çIÏzÚóžøþ̧>÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽæ )`J6d éHKjÒ… ô|-`Sˆ T¥$­©Cn*Òœêô§'CŽÒž²¨*QqzT¤:µL  ¿§2$¤@ƒU ’ŠÝUo¦>ÝjR£Z=¡M* @ «bH*P*8Á¬qí‹öÊ×¾úõ¯€ ¬`KØÂö°ˆM¬bËØÆ:ö±ŠÍ d'KÙÊZö²˜Í¬f-+ÙÍzö³  ­hG;Y• Ô«k!­jWËÚÖºÖ±*Yþ+nB–×Úö¶¸Í-h“BÒ–,Ä·ûhòº}A¤@ .j ¢Ûæ:÷¹Ð¬i‹ÒÞ¦â·× îp¿Z¾‹ ú+xÝòš÷¼®U M2…ø6Â¥Éë¾Û„¡Ìt¶A¯~÷Ëß˪¤ «ÛUk‚Ý–Ä·%¯KÅZC:[úî#9an'Lá 6%Ç ©@pàº÷ºð¥€|g[“À"í°„-ÌâO%kõ)€áš÷ö¤õuð>Ѐà#ìîºM€@~]Lä"—7% 1‚²“ €Ä~pwAlÜùÊX~-’•œŠä.%»ɲ˜ÇLZ`ÀÁþ'âa‰ùÍpÎ,†URš*%ÎxÎ3lS` Ìbѳ ]Xõê¤,„N´¢ùÚÙE;Zφ&p^åzè²P`˜Î´¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV·šËì]/Y&”Zó„ֶεMp­k]óº×¶þ5°k-ìaßÚØÁFv²=^DÖc)¶²w=m H»Ú-¹6¶µ½mlï„ÛÓw¸ÛÜlhïÃÜJ7²Õ½noë„ÝÆw¼Ý}y÷ÚÞ÷&·H~k}#ß¹xÀéMm‚ÓDàË6x*nm…Üß™À¹+M–;Üâwx*0npŽw\ã§wÈþEñh¶º³¡]r»üå0¹Ìgîí“0ûË”^¯ÎÑsšûüç@ºÐ‡mó3ª\Ò=ÎЗÎô¦;ýåE‰‡WNuÃwˆ€·é«`¬´ÚÎêÆ\íÅZ÷6´¾`ë*ÛÉÎ0—‘:_=ëÕF1ƒÛ.d£½%r—ÚRw4Kýè\­´M¸nâj£tÀ öñ´§üºîrýí³õ1ãGìãj=òiWv‚qb€1\ÙG˜í Þ®rØhBì3zcoÞòã}ˆƒMìãàB@«‡q{%ÍóÕó8õÕ6‚ZOúßÛ©?ðL¡áîÃ.ßÚSþÑü¹ZÚu@V¥ÿz[S¤~>ò±Ýýî?_ÙíoB|÷1S›Z߸¨? <âS\ßêW{ÈÆuîWzØ–2h }Ø{gÖ8mô%yØw°×z¯£€à§uåóß'yy'bÜç|¨l ÖaóW\!Tú³vú—{t¡~ Xõå~ß7mÛb=¶uÀ×7mz÷d4Ècä7mÝ÷]HÐ1ØkÓ‚Þ'„Ʀ‚]'hg ‘að:“³iU›‡m\zÕ–=Qz§gl V>bÐz7„]F{“g€&†{õõvZ×°†õÕ†$a‡ÊFy@@‡á`3Õ{þA|!ôw`Øz|çƒXGvrWv^Çaw'€vØu_7m–v“8†vLj“WƒÈ‰Xu| &÷`SU_ôt°‹²8‹<u!D‹¸˜‹ºt¶H»ø‹ÀŒôÖ‹&„EêeŒ(qsg„ŒÄ8Ìè‹ÂÒ8?ÇAÏ(F׸ŒÙ(uÛÈÉhߨ!¡ŒltŒá،ڈŽkAŽãhŽÄˆ~O¶\¸uçSð(~ŠølkfìGîˆxñKÎH\T•f^&Sau(µô—Sy™ü˜\%Žíxޤ¡‘yŽY<•\Çuk•UG0Pg–cµw•ˆþZ@Skµ;7•]R^…~$cjV>#å9hÐ’UÅ^Y .FS'9”V’My\²'”3)JQ™~IFU©²‘cTµ”Pé9å“S`é€S>¥gË…MKYoY“c‰’ y‘[)qÉ–!Y=*µ;?HU9™’U ”W~ ‰M{‰—°…l9—CY—Yu—i™–•ÄER–\IIxpE˜†˜‰b‹ .H÷eÂÇs1«ÉfVgÉ•BY’‰Rt©?vévWy¹• e’Bi—Tõs©–Zu–ºY>»S•;Wr“ñq$k±Ùþ'fþ±sÕicÇš‡rQu^6’;æunµ›‡é”¶èœ4iœ‰u8~ZGÕez†ÙvB©wçžÞ9޳Y‘Öší5ž¯(|Ýé ®™rÀ¥ : vÄZ¡Öi 5vhŠú¡* ±ÑÙš úÝùúÃÇ¡ ›0J‘Ê ãIu%z¡ê ÕY¢-ÊdÜ©¢ J¢?JA ž+×£**¢f¡²I[E ¢:£þˆ <Šs,ʤUº Bª¡I:Nz£!ª¤;J¥‰·£]ª£aúšU¥#*£+š¥HJqRJ¤Zš×ù¤hz¡z§[Š£ld¥Úo–fú§$§ *¦€j¢ê§“ö¨©’:©”JC‰*¡MJ¨n:¤á|z Ïö¥Áשú&£½Ösâé©/:¥ÈÆMè†m¨š¨'Ú¦¶V¦â™M¯J«Pš ³ h»º9W¦ ¬Û”«V ¬©Ú¦¡Ú§Ë¬¿:¬yZM½škª©²ª«DW‹›êL«Š­Çztžê¬Äš¿Z‹ø!ùû,Õ¥Uoþ÷íÓ劗2*\(P Ç:ŒHQáDŠ]tXéÒÅË™T CŠ9f¤É“&K¢\™eÂ2:x„ó¥Ë38sêÜ)e§ÏŸ>{”¨¬/:²¤:S±©Ó§P)Š™j樎+K£jÝÊuáT1Uaò¦«Ù³M¿VÝ—ÑŠ´pã&T+k_*0xåê=K×.H¦{kí°àÃi©Ö-Œ¸±TÅ~³:žì2cʘ÷þ›ófÉ6ñèÒ¢-sFmZ1X4 Y¦+¥vÒ²õÒE3¦'îÜqû¦‘2æ7p´´¥4™z|/’ç=ygqÛ\·b)>nP¯\q<Êþ˜áÞ¬¬ŒX“¬Œ,±×s¥kF‡/`äó…ìEã[ý]Ñ—Še”à|×}ßOѦ’z >fžb¬!T´™aá…NÑÛ‚FD×GS”bƒ)1DJœØ¡bc 1Äc¸˜˜yK Qˆ6*D:òØ£@t-!ÄSÔ8$D´±b‹K>ä`‰SD)åu~A8$]* ¹åTc !ÅeVú(…fô´a™š‰ñ„b>ȦÊÁIÜšež¹\šzÙãTL€5&žVžhšµ!Æœ =—„5’9çTQÀ™–ž Ö¥Œ &œvzš¨ªùi㨌¢:é§¡·Awþh¶ \YìóÁ~o‚*inbta÷X§²òZ… bq„°MŒ±¡¨Y‘Ýñ‚•ê mi7l° [M뮲y{¸T{mT_…Y¬l7dÑ…äkÀ>ËbX*¦™ÝÐ…F¤/ý¾hÞ¼¬ÝPÁë+E©šÛ >XQpBS,â¿Õñpú åÛn_¹0º¬U!mÁ&ÇlƒÜÜWøîc2ô|„ï2ióq8çÁíŽQL[ áZ,£VôRD¯ßõ¶¡}uDG„uÍ+sÝsc$ñèÒÀDÊ*omªg¨–HŒ1sØ Û phN´¹OHqÚ‚3Eð^—è¢>‡DO GiqžC¯šöÝ'n^hçlŠ®9éœ>x訮z§m¾Nªì©¶~:íž7>»îµã^ºíKš<ð_úž:ï¹? zˆÂ/øWÐG/=ÒWoýWÔ_¯=ôÙW¿VBg”Pâ‹O~ùCïS]g)Ù>\îC!ùû,@¥ëþ÷ H° Áƒ*\Ȱ¡Ã‡ž3ÆLšˆ3jÜȱ£Ç Cv4#¥¤1Jñ¸²cK‘0cÊœI“aš’f&ŽA“ª§ÏŸ@ÇJt¨Ð¢H&MU³©Ó§P ž9¹/ÕD3X³jÝJ’«×¯Y¥€»U,Y3²¢ª]ËvãÔ1UÓPl;SŒ]´tóêݻ璉ŠÍð iW ÞÁˆÓ$irÌÅ †L¹²Û1bÆÈzl£ä´C‹fè“ó膟O«^]µ§iÖSÞ]¹4íØwAßÞÍ×6o‚² gë{xðáÈŸ~<¹ó™Ë7N=dtÞÓ«kçx}wöíà#þv¿ý=¼yÒ®“—?Ï^êxÚëÛËß'æýìøóÙKI“9þüæ1Å_b$‡Äb¨ @Q8ñW¯I—Û‚>ÑÄ€EˆÝ„Ê'ER ‘†©Ça‡ì‘W*rÁå߉(š7U© Q⋆écx~e–™`8N¶cxŒá¤¡w0 „9YebŽJ¶çSAê%x=©áÅ“B^¹]OaXÁ¥•^V—ŠVˆYe™á¥fc²©Ni~§œÏY…[®‰gr~å4Fšwþ9ÜŒU!œ~*\I+¦±h¡Žò&Å€€Ñ§qIVz›I†A©§»M”Ùf£’º›}°ý§êhþ¬²¶§^ù*o±ªÖÅ ¼ò)ê­«ö'7\QÅ^ô°³å:Z¼^A«:”Á,mSW… ôº«µ×–¥nÐiZ±áŠ{榼5QC·7XÑ… à¶{šI“ w„ 6ø°C^ÜP…¾«e /oÄfQ…^ð°,¢ùô«p»ö 1Ū9{ZÒzq$ǵ Ë\§$—ìd£)wæñh®¶œ×Ë¢Å,s[Ù"7k­7»ì&£óZ¯÷¬X*eÊWk,²Ê Y*_\¡æ^!ppÄFÐîp_T+õÑU_­W±uF]Ÿ4FØùŽÝ[˜fçåAbþF ?ñÄrëÕ“@ëeƒGpe<ôÂÏÜ•{ÝPƒ ÆÚLëÈ‘gr^Và`Ä>€çÙ:M4×åÃ餾yD®¿.Sì2‰‘Å饾:D¹ëï1a‘ð¨§.ë o¼u£ÓÅ+óÁ«}BÒOÿò0eaöÍÅöuï}Gà‹dðµÇŸú{GòúNµO˜FÔÞ<àªðûA kÑHÞW» uc(,`*D}̈?ÂÀ H!µsù.x¿ B§zm±Ë* 6$ !÷Jh˜lÐ#vC’  @þL@_úhXC‘Ü02û(胄1hOˆÁ`rD8A v)Šð†HqŠ i å c*ä@HHkRŽ|q i£^$88…äÉ¡­ìx<<ÂG 'Ù#©·2NtƒÜˆób—“°+‘w,äÉ ÉH"²Uz¬$!/É:j$‹¤Ë5Êñ“Tôã}N‰JôHRBl¥"U‰ÉXÊRƒ´ì$+o‰R²e”•ôåZ€ Iaª…˜‰4fT9HeB…™|tæS iGi:…šp´fS°¹FmÖ„›`ô&MÀ9EqÖe—¼4Hi ÃÎv <ß9Ïz¶žöþìR:#r†³„ÅŸd1 @¿"бiŸ2qGº†"ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8Í©NwÊÓžúô§@ ªP‡JÔ¢õ¨HMªR—ÊÔ¦:õ©PªT§JÕªZõªXͪV·ÊÕ®zõ«` «XÇJÖ²šõ¬hM«Z×ÊÖ¶ºõ­p«\çJ׺¶E >y+^{¢×¼ºu¯Lù«_Û Ø¾òU°‡%ì`ÙZXÄV±‰eìb×ÚXÈ>V²‘¥ìdÕZY»zö³\h ·VÑŠ€´j5-jÓªÚ·¶Ö­¯m"klÙ:ÛÒŽÖµ·…mne»[ÚöÖ¶§Åmpu;\Þ×·§ !ù<û,Û§Soþ¥ŒÙG° ÁƒR*\¨0!Ç !"“ª¢Å‹)bÜÈ1cÇ5‚¬¸/á3(SªT)e¥Ë—.[œ“¦JY-ÏHÜɳ§ÏbÌàL£ó§Ñ£H•¥&UѤP£ºÏ©Ô«X.­ú4«W¨[­~‹4lW²h§Êâš¶­Z¶nã.4+·®AºvíâÍ+w/_·~ÿ¦ ,˜,áÂ_#κT ˜Åi‘ä ylc¢• •rælæ«Çxþµñ¾Ñ¤“6±P1”[—¦šê±l©[Ëx¹[hš.]xÏ–Õ%‹á`…âÈyY¡9°ìv~ti/Í©ÿ\z¸v£Kwþÿ¾]h,ÙÉ«­beºzµRÚ¿çÙ8Œûù—6%ƒ_¢âþý a¬€Ý`…p!¨ÐV|Q•m”Å»éV¡B7dáÅo÷m¸Ïr]”!ŸˆuØ{!nè† ¢XqÚ'#A –Q7b!†¨!(`CâWä|¹µX!ÄAxã> š‘Ãx7Bø…ФU‘VñyågT qÄU]NyÜg8ˆARјgfÖÁqAT9îS†„Ÿm`DŒñT>IÚ *¦j´ù¢*ƘÕ5z¨‘*Jiy†~iA€J±iOGÖ妠jÊiƒ•‰ªAþ€FO¥öõjªuÞIŸ¥Ÿ¹š*ûP'£æÇkf®@:Fýí[.–ìRD ¬·îÚ)²GTpÄH@ÝRë(_b ”„E€Lëß±­ÁÄ>b 1†Hìc/DµÆ%†Á‹DѺÙ¶†„éò$ƒÕVXðwí¢ÁªˆRõñ†MˆáqÆ61Æ·(ªÌ²ˆ%Ÿ¼.~PˆŒ²cÈüäÆÚñLÏΜÐÂ͛ѷ!-›Ò­1šÓ¤AÝ+¾"J,ÕZÝ*Ö7*Æ×`‡6b—mvÙdŸ­6ÚkƒMÕi6¥$SÜ4ÍM7LvÓ´Ö|÷í÷߀.øà„n¸ß!ùû,@¨&ùþ÷ H° Áƒ*\Ȱ¡Ã‡#îKcƌċ3jÜȱ£Ç C&<3fŒE‘(ÊJÅ2¥Ë—0cʈÆL(bfÊ4³²¥ÎŸ@ƒ X²‰”œCEòd™*©Ó§P7VC5ªÇ¥>­jÝÊUàªH»^ÄÚT¬Ù³@¿VE ‘,Û·pEª W¡ÛºxóFœ«7áݾ€ä+˜à߈ñN|8±c¶‹7~L¹kä“+kŽzYpæÍ ‡vü9´i£—>Íúe꾫[ËùZoìÙ¸7ª©÷vîßLjABü±3R’_îÑäQº˜{–eNcÑ£}Wß^PMœÙ¥þsÿðL'Ð=‹'Ï^¡ù'éI¯oO°M쎵×_^2ÊZÆóíGjuS øU7˜U‚í!688„ìaEXˆ!}fØÐÁ~Èž7lÐ`~šXÝaÜÐC…,"è"wÂÝ C %ÞXE9pØ£ÔQÅ IärFÞ°ƒ’KæVS8¬ QW”VJÖb–³M%FP‚)oxég&ehÆ%Å$qášg‚•˜MˆœhЙ[›o=ÑÄiŒq”,~ÎV bxJRri$Z'ƒŒ6Á„žxJ©¤¬IÈC—€‰ÁÄ¥ià鄜vÊà“…I1*¦þ7mš*hÂa¨}!‘Do:1*³š&á¸öÅ …&‡h° }Ååyîy³›©EæpŽÆœÔ>(\jv[×·o…+.\ä²eî¹ÙYãƒì"öU¬2š­œñz{Å!@‹)Ÿù:FÆ x˜ „*Ʋ &† $éTÅשcÈRª¦ öð$:uʼnAÅEx>‘±vìð |ð”d€°8t(ѱ +ër`gøÀïSGpvÀƒDxþ;íÏz9»´SF`€ðp…V0í¶P–®HU[}Ä`Yl×—a£ënRe[-Ú9©Ðºm?5vþHq[Ýÿ5„wÞIí Rßû àv48áBþâ~3îÐã§õöPˆ d@€k晣¶¹PïS¹WÜàC¤—.“äõä~ÄâYÜ`Eã±Ëí•Á‰QÌÑûï /¼KÄsTõRD@N‘öCÒORõQÁÙH@ÞŸ¾6–â[æ>Pc$‘Ä>Ë – Îvüœ™ßOŠÀ a ø[ÿ`÷?êM€:qBÅŠ€„"<'pÑk ‡"â a+F0‚?‘ã$G ä ù¬¾rd…QY I\¨B…'{¢¡Ulø3lìiþ:,mS*1p+ˆAá¡S|ˆÂ#"ñ'JL ßôD§Dq(<ƒrª(š!öf%3äb优& Š‘zd¬K ÏØ+ el\ˆƒÇ8Ž$q©£2G èqéãOþÈR'„,ä>9“D’‘;1£"_ˆGpIr’dL HM„“{ôäK@iGQº„”q4eJPÉFU¢„•gt¥R.‰IŒÈ2$°ã*ÄÂв–)II4sB`ó˜ÈL¦2—ÉÌf:ó™ÐŒ¦4§IÍjZóšØÌ¦6·ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºóðŒ§<çIÏzÚóžøþ̧>÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºôœBÉ…Pø‘#ÒT#N$ÈM!’Óƒì”!=UÈO Ô‹¤¨¹)R!SÌÔ©>åHQ‡Ú¥ „ª ±jA°:­"„ª^eˆRCÒÔšêªáêC¦z­ª•¨ykXÓzÔŒ$eõÈSÍJ×°U"nåi\2×­Ö$wýH^;²W½FÕ¯bmk ‹2Âr¬ví*YezV¾² y+B»ÖÁ*¤°-j¿ªY¼rv ¢¥þìgqYÀN¶´•=íeû‘Äzd±RM-cKÛÐJö!±m-Ó,»Ì"V¹¿}íUÑ:ÛŒü5"¤uHaåº[¸>W «=pA;]Ï #ו­Qq»\Ý6—·ñmGÆ[Üò:¶¯õMHrU{[íš6«Ýåïw÷^ƒÐ׺ç%o‚mk\;d¿þïhLÏÖÂ90zŒ`üvX¨Ç}°zû;á÷z··Ð¯t÷a ÷Ãú ñzý›[›XÀ(ïf©ËbÃ6¸ Æ.‰]\ãkĹ9ްkyÜbæVwÃAq”‰Ü^¾‘/G4,ã&»÷É2f­ƒg\U c¹È)0þ†Âå1{ÙÊ–²‡ÙKf*'ÕÉAr|ÓÌ•ÆwÎa~1kKã*9Ïg¾ñšSâg ƒyÌb2rå\ç#ÚÒYæóK"‚N{úÓ u§¿RQ›ÚÔ¤þè©WýéT{”Õ°vuGaÍjYs”Ö«¶õFq}j]k”ר.5°Cí댛ØÂ>¶§‹Qe·:ÙÎföE½lh+[Ú¥ö¨­}llWTÛ"ð¶›}l_GšÒç¶ó1fxÑ›ÖÉ…&@î}Ð[Á¹·¤¢ï2_¤ß‰ŽÀÝý}Ã[$ó®÷À!}…«{!?4D"NðŒ,ÞgKÂÍ;Š:!—8ÄþÑ=ò/[<à'¯¸X6~ß‚„üáy9»"s•?Äã¾xH2Ž– ¡Þ?Ç÷A‚¾ï†Ýß9:Ê¢t›—OÓñŽºÔõ‚ TDà,‰Є¶2¥),Aƒ€Ÿ7aˆÀZ¿N`,ìj߇ֹ^U°³ý b7;Úã®Z¶ß=!o'ûÏçÞuðV~ ÑzÙ÷qö´ýñÁ!ÙOø¿Cåëb/{*Ž ö&@ OaŠxqGx]ÕŒZ¥xzçxG¨‹ A€ÁÈuÚèи!ùû,¥cþ÷ H° Áƒ*\Ȱ¡Ã‡#Jœ(L—+^ÊPÜȱ£Ç CŠI²¤É…]t¨ÔÑCãÉ—0cÊœI³¦Í†etðèæŠ+7ƒ J´¨Q“eº|˜ÔËѧP£J:SŒR3f¬v¡QVª¯_¹ŠK–¦™9°XñqÃGÙ…f¼‚Mõ¶®Ý»Ï\¹Á—o¼ãÎ¥ ¸°áÂg¬bñqEÊá‚ç>žL™kb1˜ÅTŽ ¶²çÏE/gÞ,·3èÓ¨eŠÆLzpê×°G®ÖL™sØØ¸sçÍ̺véÛºƒ O8»µäáÈ“ï+îÛµò纙O¶Mºõ×ÒS¿ÎuöÃÛ»þ‹ŸÌŒ—-]F7?>¾=^01¶p‰ƒKïé¿«»ßO6Œ`”±E 2ܧ]~ü%8V2ÌÆRüWmø9§à…QUA fGÄÃHT&…R”X"†(>å_ iAƒ 9d±U…ì¥h£PóÉg‚ 5ô5#xÞ(dPUxá8TAÅ^nháPÒÆ Y`VZ4šå–1‰Ñ–•7ð%p\–YÒY7ôÅTŽ©Ÿ™p~$ËVô•æzZÆ©'GcdõDVüèd{ÑcHÁ›qyêhCg$º(žd>jiBiŒŤn^ê)q’bF£•~j*¢ŠZIꛦ~jFbþIAÄ7PÊj«žžQ„¼RâŠë¼‘t¬°Ÿ¢A,˜%DË2{éb­KAµÖ>j±BaB\n¸†:»+¯¼^±.»{:+E¬DáØ¼ôÆ9nªùd¿{öÉ…ƒ¢‘ÁŽ"zpkžÈp¡ÿrê¤RŒÆÄ…:»©zø¥Q"¶×jÀ†Å%r— gÅ (‹,›árœ[ dP°¼ïÍeb†ÄÐC‹hL0A3Ðp~—²Wï³1Óe:]˜`ÿLu”V™³o=d×x…'6”dßeöÙB¦m×ÚlÛèv]pÇâÜo}½°þÝ7â]Ö&òÝ7o5‡±Æ‚ËÍPC‰) ©\b·&¾ßjŒ uć°‘äR¨Q¹åí­V¸PbñÃ>7x ìD‚E=5é úmÒÁ6¸»WYÓ~¡í%€b`pC ¼Kô5ÉÂ'H {ÝzÄ·# nrˆk\ 7$Êõ®nY éRW¶Ì½îAÎëí"‡»©nr«+Þ…Hw ì5/E¬ÛìŠÄ½/L©lßãʼôíHsÉÛ[ˆ°÷À yp‚Ÿ›]üÂDÀ®IëëÚù¢WÁã•H~¼ßôöw½ˆ„#J¢lĹ¯‰ "cƒ,XÄå%1pg¼ÿ†À'ɰP^Ì‘ïÆ÷ÃEqDFLÉ+dÅ4Fñ-` …È1²‘œe%븽_f1Lá)«øÂVvñþiÜas™"7^rŽ›¼ã'«×Æ)žîv×¼å6³Â]¶/ƒÁLg‰ð—#> ³‹a!ÏËp4‡%=hÛVÚË ò¡{,åS¹$v´L ]éGxÂVH™ [2ÓöÎeöô™ßËçI›úÕo¾4ª ÝýÖùÔ'Ƴ™õ` „c¸õGHã{8Ê0Vµ®¥Íë9ÑVô§g‚lQ¿„Ùrv¶›¡dA7›Ð­þ5®±-lYû$c @ŒKIp‡YÙí¦ô¹-½oL¹Ávv¬µ=ë Ûµô~t­£½|\Üå¦v¸ÑÝMÿÛÐÙþÈ¢Iïc'ÛáPÑò¤þ!žj~O¼×a¶öº9oo»$ñ€@Ðm™ê»ä$´¹Oîï€cÜç,8¬¡\˜ ¡NzÒ¥ ô¦/ÝéP Ó£Nõ©S=êV¿zÓ³®õ§wÝé\ÿzŸbô ]ì;Ú¥¾öP«½í©x;Üå¾vº£Ýîw‡ûØRö³ë=î0Þ¿>ø®ÞðŸË᯾xÆ'^¶Þ–Ißßøª',•‡zæ5ù¯l~ë÷üã s˜hBP&øÏƒ¾ó®÷:ìC{·‡ðgˆé Ð˃áàÀ ‹…„Ìú¿×Þö³O¾ò)OûÛ¿”©ˆÀ¾ÂÃ# {!¿Gø¼;þÃýï£øz~Ú›¿|æ—ßøÎ=Aâ͹}G(@L’ýì @°?¦Ó_¿¦wp~s—~ç~xyâÇvä÷w0sPÀ{Øcà©€Ðù— MdôÒ‡;û‚½÷}Gg~X€-è‚ Ø€1˜{Ñ• ·àðÇÙw¦—z@0}hcÐ`z›6)H€m·€ 8ƒR8…­'ƒ,¨w5x{wà~@аø!L}œclcÐ…á„u—€WX…Vˆ~/ø„ê'`}§'s 4‡(€zð{ö—z¸þA¨†RÓ†+(‡u˜w‘(‰TH‡“xu‘‡xz„ᆔX‰x‰„7‡£HŠQWƒñÖ…{·žH (vP¨x¦ƒ¢h‹x‹bGc¨„Þ'yh‰ºø†¨¨u³(zÅèx4¸~ò†ˆ(±Xеh‡ÉhyÕÈyÓøuQzþ77ˆ—¡˜‹q(ŒäxŽk7àhŒpˆŽ¸èŽÔ8ŒÄè€a„?¨z¨!r —sÓFn)·sçj§n/7t/áƒ{¸C1â8Žæøùާ(²×w‚`A!Ù‘ù‘ ’"9’$Y’&y’(™’*¹’,Ù’.ù’0¹þ’$‘1 €îw‘7“:¹“<Ù“>ù“@”=9“ñf“ïç6!”J¹”LÙ”Nù”;9“˜za„™“P™•Z¹•\Ù•.9“û€{Ø~é•fy–h™–K –y†ÁCayz˜ ¡–x™—z¹—$É–—ÁCwS¨zwÔÁ—й˜ŒÙ•~é€é……Y¿—}ј˜™™šé“Ù‘)—~ÆV˜Cˆ”±™¨™šªi’ûeŠŸi“û }öˆ„q›°š¼Ù›ª \Â×}D· z…‰‚Ʌ퇜á›Îùœ‹Ùš 'œ"!–'–DhÞþ(}ÞùhÙšÁ9Ãiàyžè •ÀI’áyS‘žðŸ@¹žÓœåYò™Ÿúù’âœî)û Êš#qŸ$1 š Ù™G¡ *  j`ºrÏV 'Q¡-7nÚgPAº¢":¢$Z¢&z¢(š¢*º¢,Ú¢.ú¢0£2:£4Z£¹)›G1¡%¡£;j<:? ¤>:¤DJAš£Ej¤I*GMúOêQ*¥KФ=Z¥ ¤ õ ùZÇ‘f1¥ª&dbºej¦XZg:kʦIÚ}·Ö¥cªpzmJcdš¦Pª§CA–#á§z;ÛyþpªtÓ|¡6¨…J‡ª…»8€‘*©‚j7ÒvÛ¦õYŸ‹ uœÚhju©þ™tŸZš…ʧšª U¢ÊžMWªŠú$¶©ƒŠp°Úžþù©‚clØ“é‡8Ii‰£á‡T髬«©ðÂúq :«`!–Ów¬JWªÝ§œã«žy„­ßšˆ·€á­³‰zø(“É|BT)—\È'Úé…ƒ9™Tdl\Ø®ê¨Íʨ‰ø¬J8¯®‘'‹ÊCaq¯ˆ™¨Ùª†Dˆ˜î ¬@€úú«Á¯ »® Á¯óÊœQ±†c™aR€˜`˜Lø¿˜¯Øþ\²a‰¬qGvy²û³¡¼Ç²“úÐû³"Ë6k³8KEÛ<;³)Ë…žXšS!´K“i´õê²;K)³1•M µQ˜ójµ¦ “ÉC,˵ ]xGOдSû–Z»d¶ i;³Åé~ŠÈ9øú±M«¶Qµ(›‚a—'8·-¶ð¶!+¸ Q›¢i´)ë\¸´a³ˆë¯H¹†ëc°·Šˆ¹P}RC„SÁ¯q¶ÛŠ8ËryGb±=ó*¯q;³õ*}µKE]Ø»ª±T˺^–¿ûƒi¯Úù·‹˜AþرºE®ÀŠ˜âº’«¯æj·¶É½.Û…ßKE¦Ç†ÇêÜÚ–ÏË´S“¾Äzs)šѾéJ•¤Õ¿þû¿À<ÀG´ø»P«öw‘ WùðÀlË:›G©EI¼Á!ÁÚkVÌŒæêƒG¼wHˆü{³›8±wK®y}ضȿúdž@‹l™‘wDs*„.¬±fe„€ŠÂD¼&¬Ä>(ü—ÁáÄA[“|}F(ÃÆv”¬´°9û‡Âwt}]oÚ©Bw¿a…ÄõøÅRcÒÆC¬ÂÉÅ8ÉÆ7<ÂŠ®ê ÂÈ*\C<›Âú7¬"lÅÇpÜÄsL• ,Åé·p¬ÇG™Åi“€,‚<„|È¢¼1ö¨ÈeÕÅ›LÅGÅfLÄ’lÇިʘLÆrÌ{“œzE ÉgleœBi|“e{Êt)¥7šwK)üÊKüÂÅ ÃFiÀlö¨Ãë˜Ý¨Äo,Ä; ®»Ë!ùû,Ó¨SnþÇì+³¯ Áƒ*\Ȱ¡Ã‡Çœ ±¢Å‹ ÊJ%±JÆ AšÙ(QLÈ“(ŽäxÆdÊ—0÷­,³æÉ™-mê̈ÓåΟ{ÚP(Ñ£"]ªtéѦN‡Bús*U+Ä‘q•èÊ-\bt•ºqK˜.c~5ã3­Í¯b¸Ý)ÅŒ³hç¾-VïÞTZgø­‰ÓÊà˜8óN‰³Éâ—Vc\)fŒÉ()qŒ9äL3œ;l,Z$IÐ¥Go¬:µEÊû.»¾yöBœLl¿^mF®nˆš‹ü~:Éð §[WøYùò¤¼?7¨Yöô„š¥_o~yòîØ¿þƒ?Hõxê«7ŸGŸª²ïõu¥Ð\/“dNúµŸç_¾ÿxÿáÿý nÚVàlîtDA>@f_[cñÃ>Y@¨K^5†䀅a7=¨×Á 7l"†z€bð°Á7x&â\.¾hb]€ÈÓnåø¢?˜Ôà,âÀ’/"q„YÔ8i YTüà ÑFåXVî£#>ˆ‘!C êæ•M¡èÇàž–º´¥,}©La:Óš&¦…ôèjljS)ð´¦>ý©LƒšS¢R•HMªR—ÊÔ¦:õ©L5ªT§JÕªZõªXͪV·ÊÕ®zõ«` «XÇJÖ²šõ¬hM«Z×ÊÖ¶ºõ­p«\çJ׺Úõ®xÍ«^÷Ê×¾úõ¯€ ¬`KØÂö°ˆM¬bËØÆ:ö±¬d'KÙÊZö²˜Í¬f7ËÙÎzö³  ­hGKÚÒšö´¨M­jWËÚÖºöµ°£­lgKÛÚÚö¶¸ÍíIÀ[ܶ·¾µ-pÛ[âòÖ¸Á­íp…[\æ׹ɥír•Û\ê>׺Ñít¥[]î^×»ÙÕ­xÇKÞòš÷¼èM¯z×ËÞöº÷½ð¯|çKßúÚ÷¾(~÷ËÚî—¿þý¯ú;[8Àÿ%°l ¬àØ2Á.°€ ÛK8ÁÖ/…_káO8þp„=Œak8 !ùû,@¤&þ÷ H° Áƒ*\Ȱ¡Ã‡ÅŒ‘U3cÌ hp ǃ?ŠI²¤É“(Sª4(ñ¢Ë—b\†ó% ™—8sš‘¢³§Ož>}R\I´¨Ñ£HWŽ#EŒÓ§N‘8½r£ê U jÝ*u«×¯QÁ‚53T ”¤hÓª]«r)X©Unà°Ò%Ë,bµvÍ›w/ß§dS N%%ÛÈ+Þ—qëâ•~L¹²å‘gÎ8¾\2²àÉœC‹íqóhŽž >ͺõá¥M¡º~˜ôìÛ¸UJlÒDvn…µWÿNüáÒ(L’7-~08óçÐ f,räG I¢t®½;q§s}þdÀPaJwîÞÓßãäÇ‘*V0`8r^Ö`Õêó»#ÅJV1;`ðC}÷٦ߜ‰ñ„Gˆ±„ó(™pVH™U``\€A~F¡…$"ž|dp…wè•èâZÁçC]ˆÁ¢}¾¨£Z™]‹;I”D±=uc# ©äI=judŽKFÉä>B¤”XFÄ”“!â—å—Z"!æ˜Þñ$Å™h‚©fCMú¦Ý•kÆ9‘\¾‰£ˆræ)ݼ¹ùã^ê©§EP4Qä“x ªçgHÑFvi ¢kÆäè¡’&Ié—Eah¤v"¹©œ1Q¤™Žçn½9Õ¢þªÊé…ƶbª²‚Iä£Nù뤹F¹klVüªi°BJT«S ⊬”¬úi% À>$Ò>§µ:¶ ꟢r»$¶ßN+FM˜!î¸[fË\`hšzÖº×R U7î$Ñü©Ko·fhEÄ[Žá!ýë"£l¨LDñD “ØØ½7Šñ©¡½U¼p»åj+ ŸP@ì)Å#[Ü›(§¬Ÿ[P[Ÿ¾cðË“Ì^üTGÆ{&Ï=kТë/Ñ* Õ­¡BÉô+»[ܶSk'јdvg&šCg=³½V‡µØÏù2sV„.Úé¥atÙÃea…RÃíÝþÜk×Et‘¨Þ{W©­d18áÚñíÔb¨dâŠÇÊ8tŽ£šØ¤ôwà‹_þ\拉AÆû@=Rwç-:s¤+v„œw°AI¬×àúëÄÅž˜ ÆØ,’V”pEè¼ç;bÀϹó~dF%ðÀ|ó¹=XôÁKñƒ;ˆt} =lÏýmÞ³=Òÿ ×GÈ—à…úë»ÖþZï°Êêb7¤‚?LˆYþf³?µ€/ûª‘+€!¼i µÀß40-ˆšp 8²ÐXS8¦¹êÏpü € !( ú …(tÔ£B²†þœ¡ `€"¾‡bàACÈ¢CrЇ/dÙ÷düO Ö±€~ »0„,*TaÇ Øš¢xB"HBT…Y<¸ÁB£ÁälŒQ¤[RŽP# @~Àƒ+@>s´œ A'(—1cRÆ„ì€Ð·æÈg:[$C#I¤ü€ A €„#pòq! SÎ@¦„É4˜¿|aB#™Ì sˆñ¬h=O:Ë)±›» gR;dÎ….‰Íi¤,úÓõ–G½H dɰæU}è”$ÚÕž®4¨Q"ê?›:×Çfª/mèLSùÖV~0œmÍìGs¹Ö^4²µ]m’¬Ú×­>Ê«…kbÏÚØØ.uºOmé cúÃÏnr´Yœkj»ÛÏ$‘4¹ã¼m‡ìÚÛ½FɯÅlvûÚÖ¦µB¨ë}SúÞ¼vö¡gi3ZÌŽæ3ºžm÷›ßÝn7Aþ4eÜ/7Â5žïd§ä/x¤ß=òx“Ü!(oˆ‹3~ðŽoZÝw8·!p‰³šâ¶x½Ùœðÿüâ.ùÐgÞlxO\Þ³Ù¹¬ñMá¨+[æ_ïlª‹nõ£cÝ5Z77×}^r¶¯[ç"¸Øn¤§$DIûÊ{ô¦¼íB÷¸Ô?+÷ªßÜ xg:Æ·î÷®¿=ðO‡û܉w» <Ü'I|ÆíMõÆ»}í’|̧^x]—žì‡/ˆæ¿xµ{¾ï,‡<àG/úΓ½æWO=AVïúÖï]åŸ}Øk?|ÓÞøe¯ûÙïž÷Ø+Üù°ç{ñ½n{°Ó½òË¿¼å3þß|é;^ø÷þñgzäWò¸7»îÂû•s¾ðïß>è§ÿø±[?ùØ_¬õ¿ö??þø|Ñ'€ôt´sçGx×÷nüçN€é}Pwná7 ˆ€§GzòqÙÇ}Š˜ç{Á'öWãg‚˜6GØrât¨oH~ˆ ¨€hx,Ø}ø}$({/x4X‚Äwƒæ‚Ø€<(‚N„5øƒ”‡‚B(…xügr&Ñ‚>‚òƒÔw‚˜8…EØ:¸Zès\h†^H…Q8†O膇÷mÚg†%‘†Ó¦xNH„à†}h„_h‡V¸‚h¸„kXlþ.‡3Hq¨ˆ9x„UØnüWz÷|{è‡ wohƒ8ˆ¹w•Ø„=„£¸‰C臧˜‚臄¨Ç` !°‹²8‹³øüE‹¸ˆ‹¶¸_¹Ø‹±¸‹ú勾Œù%Œ½HŒøeŒ¹ˆŒ÷¥Œºx‹ÎX‹Ð¿8Ô(Ìh_×X¼¸ØhÔ˜õåߨÛ(ŽôEŽè8_êŽÑ¸Ž[¨‡¥X~©È‡õ(†‘‡Qw…¬èЧA! û–x9 qôø © Ñ‘(9‰K‘¢8¿gÙˆ±‘d˜‘ a‘ü7‘ H! t)¹9‚Y iþ é3)’•¹’©“€ø’¤“¨ø4é&™uA<'Hy‘±”0y”OyN9”1•6 KI‘þ¸•\iƒÑdp‚0M d÷Ñd©€@M0°F§––i©”nI—di–:g…l —rI—f—_©Gà–o¹{y–·ö•wéd¹˜q9—‡õc@Š©Ð˜‘i÷Á–o™ G°FMf‚¡–ƒ@PXÂg–uÉš«¹šhc€²Yšûpš©¹a·é—ÇB–GÀ›³i•«ù˜Ÿi®9¾ œji›Äé®Äº)›Œþ)–†Y@0¥‰A¨ÙœqIÐa™ @@¹yKÊv˜‚‘›Oqª ™‚œë¹í‰døÉšQša9ô¹ö9r‚õ˜ 1™€ûÙŸ„5 A@à–G@ õùšÕy˜±žê)–ý)¡§FgI¢yw›éù Ú‰–ð˜j „ùž¹œ:—û ¢ŽIcÝÉ¡œZ ¾‰ Ä¹œ4jÈy£®ÉžÚ9œÿÙ°F«4ŸõÉ™ Ê£ÆAŸº–+Z¤Ê&šby¢~™¢ì©›h© ŒQgù¢Z—M IÊŸdiƒ Q¡n9H¤@ œD:b¥ù dʤÃé±&Ê›yZŸXǤ"áJ‚á¡!Ê¥ 1hÚ™”ˆ¢b¨aÂ1MP!q¤´Ù—Ô9«äIjš™ eÊWa ªS‰Ÿ Š¥º˜º‚Z  Ú™žUf$*á œä©³z£` –ÜÙ˜šj˜“ À'ƒya†I£‰ ʘe™£þ9ªA›•£8ŸV y)ÌÊœCZc“)˜–¹ùÚ™ÛZ¬‘­û!ùû,@ž&þ÷ H° Áƒ*\Ȱ¡Ã‡–Ù"CÆ–23jÜȱ£Ç CŠiI’“L †©(#FEŒ$cÊœI³¦Í› Ç(Y2e‰4jМA“¦ /fÄxáSã˜OqJJµjÌ4J”L3%‰3`ÁváÆŒ”0\º„]˶mX)nãÊ…+w­,«xóêÅ9& “+>°ÂDŒa1[blƒ$ñâÃ#K6Œd²åË•/6sw¯çÏ 7¢AÒå†i A æc†3bpÑL;3íÚ·ÅpÍ»·oƒVºü8òÃ)‘Ç€¡87fçšmkÞý»ºu½U¬TÂý‚*’«þpñ^LÀÚ¡3VoYúeΩâÇ¿N¿þÌì^Š)‚ãFû Á'_*h GVx‘EYܰƒ"$ |VhaBaxqƒ ¦YqaAÎ÷ሞ† Øàƒ$ "-Æ`†7øÐaŒ/ʨãuf$^lpE‹9îhdoÙ}!†itÀ!‰E)¥gT$Ø 5ÔÐà XŒå”`âÕE|Ð>ü^Ê2 ŒaÆIUv%Ôi˜Ùæ›rö)Õ\Ô &`àš{èç¢4¡XYÂØ¥›Š2ªéHUn÷R‚aàôc±£O;™ƒ£fð.ÿŽMTáÅкÊw!+|Aû“Qü‡)ìÀ?-ºRΗÀõÏ |àÀ>”/”‚l‘ÿÔ µ(ahaGdžÉ:a>˜¯’¨ ]RŠ6ÈÁªÐ†þêÂÿ8@¦á‡@ó)TáCrHÝ O&Dò¢×Éh5ñ€8©7¿ M”‚F¥V¹evà§G¨‚6òY˜åµ©opZ… ÐçŠð XàÖ Pü”*MrV3Bê#Øè Œ¹‚?R8®††©T9BŽpEì£Fhc…8Œ­[uëR½j•¾„­@€˜ðƒæ¬ i«`=שüÀ ¨°˜EÓ8D²“ÕKe¥²Í} Ep$`>ØÐÞ”°z‘þB¬$4´®µÊh«¢GÓBƶZUcnñ²Û9¤”·mípE [Ð ó!¸]®TŠ[ èJ÷&Ôu¯[“ìÚg»Ü‰wëÞð’d ŒÉYÎb±/š—¸Í­Pyß+’ñÒg¾ô‰}y¤ÜüÚd¿ÖÁ¯;àêxÀ)ðoŒàŒ(Ø7 n0DÜ›KØ!æ…/Ì ‡fÃVˆ‡Aâ#dÄŸ)±‰ ‚bϨxÅiñ^^ cÈX/4®ñó’cï/=^ñ­d¹*Eñ‘©’dŸ¡.k¡ ”¡,å)ÏÅÊM†ñ›Þ4Ñ-{ùË©è2˜Ç1“Y¸5Éþ˜¥pæ3³¹Ík†ó–Ó<“8ËùËo¾ó–ó¬g:ËÄÎz~ŸíIB#ÏÏ4©©CÝF#úÑŽ´¤'MéJ[úҘδ¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾C¢€~ûûß ‚AÊwhZ¤àa4Ââè,¼ 7ÈÂ#žäQ¼!pºøGÎqCœ ‡ˆþC¾…k„â$ˆÆ2ñXœ$¯ Çîq‚|$#ÿˆÉ3‚r‘o¤åyùHbN•™ÿ»æ¹¹Hrî‘G¤ç ÿyÉ]~•+„èS1º¿‘®rãüà:gHÊm‘±[èºH°.­÷›ëûPzH˜Þ§ûü!f—ºB¬îõÃ<é2w;ÜåºsÄîQÇûÝ3‚vލ=$lljÛîw‡]âa_ÈØ¯øÄ3~êA¯úß»^tÁWžå—/ÈæE¯ù“—}ñi¼Þ?>tÎgÝô´GýÒÁÞt±»¾ó‹ž=BønùÓCÞömÇ=Ù‹?wÞ×Ý÷<½çcú´³~íÈ—¼ò‘þ¿ú½;ÿðкôƒ¯Ù—ÿúÇ'ýíµ>ø¾7ó½o}ôßhá£ßþêOÜÏ~ãwŸáßgpò'~ôÇgW}ŽwT—Ú×¹·ÿ7|ø{WG{ã'¡g|j–}71yíÇ|…7óW%xÓ— øy¸€ûWz¸|º÷~ª—y&H€‘wçç}ø€È€¸}ù+H„@8ˆ‚äǂ؃2¨ć„gƒî~ˆWh€øçƒ;È…=…71…ñWƒcXq~õ×…h}-ø…$±p‡+P†Fè…uˆxK¨„iÈ„k˜€mȆ/(r‡t˜zU(€þ7ˆ…‰¨…j¸‚Ô÷‡~ˆ!1ˆpXˆ»‡„V¸ˆyˆ†ŒÈ‡Ž¨‚Gˆ`X”8‡Ï'‚TˆŠgH‚¨‡­Š^øˆ±‰£H¥h‰4xˆ«ˆ‰)˜…‹O¸…µÈ|Ã(·xŠ38‚¼¸‹d˜„œø‹‰Ñ(‰?HqŒ™†¨Š'ˆÝu›8Á؈¡H‹oH‰¸¨ŒÍ˜ÞÈŒíØ‹9žX޳WŒû€ˆ¨—¸ŽùøŽÎè‹<8³8öy׈ŽÈ¨¹Èÿ¸Œ{èŠò‘ÒØ…öˆX‡ã¨ìÈŠé Œ(9’‘'¨‘#y‡Ï’Ù„¹þ‘²(“æ8(™„*y„,ù’鑟ȓôH‘É„‰Ù˜“ÖÈ‘ýè“0)‘M)’A)‘%•q“°‡”†·”ùŠ@Y‘f‘H“õxŽƒ˜Ž©˜ŒZÉᨉ` •I9“&Y“‚h”ùˆ•Lé’[Ù“yù“l)–Bé—DI–rh–eÈŠ¨–xù•ké”qI£÷–¼!† i˜ðˆ˜\Ù—}è–`øxÕh1!™©”îH™OÙ•C ’|)•é™úÆûæš­ùš²›´™o°i›³‰›µ‰o·É›¹é›»yo½™‹v¹—‹©‹‰‰šz©˜™IŽ9–ûV€œ1ÆÙÖ‰™ þ‘ŒyÜé–ßéœáÙƒãÙ܉”ãY‡åI ±žwIî©ûŸIŸ²hŸ{qž#‘žAŸÝ‡ŸhÙž[Ÿ.H µ'šú)üéþ© :v ÊžZ 1¡á‡¡U±E¤j™ñ¡Ìù"Ú’ Q¢â8(*+ ˆ-0£z`-h`@b¦GpM€ûУ‚Dq£0ÖÒcÅr¥b¤c…¤:êóBB [$¤@ yŠÂ,pM[ĤNZvEJQMzM@`¥ò„wb¦^Êc@Jš @@¤G0§(9 1ðþ{*¨c@£MÀ'€º-_Ú§ˆº°E_ö§ ×£cµ§Aú¤§¨²ƒ©a¨Zqp’¥óq6û005 ©/ªz©hЦ˜Ú§¨©© ºª aRP£©@‰Šª@:Eq6M@Szœê¡©@jª¦‹ú©@¥û¬§ê¬›º¬1¦¸„Çš¬W7gB@@¦Â¤_Šq^ö§ØŠ­éZ~¿š¨À®û­jéJ«#ñ¨ÀfƒJ©qJàŠ¬÷J¤Òú7ª¤ûª©i*°¨´ ¯ ríŠ2º§öê©Gª ©c%¨O ­ü*v¯¦öJ±o §©€ñ¤Êº±ëêcP‰j­ýºªÿš®Äº6»±Ê*°Ó±0²Ï³Pú±a¦l ¯Új±äZp}ê³ìú±ØJ£H@²F;®”š¥A*L ¤Œf4{«@ µx'¤˧~º³ 1›«ó° ©‚*«©@›¦G«³ª©ÛJªa¨¬*·¢z²K«MPxk-zË®[K hªSÚ¨€‹¦†ª§@`¸ ««ùšê¦RjJ;¤Aʦ“êHÊ¥û §ÑÊ·!G«n*ª[»-×ô¤u:®""¦dÚºMz¹`ê±ñ:»aK Bú£©»A¤!ùû,Gžþ÷ H° Áƒ*\Ȱ¡Ã‡Ϙ9“F☉h,šÓ… ˜3 Ž yp$É“(Sª\ɲ¥Ë“͈#e昛8ÅpÙÉ“Œ™Ÿ@ƒ *e¨Ñ£@‹"5#ë¥Ó§P£J}zFL’!DŠ’DŠ(^·ÄÐR刖[fª]˶­$nãÊ…+—éÔ»xóêzFÊ!E”2¤¦Ú³\Þr+·ñLºŽCvË4•彘3kÞ<ð !A8K­|Y´éÓ¨Qž‰BCê—¤S½žM»öE)\×V{·ïß™oV±qøÉÞÆ“+©ÆL•8–?D.½ºõ‡gšð¸q}!õîàÃþ Ìn…»xƒßÏ«W.±‰ë ¦Oßö(åë¿¿é‹TÜ`}üùg`pcq|õxàƒx¥aFêç „ò¥] Êb™l†ˆWv?˜Ðá‡"¦Âð‰¥©(cKcˆaÅ 0ÀâŒ<¢`Üð 4èaŒ=&ù]„Ñ'`Ààz*i%AHH1F˜à%Gîxå˜U¡%6|ðA`¢Hæ›UQ…UÔÂV´‰$œo>AV¤Ù‘nòÉgÃ… £¡†Š1šŠº'£W:ZE,Jé›3aáÁúå•›¾yƒJ*f©J ‡C þ™ê¹*«=ªZxY‚¬´^YFVT± &ü_•½fèErâ𠩪‡l²†F;tðì®T†Im’3iÑA 4¼ ›´Þ~Ë£aÀàå Å›®º2:êC %pÈ+½2š1FDº/¿*šÁDhJpŠ_­¼….œáMLàðp·K ¡£HØëÀoüoÀ ‡ a_ ñ¤&ûwF$¬j˾Ü%Óì²\ŒnÆ:×w›§õi)³Ÿ£LLŸMˆa†W )Ýßb1àyb<ÑÄiŒQ“Õþ9wÅñAý5Ö^¥A¶~/ŸvLH µn¿}ìþ£Wl-žLÔÔN¨¡÷ÒŸ×uEPä}øy7IÑ… ñí@·£ã=ùzUmUNxSŸŸ—Ý h¯·ÃuO ¹ä©wWã6ø½Ù,ᬖNEíâ…®»f?ð®G|öØÄ‡WUÇg&ʧÔDÔSKQuôÝUE'jGpÅ"¨dôžøb fˆ>oG’êþuðË@æ»JHã„ýñO-þûŸ¨ @’æ ´NÿN3?(ð|<8Nþ"X š¦‚܇ª‚9DK2™¥ãAÑ€ûà>P½ƒlnKQ[árZÈ` þhÂjHß1lb ›Ùƒ@ Z0?‘€P-dV8¢,Ò<&,Ñ8<ÜL¡àŒG¨ˆ¸,j‘‹û"p¨™ù ¡,`\àxRóL§D9þ†Ž™1B&P…İ  :°X!X€ ãgHß 3G¨Àñ–¥Üe2ÙÌa6ñH˜µ\f6ŸÙÈp޳t»°[ÓÙµ!¾3kó\çëyµ|Ƴûìá?£6Ð{´ ýLèú´ˆ´¢ÍèEºÑ~´i#Ú€!ùû,I™þ÷ H° Áƒ*\Ȱ¡Ã‡ÃtÉrÅÊ>&IŠ$™±£Ç CŠI²¤É“ ÁèX©G•"Fˆ ¢¥Í›8sêÜÉÓc]$¶$’Š’!D¢ô\Ê´©Ó§KÑté’f_š.V–Œ1cFÌ”1UÇ;’,Ô³hÓê#FŠ[·V¬¼kDŠ˜¶S¦¼ÝË·¯ß¿€ÿ5£¶°áà ÏÜ]ÌãF–*Yn\BI‘!Bªh‘1CK•Å C‹d´éÓ¥C›‘…¸µëÊAw±‘¡¶+˜…è."#F 1d|>M\LêâÅ/^ªyª×У÷Œ}W` ,¹X„ˆFé™þ;O¾Ô°Á [ޏc¦šJრ>\¸'©}ŠaƒþlÀC¨0²ú¨U\ÁtশÎ9DH<à€5x°ÁžI„ÄV „„°,œ¢"Ù¬œº%1[BüðÁµzfå¶^ê&EXðÀ|À,ºR>«D®<Á ÌKo’E@«„TàCª JaÆ^ÿ¢©‘T` Eþ6Ü£†bÖÀAÅËØ„RAƒ &pÜñ‰UL$1pPƒÉ'c˜²ZÄPÃ0ÇÜàºAŽ\ÃËØÖªs’b0Yƒ 4ôô¨Có(Æ8lÐ —æÜ4€bta­µ$[}u~UXñ=l`-ƒ]˜!ô×'ÆÅÁ]=œ ^”Á4ÛbµÁþ]WÈ`YÜwƒMˆ]ƒøµ<>x‚)nmmˆ ªÙƒãxÆ=hl‚ Òmæ™ç' ZÆÚ¥¸ßb†anë ¾^­¤ÓþÞé°ã®­îýmî7øzÐg°¼ë^tà|<‰-o-`ìö€¿cÛÖhXß_Ê6Ü…¬¦á–òâã—ò wY>œê7áVûø­Ë§:_¡N°að =ÏyFJÐÐ…&DÁ „Ïæ² ì  ©ûÂÁº‡w·CËNâ/TA€ü`Wz‡–`À$aøÂì'…ªG c@‚‘ ^`þòI¼`&ІÚêÐr ×GJX…†‰äÙÜÏb@ è€B‚/´å~XÌâ¡b„À‰ Á?RF3 }i”Î|ÒÒF7€ #PÜCJx…Üå±0{DKýx… ŒË#jjÜïéšDrÑn܇ž€Ìñ!)ºÜ$)‰K²“ØÇ09B¤ ^À‚!IyS>¥Èå@ P…@*­!e,ÃÐHËÖØÒ)]Ì%òF±õÊ!eC¦YÌJ®ñ–˜@`€n¡ 8PCJ8C1 °š°¹&2Ýhr Ep!0Ð…&€j!1þ´‚Òp.t¦å˜Miã¤U6ÁU6pÂÍP_Ð'?ý™Nžåpá€ü Š!(žlðK„Àr† £¨aÊ1ˆm¨ÐËÔàž ÁAPšÀjªT-,]ÊÊ´+ŒÁ‰â²Q ‚…#(L PˆÂRŠ– ö¤ƒJ$Vì¢Î‚4, “©ZË®BE QÀ h²Á1Œ¨-ç$ëS¬Ú”ô Ĭi눆y¹–Õ¢­ !C¤Ð° S¬~… ]Í#…'˜Ó-bègby²XÆ‚O>£œìN*kžUi6'œ-g?{“Ð’g´¤¥^áƒÚԚĴàiþ­kϳÚ÷Èv¶"­tn‹[è6:¼ímGزCî,¬Ä®j  à*÷!¿…ŽsŸÛè¾fºÔ]ˆu]ƒÝì&d»­é®w^Ĉw¼)ïa΋ި×0ìmï>Þ[˜ø¶—¾j±/zñ›ýŽ—¿hñ¯w|g—ÀP10uü?—ÁNq°rÏÀ• [øÂ^X†7Ìa…qøÃöð…g'ß’èÄ(vÎRÌ⯸Å0&Ñ‹a\bÇÅR¸ñs¬có˜Æ5~ÊTC2d9ÈHN²’—Ìä&;ùÉP޲”§Lå*[ùÊXβ–·Ìå.{ùË`³˜ÇLæ2›ùÌhN³þš×Ìæ6»ùÍp޳œçLç:ÛùÎxγž÷Ìç>ûùÏ€´ MèBúЈN´¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúÖU€®°TÉ~äȾvÈ‘l… » Å6ȱ’ì},[!Á~6Cú)í‡Ü§Ú&Ù5¯Òla¤Û Y6¸ Ríq;"ÍÆö·Ã-j›äÚLÑv¯Ml‘ˆû×Ó†ˆº¹=ØD»ÝÊ~·@ö=yó»$õ&rBÌ=rëÝýöÈ¿Cân<Þ»ž7Â×­pfã{!æ&xºþýÍnŠÜâç^ŠÁ÷Áð’¤å¹·Gþ‘G¼#IÅIo•güà$I¸‘þqh?ÜÚ7‡8BÎñ˜ <å=Y9Ì"ô¢7ýèF¯¹ÒMò¥œÜOgzH¤Nï«Ïœèg9Ö2r‰»¼ëNG¹ØABö#ÛÞhïͽô…Ì=çpoxØ1®kÝìzÏûÚ=®õ¾sÝí^79ØåNøm³¼ìwïøA`¾÷†ØÜÎOÎó‹û¼ð@IÕÓ¾y«3žïi;Îßù¸—ê<©ûá3?ô×/¾õ}ÒOúÐãþësÿˆîUøßÛõÀ‡=Û‡/üÈ^ô#éyÔþ~y»¿ï¾¾â©ŸõÇÏÞúÆO>ÇÕß‘åƒÿûšo>ùyïù­Ó¾úTÿºí³oúí£¾û»½}ÎGó~ùW{ŧ€ýÇÇ·†Ç|˜xXj'~ùz ¸È~ðç7u €Ð÷|¬7~ˆ÷7}è׃—}!—zïg‚()ˆ‚æÇ‚9H|Øç‚”ƒG'‚=H‚'8X~G¨ƒ+˜ x}(y ˜Y%'„O(„2GHx€Ø‚*8„ö×€ÈÇs1H„ñ'ƒXhƒ;¸…7È„KxM˜~/ØncH…‚g„(€ˆ‡Zˆ†`Ø…Oø…x}Q8‡˜§‡gh‡þk¨†i˜…oȃèˆ xpƒ„…Hƒ%h†IXm¨ˆ^Ȇ臑Ø}“Øxt؇Vˆƒ{H†‹ˆŠè†#ˆ¡¨}GˆÞg‰Ehˆ™È‰\Ȉ¯è‰­¨„}(ˆbH‰µˆ‰wh‹Ò§‰»ÈŠux~èŒÍ耣Èw¥ø€§¨Œ«ˆ‹›È‡è‹Ñ胷7X«wˆÚ˜ˆÜø‹ÜøwÛèŠÁØuâq䘎mÈyÞØŽçø~¨°‡³HŒˆŒyhîˆŽÌøŽÉè:G‹iŒÉ˜í8†ûø‰¼ˆÀØ8ˆ˜‹™Š™‘½˜鋱x’lç(‘©Š"™þ‡º¨&©‘™’6{,9ƒ ’=9‘2¹ŽÆG“7Éø“wx3 ”Çx9©Œì·sÒÈ“0‘!ù’YùŒ©ŽP©D©t¡j®Äje¹jg©ji™jk‰jmyjoijqYjsIju9jw)jyI–Ÿ¤–}É–é– —ƒ)—…Ù{é’ÙH]É•NÉŽËŠF‰k'1Hé–ù‘ ‘™K‰œé”ñ™÷šC)¢ ¦ùt§Ù™Y§)„«y™›Š9´Ù™·ÉйY»Y}½i­i¯)´ s»in¿‰”ÉyŽË‰x͉vÏIÁYÃÅùÇ ËþÙlÑlщŒßÉqáG › Qžš¹èÙ™±ž 9î9šðYšûŸ¨YŸOgŸ”¹ŸüÙ€©ÐÀ: Kå) ûP Ïa  0$ zb,7Kº ZPÈö *¡^„ê¡:k º% r(ê@ LÚ¢Ó–b*J¡,Ú¡8ç7Úª£ q&êG0¢hå)…@°G%à£Jšbçù¤Bz¤TP ú¢ ¡¢G ¥ êž^:JGEbª¤ßV"(&Gð¤cc0¥cºp]ÚHÒ@¦úÉlÎ!¤ sº¥þ}ºyí¨þijÚ¤ hþyG>oš¡-‡ð“ZŸ@°#LjB§”ú¤–ЧÅ&¤J*ªœJle:I G ª•z¢µ‚$£°*«¤j¨mÚ¢ª«Öö û@JÀ¬ûG GŠ›:«1F ¤±« ©²Ê¥Ÿ¡N¤ª ê©Ý† š­Vʤ‘šªÏê¡jªçB­G¥©”:¤–ZªÜvPPïš®vZª š¬å™­:Y¯Eº’¯èÖ@P cPÎj° A­ J©­Ïú¤³)€A¢š •Š$ÛJ˜ ¯«Ê¯š¸:¡ ;²,6¯ Ù) ­kê±÷AÔ¬»¯ëʬr ¥7»’Ü–°Z" ³.ú:›Ô³ᮓʱûŸ¡¦KMPh:¥© µºN ¥_D§UJ²«¤|ú¡%;J]›¨`Ÿ/Ú¦õYPU+¥„г0ÒM@Q¶5zÏÑ©Qº¶Áú€§ŠZ¨o[òº‘ú£z±¡¢¹DŠÊ®áš °š£Û¶Ý¦¦4úrwÚOG[¡@J«õú;Ë¡ mš;²˜›¸~Ê·"Šº®«ºÌš ’˸³ ŸÊ !ùû,I–þ÷ H° Áƒ*\Ȱ¡Ã‡ ¥˜H±¢Å‹3jÜȱ£Çe´Ä!ÃËÇ“(Sª\ɲ¥A4gÌ Ù7fa32ÏpÙɳ Í55uI´¨Ñ£ qŽcF"ΧOÅ„ñò%Ì/[ª@ã…K˜¦PÊ‹Ó)Ù³a¥ÈBʶ­[–bÆ4‘"‰˜»xïv¹Á—ï‘ /‚òcdݼˆ+¾kw±ãÄjßJžL¹bܹ‡ó¸q¥Ê•,ÀðQDˆ…[2?^­šµãÈ•cËž½/.ÆŠ›ÜØAWÊ `Œ„AÜJk׊#_ ›¶óçGã"™N½:’#6n¹~ƒÄ_þ¼øa½¼ùóèÓ›—(¥½{)ÐãËï—®j‡FЦ­6•ÿÿ©Ì'à€i±“]ÜG Býèß‚F8 .l±E .t¡œ„5è ‡ ÎWE -lÁ… ,¸ƒw…(‡º(#mVPx× ,° \ bÈã3YY1Œ 9!Ò>ÊâàƒBFéV3”ÈE )"ÁE Z4ùd€R†yT/T¸EŽZ ±E 4xù¤˜pµ &²ÐI3ÀЦ‹?BçŸ)Q¡ÅaˆA¢Š1Ìpƒ›êèG^ȰÅR#ÆàC{1㣜né¤cŒ(ƒ]ºi d§¨^Ô„q‰”æ]þV˜êgª´>T…\tq<ø`\|:ùf­Ä6„Ê\œpBÜE²‚Yì´u GTa Àõ`C ÑR+nA[^PÄ4¼ƒXÔ°A¸ãÆk­I|QqBXaûÁ~o¼UÈ€­¶ÜVÐà ü†Ø§´ÿR[…Ut¡,³|]oÃâVQè?øÀÃ-ö;,Æâš1†}ŠÜ(ÉÔš,ÅYø*F³\l\›ÝÝÔls­wÝàk<ð€Ï?§:Æ}UàÀT ò¦I×:x€AXðAB(,ìÊU§*t`ÁÓ´-5ˆ —mut€\ÁþÍaÜr›í…7Dmô\¿-!{ﵸÒXÛ€78à@8À=6Õ;Z…T„ðá``ƒØþvî¹Æ^˜@‚mcA樬:œaÈÀ"%˜Pƒ„àÒ·Gùi]|` HlBÄdº[aE%pðÙóÑKï£5\ÑE ä·¸SŸê}˜Vl€E$¼.‚[;Ùë ɃYt1ÿü!Ø@Â4—ºü ©}À‚:ð<0z"ƒè2Ð;°B< :,Ø€{ý:Ù{Ò`Á yÆ aø;€„€Plg àMèRÂU> ‚>è <°IþMƒÒà¸ʈ5ðBl ‚ç=oQÁŠ¡üƒQF ÃþÚÖ,ØOsD¤‹¥0”+†H‚½²BÌn«~5Å=_4#ˆ$È™º|Z"KCLÎÀ09r( a`c]¬à¬îù‘@bÚ|à6ò:Cè xñ‘”ϲÖ6ÂAñ~œË$„ThÙÀ•(A´@Ô…Á“ W¡ú .>ˆ%[†×‘>Í’– ŠppÊT²å ´ÛHŸ€¡3Ü•HÚ"2€€# :ƒÇPAfH’È.É–#à¼ÌH&Ø8o(‘Ckd‘b„@ ÓþÜ+¢–*’Q›R`‚;†:"áŽm©§=€}žŽŸ²ˆ¡SC4>¡²A¦“…R&4 5FÀñÓ O`Â\¤&Xô¢Ð ¤)Ï‚t¡Ðúµ„q LããL¾d‡œô¼iH«‚8"V(C*Ì…(ÇKtˆ%6ñp#l_·:µÁ;àØ…$ø§.{©ŽÙ‚âØ8µûØÀ œ¨Ûƒ¸÷,}‚e—Ìd§˜-Y ]‚x°°Wºj¥èþâËe·4¹2_Ì Cüªƒ.¼>ghs[ÞL›'²:¸B*ÚÃG=w™µ>*C S1Á4ôÑÐ.á3ˆèlç“1ÒG‘4‡¦;DvbÚ(š–{ÿsÂ~z%¡ŽàNÝ’TChÕ¬Fµ—1k¸zA°®5JnM \ëú#¼¯Ý‘` hØÄÞˆ±çƒìdcD‚æñ–ßãì“,[>Í®¶E®Ÿlk›"܆޷¿ýp?gÜänˆ¹ƒît/dÝüÙœúÜ}xϦÝôFˆ½eƒï|d߱鷿 ðÊ|à)8eŽp…O†áIYÐBñ§˜¥âg¹8ÆÇq„ $(_ ù—þ&*ò’ÿ‡ä&/9ÊCéq‹¤¼äRx¹Éc.s‘ÓÜv-wyÍŸtó;¨ç>ÿÐçóŠ@CzÒ}¾ô¢·eËúE¤îôª[ýêXϺַÎõ®{ýë`»ØÇNö²›ýìhO»Ú×Îö¶»ýíp»ÜçN÷ºÛýîxÏ»Þ÷Î÷¾ûýàOøÂþðˆO¼âÏøÆ;þñ¼ä'OùÊ[þò˜Ï¼æ7ÏùÎ{þó ½èGOúÒ›þô¨O½êWÏúÖ»þõ°½ìgOûÚÛþö¸Ï½îwÏûÞûþ÷À¾ð‡Oüâ·D [Ô ò螤ù ¡:óÏèwè!Ö߇ôþÒüí7„aÞÇþ‹Ø‚|åO}úç7Hö¤õ$üq¿öÅÿg£þíÿFÀ„””_ègÒ'×—8Õ÷XôÇ~Ñ}~û7~HñËX8áw€ý'ý'Ù÷¨Áä—|hé‡ü”Ñ è!h!‚êG‚‚¨‚¸X€.HH}Ü7ƒß§8‚÷÷€:è„xØ‚@øƒ,EƒI Ø„ è….÷„%(RèƒT„bÈ„DƒHȆJØ…7hƒB(‡Q…KÈS(€#¸†9ȇÁ[}p8‡4†tx‡þ}ˆˆ‘‡/x…8„X膂˜€\Xˆõ÷…q8†uXŒè‡UH…ލ…h„’È€hˆ–Hˆa‚;H†Fñ‰‘8г‰¦Ø‡mXŠ•˜Š™ØŠªè‹›x‚°è‰f¨‡´x‹jX‹Ï‡ŒË¸‹”ˆŠƒØ‰À8Œa(.!‹¦ˆ†Áh‹¤ø‡Ìˆ‹o¸Š¿øŒiØ…Šˆ~çˆØØ¡hŒÜ˜‹ïx‰GèŒó⸇ȉéxëØ†ÚHíøÉHÞHºh¼⸡ÈÑÎøÖˆ†YÍ8‰ YŽõ¨‘©ÈʱÇè±¶²Í(²K° A±a±±¡þ± Á±Ù'²ÍG²I³èg³q&‹:{®ѳê*@Û®1´ðZF;¯B[¯Kk¢IkƒO®R›{hM h`@õGV€©û0Ær1°µ5ñµ@‚¶‘µ:Ûd ‚‚¶@Vf›µl"Gv a»n Ï·@aŶ…›·‹ë/`¶û¸\[}D'Žk¹4ØÐMѹ6}:뵡¶f› @·G@¶³’c`5ñº±» +¶2»:ûºµËMVû!¾;ºpUë`©«·1â»Í+¯‹¸’{*Œþ+h·{û¼Ø»Ï+-æ½Ñk€rº»;½¹Û~à+ºÔ«»¡û}_¶î›»»;€{X«µú‹RP¼pµc¼KG€»ѵÇ{¯Û ½ú¾ »©;À3qÀ@ø&³ ¸x»Lì¼Æ &MZ¶ Áß«Á$üÀŒ¸Šk¾âÀ©ÂüÂæ›ÁÀ{¹ÚÛh»KX=œ½ —¦¿H,»±¼©Àïe» ̳a ¼X[¼àËj˵)¼¯;±»±r:Lû©{ö«·|½ºKÖÀìÅ1,¸o¼·`rÃd,¹‹ûÆþa¸3ÁƦ¶ÂRsìÅ:뽚;ÄQÄVkºÿÁÄáÄPlÁSü»ykÅ٫ÙŒÆPulÅÌÀ;H@Æpë/Süº|ì¾`¬»u»]ÜÃ̾×ûÂ/ŒÁ¨Ç®5\cðÊìûÆn[ŠìÃ…¬~5ºD¼È?l<¶“нÅûŹûÉÁÃQÆ ëÉë×¼ +¶ëÄ¥œ©K¾@˱»Ë«¾Õ‹ËzlÍÌ ÝÉ·œÇYÌËêœÃoüÅà ÏÜ×¾¡¸ö¿ºõK¶÷+͓ܿ•\¼˜Ûµ Q¸a·dÛºr‹Ækk¹‰Ë¸Ú,ÎwëǾÜ|}û¶—«4µ™+ÂòL¸w{¸‰;¹-̹Ŭ·¼È*½Ì‚ $_ëÅœ[¹¥›ÐîÔž+Í\¾û!ùû,T“"þ÷ H° Áƒ*\Ȱ!“*\º”ixpI’$KÆPÜȱ£Ç CŠI²¤Â*U¬pá"cbÇ"Cˆ1‚$ŠÉ›8sêÜɳ§Á‡G¤ˆÓLA²Ì(]šF %JÆŒ3E ‚Gfõɵ«×¯9n‘* —/`–JY*UêR3S0*s‘ÌÛµoóêÝk/ß¿fdL¸0·ZƘArÅÄÇC‘ ,t²$Bˆ =rF \"[Mº´hÓ¨Å6̺µk†¹€¹Œ…†7®\N-F‰!EŒÀxCFŒ]vó^.y9éÕ¥¼žNÝ0J¡Hløøqå†å¥Sþn§ Ä Dh‰±¼sÓÍßOœª¾”TÕóë÷Éd¨/¼Á YldæQ}öá·ßƒ–$E5àpƒ &ÔpE„1˜Ê}†("E[xaÃc˜ Áˆ y"‹0Ƹ] á˜ðƒŒ­%Å?ò($‡U€qÄ? WÅ¹è “PR7”-°À‚ Qäd–\¶6” ,¬°‚•\nÙ噃M¨ V¶™¥™hÆÉ•]h!ƒ 0TÉ›²Ô÷á“rªv¾ƒž|úù¢ ŒÞ¤f m’%œV*’aÑB (ˆYfŸ Z*jHb8ñ@'œ€B—”ŽêjCþH$H  Ÿ* 諼&dD?Ä Eì˜h¨½&‹P¬ëiD±¹"«ì´9‘DXðÀ>Dû'µàî#F@`° ê·á*[$`à /¨«k»ÉJaE]ÐàÂy˜àí¢øºÚ[Ô ÃlPÃÀ»li2$¼Ãp±Ä¯¦Q1þ,ð±ìr<*Â\´‰–›ìª\¸ ³Ì´ì²¨+…V…q]Ø|s¥[pq…YôÀžÏ?3º 7Ô H'èJ†Â0¯ QK§]  g­õ™)ÙPà t‚ØcsÙDòÀAk“LpÛh¾}"þÚ|À6ÞPVÑÅ>ààßYú¤t€Ç)¸V`‘ÃüݸbxQCÆ@mùå2ŠñE ”`‚çvG :“™MC ðùê,ŠÞì!ˆ0;í"f¾^ä¾;ï†á0˜àA ²§Nü¤^P. #;ÿ<“fDÿ"”`¯´×óè…;xÂt¾~ûÚðAj×=éºw¯Ïâ¾sw¼üP¶jÿˆW°Âú†>þa~ªû‡¬`˜nº³žYÔ/d¡pðÀ¸Ô…3Ük‚,ƒ '¹`-KVøBÀB™á ‰a—¬à…0°°…"Ãm²`…qé þ\»!tËX¡Kƒ‡¨Ÿ"Zf†^Xaɘ!'N¦5D¸IÐðA*VÑ24L`€1üÀ;|D ÷“F1Ô1„DnÐôq‘†iä#!i1ü`+òÈ%1I˜4‘5F€$'‘p²CŠ$%u¹IU€ @[GF)K¯L À´Q-my hqQz)GËl•ª„¤@ …t@cá%3¹¢IhFS ‘Äü²Ù'3Œ¡ fØæG3ÌÀð ppÂ…HLþ’:3ÉNoF`è$‚,ðƒ"ˆ¡;-BƒPÎð1H û,L7 “Ê H!@€Ð,Lé1öTè>4(™«“ãòˆ§ mèZ¨[]ç »#•‚p·Â]“XWFØõ)PÉ[^’œ7FØ­êU·Û^óz÷º²ÀçtëëÞû¢·œçL'íûDõ X!ï…‘6̳hÁ F°áˇÄÁ#‚°…‚aixÃépˆ> âˆØ….ñF~L$tI™ûU1EN!«˜Æ²q‰qü ƒ˜Çûññ†¬![˜È J±Œ‚äê9ÂM¦Î“åéLùÀgà‹_Ãå-sù/^þòR”¼dŽø)#=³þš×Ìf4·ùÍlN3œç<Å2›YWtžó}ò ç=ó¹Íõ³sCÎìç?¯¹Ð†&t¢ÙhAo®!4H$ýJ;úҘδ¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~ûû߸ÀNð‚üàO¸Â“¦€†7¼ t=2ñ!¤âfnˆ¥ ‚ñ$pã é8EþòA÷Äá8ADnrŽlœå yÉó…|¼#5WÉE’sŽ \íÊM’ó—]ã-'ÉÍ3^’‡¤çùyÐ"ñ£»üâVz̳^™sœë^×ùÉQ>õ„T½é1:ÚU¾õµ$ìCwû>àŽužHíúAŠ^w¥#½í~?ÝóvžÝáe·8͹þ÷¯ËÝìIÉÒ=^ø§>åZ'üãñ^½;>óbß|tp^yxž!wýéãþè¾K¾ñ¡üÛI/÷Á^'©§:ë_õ½3^ñ6ÿýè™.û¹ÞîdçüêßûÛô!¾ñ‰Ïûá›þò@W¾ô!þï{ÑÏðÕï:í‹oûÎc?ñ‚ßýó“éúõs?øÞ·ýò5oýÌ!¹ÿ>îÏ÷î_ÿÑã~å§~8‘À·Û—€x}œ€ÿ7òWzöw~Úç}È|˜~àç~8€ëW€ˆ{ɧz ÈÎgyØ~*ø}”W{xˆ‚ ˆ 8‚1肱~10Ø‚6x¨{þǃ7X„9èƒä—ƒ8i XÍg~AXh„TØ@(‚ô§bÒ…^ø…+°õÇiÇ‚MH„ð7„JH€LH`ø†b"†Ó·…mHƒOHq°W…I‚YX‡"‡o(‡g7„H‡@Xˆþ)x†=ȇg¨…5¸€†‚x‡Œˆ†RHˆ‹ˆ…ŠøÔ‚~‘ø…“ˆƒ‡¸‚†¸‰vh†P¨ƒ‘牥x¡è…£xŠ«È~HøŠ¬¨Šx˜ŠiȆ¸X±Ø…³øˆVh‰”؉G˜ˆµ˜‰/¸„¿è†Á†cx‚¹H‹»}ºX‰k芨hÑ(sHŒŸÈŒ—XŒâ§‰ËȉÍè‹ÝŒÑ8Œd…Ç¸ŽˆXŽ{ˆŒ8ŽñðH稌×hŒÙˆáØŠ}øŒ#ÁÓ8…ÿhy…·ØÛxíÁØ IŽó8ry8ô¨Žùƒ™ï¸˜È‹âøé‘!Y;Èéþ‹%ŽñØz“.éY‰éˆ÷w I“þ¨‘¤8’ ÚÈ“è“=”ƒhŽÉX“y“I”½“U‰N¹‘‰Ö¨” ©‡¸8‘ù¨’…RÙ‘_I )–;É–K©]i“ky–S™’F¹’T™”pI}L)py¶n©nƒ™n…‰n‡yn‰in‹YnIn9n‘)n“n• n—ùm™ém›ÙmÉmŸ¹m‡yzO)—z‰—8Ù–,9—dY‹¹p{ÉÀ•蘳›vY·y•¸¸›-~¾™•¦©›´É› œE™šœ¸ù›Ë –ëÈœ¹©œ²Yœ/YÍ9—ÄþY›•(nY‹Þ©ÔyœÖù¶¹šÜ9ᙜ¹뉔Ô÷ž×9œ‘Gì aŸð9¡Ÿó‰“þ)žÔ ø ›*šõ±gÆh`G MP   zg~b¡× y4¡P¡º 8÷fQºZ¡4· "êqo†QÐ(J¡!¢-:rõЩР@q 5Š"z£ûGø!¤uyg£ Ê@@¤cÐI;š j¡w¦¡HZ¢ª£2ªfQº¥OZ¦IJ¥0;ºbú¤FÊfö· GP¡Ml¦¥dz¡8Šf£©0¥þ©P§U:`ºwk¦¡Z*@ð¡hVHƒJ¤p§Ê ðÀEJz©ê ~šŸ ¦c°©iº¨¡s,§B ¡¥zG@©mv¦#ª¥M  ¹«iª¨|ʪ.š F WJ<ê¦U  ûZ««ê c€G ˜¡” §¿*r«…Àú©šªq§h@ª¦Z§¨Zs6š ê¦Z¥š*«*š® ÚsꪮBjhÀ«uzqPº®ƒ– `š Æ©H ¬SʯÏÚ­Ñ© ‹¯+W¯žŠcP2ª¯Ó ®ÿú­ªóª0HP®@p®¦—®a¬û £+«´:¯;¬¢À vZïÚ«ǪõŠ®9Z¬ÇÉê¨{Ê¥6ë© K¤»Ò¯×‰š¥þú¯@‹±!¦K«¤«ê­Pº°1+mú¦ZZ«fJ­Tj¥zê«}гø°„j¨…”¨Šš°B«€²E{³Pë¥ ¤ꦌê­{›oš¢gÚ¥ªJ·€b¢J£+*¯÷$~â£' ¸mK¸.°> ¤I:¤Eº·Hª¤й!ùû,bŽ'þ÷ H° Áƒ*\Ȱ¡Ã0^¾”qH±¢Å‹3jÜȱ£Ç†hÊ„1ƒ°‡Ž“:º|\ɲ¥Ë—0c&ƒRǃ]tô°‚åäD™@ƒ J´ ˜1cŽ"Ms©™3fÌ ³/M—.¯†)è¥ËÏ/^Rå8¶¨Ù³h[£tLT3Hãš‘bF Ö¨aî¾5s•LT0]FÎÝK¸°á¨t–•¶±ãÇG™09rÄ .UÄHÙ̉˜+7®°sãÆh1F~døÄ ™Åx>M»¶mÙ·i›aü¸·o¡›‘dñQ£„ 2bö\åFÌVœ&"„ JûXž»;íÙÞwþÿOžå%J~lðP¢ƒ{Z¾k~ãõ +l“RäÈ ˆÆw‚×xå%¨àEP8!Å t5x` Yˆ›UôPÚs£!ù „§Hàm*Þ¶[*0ƸàŒ4TEaØPB ÁC%,$E3¾#Œ5&©`”z¦ÜÐ']›µ*E¨þ°®D…6ZC&hà§ž6ë¯Iº|ÀÞ™¼z ì²=a…xà˜p‚£Œ2«­CZ¡|ðÁ & l–Û¦›€–†C”ä^Ëg¶êÖ[БZa ñž»¦½Sq´;®µþ~ °½’få˜d¾.l¯´Ú A ;Xð(½׋TGT!|Ü©²!‹ü™Yt1AÊØ®ŒnËé†ñ…¥ÝPÍã¬îa7`ô¼6ÿ+ô²Ux†dOì Á*÷ú´¶]zÑDTÜ€pÍZoͬ^\Ñ%fͲÙÀêìƒ>X«*ÙoÃëþQ­eà w­÷¯Gµ*à 4¸}óࡊAÝXhC Š;Íø£lùÐs/T®ð厢ˆD ½ƒçƒÞ'Øb4±Ù.À€ºêºÞúë/¸0;펲…7lPƒîx/Î{›R„Aw´ï~<žhQëüóh"qEA qÄÃ6o9öV’nE÷U„›¨øŸ“_þ‘âàÁüú\®¾ê>šÉ_±ƒ¸×Ù•àög%&DÍ`È®GÀU!R5x:8¦•­Iê–¥|0¦ð±/uœ‘UЩ CÓÚÂu«V5 “ûd3\°… b‚¤4€CQðQþVøBò†Ca¬ëá@tu/”ˆE,À`ƒ Å€ ÄSœÅ(þ† \à‚¬ 9d±M1CC½Ø›*l¡ U0  L€BGÅì‰Æc£ošÀ….4 M˜c€ÈÅ<êñ1Lð‚É ÒQA¢!é)p0ˆA jðÈ>]iP )Ù¶hAZ¨Âk˜øÍ ( ¤Îpš,œ‘Mâ gÔËßÐr4ªDËXb…0<Á†iÐ zé›_-”Y ~ž£Á0³7Î Zw„S g€‘fÎpÍÇd“-Ð4ÀØ‘,3®äe9sN1 Åþ0€°¶"h¤#i•`9OzÖæžȧºÉ!dd'fP$YPƒÒ¡€öa4!#;éÂ+”zb uÀˆˆd‘xa¤$•‰Iςτ@£ ‚¬ÐÒÂ4¦0™©YjšÐ„À{lÉBEzðÒŸÕ%B- Q‹šÏ(Áž7qSoøÔ’”¦T¥êQ¯*…hÕ x_WcU¢L50€\ …«Za† Ù œºV$ € ,F#0ŽˆE,`+Ãlà®ê«PÚ:|N€ H¨ŒPÏð4¯V0‰HG)Y–PV(G¨À0p Fþáܲ0üàÕ =èÂï² ÖÒšö«f‰“Y0¥éjuƒ,ˆA©%ñ‚-}Nú¶%§ J˜ $ŒAÿ¡Û6ÀƒáÜÇžÓNL­ ´¥•´ª.«äêT“zÖåAõ$c-ëJk ­æµApM1¬ÐºÂîõžñ²d„Øãi¶³ ò×ÀöQ¬Êß´íßH{Ûé¶o¾ nq÷†ÜÛ6÷cÐ=mu;†ÝΆŠbö’˜yÛ{0÷¶w½í}lp3¤W1ÀNpüàG28Âîï…ܽ G8Ä#þpŠ¼á ©¸Å>ñ{ªãÇxBþ4îñ—¼W ߸È-¢¿´\#/_¹ÌgNóšÛüæ8ϹÎwÎóžûüç@ºÐ‡Nô¢ýèHOºÒ—Îô¦;ýéPºÔ§Nõª[ýêXϺַÎõ®{ýë`»ØÇNö²›ýìhO»Ú×Îö¶»ýíp»ÜçN÷ºÛýîxÏ»Þ÷Î÷¾ûýàOøÂþðˆO¼âÏøÆ;þñ¼ä'OùÊ[þò˜Ï¼æ7ÏùÎ{þó ½èGOz²+àô AZŽl™Ä\ ­oÉë {Ìþ µÏÈí3þ‘Ý;õ©_ý@rï’Ùßöw½CŽÏò‹0Ÿ!¾oðU?֛ŸÀI~L¢þ?üâ;¿÷™¾ða}ÜgŸ÷Û_¾÷-ò|!…õÔˆõ‹‚ý D¿ýá>ùeÿ}äoDüÕ×}ôg~ö§}0¡û€ €¡€Á€€ò'€DQ@qÊ×è~ì~ÿã—€ågØ|èw€êÇè!û0H€h€/€%˜¸‘ƒá‚08˜#'„4èqƒ!˜>H@(ƒDè„ë÷oQHH€xzñ÷‚Lx~´Ç…Q… aƒS˜‚;ø~X‚`XA˜~'Xƒd¸<˜†ºIº¤$褽ÅMË¥SZ¥LÚ€Bš%O*[ºLc@JʤGpSµ'Êb<ˆµßuX@ GkѦ>:§Ý§ˆþ@@@>z¦cŠ¥À¤€¨ûp¦bš€dª€mz¤Ž ©’J©M:¦–l‘©HÚM[*0›º¨¡º§,D¨c@MaØßÕ£û «tú£—ºM4z¤‡ú£…Êp|Úº*«Ã «•*¨@p¦cËtˆÕMÌú©®ÚªMªÐê©bU@q¨Ÿs©Ö5£Ý$«’ª–ú›š§¹:«uÚ«ÑÀ¤‰…­º¨ Ú¨4ÚMËú¨´§5ÇzÒJ­¦Ÿú®èr¬à:ð5予¥0‚ Bˤ©p±•êQê¦ì:­jĨ÷z±yº¬Åꬪ(«K¥×J¬æ¯Ûz [­3›­ë¯ÝºžÚ@Mp±æZ1pê²!´T#€ª§ïòú´Îú›*³òª¨.û¯0ë®×š¨\Ú¬öjgzµ‘šµa[«ÛÊp@¶Gp¤b‘ª«ºŒH«´‘¤7¥£@º£2!¯{ë³,¥p ¦ÚÊ­$zSƒ»¦‹¥‹°aë¥A¸bºµ[ÛeK£ˆ‹m oJ¹•!ùû,fŒúþ÷ H° Áƒ*4Xå Vš,Ü"cÆ/ 3jÜȱ£Ç CŠI’!7ll¸ñ0¥>RŒc† Œ%sêÜɳ§ÏŸ¯ÜèÆŠ,H“^¹fŒ.\ʈÃÅËiÌh5sa™¯f^%9¨Ù³hÓôqã‹£X² Í¢EË+bÂpé2fŒ¨R¶’#†ÌÖÃfº(V fk`Ä##~ Y–Ú˘3gô±án%L„ø`ÂÄ ¬p‰!£ ”.¬‘î‚e¸Åå…í*„eçN¼¸áÃÍXÖ̼9f²XñðAD‡!8˜ÞСJš2´þȈ¡E6”Èä—y…tÅœ‘Äû*Ç?¥ÒáÙìµ³ðoþ+R¤‚Ùöê][¨F%(%8Áuá.8P3Páq°ƒý1_H—Á`À\48! +ì %À޽B±Ky \þßš!‘¶FÚèF÷šTÄ'Ä•M8ÀA ÔÁ$NÊ f€_YÔ… Ï^ÀÀDe…0hq‹ýy"m4p Ìã¥HvF4:gd`â8WIjŽ_8ƒ_P5>;6zŒ£½ÚöÇ+†ˆR@ƒ!ûC7´ý zÊÕ3Ha hƒİœI6Ç nºÁVÉ++*Ê)ŸœÖ·Ò`Jþˆ! >ð@yeQ9¥ à&-kÙœ34a¨ ^Pš¶k Àôdœ b2Ç ”»ÎXƒLn]J fæ·\3Lk^& mâ>ÀÖ@§ss$C,E)…rš3-#þ+<ภ™˜²>Êo•òžhé¢ËÄp à¡Í ‚NH&(8! AèeŠòàX!s5p&sŠð€œP4 ¤F/Ó&œDüéKÏH"P‚ÒS +UKp`!àˆ?G0@Ø9+È3 ©Hƒœ€Óœ¢å Rð)aøc„@ àH`©†T¨A ÀtêS‰CÕXõ<ЪG¸bS)<¡©bJ£†ÃŸ"V•‚Û>b40á¯LpÂOãj–¹æ¦¬f=+$ꑾö¯ƒ%ìO ‹ÄšuPUd52Ê10£€µ§dwBÙ©:§ª‰Ý‡Up„Í./žeþ”þ–Ñö¤´®Í j/+ÌañA<›¥ÒÖ¶·%ëi€¬ÖDÐHš &3´Èå n-kVà»GBk5Ò…ê>®ÙÍÉv—€ ³bˆ€PÓ*$ _x,Л^’¬·9UÀ" Ú&\€ÊMHº`]14ᯢíoHþËœ#Tà8‚öQ€Ü€ºtU0ƒÏ óòWÂñ VÌâ˜"¡xBP… ³¸Å öÂ~õË„š)B÷1à?¸@e¥›_ëV õ1H€œ' dWG¸ÀP–œ¼ð8 ’ò”ÜŸ*ø€ûjþà©Öz–ÇÖóG¨ÜÓâ¦B8XX«_°YΡ3s6‹!6' Ôõ¬yÿÊT@{DÐý±¯ž‡ëÙ1<°'vôB m§=¿ù±¹Õ´B8m'|’Ç¢î©ít…EÿU"©ÞȪÕt›ÀŠÁ Ít¬ 2ë4µš hH…_?»ëŒôºLíaRÓð×({Ód¾T­©ûàý>{ÔўȠ_(\Û!ÕÅpiêòÛ96–”†n¨ûJìn÷»§otÏÛHõþö½‹¤4˜aZ$n÷AöM$3(ˆ’xAþ#¶‚R”¤Txº³ýJO.{–Ã[ÄU¯b<ãûØ8‹þ^P)Tá"_‘Á!Y[§Ü?ýd°AînŠƒŽ‰4yòýlß ¦ú!ir›ŠçÏÖ9^ì>IæéZ¡ Ô§.õ©?½ê[Á9Ñ â¬®£h ^{×Á.ö²ÝìÈÛú>ÐŽ")°]ìn»×ãŽvµsít—û¡ò®÷ñ]ìvÿˆ®;2xŽ>ðˆO¼âÏøÆ;þñ¼ä'OùÊ[þò˜Ï¼æ7ÏùÎ{þó ½èGOúÒ›þô¨O½êWÏúÖ»þõ°½ìgOûÚÛþö¸Ï½îwÏûÞûþ÷À¾ð‡OüâÿøÈO¾ò—Ïüæ;ÿùоô§Oýê[ÿúØÏ¾ö·þÏýî{ÿûà¿øÇOþò›ÿüèO¿ú×Ïþö»ÿýð¿üçOÿúÛÿþøÏ¿þ÷Ïë+àÿ€B}X€ø|€è| (€Ø€ °€Íø€ (ÌG¸|h ¸Ê× ˜|"È€$訂 È‚8‚Ø2èx+Pƒ6xƒ+ ·ƒ<¨ƒÍǃ8èƒÌ„7(„ËG„6h„ʇ„5¨„ÉÇ„9¸ƒLè„È…Tx|V(…Hx…Æ—…?8…ZH„\X|^8„`ø…[†@8†3؆n8AWP=Ò‡ç}tx}wh}yX}{H}}8}8‡rh‡ƒ}Hˆuhˆ…ø|‡¨ˆ‰Èˆ‹è|‰©!ùû,fŠ+þ÷ H° Áƒ*\˜PŠ-\À0œH±¢Å‹3jÜȱ£G‚cBŽ1CòŒ™3bĈ#†‹K—aŽÙ‡Æ š$+Îì¸ó£ÏŸ@ƒ µ¨2å—.]ÀURL.]žnÙÕŒ’!IšÉªµ«W’W¿Š ¶«¬¡hÓª];Qe62hÐpã Œ\´ÄpÑâ^$)›4yÅI))+^,0ãÇS:cæ,Û˘3ÿdùÃFŽ :„(Q"„ihà!ãî]-Ž¥Œøq+Vª(ž¹·äÄ•5 Nœ¡š%l`¹‘Ãè"LHïеß«gL–R¥ÊÈUñ÷ð3‘âCÆ>„8è!FŒ.Ä E| ­×&¸™>x€ß ìwPZ¸ð d {vèaEi,QAä‡mèŠ,¶(K`P¡¹8‘Š6æØ¡JXdðG踎B _HVˆ·ƒ‘ Éä“òÁ„;ÔeAN^©%Z!1qX[â$K{+†iæO*y‰Ø™YžéæF)AÄb°9æoæ™QR,!ÄŸSØI¦ž„V´„KLaB!(ž…FšPFÐ)EB ñ(‡’vjPD(јžy•¨Jáéªù™þr QĦe²êiGTaAA0A«­¬bqEذC¿Û):$ÕE5l¬²‘Z¡ƒ)YAÝ´ÔÚ…_¤tEÒšÙf·o†ÑìTv`e˜ç¢ë&n]ذÁ5ð`îœÊKh_D»!|/¿µúû&K8dÑŽ\°e¼ ‡ÉÒ]4qÃ11ÂëÉRÝÝPCÇòÂaÜàƒ5t`)Cº²™,Wƒƒ«|³™iˆ!Å>ôPnÍýþfbR軯ÏJo™Xï"pÔPN]µ–c]$ÓK>m³×O*–·d3™Ø5 ö×bTQƒÁb'ý6ÜWäçöþÝ9¦”j{óí¢ßÐÝó؂ۘRž8Š)=ìã{˜mÌ”Wža¶äf®9‚,!Ñ`¦Z•ê犋aÅGo9ÆR¼~5ê;Æ-ž™GP–ê´·˜X¤o¹Ãc¤‘†U=õn¹ímoécŒ™ªÊC®ºËaòPEô©À^gõË'yö–V4!Æ,­ ~íTç`&b¦ ½~†A§äÃÖWo†ñªÎŸ K‰±Â£¦ç¿ÿ0%ÅIBP ¤'tó‰›Ó†ƒ e hP  ð@…U9ÀfFâŸÀ/Uýëà{š’¤ç8<Òˆ 裞 W(.òÇþ0À‚´Ge„<àìR¸ÃáÔj.$ŽÁû2Ò$JO‡M$NbÂóC*VQ A•Ä.âÖd> £×VF3 GHB2€+èîtrÎbŠSg,B06‹`P% ¤p)E*’ŠVtÀn0¾Šlè{’Ì %‡3Å òXÂÆc‘ %/”l¥pJ‰H3VXðA¸‚¤ð„~"-4ÕLÌg» F LÈ lP)œó¨_åIc@WÑ5H78׺°t ‚ñj\gªT!pœ¬Bn0Á 2á|B³è` Ô‰³§1`g3Y „>¹t¤UœÍÎ&µ²³ŠàjZŒ|VH•™þOi[ë‘×ê(¶ˆa-m+bÛUÆ{ºÝm[ ëº%ÎV¸é­‹¢ ’Ùü¹É%.”´w¾ùHºQ.‹² ›1I)¸ØEˆvQÄ]1Œé ‚ /Q¤û$–BV6àUoAÆ‹"Í æ L”/Cè‹¢‘ æ ³Ó¯xÙ ¥® x¾~’<þ~hÁ Þ‡ƒ=aO¸C>ð…3´!ÞEX!VÐ'? â3É@‘$ñABœ çWÅ 6±‘F cƒ°A®±e\¤ øÆò±~!«7}(L²’—Ìä&;ùÉPV²éÀ¨ã „LXÎr–Ç å.{™Ë^3™À¬å*#DÌa–þšÑ¬æ5§ÙËf>ˆ›µÜæ9ÓÙÎ]®s–ãü“cÄÏ4ŸMèBúЈN´¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷M;øûßGËì ”„à1xAž…Õ'ßÀ'®„áq8Æ7qlÜ"_8ÄþÑBñ€eà—ѸZBþñŠ„œ -WÈË/Rò[Ü 1׈ÊÓÂr¡ÌÜã#JÍý}s‘³eçE_HÎòó},]Î$zÒžr¥¯œ"O—ùŸƒ.”¡Wü䯺B²>‘ž¥éd'HÓ)âõ©ýèVç9Ö}¾õŒG½ænwºØrõ‰¤êæzPÚvœïýár÷;Ýow¡K½ðF_ Ò!÷‚×=áw/yÞÿ^öÊS~ì‹·ç÷±ö‰>ô0?|ØÏÑ«ýò)=CNöÕKÞó¨}íyÇãýóo¿½îónûŸ ]ð@¡½å ÷áñË=ò¢|ãŸõÐþõþ/}îþñ¹~ßùŽ}çO¿õçç8Õ5ƒò擟øÌ~EF/{ÀëýÛ×þúáyëó>ö™±8€èýW~ñ§è}sÇ€jA€h€©ç~Ù~è€è‡h(û7~(æ§#¨sÈ+à⇀ (‚ï§‚Ø{—‚)x~®§u%ƒù€ÿÇõw4ƒ7˜“g;胰‡„˜„6¨z h„OH‚/¨„3ˆ‚Bè„-¨ƒQ(ƒó—~CÁ„8„g†{»ƒZ8…ê·„VØ„x„R8.h†TÈ`È…È‚p¨€yè…hh‚jÈW؆[x†{ø†nrþ'ˆl(|}Hˆwhˆƒˆ?huȃYX†!èˆ+‰—x“X•8‡¨‡bøzЇ‰ h‡¸†aˆ……‡‡ØpI˜†U¨ˆ­(ˆ¨Š™(Ч¨‹ÈŠºXŠQ„¨Øˆ¼(‡´È}ÂH„±ø†ËHz©¨‰¹èrÑ÷Œö÷ŠP¸‰°‰ÉǸm"Žâ8ŽáÈläxŽ"`Žè8Žê¸Žå¸lî(ŽíèŽó¸ŽõˆŽ÷xŽùHŽûÈŽðéøñØò(ôhöˆø¨úXebÃØŒ¾8ŠÒ(†ÇljàfA¦¸ ³Ó‘¸H É"™1’–˜( ±’¸è’zþ“Ò(“ߨ4鉱’er“ÏÇ“|È‘ h“&y>Iõ§“Q”q¨”)I”A™LI•8¨H)D”³“•OY\9”X –^ù“ ñ•üv–ŠF&h`†t©Ð yî¡–1´p)—g7(*m‰—q9— ‡'P#~9`’—‚)‰¢2…‰&k 0G`H¹‚e¤w G ˜—D7q˜`Ҙݔ c`b™ûfi|Y—ûG™`âš°Y—›ÉG`AccÀš¸éƒ±Yœñ™x Áy›¯©™{Æ—ý2€˜ûÐMû𙩉A¬þ)s»Ù™Xz™Õ ÈÙp¡–ð\×™™{›×ÙšÄ7Ñ›¹É™íAàI›¿YŸî©sºiœÑ[D ýÙžžXfÐY&F ¬I`ò›U°EÕÙœé 5*3F€œÇÙMA‚Ày¡î±–ABŸ×¹˜æ ¢óYŸ({Å 0—©ù&Ê¡Ä Ÿcs™€Š£ ‡Ÿ»Y+¶¹º©‰jÎY&œÙ™@ Ch@ž4Áš2`öésZ¶Ÿ0Hp£ÿIœ" G ™aêŸ0 J@0—MP6ª¦Œ¹ m:Ez¤º¤zeœÒPº˜o*T ¢d†—ÀeUÊœ+§›éšE:œ£ÞYž°šŒš£Þy§„ꛪ)œ}J„B* Q¤Ö‰BT¤MêeÊ›‚™YžçI&~iH•©˜t¹©©€›¸š¢Cº"µ$½º¦”J“I›€©— W¬Éš“Y™×‰™e·f'š¬ÒJšû!ùû,b†/þ÷ H° Áƒ*4(fI’' #JœH±¢Å‹3jÜȱcÄ*?,T¨ $‰À$(¯øØqã‚ U¸HdÈPX&‹=ºðÜʵ«×¯»X9‚ä‹+H˜,QR¥K•³V°ÀíÂ%ŒÁ1ûÔxéÒeo»Õœ1s A¼ƒ]̸ñF+]T!#–l%{ßZñùl—-uØmFŠ1|ɘ Ó—´ëפMÞM{´YŽsëÞ}ò 74d¨DÈ,7|`¸à£† ^´p#Æ1Ø‘ˆ¹rã v17nþ|Oþ»öòèÓc¿Í»½{Æ|/T°tBŽ øm\Àð¡ÃÓ&¸à‚2(bhWErØuÁ zäá„bÜ–Ê…©¼§á†7IÑÅBõpdÀÔ!¤XB '´CBT„Ç9tǘ…r¨ãŽñÅá]`ˆû`àAŠ! ‚ „p 9Þ Y4†ã…ûíªþöÖÇ/èüÕ_ÚEÚöÿ‰ÿ©Wáú·¦ÿ™j3eصdÀMmÆ \ žÞç@/€!‚äÐÂÀÁü9* ]¸‚̤ÁRPS ÔAVB55PSMƒtEÂ2ð„³2ª †3Øð†þëÛêV…1@¡£6Ó‰¼y᥮°™‰Š;’¢£ª†TÄ ‹*CݰšáBið!߆ÍLi\º£M¨xÆÛ­Ñ1i8\⸃ƒ:æØÇ;²ÚñŽŒñå*´˜)7b Ãè—2v!gP "s£HÐ5ò‘4ÀF)ÇŒdÁ øâ&uƒ:Õ±.<~lŒ`1` <!|`šUî†3 Oír3KZá]$F¨¸ˆá‰¾ìÊj|€…Þí¦˜´ÃÔ•‘+t¡oƒf4¹¢ÅÅ`“–HÀY–ÙðR“ãdL9Á‚M€ BþÀÎENY‚.Ì,žŽ™çWŒ€‚ „–F˜ä,‚/èÀ ™h@•hN ¹gˆ „Š` :øÂ!%ºz… %BqVÊ…€ð $éNLÚ”b40€N#ˆ”. aLeššr¥À> 0†"á©úÒgD®RŠ“¨61êVf9&DÀ¢Nðiy$ÂÃ+XAkXýŠVyrâ@À@ĪL²J V¨BñÒꕵîd GØ€P€ “¬kDŽ0D˜O|å‰_uòƒ¼‰bå(¿3‘äÕ ¼lÂ^#+YŠ‚åm‚DÂŽ`ãqV"ciþBÎà&Ø–´¥ ânÆå˾V"bp‚Ð0Û‚·35ín¤Z™™á ¶%rm²ÁZ­¸¶uÂtu2Ù-݆x£ÝîMº«¥.$P ¢/NÈ›%*ÂRê¯rU…ªŽ4¾a/¤à9˜^¿ÑïŽø«ÿ¸#ÖQ}'wßÏd¾Ja*Xè`$˜C/Cz+œ‘ o¨¾=`Âq9Œkˆ¿Õ”ÂI\¿źZ1‹âb÷Ô×"–îŒ á;¹½MØqEjÜó^¾Bž‘ysµ$·¸Çvj²“i å9IyÊûXòn®4¢kh'‘ZŽ´¤'<éJKúÑ–Î4¥ïŒgLðÓ µ¨GMêR›úÔ¡î´Bt|V[ÄÕÍUµ¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~ûû߸ÀNð‚üàO¸ÂÎð†;üá¸Ä'NñŠ[üâϸÆ7ÎñŽ{üã ¹ÈþGNò’›üä(O¹ÊWÎò–»¼Ø P<[-GÖ ±ù@p~‘«ò¼#Àû¹Žb>óW×'B?ˆÎ’ô‰ø¼+AÑ9"ô¥*#VoúÑouƒhý=SßHÕ‘ŽõŒ|=ç9»E¢þ§°kdì*;FÔž§s…íwr»Ù·Ž*¹Óü&vß Þí¤÷¹óÝ#tÏ:NÏ“ÁÏ©ðG{Ü1¢xÀ§½ë¡ûn oôˆX!‰ß»å¹~w¯K]æT?|GBoøÑÓ ó˜:}Ñcíy²S^ô6aüN¿&ÎÓ^ò}¿}ësùÒg^ö©¯ýä{Ž{è^'¼W“ï)÷à3øÎ/¾àMþßvÔ‹]õÉ¿~ä³Oúí¿û³§>ø¿/üñ]ûç~Þ½ÿöõ׿ý'~ùãþù§"Õ‡x~—ä÷zÆ{í¡cŸ€ö×|Qyúg€æ‡€Lf ug{âG€ï·»'jâ’vÀ'€ø÷{¸j°·sv"‚‘F‚ qý·vÈÏ—ÑÇ#.i0˜ËGƒØ×È‚#XƒJ§@è~6ˆÐ‚[²ƒœÆ€Êg}KÈMèO8ƒQx„Aˆ2X„ˆ„qƒ‹…Z"…C¥‚Uh‚ˆ‚BȆÈ‚s¢† &‡1¨„cø…a†®G‡kb‡Ÿ€«7€p˜…þCø\˜†^È„Iøƒ{èˆeè„8ˆ†Y"ˆ=†zH˜‚Ww€€h„/H††¡‰X؇”x†‹x‰˜Š¸‰NGŠ'z+Èt-øŠ‡øˆVˆŠ»8‰ZX‰­¨ƒº¨~mXˆµH…ˆ‡Š(†Œ8Š|‹È‰´ø†¶ЏX‡Å¨Œy8³†xs˜¸«XŠ²Ø€‰ˆˆÌ¸…Îx&Ò¸„~Wø‹á¸(·¸š7¦ø‰%ˆŒÖÈqèDxŠ—šfŽî† ɃöÆ ¹†ë‘ÙÜF‘yoÙY‘©n™é‘¹m!©i#é‘)Y‘+™‘I’%©þm'™i-É‘/I’5)’‹“ÈŽÞ¨Ží¨ŠÁÈŠïør1ñÈHyŒ±”)N鉕Í8T™WÉ“q•¼è\ ” ñ•÷hb¹ŒQ–`‘•]–a‘l©‰ñ–Ôr Œ[éjÉoé–cI—D©uŽÉVI¹G–q˜É(Ši–ј)i””ync@0AvX2…1°™iŸù‘Ñ`_¥™ž—# 9œ @ ¯d='›{Š™›û š}xš©€Ð†™°)œÄ t©0™á›Á ™5È©œ¼þih`š9L•¥ÈƒwXhx£DœM†QwBžOžØ™œGÀžî¹vñY/XÒp˜÷Ycœû@”Ç›—©™©à›dZ†© š­™cU@¡žüiŠO•œM ¡û ž1‚åIMÀšh@¢çù“¶÷õ¢/ê¢è©Ÿ2Ú¡û)z˜6ú`Gp›zšF É™&b¤û0¤œ@Ÿ!š¢%¢O¥£qŸÙ¢J¤9ú‚':M@AöT.J ð¦§¹ z¦‡)žú)ŸÓé£q Hऊumšib¥*¤Ï¹Û¾ÙžÑ€^š $ºñUEH€¥£c*^ú¤`£0ºŸŽz¨‰ÚC§7ú€žÚ¦¸i§ûpž@*¤`º›û¹ªIœ‰ºú¤¯Š—YPÃiGP¡¶žú©Qšœô)§%ª¥!ž› íùž`xª±šÏZ ëYÐêtÒš£uª©J  «Üê§¿Š¬ҹ›ÕJþYP­É™#ª!žµ™«myª!œ œ9Ç›wõœüjªþÚ3 œÎYœžšÝ¹îŠ kƒ²®!ùû,b„1þ÷ H° Áƒ*\˜0Œ†#JœH±¢Å‹3jÜÈq_—:v\éH²¤É“(Sª$8æ š10Ñœ#¦30Ç )ӥ˗ž`މxS–À3fк²©Ó§P5ŠÓŒ1UŽA’¤&M1RªX¹b¥,Ù07Ó¦•¢–ŒµjsÞ´y“-Ü»x‹FÝË·¯Å2Z¸¼xC†Ã*8H’„ >€ø °D‰1®j¾Šäj•7nˆÜ|UJ“Ó§¥hîLºµë·FýÊžM[à -]´ô0ñÁ‚2<2DÈ8>(/¡üƒŒ[Zw–ÂyõWZK‰â$ŠêÕ¯Ãþk6[JíóèW¾Ðæ*ï ?¨ìÐP‚!>nøPÞ¡Då>¼›uVÅ èD$ဠÁmÆã)DJAD qÉ—6PÄW ÑÀ¶ # ó¢2e3În¾ºóœC L„Í2¿<ôŸS‘ËT0òÒ/!Ü05Õž!Fuçœ.×sžÑ„h‹]/Ùkžqàií0ÛeK±Ã YlM÷þš2ñC¡ªMòÞWޡĻAø¦4áe.¡H qô¡rë̸˜J¤|Õ¾D~yãš‹„W>öçP:ND*Ÿì9ê[þîì‹Ãþ$L*¯®·íJâ´çî¼ùš¼‹/ý^ûñ.ú^üœ-©æÄàÌg襜ÿ¾0LU¼×Ï»…h¤FR(彋à_5§b¤A!úf¬Ï>öâÇŸJèŸa?‹4‰\äæ„)Œ dÂþÇ¢öñéM^9  >jÈwºÞ 0†€Y0C„^R*„)8რ᛼4†4p P@azTè&/ }”áyhÈu$)þÛ3 ÑÃý¨Œ$fPƒL †!ñ*]“lÞ‚„ŽtÁ ÛóŸkãµiM6F0Ž ¦ŒìÀ ¥a‚š¸ÅÙœ:Ø™M`€1'#>°Bö‡>õµÑ/nC›@Ç#`  É£ï˜à„úýo‹_æHHHá’”È ¬@†&$ ¤Ýð6HBò_¤È ª0†&< ž Ð ðÀ"5q¥a9KZzFŠ~1‚)°Bᩉ¤ @b*EŒŠ2M9Da_ &5=Ù„kb}Ù¦.BG$8 tQÍ?™Is¢$›PþQ'! `€~ A€¦BLLO¬{öT >Ÿ²Í `—cúå€"a!IxBl&€E‹ U(:ù¢Ì H!Ítö5»w)dIp‚N::A„†ôž#ÝK§´bì£@ÓTV„z dHx‚— €À(Øô¦&Y¨SÆÀ˜}¡€ª„ЕœädÅ"5ÁâH¨žDªM)(÷ÑïtDˆ¯1x~Ó4+NÃ×YîC ?8*`È5ž5©‰O£×³zµ/V°Á/³Ÿìt!×£¦ àׯrä h…J¾ˆ„+4è² ¢AcèY’$–¯³ÉþÛ@”Ô¦VcxåS[›˜ï°ÊOEÈ# jî–·Á ü€»¦ ™AÈíHM’ À;l;.Fô[3 M»y-sÍä…2H¤àMnhŸ„Æ#¼RˆéÅÈz•Ô²´‹-ñ½È|ƒ42d¡„o~)²_ õ×~ãq¬ÿ¨¾V€~<»HV0¨Q)œ ·¨ a¦S9\áÇrIƒ÷]+‰âaâ¤B¹UñŠlâØ™á :ƒdôqþO}åk$ò‡|ì±?|ÛŸûߟ~ú]/~Ñ“_%Úo<ú3ÂúîS¤÷¹~ðÏž}óoóòç}ôW}³¡µÇå—y·€Ç'€~¥·|„÷~¨÷|˜z8øGx—× x~ (}xØ~Ì·’—€§·€Ø€ºgêg‚‚ƒ9h‚1ȃH)€7h!è‚;˜|þGƒ%8€ì×+ð„P…-puI(ƒ•Kø€Qø…S¸G¨…-ø=ˆ„Y¨„3È_…a„cˆ…UH†#ˆ† ñxAȆmø„o8Eh…uH‡/èƒ[ˆ‚]þ{ȇTX†8‡rx…L¸†‘Xƒ^˜ˆ}(؈Œøˆ¸~’È…M¨‡{x‰Zçˆ$8ˆf˜†„x† x~‘ˆ+@Љ‡«~:x‡‚‰bŠ{‹²È™x‹›X‹¨¨†ª8‰'ø¾¸ˆ¶h„¦h‡´‡É¸‹®è„–ÈŒèŒÃÏhˆÓ(Õ(Šmø‹ Œ§ÞÈŠžøƒ~xˆÊxÔØŒ€XŒœHÈ¨ŽØø‰ˆà(šèõˆŽ˜H‰éHï8ŠØ8„«§‹Æh­ÈŽÉ‹•ˆñ˜ó(ÉùxŒù+±Œ©§‘÷؉%‰‹ ‰N’ýh‘ÿè’Ùþ‰’ëè‘T(’·‚ØÑÇÙ’ú¸~8¹2IŒ< ‘@É‘i“ 'Nù”e°pO •R9•"• g•WY•S‰•Mi•^‰pZ–7–\I•Y –gé”dipf™–]¹–[ —hù•qI—l)—mYŠC9’Ei‡GYˆ'I’¥Ž%G9q' ¼²˜Ú¨˜/YŽé“’I™1™¹"˜éŒ›yŽÑ™– šK¹¢ÙŽQšÀH„ɘ•)‚š‰°iš1›©yš÷¨™QјÎ÷™‘é›1ùš–)œ±¹".Úqœ¹Ê™Š Ñœ)yÐ9š1´‰sØ9lŠþ†p©À@Kö€yç‰RâIž}×hè9? PNô§h«§ŸÑñ‰RûPŸ÷Yt+âžîÙŸ þ * ø™hçéhï™1M û0`ŸåtÞ¹-ç™jÊŸç @ cPG$ÊœÚxÆ¡(•¢G°¢ùy!Š0@ (U¡~´öžû€PN £ûÀ£~¨Ÿ!ª¤¡ó)¤Dj¤HjyL:¡4j)NJŸ.S¡bPGPú¡Vz¥–²7Jå4У_çh' (e¦š¦B÷£©ÐÐó™£Az£æùh@ºpj¦zj£þåÉiU*¡‚@À£hªo5¨|J¥`j¥è9Š:¥KU@‘£J§`ºn¨gZG,ÚhP£¤z¡¦ú}KJ£M@åD¢ƒ:¤3z¥*ž.S«Љ¸Š¨+¨r£¼ZŽ¡ ¬©cP§Á©VU¢ê§"ºhÌ*pÚ¨§ªh Ú¡×*§®ºŸÚÎJ¬.C¨} ¬²¬DJ¢å©IZ©Èz¤ Š­Ú®å9­bZ©1¥MPC!® žbŠ®ïÙ¯ï £2Êu-ú¢Û¢ªs:°¥š£;j¡(øª@z¢8J±òª¦‰÷«¬ *±Eªª˜ÚvK ®ú:HzLó¹^:¤“Š®VŸÞIžëY¨*¦ªš³íɤð)Ÿô©¡®ú©üÉœÞù²J à ±ª ÿ)Kk¬Ÿf<*žI‹ :¦!ùû,è#þ÷ H° A‚T¸hÑr¤È"IöEarŇ+3jÜȱ£Ç CŠI²¤É‚Rªh‰á!B„)b!ƒÍWNvÓ%Œ@ƒ J´¤˜1cÄ„S&‰‘*H•$AbÅ $Gº”ٗ挙3h6Žù¥‹Y­Ç]˶­ÛŽG“† 3f*ºJбÆÌª_Ì#¸°áÂR+.\¶Ë/fÉN¼¸²Yo3kÞœÑ+“*2ª0B!)CX¸aE ‘ 6²ˆ¹ÂãÙbrëÖd·oßµ«äîq£KîÞ¿“ç¾Ì¹¹s·hÆ|Ž¡… …ŽÒ B†?*þX°qÅÊ>ÎãNŽ\ùn7¨äÎÂú¸ûäÌŸë߯óèt-`Áâ]×"˜ÁtÁtÀÁʵ'FJÊwEUìpƒpbXxßr²¤bb*ü¥¨"Gc˜‘€-´ðB îS?X`AnUqÞY”tÙ‰(®hä‘!Æ .Ð`d=àÐCNB–x"’Xª¨ä .h‘åIC^ùå˜ÍméFdަ‰i¶ÙÖK,A„.ÌàfHkyçž:M1DJÄD'ŸåIè¡$94…IÀä¢ é¤ 1JnW¥IÊé§1Äad„:§ª‚êÐSþ<ðÀ£­îÃj­œQL?°Vë­¸NzILრ0üj%›Á~êÃa4QÅ '¼ ,‘Í~j…_dAà &Ъ*°Ù Ù 'œP®ä–Ëçaøð G°»¬žî"м€½Øæ‹(qQÄ ,¦À|.ñP Bˆ§0³ ïéÐcáÀ@LŒoÅmŠ:†’±nzí ·“!ÆÚq­‰I!³)ßyqÆ‹;î½5»9ÆŸWÀãÉ÷L&SL1SxlôJr'„ÓO·©d ˜Üj»Uyu©nÍs×dFÝÀÔDL6’J&5Õkg©¤IwÜlïv7þÞFÎÛÞ|k©wÚç­à…ïWj­!ž¸sUýØ4áó×·Ûþð@”W®_yaäÇ{yk¢?ðCé¦sö·W\¡9çbܺsem ûŽßîVîºßлïky¹éQ¹³íÄgznW Çzómm«›yAÖŽ2õm½þEì项|*hˆÁ½f p2Ÿ¯™Y0nþµþäþ[~Ï_»ÌiÜÿൻPXüÇ–¤ á€HÀUÌœÀ&°-ù[Ÿšð@¶Dp?(J˜Æ@³ å‚úùÁˆ¦ yðƒ|ÎQì&”0¹è„DásŽ`€#Ô+(a:Ca:Çþ0€€€60-Ë~<Š›ÄÀI¤Ð¢£¨-‰%Y"g€€ Jáú‰š@˜™í‹&ÑâfŠÐE8+aIš05 aŒgD#IÔ¨.ºÑ‹GPH¬@Ç1Žz 3ãǸÑH¸€ðBr…B¦Â M8C"Ә¶ё1@0 ÈŽø€Žå{B¢°É,v’‰Ÿ€@œx ì$<ˆB¡Ð¶r$‹|K#@óUÀ€ B2†®¤ ¾üeH‚é–&F`N̶ù0‚$LRˆ¦4?BͶ!¨B'a7½g¤¬ÎqNó•œþ9B~0‚#`x§Š@]!¡ˆóL…åiÏ”“-b8ÂÐ10á1ÉhLX8Ï;ª²¡äÄçf~°Ì}a <©ˆ€"lçlQBRX–GvF¤š AŠ€„"HAS! I–À)¤A-6u(N2ºF½I Hâ”)¨¡¦I-ÈC3s¸"Á`.É–`„™eU©‡3Òßö!†˜8à­ “O?zÖ›¦uEaÌÂŽ†ö‘$,¡wtB]íºÖ,IO$E˜ŠÕÁ‚·A$§Æ°,¤v Ûê€f'©½¢] Yè‚ElÐ}ŠkIÝA¾tÓ«þë ²P…òÀ%UUÌÌzÙô ´<ài³7>Ï^–qšígwf¤4··ù‚m| YJ™áŽ2kB›Õ­Þ©E‰ÃœPÝî.U_fC*8¨Kè&鼇jÑÖ+(p7©Þõ™‹2ÞûÚ4¿múÊÌžÀ„'¸wNÓeÎ0S(Áð%” ý‹ßó)Lû ð-¼§ j¤ &S?ÜÐiM–…®‰¿„ZóÞµ¸W슳Ôâ ¿T5ÞðO;¶ÏK91‡ïä[f2GN2b”¬dÊ0Y0=~°@ˆD¤ùRùÊX¦²•³Ìe-wùË1†n–¥f0“¹Ì]>3þš¯,e‚ŒyÍ\V3œ©,ç9‡¹Í!±Fôœ>ãùÏ€´ MèBúЈN´¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~û»9 ¸À.ÅaüàöÁžð`/œàÇ~xÀl‰+€â¿¶8Æ}­ñˆK|ã½î¸±¤E^l’Ûäÿ¸Çr^£\á*yÌÿMó ®àæ`höÍWsfï¼çËþ¹Îq>tžèʺω¾t£7éÉVzЙ>u§WêÈ–zÒ©¾u«×üë`Ÿ”ÆNö²‹à–Ä6»ÚÑ>lµ›íÂv{Ùál¹“îÀ¶ûØñþk½Ÿ½Ø~绯x½ ¾×„O»á o÷Ãó:ñm_¼âÏx¹;~×!ùû,b{:þ÷ H° Áƒ1r¤!#I¦ \Bqɾ*«\̘°£Ç CŠI²¤É“(Sždb¤—, "1I’%IXñaåH>²dÁâC`/\¼¤QÉ´©Ó§P£J%yHC \82„H’"I˜0A‚K$;:ìøò%‹Ž.edÈ•[fªÝ»xóê}ZäA•0UZ©bs‰’1gƈ¡"Å̘.Wº ÓL.]('M8†iç½ C‹mD‚–0R)ö¦1›ˆ‘båŠ)RªX¹Ì·.½} N¼¸ñãÂÍŒ^μyÇ#Ž„ù‚‰ 4B„õ 9®þøÀÑÇñ7²Ä!¦½ŒUÚË—d¾ýûøÛ×ÏßÞŒ,ç¸@4!ETp‚Û AÁPàÀ\`à h á…RÄÃbp±ûñg"}'ú'àŠ,â…T€Á l÷0\ð€B8à€@Xf!Æ& b~%žØ^ƒQ1_“ø©Øâ•X2œD` 0ÀÀè¾+ïTíÊ÷n¼óæëT½íÝ+K°éê+0Jí&A„þ<ðÂ&}›„',ËôíÁEH´.¾wœÐ·3!†ÄÏzl²Aߊ›Ä$o{òËû\,D‚- ³Éí"¸ìÆÿN|³ÇhL1i@xÏ ÿ\1CØ´«T€ƒêr¬ôÂá*¡„dP¡ U÷\òÕ 1„a1 ñ'„4Ù›=…iÓ /˜k5þÜùf½µ=Êè¶Ï|ë;†WI쪬ü0øØ…ç;Æ“ö8Ò„G®¯Ì^Õ¬ùÀ) ‘ÄȘCþù»Š!qH¼ÛØp§ož)wFxû¼ß21Â¥»|ûºíJ‘«í¿oñÅ›Ë/éûž<·Ë#ÿ<¶Ñ÷nóôÑVßüõØ3«½Þb;ß=±ßw»÷øÃ–ÏíùèϪ>¶ì·ëûÑÆ/¿ ü²lýýäË·§ôüT»®pƒ¢ì/€îkn ¿í!0b Õø@¶Ç `¯T¯,Ø@ƒdQk ¨]4 M˜¥v‘ªà{ [äÂ8æS±ß ›ƒÂ ŠÆnþRÑá½%RF cÀ@T„8DÑðk‚¡1‚Ž0¨0±‰ ¡ß]Œˆ¡ŠN¹"Ùu<Ñp1¸™"Æ1Þ%uHˆcë~€ ‘ @hÊë„ãÆiÑ.F°£§x4•´±ô*cí€F  0dø¸‡ÈÐüq*ld#Ñx„ ¤ä•„Ê%¥’ÉM:Ò y< (C¹/E’&›„e €„ ¨r’¬$¢»Ì¨É:ÀÀ<vF’UæR%£ŒÊ#0€.ŠjI€…†ÙÓ‰®ÜK 'P…Ô‘ ÈÀ(@æ‰.aHƒo®iÉl꥓?(Á€TàþàQ¿Fb/èàSfX ;÷’L¨ˆáØÇ*TaÀØ>C†0èàRH%*•‚ò… ¦øƒÔ Måƒ ?b…0ðà T@ÃF9*JwæÅ ¹À> ØÀܹà BÒÏô<‰4Í‹G§ò PÀÈ€Daó¿x¡ èÁl¤T¼,Õ.ÀÀ0!̧ <ªG²`…x!hPNWíòU¦~(~šð¶n ¦›ëSê*•,Ô ’½D™ô °‚m a£²§x  »$ªKu`ñE–)“…JP8°Ìfö#fPC8ëÙϦ$´O±þªö”+$bx ĸ¶¦§m\õ6ëü­dm  ‰ž0gHƒ¤ðå.·¸Øƒc\¥N¾Z™Ì…–vÅàª3àæ»àÅn´´ûÅô½¯ /³*1¾ ›OÞ…Î3t¦øýH½nÐwõS€mm€¬@Ÿ¾+·:x¬‚Ü‘v‘‡÷JX ` Ž6ð†×Õ[ÜàS\õðHÚů Âî°Š R¯x]N°Œg<~X]è|ÕgxÌ`õBËYhH1‘=Û9kºK.òbgÞ(3Y¾ÄB²’­|&³ _è«ÌÉecyXlí€þ [æ˜ÙW»ñ@ Ø\f/¯( ^ð@Â0a+ÛY@bÈóžûå?È V3¹lh¥yÍ„^r£f1/ÚÏož•1ì_3xúÓ ucBMêR‹ÚÔ¨.õ¨SË6{`°NÅbMëZϺָÖ­s-CW„×Rà5°…ë`ûÒ®ö±imìeúÙÇöuG”íl€A»Ú®º¶°¥jc;Ûß–¶yÍí“lÙÜL9w¹×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~ûû߸ÀNð‚üàO¸ÂÎð†;üá¸Ä'NñŠ[üâϸÆ7ÎñŽ{üã ¹ÈþGNò’›üä(O¹ÊWÎò–»üå0¹ÌgNóšÛüæ8ϹÎwÎóžûüç@ºÐ‡Nô¢ýèHOºÒ—Îô¦;ýéPºÔ§Nõª[ýêXϺַÎõ®{ýë`»ØÇNö²›ýìhO»Ú×Îö¶÷\p{Ü-ru dÇ&±»Að½#„ïñû´Õ%÷ÂÓÝ.züH?Å{„ñqüA ÿkÂ~îxI<âA"ù„P¾ñz4Iç[tyÌ×ýï›ÿÈè'?’Õäó‘·|é?ÍמóPý>\O[ÑËþò´—Ší…û§èž÷ûÐ=èÍUz¸?*Ç~ñrüЗùj¾žþŸ{Ôß^õÝ ò•÷ß>óÞ'>øßzë“ûÎÑ>÷Ù_ùï¿:ü!¿ï™ßüùS?ýÒ·~ÿ'~î×~ü7{èWêwô—H€| ¸w©Ç€è€é¶Ý"{ö7x 8})A~»g~rç¸| ‚(‚(A‚ð×xz €˜‚,8‚È-3X¸‚݆.ˆn×g‚¦çƒƒAy1X}X„á§„÷ƒ4H„!(€:…ô'…žG…H¨‚6˜ƒOˆ*ƒ²2 çæ…}Bˆ…-¨…/¸ƒ-¢†l†W(†q€e8„yg‡,‚‡}§‡@ȇþYè‡gh€‹È…WBˆ цz‡‰XƒH‡Dø~i¸†…˜„“¨ˆsŠu¨‰Y‰!‰yA‰¡h‰¥ˆ%†§È‰‘hˆcè‰{èŠ[h†»ÈˆH‹©h‹}X~¹h…­(‡½HŠ¿˜‡¸ØÍx‹¢˜‰°H²ˆ%¨øxÂX‰%XŒÙ‹¾8ЦhÀˆÏ†ÄxˆºŽÓ¸x‚¸"×8…åXŒnȄȌꨌw8Žðˆˆè¸ýØÔøÒˆƒ¨_ý8ë舎˜ÌÈÐxŽiŒ8hɋىY ‰‰Éxƒ÷¨‘‘iŽþX‘ÉŽ ÉḌyþ’òˆ’+i’ é’ÙˆnÈzY‘)’¯x“ùO¸“¡ŠÎØ‘AI’M™“B·m4'•3G•2g•1‡•0§•/Ç•.ç•-–,'–+G–*g–)‡–(§–'Ç–&ç–%—$–)f”oX“!)“Lj—µØ’Cù’T—J1zi„Ù“Pq˜™˜ IŠÙ˜ ñ˜~™q˜v9˜|y’©”)±™é¨™Pé˜í(›i™¡!™—é™)ɘBiªÉ”¤9š!Qš…9¨©ªé†¯‰˜®š#±›ù›QH—9…Ä™Mqœ™)Êœ(Ñœ@iÐ ™ 1nw\6þЩp™ócP|žP‚½†MÐL€ûàMÝ©0ãYè™Jòé]»W2õ PHù¹í Ðx°â3ýYh|¥ÜÉWZ ‘w 31G ¡qïŸ*ÿ) 0Rc° |i$Ðc`#Ê ûù¢LÐVq¡ *SûI¡£·¢-Ú6ÚŸÏR¡&*( Gó£0.ú¡±–£Â²hÚÙñy4@ʤ|õ¡J¡Pú*@@ ў賓ð9cU@®b9/Á M ¦©À¦:ÊMÐEhÀ¦2ºkCZ¤ѧ}ʧþ3zwQú§€—wz4„úŸUAkPÚ¦ aRà¢fbð¨ûP[ê;>3 !¡ AJ¨CVžM§Âò”êJ¢¬:§yT§*ÑÀW/Á§ z¨¶zSª«GC¢†Ê¥ÈfÊ«HÀ©6Ú¥¾ê«A¬¡ªg ©Z¬Ÿš.®Ò¬ÿ ¤{:¬˜Ú0kq*t ¨áMDH §V¨vJ¨Z¬Ðzâ:¢ST¯ˆz¯#‘¬ÃŽ ©©k“ziÒš¯ûP­*˜ØÚ¦Û²­ê D¯Û*S¢Ċ®!A¢<Ú±‡ÚÄj±ê­Ïúª%á­UJ`¦ ¯$ñ¡JŠ¥Uj¯:ê¯A¬k¤MÚœ0 kÍ*ÞJ¦GêHhpŸ ª:žÏŠ}º¡ùIŸÇÚ_ ¦J¥6;©%¡¥¡[ªµI;$Z´zêµJnA{£WËW!ùû,bsBþ÷ H° Áƒ*d‰‡#JœH±¢Å‹3jÜÈq`+\ªˆìÒ±¤É“(Sª\i±‰È*GbÈ%†–-e”ø±Ç+/w²J´¨Ñ£ÅœA&Œ1]¼té¥˘1aªziÊeg+S­Xñò%ê„hÎ,8†c[¤pãÊí8挘ªT¨r ¹7 Ö0`’$Ar$Œ™.V¾„±ò3LÙ/f"KžLY å˘/[ž,k®çÏ  Ž1Óà `h±rćê&>´™ ‚ƒ"6nH±r£Æ&]nðxJ¼¸q1HŽ+_N<9q3CKŸnTŒ—&8dÀðB†–©µþHÁaá *8p€A7oßÀ…3?î|¾}äÅ¡SßÏeÔ0Ð .¼j0p!†<€^8 ÄdaÅØÐE7dq_sÚWŸúõgâ‰UÅ1˜°Ý 0ºpAI\Ñ`V D` Á |ÀÁÂU"~G.7b‰(6édB@q ‡ÁçY0[?8ðÀ4à@yhðA ¤Yƒ‘Rp±RÌ7b’Å-Ý“xâ9P¸\°Ïü ÄE4 ( < £Tâ –7\QÅ 1dƒ‘JÒ©œy†ê$4^Á_fPeXÐ¥þDÀëzéSWÜàƒ 1Ȱ¯Z07§§H>Ť¨ÈR·„EŒ§A <0  @€e HHE48AHààH ‘… <ÀÄ©Ën¹ðÆ+ï¼ôÖ[®eR䫯Éö+×C$qFp¨:øþ]* 7œJ•¢cHqƒ•èUñÂ3ñÇ,)Q„³YÁÌqÇ ·œÒË’Ú„½ª,KÇ»¬3]v 6ãüðÎDgT×q>sÑL[tôqJ³ÜôÔ=m\ÔS­µBV‡uÃ[‡mP×Ä}³Øh“ý”ÙC£-¶Úb°ívÚ=_½ñÍKÏ6þ‰ÊÉ­÷ÖbŒÑÔw ý÷Ö£5§×…ç}øÔ‰Ca7Ä+gý8äf(>y•ƒ}yÓ$jÎ8åxKýyÑ/.ÆEá÷éDÇMܳ5ð:ì:[ëBÜŽ{ËVó«ï¿<³½7nzñ []ġɓn8óÀ×Íï¶+o9õÆ×-­Ï}¿p‹?>²|.½ãçûø`ò~Œï¾ù¶q⪯­½çö“Ÿ¹ä£ã\é¶×?dáosþêÜÙ (ªÐå/nû[ ó¸Á!°_ lÛ)h†¾EPƒ|Rù>ÂP0aR̆啰I'ì—g†8µ/„¡÷Êæ¯:a þi¨Ø˜ÃÅY3„tÎ'"±?GÕ -#‹4äˉOÜOCåC VL XÌât¶ÈEæë†#uÈÈźŒA .Tãw¨¿õÅQŽ¡ac¨2ˆGþè1O|ìãéÁ¸dÀ$¤tþ¸’+ü $‰T$h©’Ñ<’#‘”¤g(™’# ;Àäq¨É¹p%F€Ä0‚d²”q9åIŒU^y%,‘"K“À4€D]îÒ(½, -©Ê#´ò"Æ<&Q’Ù‘e –¨Â3+†Q¦QšE¡&G– €}`Ó™á‚7AÎpr.ä4ç5³‰0ŠlaþlçJÆ€œyÍå—ÀÈ9`O1Я~ú<Š87ÏÀ=Bp-€Á u¹cB+ùN¹ü2À>V‰±T” †É×ò¹Ñ“,T#©œ€"°4Á&(„ ¢‘¼±†C$eK9êÁ¸¡?ˆ‰öQ€ÐÀ?(×C¦+J¥CåYQá"†+ ç@J`‚ì@ EPH¡ƒ†& 5«.íh\,ÐÊd@ 8ÂBÓø Ñ3È › Bƒõ›p-ÉK3òÌÞ\à ( ^€@!°‚5#±šX£Éu.2Á pú‚ 8`"K ÃÎÀO3 ¡³)YþìFB@[œ– _¢ÇÚJ?ØÆu«rÁA6°ÚÊ&•ÈÂdaXÖJÁ ¾5‰l5¢¿|à ø€&²°Ì).NÏåWtÝòY¹r0“t[E4_Qïx52Ý’Ê"l•ÂR8†'8!NóÝH}=“B4Ô QpCÌ`—‰fünšÀ„&œ¥Áž®›ðà RXÂQ8/†+2ฬUL¬p¾*&…4Œø"%†ËWÕ)¢!j8ã‹öàP ! ®X|U8( Çj¸/AŸÕ ÆÓʰœÚW$ÕF‰æ¤q]]/š×Dñõ =aï™Ø,á£ðÙZ4gÈŒ´53íjKf3Öž6¶#s33D¡! y˳ÅM¡™›a‡=·ºÑ½îv§"Ý ³"C6Í„#?;!îvØUó}î}ó[hþnXІ7áµ÷>È¿Sð…7¬ág8Ξy<á ÿ7ľñ…C¼­  þ™Œgœß×xÄ;¶q1(Üä6¹@<¾r}×üáæ¶ ³S(sƒÐüæzÊ9ûìŸß|èùF:b{>ùfÄé:Ó§Nõª[ýêXϺַÎõ®{ýë`»ØÇNö²›ýìhO»Ú×Îö¶»ýíp»ÜçN÷ºÛýîxÏ»Þ÷Î÷¾ûýàOøÂþðˆO¼âÏøÆ;þñ¼ä'OùÊ[þò˜Ï¼æ7ÏùÎ{þó ½èGOúÒ›þô¨O½êWÏúÖ»þõ°½ìgOûÚÛþö¸Ï½îwÏûÞûþ÷À¾ð‡OüâÿøÈO¾ò—Ïüæ;ÿùоô§Oýê[ÿúØÏ~ÑþÀýî{ßûi½;"õ’Œ_°'9?BÊoõ«µiß÷Ãßþõ¯Äýa?Fð_ýS„ÿá#òGq~˜˜€ÿG ˆ~LC€ñg€ €÷(H~ѱÉBßg€"ø±‚¢B‚à·ö§ ˜~-È ˜ð'ƒÜg‚Xƒ)qƒ&‚0¨p/؃>„‚6¨8Øt.hG˜'>øƒ4ˆO¨‚Q(UX;èx…LÈ‚Z8„PX„9¸a¸_ø$W¨eøNˆ†\¨†R¨ƒI8†K˜…h‡SÕ…iȆ{¸}dè‡þý—‚ˆ‡^8…ñ†N‡s¨uˆDh~k¨m‰M"‰ˆ‚Šø~Œ8ˆšXˆDã‰â'„–HŠz˜‡„H…J(ƒ“ˆoª(ˆ Á‰å–‰ hŠ;ƒŠõw†«x‡˜èŠ¥‹Qˆ‹AŠDQ‰¶X‹¯Øˆ­øˆÁˆŒM˜ˆËèŒÃ¸ˆÙÈŠŸ&²àŽâ8Ž\BÔH‹ÀØŒéøŒÜ˜ŠÆøãáXŽÖ8Ì8Š·¨Žùº¸òøô¨ŒöˆŽ(ŠÛ(ŒÅ('òòù“Ai“y”7ù‹Þø’S¹”2Ù”i”ø•x‘Ré•Òñ“@‰’YYÁ¨”8‰–î¨ðÈ”dÙ‘Z¹’f •;¹–`b¹X —s¹•Oi—H ˜Uéb¹—,A”t—#9˜‚ÙŽÓ‘—†Šy–u¹˜ŽÙ•$i•&™[¨˜‰Ù—TY™˜É–%Y˜oy˜\™”“ù™y‚¼H˜n š”¹š~é™™“– “­(””(›úÈ›3w™·™šfHšSœC雵‰œ—X”ÄI‡¯™z"0ÔYÓJšgÚþ)Ø™yÛi݉yßYáyyãIåiyçy›·žÜٞ뙞•çžòIyô ŸçYŸ“wŸÙŸø9žú)yüéþÙŸùùŸß©ŸCƒœHH›€¨œ¡i›_˜ʘ·g‚C3HBZ–Ñ¡š êœ1¢¹)&:š‘¡Z)¢ñ¢$£úˆ2ª¢q£Õ†¡£³é£E ¤ªI£µi£3j¤'Ê¢GÁ¡NiB:œZ£%z¤Sš¤1KZXÊ—±¥³‰^Êš¦C ¦ª}hŠd ƒLG l sæç1`CSq:§u 6ÓðZþÐlè9Ûã§@Sû0¨…*~C“5‰ ¯¥¨Œê¨†J‡v*©BÕ”Šj@¨†zÀDP ħˆú0@ïfc°ªûpŒš~8Cª4«²ú›j§hm1ASžŠpº4`ƒ`¨¬û0¬ÿ‡ªûÀ2CªŠ¬ÊʬΊoÐ*­D­‚:žŠW*”¬gc¬§Ê0½:`¨cĺ§ 34µ®¨æ®:h8©ÐЦ ¬û® ¨¯9c¯ë °{‚ÛÊ©hÃÚ®¯u«: ;”8Ä­ѰÙ*íZ Àš3Û6kšWû`¯òJ§šØ1Öšþ«(ëªùú0’Ú`¨«Š°Éº²›­9§3³™ú¬<˳ÐÚ¬” ±±@«©$‹±±±cPMл«*²æ©è&µ²øZ„£¨¥êµïºZk§T{¥JK±¾:°Y‹j\«´Û¶(³v[´‹¯;·*[nX{·Cû´ŸÚ¬Ðikµoz·Üz·MPD‹«³J«“«««Š­ÖÚ¨À*¬ƒ[¬D ¸)kµ›k´eÛ„vû¹§Kº™»¬¹º± ¸D¸ᬞ„p)k­çŠª3¨À4§yÚ·‡z·¹ú»ðª¸Š¨*ªÁ{¶mCª€*¨ÊÛ~Ìû¹³û©–*˜ îfçÁ„Îkª¤»!ùû,bnGþ÷ H° ÁƒÓDŠÂ‡#JœH±¢Å‹3jÜhÑ‹–-\ÌxábEËG-G¬øÈ±Ë•+]rÙ’£Í›8sêÜys‹Œ2d¼ˆ¡å…‹£/d¹ÉCP£& J5Ï«X³jÝ ± .^Êt¡9“æG.Tš\‘"&Œ™4cÆœ8&L—/aÊ|éæ`.]Àˆå®¬c¸*^̘çß.RÌŒ¥¹e¬•¯U¤TùÊYL\3f¤ìukÌ]ШAwáBúqèÔ°cËùpãÛ¸sC C‰,\ü4‰Æ‹F`(ÿ CFC1bd¹aú•> k‡®%è[ˆþFßN¾¼yèHÌØÖ;=î21fø°#xŒÍg¼8Aƒ‹ãGh¡Üwè]çÃ*Ýp…yáÉÒO[Œwބ奷ž{fˆÕÝÁC -´à $²€ü÷‚ݹCÐÅ5l`c 0š÷áŽ1 GáÚY¨áDât†\°¸Å !¶@¢ˆ/\  -DYƒ -x'†6ÜpCp°Á <äXÞ&Eè#? Yä›pVtäx]Ä`œ“UVp øÐ 0Xl!Å6daÅØÐÅtY°¹¦¤çY˜Ê¥©Ä©é¦͉„C…èB /X Ä (@ðÀpª‡i¦þœÆçœGÜ`Ð`e\qºŠ©¬Ä9'¶›°—*ë¬{szùìAÌÂ:íµŒE{¶UËí·[¹„¬‚ë-¸èòtäP€î¹éÆkÓb!„F ™ü‚*￱Ľb¼+Ë«'ŒÑRÜ[„Á+,ñDGq/ÄÃN¬ñCs1ÆÍn,òAžlîÁ¬²@%ƒlíÊ#·|rÄ0Çì£Ë5«,ó·ðæ¬ñÎÜöì³Ä@c+ôÐ }íÑHÿ«ô´L7ïIÄÃ3§,5ÀSÜëuÖ!oýïJDµÑ(‡-v¼K(QDtÁsÛ/¿nþܾ!„ÚwÓ¬7ºS”]ÚB$¸Öƒƒ«„×Bؽ¸Û{ÆáA8vÞ•cûô³QwîìçΆ.z±¤+kú鲦^ìê¬sê:±°Ç®éì²Ön;œ¸ÇªûîÆ^‘Å8¯ì]Ø ¼òàîÛ¯ñʆáÅ|Ã+ømg=§_x± ~»EXŒo¯i÷Õ!aÅ ,q å›gÓ_áƒÖsk×ñËÿ&^êR²¸å…ÕôÏD:ƒ²Àƒ+ «2^8 5Ô;Nm! Ú› ±n.1ì qÑàí8x·ÐðK„³"aÐÒÀ/3Ì…ERC7õ¶P†’¡ oœÔp þ7Ä!´vؘ#øà&5”ÂÌ Ä‘1žGÔcB5C3ÜÉ p Lñƒôº"Ÿ¸˜#Àbð¢F~'F­dQ'f Õˆ6¶qCdTŒ G*ð #v¼£ºòÈ•=@ŽGøAñ&Èö¼1'†<¤š@Gв‘9y$NŒpÈN"²’¹$&o¢É›pÒ“’l‚"-ÉÈQ⦔69å!÷>Α•t%W`ÉNòñ0€0P…LD”º !·rÊ `hÔS®ŒÊV&s1¼Ü''À„ì#L8@B‚‰ A.f@C¯‰Çñ°çÂŽ €} ?þø9#"…$ˆÁ„Ùc'6—©1áû°@ö!€*`L!ðÀCÄð„$Œ.6¨b²© ¨bà"0ð%tI!Rh‚EeÁB)¤A£[¡—ßüÆžm dUE8@îç á GH’„}5 P€i¸ª 8ÀÀ¢8ÐTÁ@ˆœ`Q¸DÁ VTê ݉¡xT€5à@ zÚ‰"¤Ÿÿ|B V±ê„£6Á@pP*ÐÀ€•È9Ï) á¥v½ ^9ÑHq!¨Áމ%®3±YìFÈyÄîÔ`8ø@(Y(†ËbV™duÏ>÷ñ×ÔL£åþ—gN»Íj¤œ1 :P‚‰AU¤í]™z¼ "V(C*Òp& ×HÄ唼 >WNÑÝ” hÝëR,»šƒ²Ð]ïFĶºé^Êk^Ž7NÓÅ iÛ{Þ÷ÂÉ fHCߌ 77T|^fß7%Ñ…&pj¹µÒÎ7Áù/nаËr‘ðm’7%:Ãß]0¶–°„¢žÄ!ö á–Pš Åõñµ8¼öÂXÃ)Âr c’¸HÓåq;õc"u»2ŽÓ½+ØÅK^ ¼÷dGY1G^o’{|e®¹Ê–éLÑU…*ÖuÈéòþVÙ_5k…ÍôusVàÜÞæ¾f6xŽMdòÌgÚô¹Ïµ2š ÄWúЈNE¡ÍhC/ºÑ~ô–Ñ,H[:•¾t£3­éDsÌ(þt§ -êQcªÔ¦Æô¡ÍÏTÕ¦†õ¨KÍjˆÈºÓ·Öt®/MëZ£”#g®H°)2l_ûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~ûû߸ÀNð‚üàO¸ÂÎð†;üá¸Ä'NñŠ[üâϸÆ7ÎñŽ{üã ¹ÈþGNò’›üä(O¹ÊWÎò–»üå0¹ÌgNóšÛüæ8ϹÎwÎóžûüç@ºÐ‡Nô¢ýèHOºÒ—Îô¦;ýéPºÔ§Nõª£ˆ ö…mRìl=ëù:±/"ö‰aý"Z¿J×Rva‡ëd÷ÙÙ-’vž¬}mûDònë¸çlîn?ßé‘ÁóóíÀö{ͯwƒ¾ñ yüCîîu¸[DòébüD꾓»c>!”g»å+òypi^"œ×‰çsz¼ñ‹ÿ|êY_xÚïýõ·ÿ»ìRúÉ×'­ïý@Z_y݃]ðjÿýM‚{‰[§?<òí®üæGÞúyþµ¢þß÷éw¾ú‰ÏýFˆ/zã£÷ÉϾíý‡hZÜ÷½÷UþñÃ^#äw½ù ?ÿõ_ø÷—ù÷~Ͽ֨~ È~áGzr·{¸|õ‡x|¸çxéç~þ§€öwyô§\VzèG}Ø~Ș€%(Å—³·€ÿ'âG ˆ-+°ƒ<ȃ13hƒHƒ)(€Š7-=˜„?¸ñ‚Ã7FxƒQØ‚H˜„>(ƒ'ø}Eø È…¨,Vx…@˜…!8„B…ø…ņ;¸„(¨oXƒ*‡^(…Ά+à†ZH‡e(‡ ˆ†g‡`Ȇz؇á„þ±zDh†8H…Ï‚‡…Ø1hˆdø‡[x~9x-ˆ…+h‰•X‡Œ8…jH,›8†Š§È‚€¸yGøˆ„ȉ|(‰©˜†—¨Š‚¸†¯hб¸ˆ³ˆ¡h£(+¥È„‡HŒŸØˆ«ˆz­x‡¹hŒå·‡~ˆŠ ¡}È-ÇňÇ(еH‹]X…a‰¼(‚”Ø‹¬x‹Þ˜Œ›rИí¸Àؾ¨ŽšÂŽåH޲¸‹È(çèˆá‡ˆoÕ„[„søŒéŽûø–A‚ Ò(ã‘ýŒ·q©i™‘·‘—Ö‘ù‘ùp ii"’$9’&Y’w’–þ’(¹’*Ù’,ù’.Ùp0Ùh2“49“ iZáCI‘Üh”ó8©01B™P‰W1•LÉV‘•ü(\©ñ•SÉO)‘R‰”ñ•æè•É–™˜–e‰gI•<¡–ryvi–6‘—k©|©”!–Q‰\I–a—ù—ƒ˜mŠÙ—qIAu—W1™“X™hi–©•±™]ž –šVWšÏ5Щ°G@ h1Ðû°G®gh7ÑpFh° š¬‰0° ®)¿¹š^—1Ãy0™ÇI›º ÞÄvÍ1É@›¨þ œûÓyˆÕ)œÃrßY›ЛÏiœAÕRc€´9i°šc`ï™x—)ò™h‰G@žÝ ê´ŸáYz÷™ŸM@ ìÙ›Ã"ž†GŸT c ŸÍ‰ ÊŸ¦…šÈ¹¡ì™¡´¹¡ÉžŠ)@ ¡›çy¡½9cU@—" ª@ÙÙ6š 8š Ñ`¼‰£ÿ)iZzIš¤H  ÔÉ¡9:I@¤Aõ¤O½y¯‰hR*AF ú)£ ZZ›NIZ1c`ºØÉ£hð¤üµ²Ù>š);:¥7Ažð™§@ê”BºþM@´¹£HšKº¤¢‡Tð©¦ʧ±¡‰Šgú¦«&¡©t1›3º¥åɧ¸¹ŸoÚœš¨´ÙD>*AÚŸñ›µDH𣡲j¯Z›@§’J©Ñ­JŸ\ ©Pª«ƒJ–ЬYº¥iº©ÊÚ©cJ¬‰ªhÚ„!£«ÔI ÛYK¿Š¬‰±Eê”)ø©» ð¹ è:©©áêMº¨ÂJê›*¯š£œZz¡$Н’š Œª‘J®¯ž9­)Úšåù 0J¡µô¢µÔ¥éJ­´éš‹ú~Ñyž¾ œ¸¹œ ±¢,ú±È9°Rj&šžÞy¢){Ù)±¼Ù²ÚŠ’o*÷šž!ùû,biLþ÷ H° Áƒ"L’¤ˆ’1 #"4SE¢Å‹3jÜȱ£Ç 5&!b¤ˆI&!šÙ"#† /)cÊœI³¦M„bÌ„HPÍ3cÆœIƒFI1A¥ˆA3pŒ™¡h̘I³ï'T©ÓpÙʵÌͯ`ÊýŠTªY)fÓ :F C©b–<4‹”mP³vª Ã% )^úªLXª¬±ˆ+ž‰TŒcÇH?¶²¥ )\f<˜£3 O#‘" iɤM+•,¦e1Hº¸dM»vÎËsëÞ±±äÈ’[vŽ¡F \´xÁ\†hÛÐo‰±¶êÑ¡›ÁÍ»»÷ÜbÎþHæâCËÌ1\¸èì .¼°A \ ;¾â#Ë•ì[h!`g2ÌÐY~ÙÕ¶Ýw 6Vâ9vqéÅÐ -´Ð^ (d€züp„…ZèwÉ(B7\g/x†]‚ rçàŒ4†¡l3ø`… ²€¡ ' À‚ ` ä ZTC %ÂvÅ >\a…7üGÛt2lÁEK[TqÄj0Ò¶5–i¦FrA\dZ`˜á (©! '¤à£zÊU[7X±”µ]‡àt%¦˜²H‘J*g6ê¨Aidá…îµ€œvbèc 18梆êè)Ç‚{i€‚z=² ƒƒþŸ2*ê¬5F(GƽÐÂŒ±Ò꫃¶þšP¯Â«Á[±Ê6+ÖÉ:»³ÒVK´YK­µÜ†„l¶ÕnÛí¸E문䦋‘¹Í¢«î» ±«¬»ðÖ[¼ÆÒk¯½m!᯵h‘Fæ¾C ®´úü.¾Å&¬pº ëðÃÝ~똶‰‚J1ÁØ^nƲnloÇ·~¬hÈ"ÃkqɃœò¾ÿ:ñËÎÆìëÌ4+k3­8ç\ìγöìó¯@‹*ôдêÑH‹ªô£L7ýh„D`|²Ô㊇„VkŒµµâÁµÉ^-mØc·|µÙÕŠWDÚçºÌöÙŽÁÝ®Üs7û´þ£Qç=ãÞöíwƒ€Ÿ)øàßnæáˆw§x™aàݸ¯×è…ä“ÏZ9\`žy¨›ÏxùÚŸk~°³‘“^ú£ý"aÀf¼ºéOVË„PMŒAÕì ‹A¥´E„fJíÎ{£âñ)­¹§Åh?ªVÜÐ 1A‘{¢±ó$½™fÑÝ/¤ÄüÜKáý÷µž؃Ä|xMvû໿ØüÿGJžúðW¦•uÇ0€þçÝ=! (#`ƒ’â±Ý 08B@Â8 ^k ¡a™n€J¡|ù”ñ<È 5Œ¡~"ÌÍ€`ð?HaÆîÇÂÉâ…þJéÎ kXBHáù{˜¸1D¡‚#,! mh$äp#*d"ƒ(ÃÅPŠTŒÂ3ÒA-‚Ä€¼ùb kxC h¤ŒfüHè>¢ÆÀx<Æh8Ʊ\úSÌ#0€}` ñ±Š@FÏýQ1sôˆ0*D`h6 ¾7:ò‘ˆ‰dGŽ@˜àØG~°`Á“ª%$™1háû¸@ö!€*üàD¯Ãˆe¹.Z"æ.HÄÁ#¡ >¨Á„ùIb‚E”éÔ>ÄP„#A)Rð |Ð…‹ ÓšÁæGøÃ?uÁøÀx°A‰œñþ2æb€yƒXàø€:O{VŸ5Q§Gt”… <à¡°@ <‚4–ýŠB;r¢ 8਀ÐÈ(´%£cÙ(G<€Dô‚Ý"2â ¥×Ô§b:`’~à§xÀ„y†Û1§9…bw:àÆ\fàx@=-â”&@©_id½<À¥»ÄˆúêwS¬&T§»qIßXÔ}Õ¬gUê¼Ò`S¸ÚD¥ºÙŽIËjטà57ŸÒI_gò×Å(q°„EkÐ2¶BÄ‚¤°ŠÑÔAÇz²ˆ ñkÙÇ*öQó;Y—ØY@ÊUXçKôÖWÚ`V,ÚCþŸÐÄÖš¶‹¨Íž=Ûräµa -YßêÛŒ7¸NaBxŠûÛÏ.ÍyecîEŽ–,J׸Îešð”ëb÷´ÂjBNêêÝéf÷LçKChJS^‹P÷&±U-iÚ›Îóš)µR8Ãé›Oðúê|hp‚òËßþâ6¼HaÂó làj)e æ+ƒ ò^›¤w½œ0…+'8AÂ)áÀ6À„ðÀXð ‚ßÕjØ`þ-H˜@c&4&RHñ‰%,…* áH¨_ñ«_Ö¾ø'iAKaÖ’½$yÉP–Ê]ê2¶0Y-l ð€3üb‚,*cø²˜¿ 3,ªÌcsþš×¼æŸ ÌQ9ƒ˜Õ`˜TÈB*³YÈÛå‚ìÏ€É:c›úЈþóŒŸ@š'0áÂ6cÕüÙ Mh‡û|/ŸŒÍb€á€s—æO³ùÔ±CŠ€ó;æ Г‚,ØŒQN{úÔ‹Ê´®M}k\ãZÐêK³~ÿlfZG—Óûè5®¬cû×@qB±Ó k¤¼ùԵŸmnûš×Þs¶‘ b”;#ç+²×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~ûû߸ÀNð‚üàO¸ÂÎð†;üá¸Ä'NñŠ[üâϸÆ7ÎñŽ{üã ¹ÈþGNò’›üä(O¹ÊWÎò–»üå0¹ÌgNóšÛüæ8ϹÎwÎóžûüç@ºÐ‡Nô¢ýèHOºÒ—Îô¦;ýéPºÔ§Nõª[ýêXϺַÎõ®{ýë`»ØÇNö²›ýìhO»Ú×Îö¶»ýíp»ÜçN÷ºÛýîxÏ»Þ÷ÎwŽ(àï€ÿ{,RY¤û#…gñLÃ_„ñ q¼ºøÁK$ñ’çä’ùÇ£;%›o¼È(xËGó7 ý>:OøÏ‡DõŠßéßúƒ°>#ª¿ýé]Øûyô³7½BPo“Ü/ž÷ˆçˆî­5{?!į‰ñeâûÕƒ^ùÀ'ýóþ}šL?&Õ_¾AªÏùìS~û}ñ-"~î#ß#ä7¼ù+_ûØKŸýÇW÷ë±/ûà×ÿ÷ë'í—~ú×{üG1͇~ã§~÷'€ùw8ñg}ý§}ÿG}øG}ïטƒ€X~ (¨yø0"xy è}~)¸8¥7‚òW‚‘÷€1˜|q‚ÂÒ‚»g{©×ƒûW€>hn6H{.H„:˜@˜l3¨5X燃Ø€&ȃx„J˜Q8„$ÝÇ…/è…Fè+x}Y(†h0Ø…2ˆ„ð·†Ö²z¸‡|؇+4x†H‡l…8…þÍ⇊¸‡€H…‚X‡[8ˆ†ˆ†v˜‡‹¨ˆˆ{xˆPh†;H‰“8.—ˆ‰è„Zø‰˜†*øƒé2Š~˜‰Q†’ˆŠœ‰ÈŠäâŠ}‹žgŠsh‹µH‹wˆ‹¢¨‹ŒXŠrè‰Éx‹¡˜„ˆ¨,ÆxŒŽè‹Ê€©HˆÃø…żˆ…p(ßŒŠª(-Ѩ‡ÝØ„Ëx›¨†•¸ŠÚØ-çø‡Èh¿(ŒÌXŽÓÜ2é(†öX(ëXïhŽçøÃ׎ù8ŽìˆðøŒÆâ9†÷èâȱˆ‡ÕB‘û³ˆ‘ä(’îÈ–È™ƒ§H’Έ%)‘Åâþ‘šH!©‘é’yÎ"“I“Y/‰“))”Ãøî7a!‹? ’- ŒÙ“3i”q¨’_¡”‰”Ké“Cé”?'^ù•^ÙU5–`)–4G–_i–3‡–aysl)j)so—17—nÉ–t svis{9–xy—h™—/×—gù—|i˜~˜€I–‚ér„Ù„R¹€XY•6”•Ù“Íø”:ÙtŒT²2‰A ™”1š+YšY¦Ù”±šù™¡©šnˆšOH›Yi®i™­Ù”°‰¢9›‘›·)©™Qœ7yÈ9‚½9¿IšÁY„±œ[#yœ¼)Hþä›±dˆÞy‘à™šžÆi晜}·ž ôeh`%t©ðž@Fz òeûpIôiŸà'fÉöeM0 Äpcˆ§Ÿê5 —´Ÿª T(+  ÑÀ¡š ——ŸÊ ¡¡L10¡ûp%êe"ª1 @f‡D£+¡‹—f,zI8z:º Œr¡hÎtI& kÖ n7IºK‹ú£"º¢ŠPºŸSZ¥Üw¥C:¢åY ^J¥ÀL¯¥^“fN*‹R¤c@76@\ø)fc`@pItºvЧš×¤þ©ÐŸ€HêVFʤnÊ J§‹*§¸'¦ *¢Šj¢º¢4ŠD” }$z©c:™º¡a§U@¤2ú¨m§´º§÷é¨î ¥@*«uzH¯‡¥ÉÖpc4:©]j«/Ê ô‰DÄJ¡Vz¬dЦ°v§9º¬§÷ªÏ:`:iªº4Úªcö¦_¦­ :«ÒJ}cö -j®‚Jƒz¥MЭ@:©ê„ *亟ʨµ:|£®£©Òzûú© ú®c:ª%jªTZ¯«*Ÿ °1ÚР?¤è:f¹š±yz©ÉÆ¥QФJʰƒ:±NJ«ÞJ²Ðê^®Îj©Ð*²^š«`¦ ŽYº°LÁ?GE«"[6;fZBöÙŸý*¤ š«FûŸbškš¨ ¢Bа!âfú¡ÌÚ¬ ‹¡-Û¡µ!úla¢ôYDh`µ. ´¾!ùû,{`èDþ÷ H°àÀ&M–±’åJƒ#JœH±¢Å‹3jÜȱ£G‚Uxܸ£áÈ“V:1RĈ“0cÊœI³fÍ4frŽÙ饋Ï.V‚ êe %H‘Ž)ˆó š4g̤¡¸Ô¦Õ«X³bŒš³«™bƈ!†‹-[ÒnÑb…LÎ%I”LQ’$‰Waªxù2æ®_3Rþ ö*K«áÈ%žø1c)Hš`Ùp#è 'P´hñ…‰*bž²DÌ’ÑO‹Érr¤ê×bÀžÝØLáĸsg]< +5p‘qŠ.^À@qâl !íØÊ A}ܸB›±ìî°mþëO^&oزµp¨QÄ{ð™gŽAÿÅ bŠŒ®bÅG†Û‰Ò8tÑÝwà©&^y 6¸m²uîasšÅ°E?ÀðB£!ÒI8TÖÅ 5t@h½%øÚ‚Æ(cAç½&›2Äà‚ ›±B -dÖB R0æ  ‘„vØYƒ 6h·”=ÜÀC‘蹨àm3vé`O1ha jßU!0ÄÐ œÁ°# @‘DŽ\0V"‹ÚípCÜ…z²¨šGhéŒ^&ªZ]ÀÀÙ˜!8}j)ƒ0X Ü¤ÉâFVvÅ5X! 741¨1À Cþ†"ªè¬†±…j1$™ª™©ã bnAÁ0«Ü1¨"eQÅVJ_ŽôAš ¬´fkUZxa†UèÈ+l[PCAHa…@‘cGŒ8RŒØšµb”»¡¬"«¥m©œŠ¶ÓdægHa¦ u¡… 0D@þ¥4гØ=TÀì1LánÑÅfÅðqE|òÊ¡ÅÅ¥2hÌ2D)<óÍ…!V€†³A5ÿ,4D;AÑØÐôÒûÔÈ´ÒLíôÒPGýóÔHWmõÌQa)ÆÓ²lóÖ7ïÔ–`‹M6Îh˜ÅÙ_SvÇkßœ0ÜiÓ]7þËw™·Ê{¯ÜöÛ~Ë­và'›¶áz#^p×G3¸ãc=´Ö”gk¹Ð˜g®¨XH„ŽÓIaºž?îØßc§>+䌱îz¶ŠÇ¾ti>ûçnã½´^xÑøî]ŽÑwÜHwá…eLN|—fÿ4:€áüó2öE8ò—‡¡ƒð­c£Ùªr/t_0½ø ÂnþÏÀ_1<ûí¯¾taèN¿ƒ›ÿÜùþãéÎþÀÜðf,`b8³*ð0 dYΠ¿ê&‚+^ægÁÚ/yËk^ø:èÁÈez_X !øÁî}O…+Ô O†ôåo„1”a …¼,p0‡þX™áǬ BÑ*BôXr÷Ã#Ú$‰“ ÀçDšph"“ ¬WEÃ@‘`JÃ^…Ã.Òä‹Ú*†³…2šÑ<;ÔMh²„%X \pãa‚F›ü`3áB£bÀÅ=Út¢kÈ€™ì>¦3$㘛#àˆ ˜ °Hò‰”Ä`1 &jT©P÷É3†21G@ ðƒSzd }‘¦ÒÊ™ô±&±)¥`KŽØf Ñc/3òKšS–—,¦FŽ™ÌeÂñŠãy¦0 PibÄ6 +œ5?ÒÌ™hšGÂF†LVŽÓ#c(äÔ Q sÂþ$e~0͹AåðTÂhF“Ùøæ,ð‚Œ8 AJ” Ÿ!0¨¸g`€Ž¡ ¼ÈC!j4$a Sæó„ò`4À>Jy üàøæÜŒHÒˆŒá¤E’Ât¢àÒL@  @0 ›„è´‚=¥HâB%A-(ðƒ Aû(À¾c1‹Œ4ªƒ@ZÐòˆÁ°@*°TÁ¦ÃDºSe¢µ ñœg[ËsFºàb¸ä0à'ÖÜÀ¬}ý냰©ä°0€ ôÀÁ±B*²K¨J6"å¬É"Ð (GX¨þÁðó>ƒ Shâi ’Zš¬v(–¬`ƒŒ\¡ @Ow+Þγ0Á :[¥Œ|Á xÂr™ë\™° ;A20’½Š¤GØîn»“ ä>àwjëSôª÷´ìAbc»‹|4"d.EòK“,ø`#Xè‰nLày¡ˆ~]ï+YvVÉ6˜Aþë…Ë“24x"&ÇdAß7mÂ+ãXÜib‚ 2t¤Œ À„C$Äã1‹mÜ\Ÿ,efà1|ü± £Ǻ1rT‘œ%÷”ɸq2I¡œŽ©—Bî1e6gy TF 8MWâ‡ù0ÔCþ„ñKdÙæTÊý²–û{9Yf—r>ñ–( 4œ.Ïg6 À³cZ+)Ãr–)C”ÑXáØWÝæ‚ìÏræÊ_3˜N{…ÓžþK”½àï.V8p·äÄÎE:Cž "¶€©¹Ö¸Îu­o­ë^ `ú¬ã¥! ¬Nv ņL5Ïz ¸–‚¯§-0iS[×M`BªÀÓ!!OÃvZå2[ |¶@¢}íi[»ÝbÃÙÆp¶Mª ™Mp‚.Ñ€L1ô0aQ÷>Ø o]¿»à©‚ª.†MZ{ ÏÝŠ¥ÀÄjK¡¥ê&8Âk}ð‚+\ͺtxÀþÎm)¨!j0]ÅmŒ?[ã¯vÌSAï"-ÛpSø¼ùmºÇZà0×u;yÍñ™'|“ö–÷·›€L(8! É<·³3^t_ë!XÖá='dÛ jn§ "…(<] æ2ÓMu±m]`òÖ%¾Åtxü ©‰:Ü÷ÝoŸ\à)4A·¸Dðiö>t©K‚L|*|$OùÊ[þò˜Ï¼æ7ÏùÎ{þó ½èGOúÒ›þô¨O½êWÏúÖ»þõ°½ìgOûÚÛþö¸Ï½îwÏûÞûþ÷À¾ð‡OüâÿøÈO¾ò—Ïüæ;ÿùоô§Oýê[ÿúØÏ¾ö·þÏýî{ÿûà¿øÇOþò›ÿüèO¿ú×Ïþö»ÿýð¿üçOÿúÛÿþøÏ¿þ÷Ïÿþûÿÿ€8€X€x€˜€ ¸€ Ø€ø€8Xx˜¸Øø ‚"8‚$X‚&x‚§*¨‚Þ·‚,Ø}.¨-è‚3¸‚5ø‚܃7(ƒ0Hƒ=hƒ?ˆƒÛ§ƒAȃ9èƒG„I(„ÚG„Kh„(…u³TX…Vx…Vχ…\ˆ…Zè|]†Tø…Í'†aH†Ìg†]ˆ†Ë§†\ȆÊç†^¸…r˜…tX‡cx‡x‡É‡‡UȇÈç‡y†‚ˆÇ'ˆT+`ˆÆ‡ˆŠX|Œ¨‡u؈Ä÷ˆRX‰ó'˜dÞ‡‰" ‰ÝljžÈ} ø}£¸‰™HЧhŠˆŠ«¨Š¡¸}¥ø‰©(‹­H‹¯¨}±(г¨‹µÈ‹f!ùû,zYéKþ÷ H° Áƒ÷aé‘ÃJ‡#JœH±¢Å‹3jÜч7°8äH²¤É“(SRTcÆ —fÆœif昛hö¥¹9&¬˜b¼€1#&ŒQ1ÍLãâ•P£JšpfË«<Lj)z3fVž^¿Ž Ó%Œ)FÃä¹vÌÕ·p¥À WÕ»xó<³µ¯)Mšh‰ÆŒ-[¥ÀàÃ:t ã x  4Ø`ƒ&˜€ÅGáß7ŠD¢D”Xf©%rÍèeŒ úånIÆàÜldzTÈC_O!§œF˜ÆŸ¢È¢‹¬¹öåŸÞ…éVØÃ 4¬™$ Œž`‚ Ml VžFDXPÁŠ{òÙZ* *j^â!T…÷•P!-ìs þ-´àF6d )}꨼JU*BGø°A ØC¯º¦Òë²*ýšP%pÀÁ²É2k­IÎ^›PµÚv‹Q¶ÞÄm¸äFÄ—3”kиê¶[Ð3ÄÚª»û°K¯»^°À‚ /Ìꮽ÷–û¢û¼êp¸^pÑB bhq𿲀ªìÂívá° 3È+ÅcÜîÀ.ÈËÂå*,òµf¸w°¬ôª¼²µdh!p÷Ê<ó²à&\ñ®;{ÛsÊ?_t·C“«óÑ¢ž±Ep\ä\4ÓÝfx›†1OMµµ`º¡1DÝîÒ[Ϩ1[mõØZ—ÝëÙi¯ísÈn÷Úµ _?(öþÜ@×=ªÕ·i‘5Ý~ê4ÔR^8 I‡Köâà5îíãǦ•ËEœ•3ÞWÀ”w®WKVd‘ÅH ÷-ú‚c|aŸ}YÄÌi¬>c^ø°ª¨—ëÖæJÙ¾à^dñ„„ùµ[ÚZW»ð oÅñ–Å®îñi€ú×Ð'FîX\êí:!† ŽÑe÷ßÑÅëö¹…´£ñWNìj†ø>ïnig!ÎSò÷ÉuK&¥ õ¹Ä©nz1 ¶BA”Hem«`8¸j*´–˜r†ç}Tt×Þ%„ËZ^ó¤`Bú*…×CöR±=‚‡þå2úR¡¾úP6@$—¦p¾HGDâV4“ a~H@Ž¢xÃ)&è!0‰ÆP„$‘‹(I £"2T $1jˆÆ“¨1‰¯9‚Šp’¸ä?Ë«#Jd±F¨C7ÂD#†8P$I ©’CÀW0ÁF”0#Hyc”$¶ð¨—C‘Rè#F€0F錱4¢, %SbJ\òš¼ÈÄ •³ 猱ü)ób„œò”¨B .r͉ Mød07r9Ñ(¨˜Ç À--`Pa “aB¦ža⥖¶4¦šp„Šà€~D!÷È©‘Y¢¤–ûþȦÐΉXa…¡¤g=Íy|îƒH褊Œ MèFìyKF`ûHä6ºQ‡fP¢¡¨IŠ9*D`h ôe‘1|¤iIŽPœàØG. /TÄ¥ŠƒiL J …àx£ª€‚}!Á£A*ƒ¶’Yû0AÄ`€#ü ©Z@Â&"†—J"|á‚2  < ?À@S[p³+"eêY—!AF,  À@V0xÏ "Tî!gˆÓû Uà•ºð¾ˆPð¬g`‚&£˜ÅME>+Õ3HÁ¯€­þÀÜúÈM"¬*_N$£ @û°.€ø€"_0ëc2›%È©9Á Np[Qä ^Ðër÷BT¼Ð€ 0Á 0P‘.x¡ iPÃvÍÕ]Øl ñBî3ÏõB¶½½:BºàƒÀDŽöˆLg¤;/XÁ Æf€÷1`Ù» Ìü.üòjU@C*ÒRX Ž‘x)…(À ñ‚0ÀKÿ*8ÅæýxYß 8ƣʫcaìEzéØh61ŽEEåòxÂôj,ƒ¬âý8È©æSïU…rÊBî±»žŒePK0r€›¬ ",A»GXþ– „ùËZŠàfû’9AS˜s×{gð¬ùÌK¶qŸ¿³D4¿YËí2³¡í g‹HÁÓŠÊšÛèº Š©Ì¬Wåxdá€F _9Ó¹Ò¶ ]®"—U¯š œ½J¬@†°€ dá‚® 1+à×a°Â'…. ûÀfƒHÂP´tA zFµ-Fm‹¡ÚØÆ¶8;0pV $LÅyÆ-< º&·ºpka_Á Wö»g…*„ Jp‚¨ÀfJw™ Ù¶˜NpqäàWHÅY̪\á ŒBÁÍDÔƒ” 6ÆCP”àã%è@þŽ`#ÁFÂ½à‚¼à/Cé¬P†»V‡©8îð!s—á/ÆÞî°`ƒ XèÚY(ú Ä&<4”Þs€]óª§âå)<«­>·¨O:bˆì_n1YH c€‚dv(uûRÝêo'"qè‡í4d}sc¼zשÉlN2g ö¿w¸gûå ’ÛëîPþ“y·£•1&ð,Ÿ:Áoxls¾ó7ƒù>Ymk® NbÛ»|å´þ"–ÿÌž’õ°ƒ3œH¼µÂ‰‹ º¾ð‡OüâÿøÈO¾ò—Ïüæ;ÿùоô§Oýê[ÿúØÏ¾ö·þÏýî{ÿûà¿øÇOþò›ÿüèO¿ú×Ïþö»ÿýð¿üçOÿúÛÿþøÏ¿þ÷Ïÿþûÿÿ€8€X€x€˜€ ¸€ Ø€ø€8Xx˜¸Øø ‚"8‚$X‚&x‚(˜‚*¸‚,Ø‚.ø‚0ƒ28ƒ4Xƒ6xƒ8˜ƒ:¸ƒ<؃>øƒ@„B8„DX„Fx„H˜„J¸„LØ„Nø„P…R8…TX…Vx…X˜…Z¸…þ·rÒf€^ø€aè€cØ€eÈ€g¸€i¨€k˜€mˆ€ox€q†«€sX€wH€y8€{(€}€È…‚(2+Pˆ†¸†e €‡hˆ‰˜€‹Xˆˆ€ˆˆŠøˆ‘x€“x‰˜‰•¸ˆšX€œèˆ–؉‡ø‰Š’8Š¢è‰¤Èˆ­‰¯H‰«XŠƒX‹p7€¸huº˜‹¸‹U׋¼ø‹¾€ÀsÂŒÄ8ŒÆXŒxŒ—ŒÈ¸ŒÊØŒÌøŒÎøÐHpÒÔs!ùû,fTaþ÷ H° Áƒ .a’°¡Ã‡#JœH±¢Å‹3jœÈ ’"C–lI²¤É“(S’LS¦JšIˆ IR„H‘&*sêÜɳçÎ1SšpÊ¥ š1R¦ˆYú¤Š—0eVé⥠Á1Àf_™­aΘ;ö VŸhÓªÝ)–¬Û1Il™K”Ì—…_¾x±Â·‹¹s·péBVŠ™.ˆ[!:Ô̘ÇnÝŽLy¬¬µ˜3k.xf©ç¥R†ùã‡!Zbh¹QAH’*2T8‚%ƒ >~T€£‹7pÜðÁ‡ CeÄØò¹ùR$Σ›¹¼¹ºõž32DyÑGbþȸät „Lará .S†\ˆAãÆ :\YJÃŽ¥[¨ÝgÐ øÙt×%¨àIÙ5·ÝjjªÙ7!`{'ÄPEh¼@ƒUqC=,ÕƒKq! b£.hã5øYhBüõ–…}>œXCUdÁm¹YƒˆVÄØC6\q…q4Ì¥3Êãt©¤‚ã˜d&”AEœv -´à‚YÁà srÀA ÷Uðž/À0CAl`ß  œ#)f™Œ2z¦B>iPÑw\Kñƒ>pqPœ^±OXPAIŠ6ªê˜®úPªþ®Æz]«²"k­¸ª5FŒHäŠaRHáë°>ÑJì>·«,ƒK-+P²ÎF›‘±ÄB+íµQ;¬µØvû¶¾rëí¸›«¸ä¦+¹¸¢«.¹ìÖêî»ÞÆ+ë¼ôbÛ™ Ñ⛯´gpñB¿²„ùo¾gháÁünÂ;ë¯ÃÊËR oüJ\pÆ×+¥7²Èb`|ò¸öÆ:ñʸ¶ìêË0Ë*óª4×ìjg— ²ÎËž Ê@G›pÏËæ\´£©½tÅCûütÐb ­¬ÒSãx³ªXgmãÖvíµ‚`3*öØÖíêQ´À ‹v®e—yöÛše“RÓ«þI(!„ÜtÁgèíêC$1ÆXÄàb¸mx£K„tF6T0DÒhH‘òä”[>¤æIŸ! 7ŠxSüÐøI›!ÅK©3ZDßB`àW›nFíªOa§,ðÆMæti|Ž<™Ê9÷ól5›7õZ[?¸ÉØ­ýÕwŸ½óà§‚ºøÞ“Ù&M9úéW÷ƒ ¨v®>üÕEO’d„@’¦+þ¤¿‘ÁW(A¢LG»V ܈`1°O#c0ÃÄ @j†FßÃÌ€@bÄsfh‚ %çÁµˆaW÷SË@A*|à"Rh‚Ž’C¶0-þ/ô\ufÁÉáw¼º)1L ih€*\p"J$ÜpâDµ¼0„k!â>Šh€#`€"9Ü¡›….zÑ `”! 0F*VÁÉ4È„&œÅ>`¥8Çñ H”˜À’'Ôˆ@XòU8£D¬ð…&<á‘j‰dFF죂\†&b/|”hQ[¯6c„L  ØGš€Øv›ìœK´ˆ#G¨À@pì£? Í ªŃ´R ,fõªé1¸À©P°àM]à&A®P†÷i’ƒD àö‚ˆ¡Œ?þ@Á XpNaB¤hHÃïÞ¹Íê€@ 'Lp~:Ô0øçÒð3‚¢ä˜ÁÁBp†B´AÂÆPQ‹š£©JÐѼ  FZR“’¥ÑÀnÈP‡²@¦.©©M7‚Ó‹ìT %h“>W0‘+TE¨CV<Õ¢‚Tà'@"ŸÕ‹NU3HdE8IÑ®2K²²B¸gV¢~UV^_[oúÖX©•­sÅHQ5ƒ¿@5¯Ùkf²`…è°nEk¬®Ð…,ä±,f°†+ð²‘ìZ´ ËbV¯uu•¾0§Ïf6´«ÊBl€Ó浪Êþ‚²M×FD³jÑBƒ8Á ¦¶ó>°9j&8œ!ûþø\8zû1â(O¹ÊWÎò–»üå0¹ÌgNóšÛüæ8ϹÎwÎóžûüç@ºÐ‡Nô¢ýèHOºÒ—Îô¦;ýéPºÔ§Nõª[ýêXϺַÎõ®{ýë`»ØÇNö²›ýìhO»Ú×Îö¶»ýíp»ÜçN÷ºÛýîxÏ»Þ÷Î÷¾ûýàOøÂþðˆO¼âÏøÆ;þñ¼ä'OùÊ[þò˜Ï¼æ7ÏùÎ{þó ½èGOúÒ›þô¨O½êWÏúÖ»þõ°½ìgOûÚÛþö¸Ï½îwÏûÞûþ÷À¾ð‡Oüâÿø=Q€ò—Ïüæ™ m3I¼+}ŒðW ÕŸÈõÇþÕüî3ÿù‰¾N¨’íg_"Û÷–÷×þK;äü!ÿIÌ?}­ßûí߇øs"“Ð$é×-÷×}ù·*Ñ%ñ€Ø2€ÎW˜XhD0ã€ßï7~¡€Á€×‚ËW€ÈX~Id+C‚Êg‚ á1¸‚5X3.¨0˜2h4¸€6„8è‚;ø+= öG„ƒGøIKh}MH‚ExS¨})8A‚WˆYh[ˆ~]è_È„Cˆ…OȃQx‚hX7xN}p(g˜€ih…k(†mh„(ˆ„*(‡BH‡lh‡þPˆR8ˆæ6‡sX†|XÈ…‰x€yÈ‚—‰¨%5‰fX‰x(ˆ^Hˆ`<+pЍ˜Šª¸[`‰¢‡Xˆ©³Š´ˆŠ­Š‹8бXеS‹µx‹Œx‡˜øŠ¼8‹¾¸ŠÀ¨‹Â舙8ƒÈsŒÈ芹‹á‰á÷ŒÐ˜ŠÉH 8ŒÓXŒ “Ú(Ë(‹ÍèƒØ(ެHŽÝȌĨ†½¨Žëˆ‹åŽï¨‰á(Û¨‡÷hŽýˆŽ¦¨ìè†ÊxŽ!‚+#óŒíèßƨŽûèZÈÿxé(Ž鎀Èù™éYI‰ñ(‘Ù‘ü¸‹™*Iþ i‰“£#I“%é‘5éŒ-i I†=É’9“ù“œ¸“oÈ”Eé”YIiwX€WpW•ùuXI•Vùv[Yw_Iwa9wc)wewgy•]évié•kÙvmÉ–oÉvq —s¹vu‰’@‰’±—A”&i”Q‰”AÙxƒˆû0KiŠÉÕ˜+yÙ’1™/I–™1–阒9”±™<Ù™ù™G ™9 Ù”‘š£‰šª‰¬i“›|Y§¹š‹Y´Yš‰ùš¢š ±›J˜·¹G›qœœÙʙќ”Ðy™1Èw+“dhþ`4t©Ð¹4~b¢e´à)žó—dú‡d݉žá9ž!èží‰Wð98‘žôùÞ÷ù3h8±@Xq4t˜ùŸìÉžG0ž JA¨“Ÿ8ñŸ@©0D ûÖ™€ªctŠ *¢åéžHv30š¢ñç 6šœ¹Ô £(¢ ªdzŸ1 Ÿû¡Æ£`gÚº¢JêÉ¡X‘¡ŠžPqÚgœ ºž#zœ £¨ã¢* ¤HFR G@¦^º€P ¥Ñ€žà¦fª}N¤ö¹læ¡€0Zþuz¤>ziЍ 3Fp¥Éi¥û€1ª¨‘zžP¦ýy¦+ª£’Ú¦—š¤*ʧa¹Ä¡Oð©ŽZ£#Ú§¡ p¨š©Jˆ¦,Ê='ººŠ…Ф?~&©jœ¯ Çy«î9ðœ_ª©H¶¦0H€ªÍZ£Š ¥¹: ÓZ¦ÕêŸq*¤9Z§Ê­>H«7:·š«ƒÚ«‡ ¬‹R«ˆšªâ*Æ©1êžMP$©3ú®pÚží ¢·J£h£Pz¥û*ÿZŸßʪHÊ¡ Û£çú¤é*·Š¤J¥·ú«öÙdŽ ª°YšdðICÊŸ;°©0¢*ë»('{œ/ë°[ʦò©ž¤>«®j jœ J…êéY´º!ùû,ZM hþ÷ H° Áƒî«bE‹–. #JœH±¢Å‹3jÜȱ£Ç‚\¶p‘C“(Sª\ɲ¥K•hÌœáF̘0\lžÙ‡fŒM‰cö¥‘)p¨™ŠA_*]Ê´iÆ3f¢JJÕŒO.`Æhí"¦«&]—(©JUJT­c¬j%ËÖ,[YNãÊ›òL×»xóz­cF•& ­T©r ’#C„Ñ‹IW(M"GÆ»ÅáÆb36—®çÏ ÚÅLZ •IÎðQYK B†Î|WŠ(RðÆØÍ›±f½œC þyôl½U`ÄxÁ¢… -.X¸£‚Ù¿o))’äå¼Ùñþ'N¾¼RãÇóÙ"†è-\ ÑÃì³]ç³]‹ÞðwgÞ€z„^zw9¶E|2Ìà‚ 4`Á €E€xé×Uÿa&` †HÑéIA˜‰/¸Ã-´ƒÀàÃqÞÅwÔ}ט‡‰è㑈YEѨm÷ (ă &`Ñ‘?øàÃ~]ivEY\!–¹Æ›{nÖ#hú(¤^IQÁ’GîV\@ &|A7Øðæ’/Àà_–bÜ`¨¡Œ˜Zè©Diš”x`¥q&餘vÚ¥ž¤é¦¡–:¨¦Ž*©©¬6…j©þªrÚê¬-½j¬©ÐªëJ¶zŠë®À~Ôk§¿k¬FÃbZì±ÌRä“aжj–mÔ6kíDÉVºìµÜ™W«Ûv+î>ÙRî¸Ü–›æ¹èZ«.šì¶Ëì»@Æ+¯±ôþhï½Àæëã¾üêꯈ<ëÀ!l0«ƒ¨ðÂ¥6\àÃ{*1WŒéÅf¬1¥›çñÇh†\ÞÈ$ÿh2y(§,âÊĵì2ˆ0'óÌÖ,ÜÍ8›§sh<÷LÞÏ -ôpDfôÑŸ~ËêÒLçtª²lškÔO kÕ¤bÝiÒžAíµ\`Ó%öØN•=×Ùh/õ,´HHkµ¶µ ²Ö·r½ªþÝ%ßE$¸zËÊ·š]!„€[=8v1qx‰w½øË™ q_ä‘&5yˆv!a¹˜§ú朋¡Äã¡KAzébqx裯ž³ßD„ކìª=(}øÕ¸ûŒ÷\WXðQ¿oéðrÙôGV$¯|yºKt„G€ÐòŠO?4óq€bŒÀ‘ïÝ{4øN‰€W˜¯Ûê?Å~Sâ0>%Ì/}ýMÃKhò€÷áy‰•æ(5‚†€ú3@´wôI*v ôLõ"ÁbïឤT—ÁÏL!1DXÌõ€}°P‚?°Hô$5·zf Ih€¹²Ð…þú3 *"Â1Ô͆rIC’0„" A‡ €÷àŠG¨B '†ª¡Á6GAb\¦°D°0A‡Jx #0€}¯À€)¢©¹‰xbd “01LA‡IPãª*6Á<AÿèXµ3¨yTŠÛd8’Ð3G¨À*pì£Ø€"iÀÈT|Q Œ¤R”ÃÖ4b¸Â0` ìCU Làƒ-FÄ CCµTÙ”1NŽ ´G1`ïº4Á’2”¹Q ƒÄ|ÉBÍ}X¸>P‚hàPªADÒ2†žÜ& Ù\Ê6%ÒŒàÉþ16pƒ˜@ ÉÍÒŠ4H¡ PˆçKÞ­át!°ÁžtNŠ •ÔjN¢P—Ì3"€hjú)Vq'n:êÑûÅåõÜÇp €"6I©Åp&§ Ë¥ž)Á ,’«ØÄ6iðªJ ê’ÜÑ6MBZÕŠ¶ºä¬sk‚Ô@׺‚õG:8+\¡0×¾vÄ®,ÉY|òÖÂv#ˆeI´j›®>#‘]‰¬ ÆËö¯?ò‚#€JHzö"™U‰ìØÙÓ" ´"ªþÂjÑŠ1<á ©tíˆ`¢!¶qB\/©ÛÝ5T8Xln a涸 ImJ&«Ñ&DA˜Lht{—VUa ݨ»]lñ6D}BÂÛl–÷ ÒýÈ l2'â Ràkm|\§øÀ GÀvà‘ƒWÀO°lÙÚ$CàAÐŒä@°m‡¼ŸÐíÌhN³þš×¬f*|¡ÊW°Âæg9 ¦ ^蜑|„"¡€F€ÏÚÓ>Ì ±š¢75†E;úÑNƒÞlg9×Y0VøBÂç. ¡‡YâXª1 ÅMP1¢òhEK¡Õ°aAÌ+X Xà3Œ`%,ø >ÀÁ |„ <ÀÏK Ö‚…Ë„(„qÕ‰µ¬¥ÝêWW2ð`’°Q*&‹ë=Ÿ—4T¯5ô0¡Íjj[›ÚŽ~µAqã„kTÀ¹¡Vx¼ÑFÏ´sòìbv·[Ú«SáÈ;~%¼õN7¶:ÞÚfÁw4âuäÚúÔàû€wÂþ¾©W;²Ñ_Yö1*~[÷áùŽ+dØ+†“ƒ<äî&ù¢_}J#þx£’YnºÎÜÀõáë°0Alp‘ëœáŒud¤„i¢gwn¶:€+ÜÛ:»†~îÍRa‰œ’ZðÈ´Bôž:2ëD·¯ÕŸ0ô´=DZ?ˆ½ÃëÞ»(2?¶{AˆžP¿û¨Þ@Wà#cøÆ;þñ¼ä'OùÊ[þò˜Ï¼æ7ÏùÎ{þó ½èGOúÒ›þô¨O½êWÏúÖ»þõ°½ìgOûÚÛþö¸Ï½îwÏûÞûþ÷À¾ð‡OüâÿøÈO¾ò—Ïüæ;ÿùоô§Oýê[ÿúØÏ¾ö·þÏýî{ÿûà¿øÇOþò›ÿüèO¿ú×Ïþö»ÿýð¿üçOÿúÛÿþøÏ¿þ÷Ïÿþûÿÿ€8€X€x€˜€ ¸€ Ø€ø€8Xx˜¸Øø ‚"8‚$X‚&x‚(˜‚*¸‚,Ø‚«¦0ƒ28ƒ4Xƒ68ƒi$"e—hdå=7øƒ@„9ˆ^aZåƒA˜„J¨C";ˆFHvH¸„ThƒMX OxQˆY¸8Uø…4x…Ò…ÑÖƒÓ†hƒb8 dH[xmh7iˆ†khq(ohwˆ6s†uX{˜‡±‡cÓ‡_ø‡þäˆf¨<†X…ˆHŠX„è5H…8‘H“ˆ5•¸„—(™8›5¨„Ÿ¡(£È4¥˜„§©«x4­„¯ø±x„gX‹?x‹ž‘‹R¸‹¼h…>Œ\8…ÆÅX„‹¨Æ‡•òŒzÌ(‰-!ƒH!ˆ1‹¿XšèT+0ŽäXŽæxŽè˜Žê¸ŽäØØØâÈŽò8ôˆŽîŽ¢õ¸ü˜Ž÷¸Í¨Pý8™TñŽáÁS YÿHܘúØé‰ª8‘¹‘íx‘i?Å‘"Ù‘¡¸3’#ù‘'©‘*Ùþ,).¹();1É‘35y“«““¹“ Ñ“0 ”)”<’,e”‰”PñÄ”2é‘ •Ù$• é”Zh•Ä„•©•A”!é•GI•-É•`Y†J‰&&i“ÚX•k™mé“N1—oy–q ~"°—|Ù—~)¡|9˜|˜ÉG˜„i˜È‡˜ƒ©˜Çǘé˜Æ™~)™ÅG™}i™Ä‡™…¹|œ¹—š9|Ÿ ˜žù™¡)|£yšÁ—š¥É™ª |¬)˜¦Ùš˜ùš¿›ù‘áH)vÉ›)‹û7¿©ÂI)Å —qœh¢œ4ỉÏ9”ÎY)Ñ™”ÉIÙÕþÙÛ¹•ÑžjyÆ™!žž¬6å©›¡žåŸ!Çž+Bîi=•bŸÈ9ú‰&ýÙœñŸ.8 «ƒ ]h`@¹¥8’CGA·¡‘ 0ÚÅ@PHÊ Z¸*: D¡ú&ªM0,šJ"J*Ú¢PHû€¢y5-ºP[ ¡ûz8ꆹâ 4Dʤít q@ FÊZHDš£GÚn87£±¥¥º¡a¡cÐ¥)0º¥WzpI§Š&eê£G@¦RÊ““R§pF RJÚ•¦þc†¤`ŠTÚ @  )ošLÚ¡û ¨e…êh8g ¡ŠÊ¨ŽZúªa©–J¨pº©]#+¨‹:¤¦Š‡«¢ª>Ê:¨M¢4…׃Ÿ:¤)ª¦dBº¦z¥©º«Hú¥“"8¸ª]D¬bªð£Lzh,«Ëº@`ÚªÀƒ¡ D­Ã:”¹2Щ@©ušª˜–Ï¯A©û@­³J“‘‚ ­a£úîš©r*¯ˆöÊ–j q£*k¨¬º7œ‚®Ú@p°ø*ZZ§nª¦_J«qʩʧfš[ʤ}×Wúc±ðê¬#KPj§&;¥…Ô°úš. ±ñ:§e¶«OÚ¤uš± Bu£ðª©á¯š£ÿz¡ ÚB-q¥J»´{Š£:Ê©²(a«W{p0ë¬*¡Pû´-™¤öY¥!ùû,ZE pþ÷ H° ÁƒôråJ…#JœH±¢Å‹3jÜȱ£Á+:B†ôH²¤É“(SªLùEG–._²èx¸²¦Í›8sÞLcæÌÀ3cÀt)#0L0gÌôTŠ&ᘎOuJJeR¥X³j3ë1_º„Q*ô‹®hµj•¢¶­[¬lÕʪJ·®ÝƒgÄèÝË·¯”&M >rå†+V|ܸ¢WŠ”¾Å ‰L¹òä¾fæÞÝÌYjÞÊ¥<©ÂE Œ6l¨aãEŒZ@ó½,ÆJ–ÃOd÷¥½7sçßÀU~Ö½—‹Œ2`˜0¡Ú‹Ð[Ä ~Yñë7r— Ùwðïà7þß¾%Æ)7Lœ€Ñà Zd¸h±E÷ä*7r`é’Åðvî˜iÞ€F4qÇu±År-¸C ,ô ×ƒ±É6™7d¡W7ìðozyWàˆ$þôŸ^åiƒ  C ŠÁ -È`_mnØá‡Ý Xâ¨[1Äà ˙†B .Àpt a¡øéÇŸÛ(Ff©¤ä—ß}V‘qЙPêÁÈÂs/TdvOlÄ¥—`ÖÉ™˜%' 'ôÚ ^dnÍiç¡vá‰(A†.ê¨NŠ>Ú裔Ö©£“VªéI—.šé¦ vÔ)¢Ÿ†j*F£ZꩬN”ªþ«¶*kB¯Ö문T+˜·æšë®_öêë¬À)ì°­ûã±Èžªl‰Ì6ê³$F+­¦_!„¦l9v-²ÔŽhí·”†[à¸ä:j.見躶ë®ð†'ï¼`Ö Þ½ø©ïwüö[â¿Á,ðˆgðÁ&üÛ ‡çpgG¦^šVl1ps¦ñÆu¼ÙÇ o&ò]$—œ(Æ•¦¬2]'ÛåòËTÅ\×Ì4{Æ2¥8眓ÍtõìóM@W%ôЖî,©,]"M¯Ò˜2M§Ó_MÕÑT[µ§Rg]õÖ¤víõVO…õØ$•-ÕÙh‹ ¶ªb·M`¶ÛVÚ­r#ü6þ¬qç-ñÞ:aPÛ~£ xN?|@á…[¤6DbŒaGŒ7NÑã a€‚Ú·å¿až ž7 zp¢#DzéGü çç«›|8NF »T(žQåµ+ÔúA¹ì#ddEªçí7<TQFVxÑÅÔÎß5¼AE ¾ñ°‡p^dÁ}÷u}_PøãëN} Xd†:¬Ï~Uî”»îÆ Žp=Š„!}úÛßTú7ÿ` €&x„* €"ÙÛžW&†Í/ ž0@ tN"VCó6X3èÙ„t¨B X…˜à"b  æÂš¡þ?Á°\à°H W¸CHõp%b¸ÂBp ìCU`N*’=+$°‰6a @,P?Œ@ üA :°N}êãÅP„ÍLn" °‚€  &r¿üÉÑ3EhÀo>° xà%(Árd 8~ñ)1CÙ™`aB¸Á"ÙRÒ@ ÉÞ.‰É“Œa“¿¹2à” ¨L@ƒˆtÁ Ll¥J^ÉIÎ\aø@ly!Tx;<,;ƒ\f`”&8¡D ÉÂ3¡˜›Ù¶xÍeF’"\jŠ0Ã(™ß\S å\& Ð)‹3àmIþs#g2@|`[¤§î‰Oá°ÁWþð7R|’úÉ—íò©þß|ë??¯Ñ×þô‰Û}S_®á'ê™/ûÌ×~óˆÏ}ù1R}ó¿¿õñ¾çwOÞCäúà—}ê·}ä×~«wÈ—Ò·ìÇ{î·þWQé7ëÇ}è|hÜsj‘@†÷("˜G€Q»X‚á‚1{.X€x€ˆn+Ѓ>øƒ@„B„1@ç'nC˜„J„Ex8nK…JØ„ñ„H(…XH„Fh‚P˜…^¸Tx‚\x…_ˆ…aHVne˜…gH{;xnkh†[ø†æ‡R؆$x„jh‡Kˆ‡¾7†{ȇIè‡ÿ§‡à&ˆþS8‡,8mˆ8ˆŠÈƒ(„„z†øm‘(‰‡—¨…NX‰Þ¶‰œX…žØm øƒ“˜iȨú§2x4X6ˆ‚sظˆ (.‚­˜¯Øb8³8~)8 ˆ‹‡‚qÊøg̸Œ׌löŒÎÐøpÒ¨fÔ8ÖXØx—V¶ÚØÜøÞŽàØpâ¨cä8ŽæXŽÏ·‹´¸ô8‚ò'ŒÁø‚ÅHŒµ8Š¢ˆ9Ç=ÀŠQª Œ±üˆiŒ‘™ ‰ŒÝG‘‘@É Ñ‘õ˜ )‘ ’¡‘I™i1’úþ0©{ )‹i’5‰±’(Ñ’%Á‘1X’°x’6 ‘89(yƒ¡”*]¤xß÷–ûú÷qÁKä‚×… øÛãßßRþD«ü5¤_€Rä_- ˆà‚¯‰Á`‚Û=(¡ANH‘‚JXa†aÈá‚~è‡"b‰‘ˆâ~'®XŠ.¾×bŒÁHcw3ÒhãÅåãŽ<~ç`Iäc>ºhä‘s%¹â’LÆ7d”ìEHåqSF å•I9‰â–\²äe‰`†©Ò˜"–i¦Eh~¨æšfÉä›pF4†b\ê¤^޵É!|>äg†€Ú —…ŠÐ &ª(…réè£1:ᤔ d©„˜fºéƒRú)ƒ¡>:ê‚¥*z*‚©ºjþ­ú*±ò9k€µÖy+¹Â¹ë~½®ù+~Áš9,|ņyì{Ér¹ìzÍ^ù¬wÑR9mwÕFy­qÙ2¹mqÝù­ká9nkåyîA,•.ëtÅJ½{c¼‰aIÙãüx„GÐË’ž™*…/AF`€°äoŒ dqÄJ»X±@€ûlQÇ+~¼OÃ`l&_heÂbFúË-;,ÅÆ¡Œ¢Ê ï“sÆËìÍ+À>"à´U-‘Ï% À@-»|D ‰13Ò‘Ù¼^Ð`ÀÙGTÁuDV|ÑØI‹íË °ÏÃäÁþuá…pŸ)ww OðDBWñA \Xà³9¸qFPÄG°O0þÁDoAyœŠq„XPÁ>T˜0µ¢.‘Ê`A ˆAð#”P -DÔŵۑÊT€!`€E#ˆB'¼À¶W(?‘ÊûÜ!”P½ ²SÆG{/èäÆuàÁ!˜ð:úYÁ½ûMÁ_Üü ØGB˜à9^òø·(ÿ¹f~)D@¤ojcàû d‚€€!è™,¨Á‚€¯ &šãHXœaOTÒ [¸3$KVCh¸¨^Ékþíã¡ a¨¥¯ QSü’xÂz-QˆMì×yE¥áˆXžÌ,V*‰dš" «(11¶Œ3c Ѩâx±†`L“,®øF8"jŽu´ã•¬0ä‘)Ñþlj¡ ƒ¬# Ov†ž±¢!¹ääMr^ÀÂ%c†2l2JúËÂ'™†0ì`”’’¤"‰ÂÊœÅma€ÉŠP… $a ®4ºÀ+\¡3úãÂø’#XÁ [àBÂpÌ/À¤‘yÜG*¦IÍjZóš©6©Y†ã•² FHF– mj3jà%ºÙ™õÉB˜[ð‚0¹…a"ÁmVþèB'ÁàÈnóŸÖ”@Sqd >xÀºÐi tš]ÀÁ_lc1Œ 1À6ªœà»Óº Òà ‘uè@úÏ1Tá¥U‚ŠP%Pó¡\DBA}p&µÊÕ©RYGÊ›RŒÙ³hÓªE‹DÌ–ZÌV‰c­Ý¶vóªÅ›,Ù¿€GÔK˜­˜¹2¸Hy+£ðY¾Žïž5ãW°åË€#Æ«…®ç*š!kNË—2æÓ¨ŽÎËw‹Œ2ºŒ½ÚléÊ©sëöX{-í´]¤lîMz2îÝÈ“O#‰óçУKŸ.ÝG 0dPßν;’§R¤þ(O!q1RºT‘ò[LgØtãJ>oÛxªTåóç?«_"åûýˆ&ô~&ˆ t`ƒffV„=Há…ªMˆ¡…vȃrèáˆ'¡ˆ$¦¸‘‰¢¨â‹±Ø ‹0Ö8˜†ÒhãŽɨ Ž<) )$Dhä‘6&)à’LÂèd€PF©â”ýUi%‰Xê§å–v™ß—`b(fyd–Iá™ä¥©&„lŽçæ› Æ©ÜœthgrxæàžÈõé§~€î&è åªÛ¡ˆŽ§hnŒ6šÜ£©E*©nÌ9×!xâ]:"¥¨Yêij ž&ꨧ•ŠÙ©¨^¦êeþ¬¶*Ø«–Å*«„bthë­dÑ*Ø®¼Šåk`À{Ô°€klQÈþ¥ì²C5KÖ³Ð~ˆcˆ²X­€ÒŽEí¶e]{b¶‚K¨¸-’knÝŠõíº¼¡;£ºð&*ïôÖëè½Eæ«ï¤ü*éï¿»µÖ»cd0D?˜„pÂ1Öxô0ÄHI|,st1Æ-¬ÐPìñÀ /¨±QF`€gôqÊ ‰œPË”óE3ÓlÞÊEÀÐHÑ0F=ûlÍ =tÎGÀ€4ÊJÿ•)˜9=´ËUHm§U«œëeBïó4ÔGX”tØCMTËûÀÖ4‘6Ek³ÍôþAp tvÉ^ûG5Ûº=TÙ`ÀâGTqtDy‡½·ANG0À>ŒQœ1QäUO^ÐLÙNüÀà ®´èQ!À>üpƒ¬>8áÇÞ’c`AûPdÐÁçj¤Q.ð½ ÏX`Áb”üÃ%˜PB I1†á•D}õc[öCñƒG|Â%| ?Db<‘zOYßX`G a>°ô÷¼‰H¡ cúÚ'À QP0@8`v‡4P"¹ÂSÁ°p 9¸Àöaƒ tÐä!³uá”0x Œ2 ý} þ@œˆš †3P(7Œ–õXÂÑ"¨ˆpÐg¾$Zp7%F"h†4XñŠšbxøÅ•œÐ$C,âË8¼%î&„4´!ÍèFÝÀñ>$œ£JÎè‘J¯†9ÔcGø8Hÿ™€>$JÙóM Š W ½éE2^“ì—¶.‰Æ:êwœ¼#;â:Ÿòd› %GN¹‘R¦ìj›¢¢*1©+PÎRˆž\”-oy£L ,•¼e.!µË`&„•qeÊ)³b³GÃLÍj8Íg Ó—Š ¥kV„™‰Á"˜ô=Á›æ~´0ÃT¤A N@§»²“1$A çþƒ’p¾l…žJ!CCÈ`^Á a¨Ž„(4d L˜‚P„*Є¢Uؼ¬d y”'D^rŸ’šô>hP‚Ô 5¨Tz÷ÃIg:Ó3tÁ ,Uƒ®Ð3ØÔ Hà'Â0†)&ÁQÃQ©@™ª¡'Ì©¢H%BS“¦C(‚æ†@“J¡ª`Mtp…0\A5м`X Fjа)Á@¸€ð-Œä«©Ø¦pÈ8U„€µ¤J‚b;¯ö>W™f`­€èà²g½¬ 6àƒ"Á9EHúš 8 @8ÂLàyÈ¢g ,þlDÓ¹&¡±Ž=lxvû„ðT! =èB¤€†.`Á V¨Bâš„ºµ I Âg•Ë&4 æ+ênC*[ÃÒV·‡àjØ)Œ=fH`e@á4ˆá¹nàYÝ÷¾— èÂSÎPÔsv7!ß/X‹*Sõ–7¦æSï}Þ 1@ OhBÞk>)Ô7 QpÆÿݧ†Ýü¯wLSÀVµ¨­ME?œ`À2 è¹î €'T¸ºN€4Üß¿3Ä"NI§H¬<0»èÝG§d1Fò:!‚L~Bo¯kÝë^7¿Ž|nÉÒß'Lx»CÈ"l]'´¯¨ÕMsþ𝛾ÝBáº[&K‡g¼ÈªÏ jfBAú×'Ø7ì Ì;á¡Î÷ƒ€Ýî‰Gçâ·Þxƒ<þï‘÷æäox>#€÷¼â7/ôÊäò( ½’GOz´OÝï©Ï¼5[?tÓõI—ý3i_úίòåc=ïåîûÏcDõÆ¿äð]ÿuØç>ø’_¾Ïm?Ü›ùÂç=õþb}«ëž#WOþDTøã7òõ-éùÙîüëŸýc=EÄúó7?ýÜ/J÷;‚ý^¯àÿ€8€X€X€¨v€ ¸€ H€ Ø€xjXø€˜ ˆØhj‚È"‚$X‚x‚(˜*¸‚H.è-ƒ8ƒ4È€6xƒ ˜ƒ:h€<؃Xj@ø‚·‡uòç@ gt‡û°~±à÷~Oh„χ„æÇ‚ç„¡~ú×~Þ}Ú&b8†dX†cØÊf†jX†h˜lkø†"ІÈ‡k(‡ÇF‡jh‡Æ†‡f¨‡ÅƇl˜†€x†‚8ˆqXˆþƒè‡Äfˆb¨ˆÃƈ‡è†ŒèˆÂ‰”l–ˆˆ€x‰À–‰’hˆ—~G(…¾‡Y8\HPAãæ9Eh0Š1‹W¸¶¸„,a‹Zx‹‘‹´ˆÀx–tuÃ…qŒã×Ê‹»˜ªX͈Œ1TU²Œ a÷‡‹è÷ŒMØ…ÁIX‹¬Ò¨ AŽ}ÇÜØ‹Á‹â8ìHŒQíX}q7îGühðXÿèxCq7Y–4ý ‰;÷*¢-&ÕIh`@„5[&Õ„%…@ 7§s‘¹‘Õ÷‘ L )’0Mpþ’øÑ‘&A2h . “(y}97§srOxR)9=$ “ûЧãx(Ù‘R9“BiÄ9ûpO‰39•A‘G€•Z `4•’[`7§–Üg”*‰Li•e –; ~]™ x™ c9hЖü÷‘;™ZKI“biy2•r‰ @—“c±EŒ^™©5™ûP™—9b9— ‘˜†©–$5SÙ ™Z©`šyÉv'%”/ù^ †)›©‰’jIµé™YI”Œy”h¹›]¹¹œù—&9u©™§³œ™óu¡Yœ!”§SšmýYU©Éb“hàš‰›E™’XI‘Up›û€•Eyœ¼iDI‘b`™Y¹•·G˜{—Æ)™”IŸšÇy œËéŸÔ  1–¥¹–Ü)šá“rã”Dùš ê ÿy”øñ—H ž¹ z‚ û`šiO@Ÿcɘ{™ŸÆé‘pI–ö9~Yq‘üè¢f™¢Ö9RÓÉ9w™œq‰Ÿ&–i×Ižš’‹©•Ì™˜ºiœåB–™#˜‹yzª¢1zpI’‰™Üi´)7G ¥Í‰¡Äérc•û“1y’eêx7:i¡\š¡?¹¥´)œZ.4)I“Lé” ‰£!ùû,l6ýnþ÷ #° Áƒ LÈpߘ)S:lHñ ÄŠ3jÜȱ£Ç ;ž1#Ōɓ(Sªö∖ˆß‰(Z¨â},¶á‹ôÅ(£‚4Ægãæߎ< èc{@‰ßêi$}H¦§ä’ð5iÞ“Pª'åxTViÞ•âe©%y 6èå—Üqùݘdjg&wh¦9ÝšÛµéftpj'眻ÕiÝxš¦çt|ö)_˜ *¨cJg行%Ý¢Œæå(oFzפ»Uj©Xú!Á„ÿ¸i”„&¨é¨`aªÛ©¨¥ªiþ¬¶úÓ«¥Å*kO´’fë­;å:Ú®¼‚ä«cÀûX©Êòœ±ß ÛXÊfǬšÈ¸…ËNKí§ nQF¶Ú¾Y­€[€n¸tŽÛŸ·ç¢›§ºø±+­»ïrkZ@y-½éÚ[@üT,¿ 9[Ñfðpݾëf0EGp= ÜðA7dDˆqO_\PÆ mÌñÄ;…,2‰ð.fpLÅ!©,2É ½óÉ?€Çðʈ¶œ—É€Rôì‘ÍãŒÎEÃ,1Å1ݰÓAm´ÔU(|ÞÏ@7*4^/üÏÖ#qÄ×í†Ö• €@`÷Iod5þÁpTDÌ °OÇüðÃTg´7¿} ô÷UD°OM\àƒ Uƒív~ûñvD?ˆp„ûð0ÀÀQ¨›‹í/ib‘AT°UüƒqzkÞ:¯öCûx€ÿpDe[ˆ¡Ñâô6¾YW`àƒ”Y¦¸ï¿‡%}AððCÜ`q°i±…{à‡ÿÕøÙp…DT`CGU‘üm“ßüƶõáOü»Ûæ%ÀT0/ØÁù,`?+Ü` Þt0ÀÁ}` ?Ø@ˆ÷ûùÏ€´ MèBúЈN´¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~ûû߸ÀNð‚üàO¸ÂÎð†;üá¸Ä'NñŠ[üâÏxtâ RVsü£7jÈÓM:òޝúã-9ÈO.r–“Üå&W5Ê;ªò”Ã|å2oyÎ_¾ó˜§zæ­9Í5Nô†}‹­¨>ºR“îY¦#ýÔJ?ªÓ—õ¦Wý馎ºÎK­užsÝêYû×±>vª‡ì¤îºÏÓ.v¶£½èp§ÈŒš^³[zî&­»Ô1w¤&wè—îûGõ¾õJ Þ£„÷ºáéþ÷ óñp¤ÜÆgšòè¼ä!ùç$~í”î|*>ó»CÞòÏ»æ/ú¿³^õ‘½ßcŸúÙ£>ð­O®A!ùû,f3‚þ÷ 8p A²Î˜1sFÍÁ‡û Bœ8Q"Å‹1jÜȱ£Ç CŠyP¡) Sª\ÉR%Ê–0a¾ŒI3%JY$sêÜɳ§O‚gĈA"´¨Ñ£HMÊ”éÒ¦P‹"1ƒó§Õ«X³æ :4ª×®_¥!RDjX¨S«j]˶íZ®OÏ"땈»w…Ò•+•j*·€ tðZ¿†+^\x±UÄŽ#KÎÚxòNÈ–3k&,fsNÌžC‹†Xy´GЦS{.­Z#êÖ°#³Ž=ñ5íÛ‚gã&h{·o­º÷þMügpßË+×y|wòåÐAŽ}J)Õ³oíý¹öï›þãö¾ü@ñ·É›/ž¶úõßÛÇ~?»|ØôëG¿ß:¿þåü©æßŘڀþf i&¸Û‚£5èàmŠ&ᄱUÚ…¶¦¡gv˜Ú‡›…(âh$jf≡¥˜ÙŠ,næ¢e0Ƙٌ“Õhãd8J¦ã޲qÝ@2&ärD™X‘%©ä`L:æä“E¹Ø”Tºe¥bXfÉÖ–‰ué%pG*'æ˜XiØ™hþ4Õ]׿ƒeÇæœ=©9Øx`|ö¹]u :ÒŸjhHˆ¦è¢5êÖ£v$i[”VºÑ¥lqQÆ_š¢X'q[tF¨¢V×E\ *§þ%ÑS]láj‹£†Ä<­Úê­2æ*Øf±S©^¬dGp¯9mñ©²šÁú 9‘! ¨Ôò(l`Øf{Ä$eîyãVn¶R@’ºëîcíAØî€¹èÎûm½’ÝKP¾€Up ½ë ÜÅ1nÆûÀAû:{AÅà^¼d»nq²@`ÀÍGT!¯k»ü²d2ÁûhûƒHÑ‘ÈÔ’ŒíUD ïÇ7ðPÌ1­,ÉGPG@?Ôpµ¿òܲϹaÍ–E<€Ãì#@ÂWT1Cþ !÷ÌvÛ’Qà@8| †»‚Ü÷Ú·õ¦dXp?Ø’œ.Ù>à°Ï >€~Ci ,Éû\ ù>Änà رܹç‘9@h€zê~ßþ¥Û‚ýP·¿Œ Oñý¯üò„:ÿüU¬÷¤ú­Õót½«Ù_&KÓO¨̇Ïhù¦Q»ùi¢Ï ë³oU÷:™¡¾üí?þyºaBü›Ÿÿ,$½’„~Ÿ û§¿2°;÷s Ÿ¸!øIp‚„ŠàHAÉ¢ƒD`º¾·ÁAu§€%äˆG²=T­P$- Õ ‘—ÂCPE(¬!Ff¸°ê°"þÔIMìpà8]„s?ôq€…+\Á HÈÀEb¨)“̤&,ù‚©N†.|%cH‚Åx/|Á ¦êÂÄÈ„$(a c8‚¬P*TÁ OæX)…X'¡¢¦R‘Š1òˆL¤"iõ©1”ÁTŠLEư„)ÀQ aÊR!)HŽiH­œðP’I !¥ÉV&ò :ÈB°tÁ•«lÂ'ÅЄ&ŒÁ“O8àð)TÁ 9«P&8ó™Ðìå)Sù‘UâòšWÐn Ík¦Â ¾Ì¥'ÅÀJ3ôR c8fŽi)@“ º|§'“GMŒìï#^þ]6ò„&œA Rh9¹s>A gXç:›…wFÁÏ„Â/-RÏÍÔõ¥'p]Ä™O€ç;ßÙ„ŠŽ¨Ÿ½dÂAÉÉ)D PøèHgÊ„šô-ä”ÂAÊÐ(8á!ÏtÂC›0Ó›õ¨HMªR—ÊÔ¦:õ©PªT§JÕªZõªXͪV·ÊÕ®zõ«` «XÇJÖ²šõ¬hM«Z×ÊÖ¶ºõ­p«\çJ׺Úõ®xÍ«^÷Ê×¾úõ¯€ ¬`KØÂö°ˆM¬bËØÆ:ö±¬d'KÙÊZö²˜Í¬f7ËÙÎzö³  ­hGKÚÒšö´¨M­jWËÚÖºöµ°þ­lgKÛÚÚö¶¸Í­nwËÛÞúö·À ®p‡KÜâ÷¸ÈM®r—ËÜæ:÷¹Ð®t§KÝêZ÷ºØÍ®v·ËÝîz÷»à ¯xÇKÞòš÷¼èM¯z×ËÞöº÷½ð¯|çKßúÚ÷¾øÍ¯~÷Ëßþú÷¿°€LàÛV à =¨à«"¸Á¹§HìÔï„‘pH0ÌT ë„ÃѰ*œà oÂUõpN@|W“Ä(=Â⤪˜$5ÈŒ;’ã£Þx$=¶NŒ§úã ŸØÄV-ò†üa#Ù#;æHoªäGxÈR­ò‹¯üdªj™ÆL^±“›Ìe2§¸Äf–1þ–£úe‡Ç>W”ã ‘9‹™ÁYa±Ý\æ;£xÏ]Öq[0 è-k¤Ð 2¢!2eË¢ò³öl¤;;iÎVz³—Öl¦3»iÌvú²Ÿvt£êÊ–š²§žlª%»êȶ²¯ˆœ“¼E:ÃZѳU½f :0µ´=sÍ[S„×–¬–ÍìeËú²Ínö³-mfO»²Õvö>F-Øl‹àÚ”õ6¸'+îmC;ÛãV6ºÍMíus;°å~7`ã}îj§;²ôn·½Ùmw×;Ú÷†l¾ÿLë: ÏXÑõOfÚ‹ àš@Á"q¶`¸â çÆ{äÃy$øÄþâñŒ_¤ätÞÊýÜ‘•[Y$!ˆËMNòƒSdæ ¿ù®A‚s>Ãü "6DzžìšçYã;ÿÑ7Âí˜ dg6'Ôi>‘©§\#Vg9G²þòpÝÀ`ÏÌ!÷Ñ !M¤–ð± 0¯Ò¾ö…}Ûc‡;PRº³ý¨D$Dà>wµÿ]•‚üDô~04ø½š‰¿ûCèn4ì#{/)Þ y„Æ£%À×øC…Ôè/?zGró—ÛÇ"ðùзžé‰ÜüC0„Ú³~ÂVdE ß‹Þõ›/ýCЃáYR+ûÙÇéKýáyvû_"þðÚËû<¸f?ïÇ~Å—üÓ# úò[þüˆÏ=Þ'BwÁýßçq+ç&D î•÷|ûÀ|1vÖg~‡·kx„‡³gw w|ç€ ¸aé7iÒ×~|É'kôô€c`Ý÷~xl¥§}“G%uzè{ºg} ‚Q%å€à~)8ÌW‚ù‡c¯§~@Ð1€æ·ƒñ*¡öyHø~Jˆ‚¨|ýGë÷‚Í‚!}Æw{ñ7„ÔP|«w| –‚xz¡z¶w†Ú—<¢×…¸¡†e7eè…6˜{—f|Ï}f§…dgvhgxm§H2˜g00gâ7qgdgˆnøq|ˆo¸`zÇw“Xw®·‰ô§v•wy™·!ùû,j0ætþ÷ 8f @3cƘA¨Ð C‚#JXp¢Eƒ/jÜȱ£Ç Cn<³ÐŒ”’ ªT‰²å“.cÊ|9³fK)²DêÜɳ§OŽgÄE"´¨˜&H 5Ê´(ѦP£•J•)ΟX³jÝ:1èÔ¦PB‘RUÌÓ²TÏ¢z•«Û·pAz5»6m]©jïÅ™*®ß¿€ ˜/áÈ}NüÖ0ãÇ5.ŽœÕ1åË—'cîiy³gÄš?‹ì,ºtÜЦ=’NÍ:+êÖWÞ­ó5m‰²oë*fwlY}} ç=|bîâÈ÷Ù.~<ùðåÛ;÷ ]¸ô麫û¾Ž}ö³Ýþž”Þ¹öÝÜ˳>¯;½zÓìo»/:>íùô=ÛŸ??æý°õçe¶&à€ÈÚ2¦`j 6ZoáE(!ašfá…€eXÚ†úå¡h †׈Ÿ•h¢[(z¦âŠ[µ¸Ù‹0ºFaw4Öø“Œ˜å¨cO<^æã;IÙDŠdddH&)ר5é¤GKB&å”ÄU–B9Ý•\^Tåc`†Ù•—ΕifDc2¦æšµ™Ø›p^…&Õ9aytê)'b}ÖùçaÂ9(a…®yh`‰š¹ha[ê‰!šÉ5æ£Q©¤RŠÜ^xÁi`˜úµE2ŒÚ)fIü$þ©ª*¢§ˆ!q„O'Å*뉴¦œÁºëi½v„GܺөºËU©€c`°l\8û´M{:U‘FpÚÆXìaL+…² YÊ%·MkkHîb o·ÓRîGõNyo¼ÈZ«Ú¦åÚhPÅ“[0Vÿ^ä-õëdÄ@€Q»Yœ$ÆÁ ‹ÄXPG"Iò>L@E47ñC7dq®x?¬ØÏAÁ!üÐnÐBóD²F@ Áì#@W uÔµí±Ïb {D×0 vH$—-P Q7ðëöþÛ}‡ftƒHdÑnž|K-6a8\1ÚÞ‰g‰#ä‘K¶8”WnÑÌ®f®¹Dœãêùç…ÎÙè¤d:O1ÿ¸úN­ëøºN±×8ûã©?ɧ,häNååÊb†ïÝN/ïÄK¥,i$?ð¢î»ñO;ì<èÐ/(}îÔëÝüõ›g!òàŸ¹ûðåc¿{ïé³)¾†²¨Ñ¾û»[?¿rï¸}êÝ7|œù#ÑþH×?¯Ùo~lÛÛGšØ¤& ƒBb¢„$LÐnLHBlU„"$áUU0‚”p+tÁ V¸®@.há &T¡^2@Í­ä†8Ì¡w8 ªÄnþQBŒ`„.a a°‚4ˆ+T UèB¼0šÐ VÐBVBžÿ$`”ÇHÆ2šÑ$b8ÃÄp3aEXB(ð,`ÑÀ‚°² ¡ƒ@°Á |C+ ¡ 60|àÀ@ a£¿I3ZÒŒcÂR¡IMbr ‚,à,ø ]ЂÀÇ !!EøÁ°€B$ØÊ 6 ’U…4rR’“tHA’É3ìC gÐdDÐ.¡ ¶òXo>v$,A h@ƒ=È„n2¡ U8d¼É„&$3˜®!ËœÀFô᪠O 'ÐI˜LJ RþæO àM¤Ðó0i‚;³Ïrþó M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8Í©NwÊÓžúô§@ ªP‡JÔ¢õ¨HMªR—ÊÔ¦:õ©PªT§JÕªZõªXͪV·ÊÕ®zõ«` «XÇJÖ²šõ¬hM«Z×ÊÖ¶ºõ­p«\çJ׺Úõ®xÍ«^÷Ê×¾úõ¯€ ¬`KØÂö°ˆM¬bËØÆ:ö±¬d'KÙÊZö²˜Í¬f7ËÙÎzö³  ­hGKÚÒšö´¨M­jWËÚÖºöµ°q­lgKÛÚÚö¶¸Í­nwËÛÞúö·À ®p‡KÜâ÷¸ÈM®r—ËÜæ:÷¹Ð®t§KÝêZ÷ºØÍ®v·‹ÕKS©Þ] QÃËTò.Õ¼àõnyÕ{^ö¦÷’ë…o{åû^KÆ×¾óÅo}Íx_þæ×¿û-c@!ù(û,j,ðxþ÷ Hpà&Lš\Ȱ¡Ã‡#JœH±¢Å‹3jœÈ¤È!E˜lI²¤É“(S¦db„ˆK"Fž¨œI³¦Í›3Ç#eŒ¬4º‘¨F£8“*]ºðŒ™§RžJ]2E̘1b¦,‘Ê•kÔ®`Ãvý*¶ìXYLÓªM*¦-’¶p="Iˆ„H¸xñ¾ÍË·oÞ½~畲¶°a¥S„ù¨˜ðáÇ#«BD1‘’3kÞœrÊΠC‹Mº´éÓ¨S«^ͺµë×°cËžM»¶íÛ¸sëÞÍ»·ïßÀƒ N¼¸ñãÈ“+_μ¹óçУKŸN½ºõëØ³kßν»÷ïàÃþ‹O¾¼ùóèÓ«_Ͼ½û÷ðãËŸO¿¾ýûøóëßÏ¿¿ÿÿ(à€hà&¨à‚ 6èàƒF(á„î%AakHqájc˜Ć©aÀ‚hš0F‰&––â´èbŠRü £‹dxãhÐh㎢½# €U°Häf/*¹äc`À”G<áä“iÁGŠÄXP–…0ì@?tqb©–üÀÃPÀr®%Æh`AûPÅ}¦Äì3‚#þ€h¢KU „@XpÄ>Ü@iZtCb\ñéa8 qꪬ¶êê«þ°Æ*무Öjë­¸æªë®¼öêë¯À+ì°Äkìh7\`AÁ:>\ñ‚D€èÜàCœûHa…GñC ”ÐA "˜P·aÄHTÑD8PÁDüà€>ÌEªN nGààÁxàYá Td™%qÄ]]Õ¡Åb$¡ÄUShl•ÅW‰ñE_„ñÄ·]|ëÅ#[†Ž˜ÊÌ©ŒAóÍ8ç|3gŒ†Î©<•ÆÌW™¡ÄÑJ$‘Ä>mÆU-wA=aE)_Mt*Jáõ×`‡-¶Ø•ÍÄØM0…×=a„b–5!ÅA ¥Ý°V !wþÙXøàCr«==©fˆ±DL8!…ÜOô$† QŽPPPxM Qka?ÁyQÀEÙr§‘Ýf·Žú­j`Õ¡Ý ôxBkÛî8幎ٱÀ/üðÄoüñÈ'¯üòÌ7ïüóÐG/ýôÔWoýõØg¯ýöÜwïý÷à‡/þøä—oþù觯þúì·ïþûðÇ/ÿüô×oÿýøç¯ÿþü÷ïÿÿ  HÀð€L ÈÀ:ðŒ 'HÁ Zð‚Ì 7ÈÁzðƒ ¡GHšð„(L¡ WȺð…0Œ¡ gHÃÚð†8Ì¡wÈÃúð‡þ@ ¢‡HÄ"ñˆHL¢—ÈÄ&:ñ‰PŒ¢§HÅ*ZñŠXÌ¢·ÈÅ.zñ‹ÛṲ́p3â‰1d¤™Řƙ­hmäÚðÎÇ7ꬎsdcóÇ= Ž~  ÕÈÇ;xƒtc!s†Ç?ꑎì#$ùHEFÒ“Dd%åÆNªgÎeóDÉ @`Uà†rר’Z5Á0£IQÒ>pØpƒïÔµ`dr$Ñ ltÉr#KoˆgÌ1´r£Æ&]šû^)¼yôêÙ´‚}ßöîšåßà`»ùsù}@ÐnbH B{HP@!ùû,¶bþ÷ H° @1*T˜ʃ Ž1#å¡„1€$Ê‹3 ``ŒÇ„ C>I²#J#²$)æ$A˜TdÉ À“d’'ÅHÑc‹/+{ò4 Åæ>)1bÈØÒE˜…3{¨âdà–¢R¤€‘Á%•¬Z¥Ý'#F¡f¦¦2k­V®û¾n:¶ìܺJ0@øˆ#D¥R•áeî_‚,# iæˆå#@b¼ˆJÕ¯cºGNç$Ûn©R%ÍgÇ+‰‚D@€G~Õ"Tì„c˜ Ä@“®w÷þ]wŒ@)c8º¨ú©oæÍ@fr$ Á¨Ï¯?~xF{Á-Åg\¿Ï7û…!ùû,Ç_#Ô÷ H° Á}b*\È0 †#Ž1s„aªˆ @eaª‹  ``LǃA†8’$G”)W² @Ó€˜“S~”¹¯eÍ$O êܹÒgM)8÷ %ŠÑhÍ*N.e ÑéÑ J§ªdhõiT­T:%` ì)OÀ†I3Â’cŒ™‹DíZ‚#'H‰@ÓIE©v¥d¬`„  Éxá˜*Hz €IT`›&܇dÌÆ—˜µb4C’"i_\ÊS̃DYóD©zël¶o !ùû,Ý_íSíH° Á}bL%P¡Â$P”BQ¢Á1fŽXdÈÐ"Á#D‘ȱ£Ç# Œ©°¤I‰(SŠl鲡Ø)Ű,XóåAœ)“<1ØÓgA )¥ìÜWÔè@¤)«8!ØÔ)Ô¤C™Vµù4€×¯^ HÕšjŒ1cÒ¸< 6pHy¢]1j9² a@ÊŒG#)+eÌ™4fÍÜ 0AL¯Oð#å C3…oV8‰€4R5«Õì®Ç0IxHL¦Luf ZŠ–3°pÈ™³ R<íPñG&GÒH<ì±àã!ùû,î_ÈSíH°`A1îK%P¡Á$P”BQ"Á1fŽXdÈÐâ¾#D‘ȱ£Ä# Œ©°¤I…(SŠlé²a˜)Ű,XóeAœ)“<1ØÓç@ )¥h$XÔ(R™N˜6µù1€Õ«I‡.œjó©UU¢r5‰”€³G¤<Û0f„)Í9BIÓ›&ˆ‰`õIžEoV8‰€þJXÓà& €IÔ&ÿ3Pʘ„™6ºÏÌO&G.Óô8ðŒiƒ!ùû,ý]Ï÷ HP ˜‚"LEáÀTŽ1#%b*ˆ@Å¡”00æˆÂ‹ 0’cB”)®di@LÇ‚0cœI3Éœ91äISÊMAuî#J³Š“‡I+ dZô群©¤`¤Úô)V­™0@öˆ”'_QÎŒ0`¤™#p I{qå1X>ABnª#ŽDA" @“PƒJÝ7†ÉÁ#@`òiÔ¡cJ³Ñ¥eˆ0µ 4³“É‘4}1†Jð iœ©³Fœ­töÀ€!ùû, ]¥Sí³¯ ÁƒS¥J¡C…cÌHqxPáH¢P,(EŠ‘ÆÙh1ÈŒ-î3i@ŒF„*M‚Lòfª‘YJy™ð¦A™ª81¨ò ÎšûŠþd)4©Ï•& ˜zDÊ“’" i¦à‘#HJNÁä$oV8‰€pöÃDÌM 0ÚóÈ…RÆ`D™p_W§G˜I³Ñé™Ã!ù!û, ]§SíÛ'f ÁƒS L¡C…©Æ˜‘âð Ä#D©8PŠG# Œ9Âñb€+š4 ç%S„4 r›3ÄçP©þ‹ª=wîæÖyC,#}¼û›h†˜ÏMD9ó÷ø¯&ˆBì–߀M'„@Ú ± þ$EAÇF|º=ôàFj”aƒw¥âa*R|è¡D¡"¦èaˆ*¶èâŠ/Ƙbˆâ%"‹î×IȈ£Œ1þ¤‹4Öh×)Æ·™fhø8¤“O¾X¤‘t!™dfœ¤QZÙ¥ŠSR)——I69$—_‚˜&˜©ˆYå‡h~g—sF¦›o‘¹¦š].W„pî‰$žc*(ŒQb¹¢‡ÞIhZz®YgЧ1¶Ü€Nzf›æih£‰±„‡éñ j§ž2*(ŽetÑEþ.2G*g¦®Ê)ª~jk*à¹d†ŠJpfŸ¨µîé(®`Ešfˆ¢¹ôÒ-.Ik±’ÞŠ¬YÊÊ™ŠNЦB˜‹J¬¦«±Ö^›ì¸ÕöÔm³v £¹geKg*ÍvaÆTíº{,¼VÉ›ïK-Áú¤¦[–ËïUþüás[€1¯¾\U›Æ†Pž*1VRtìñÇ ‡,òÈ™$§¬²È{E±‹f€Q†q>’”îªÙ2V/«° UxèÅWt‘Ê\¥ò” ·H°“;#Œ.6ƒ”Ê:dõSݦò‘ÃNç¼oÔBõü¡I‹ƒ:dÑÅYè@¬Íþ6ÄdO<µþÅ.}¸E qáa:\Qu×lâ7Sf{ˆ´ ?Ö‚{[x³7ã¬øâJ5î¡Í‘î6Ür§¢F`Ø­¹Æœ'åùà’¹ðBdlPƒ :ÌÞwÅ©|qöÎÛzç{éEd2Ì`‚ 5ÔÀA .Dï‚ Ç­µª<¼ëÅËøgà` 1há 1l±Eô\†²T@S2P*X!&€ŽP@ =ÈÚö…/ðÀ3p•1q¨P•xn hüLL´4À€Ð0† Ï h Âb@ƒ¬Ý`ðfLÀƒNîg5MÉMÑ8}/kO 0À 5ìQ* h€…2T!È‚‡´`‚À.˜ƒÊêI<çÐ!!ÿä ÖÚ†#^! W©HÀÊÐÀ ÊBnગ΀ åô&Võj’×k b»õ…è P ÂØ-3DPÂ3é!ëZÀ(ÂvŒ:–Á:¸Aþ (ø¼}œ€ŒMD `õc-áT®üÐn`ë`Ìd¶#«€…” 8€Á›_èÈP¹œøÁ‡L”çm±”tš=` Ëì]ÒÇPž2 ¨|H‚ä>HE°„ †.`Á C+]z°€MÓ›t§q†p‰ãã¨O@Íp¡ Á…ÁO‰`Z­í@_X  j«ƒã:×e*´"½ó1‘Ø-Ý´ƒ a hÆ‚ºÀg«½AÜz†kc["/{Ö­ å‘èEfŒM$  ”­„1ÄòÙ›7†9ƒ+œa¥Ä‘‚vïl«ó4”ݾk†Èˆqzê›Áþ^pÉ¥ÊÓC÷PÜÖ};È>{)2ƒÉdPñ‰$l y”Ý ¦'ÌEš ã(àÌ"4Ýë^éM{XÂõølЩɳØr?ua’/øÖ{ž"ðÌ@AHCz3á·ÙYsﯷqØœá T‘É©><ma’Ô»;ô¸®@.€!½’‰"3ƒKš] ­z5d?†.¸ÍÚ"º¯º"“©xa,±ÙžÝ?"Âq\?û› *`"J3ë»l@÷”|'>Ñ|÷z"Z¶ â^Ð9Í ÙE†ˆræÁÈ- Hsñ,gÑddEõE½òFÙEè¯fízŸùÅþHÆèIyÈ@ô*ðƒ*°[!èŸ]äEf&Jv>h¨±\U ÑBzih{»cEFYWç·"¢ó6q“i ãÅnu4Ñ+­Ww©'"ê"8=qÆD·h,ÐmX$”38=y aUÐ26%Ñ*—÷CAt© 8D40MJÄhµ"#h9î•"±„/*XsDK£28=D$R† ¢Å,ðy³Õ6X:*RH‡Ô>CH„A1ͧ€^ãEb=1Fû€â·$wG$0µ6 Z6q"òFïâ…‘0Ð1K¹GNe˜ƒqÀA/pþ4à?Fãû¤à¶sZv¸0W(z¸‡Ý£…±„$’3IKôÐGÛ4F0`ÀiÀ7P4Îs`rp_ðб(óHªÒ"}Ø"9 eP5Àn&;?SÐà €¸³ˆ;=0‰Fþ‘†ê#†’`LÀ 0ˆ SððÆØpÆh0‡5 ‡°’^À^Po1)“û@GcP”I©†ûàpûðph”0pQA°°-•€˜0™‡Xù…˜"&BJù°Q€Š?ÀO´BàŠéWØå´[]H„V™R°<²P ˜ËŠ`¹Á*–ðARà–;Ð1VpER`~²I2w9“yù!?&B‰€§h ’Iðà®Æš®7W`5]trçt›Ù²LŒQ`VP9"þ’þ¡i€cõ“ûŠþÁ—@BðÀZ8– ‡: íó-ö†•ÊòYC`Ž8vÀ þa7À¿Iž ¨pŽJéþá?ÀXaP?½‚H÷é`ÐaÀ)?¦V`ð¤³x"b‡©2gA@z¥È @`|IÖá@`>p@О51E:`d)•r57`n>˜8wÒôNÿ¡€TC@ ‘T«DY©PЊŒ×4W‰VpZ¿“5tG_HJ+WÐO(B?85[ðe\ÇÁ3P8 áP«„‚c]`Úþ¦µqšP aZa qCy0桚‘‰5¢Ž‡…Ù¢˜"à]@£P,F’à?У8ÐlÓ6þy¡±RŠ5Fú¨S£(jØŸY£I)‚óQ‡‚Gz§Ùá9VðPe µ¤¼µ!—³†‘Gº7~¢á¤F-¢e-¦eP{/Tá9’ fjøû`®SŸ7(e`==Pñ>×sš›=ƒJ—EWðeY`ð@ðD?€> `°ŸÁ¬j’…ÚlðJí ]{dÚ6$;×-ITVp=Ph„ms®ph Û³ãwHòþ>/¹l“Óx±@²s`C ð9 HDP¦V Pš;÷ P!B8›53k¯Ý3XVz3 Ê#œ¤7V |ÀZ>à›'›°&`JwT@"ï#³û°@j{‰\´Yæ"ì´BPzÑs%Oá¹<~èº!0'ÐhZ×h2à!rYÓ¨%7O;‘]‚%Ú"µS`">pO[«…8`ùT¸%0;„.A#k—vA`_¦ A•â'˜¢"šáJµñ!eÚg@à<€A%!IdfGÈ Q$1Ó‚vŽ;­þû$™Ñ#R°`"òD!yA€Q²v—Ø`®&PkæfPåL²ãKúójNg$+­››²r™Ó‚`C°–§ùX "Ѫ†€¾®Bë¾w‡´Ï"f°¸Y°RAK¦õŒ mÖK-O,Œ‘E&"Aðé(?P¤ÛYAPñÙ+%°P[°Ó[`h[²ûBm+®*e«³«+vº­@B·ÿ‹ŠAA€]?€hp5¤ªM7€½ˆ¤¡aº4S 1•³´Õ»N;KàpRPZÕöÁ8¢«æE1.DPdJ’ `ŽþWw$÷&žUâˆ5ðËó•4 eÒ(i@<™!VµêÃû‹(i@Z– ó!X–˜¢ZOãc>PoV¬)yÃ6ÀíÖV`"8`®‡ök4`@!â°:°ÍÆÁûð¡Gà][±²Š(SvÆÌ¼“ ‡)-*_›`D85>PËÐ<¸sZž‹À¦{6à+´|ïÒ®Y“‹*",ìDJÇ²Ì Ð$hSðÌO´HÌd„,cЙcYD`}vfð+]° [¦+Œ=À¾ 5vé¬Q)!`R‡KÏÛ–™šV™1]ŠþF¦Ì Ò|Ž>cY”°\ê!a@¼ì{È5`º3€i´„?¦GÌ(E0 fÏ<OäÇHI’?pIâ¹T`&càV®Ç˾$ ÊV@8õ³/—? ²ðPSð=\+§ %ŒÒ¤Ç¦ÌBУì™] ˜<DO}ùcMeýN¸“ÖÓºiVo-²Cp±§¦P{½]ÆS ëY˜AÐnÀ «¬ –ŸŸë›ç›·:º<6`4ÇbZJ8Ÿ [,`l¹¶!NʉÞ+§išA#©³…P"þ oÀØPÖä¶¥&ð„û ÜQÁ<—@€<ïŽz›Ñaî}Á#ÈýF \¹ûðš,]X!€M`M¶~&ð‘_:ÐûÐ: T± /P°ìM¢óV€‘E°I@\`n ÝA?pQ‹aÀÕ à`Ü­>P0 Átˆ•fœ¡G¾²;X¯hm?¦uÛ¢%I8`öeæ6c°Óæû2œ°°fô ƒ¡¦O7PËK‚-|@Ü×% +a,†Ùw¥F + vÉh±ŒO¢EEšTéR¦M“¤HƒLƒêp00ˆÊ.8ÑtÁ’Å2øÐae‡‰+]¼xqâD:®`ÌÈG–}7t¨xŒ,]Ðøà†ƒ|Ààˆ0e´Q…ÆÜ©.%Š(‚³ŸêóÈ ¨ £-^xËQø! 7Êͽ⪠<ØgŒu´V[]ôBÆÉ²Ðá9ŸÊ°$‘:¸à!š ž+CÀX( ,nÀ Nh)ƒØ0° .È`¨[Ç%W9粋¨.:øà‚ pè „ „Pâ \ˆ,+´‹†^Šïûjè!>8BÂà_Ë…8bÅ„)Æž|º¢ƒ&ŠB°@ˆ%<•h€Ëd*òápKè€vOÔ B0Þ„Vbw^*Æeþ*(=ðèBC²‡hxK†\8A´/¬Øô° B(aƒ/²ˆÙ ä˜) øxF»E¼.*ªŒxA!¹Aø€# hˆÁO6H:‡ xÀ‚y¸Aî8à lÀÉ'.¶Ø‚‹œ“Ê.£ƒ{é`r÷!€°³’l/„rîZâØ‚³Ú`W»t`M¨Bþr±„Táw]z‚Óª0¼8IfÀ\•ÈñÆ#’[èöøÀ V( `(à€ l‘‹<¸Âþ’²…-ZNÛ¶@€ñƒ>1Øb6=„Á =pãõؘìà€; Á@0Å£]À¤á õ®äîÙ_tg)…XÈY40o,òÆ…Xá=øB0£ pr§\J 5V=BaÀh à€F´§@­¼Â`†ÐRD°A|þR…-È ;Ø%P°p^5. 8(ÍÌ LTV“) Ê.`AZ`|ÛÛË f°dr#!@bLfÀtv­u\ÛAÅ5b±ËuµPzÒ„s!Ä3×»Ù»ñ¥ <êê>ÐðĆ+2Êy@FþýÞêžä~íù™xÕÀY=èd`‚"zqQü€'åÆƒda{¸<`#,hÍ$©Œ²®úr‡ÀAãþ’›/oà97+LÆbßÅå7MWqsÁ”ÇîÃ3øMzð¼ Ì@#ÌÁIÃ1ù¡!¨C¶Ç œˆ7)ºö‰¬^!Ä ð*rpƒà WM„D3=Y8ØÁpÕ2b¢È§ÉÔ²œ•g»„#8HA{ e¶À@¢2™ÖpÅ)kï„¶yÌp°6a8ËÚ)n†lÀ.V¨Áfœ‘±À,Û2Ó6n0R—+ØZ;õ P0PYþ7J±lÓçAâUÔÛYõ˜ ³k8p·ZÛ@HB„ñ’÷3Ÿ„Ö¤r…½pzñ\¼3<1BIã|ä³éCÖàÂ+ï"ŸeívTT-.ùda(bDÏ ç 4×´6œY:Ų€Î|„`õÔ!F„ AÈÜÃ,½1XÚôóÒiOM;¼ÓaøZTó UM•‡(×gÌk˜ „@X¸¢Ò¥6\ÔÏζ³A{Ì'_¢Œa J˜BÈ÷hNL!;8ܦµçQ(I©.ÕÎêz°ß ;±Aù+ØÇÕÑX! Jq}¥nÀòtOæã䤈 „¦þd H¯/MxDw0ð€kñ|FN̓9â|ªm—ò¯‡I>@iˆó‡ÀFб”ªÓŽ¨¯ ³7D¼â é®K;F¾6 ÆŽ™–/4· t6Žg‚ ™J/Ærñý¸|^•ý ¡Û6º½™)‚Ä›„Ì áµ¾‚¸ë€‚Ï ÝŽ IˆÏ(7ŒØ€€ ^‘©Ý“‘.`»ÞK¿FQíó,Ћ, )ËÈÓŒ$ Ž3!¬ÒÓŠ\:ÀH"„ȘÛÉ4ßø p¼Þ®®x3©ò^™A[Á­›)ûÀXÑ>wbºÅI1͸«±…›þŠÃP9¨ X4P¤{,ÀB5‰"‹R,, »¢P/z:ê»!t”6œÀS²Ëñ2€©²6Ä«3„ˆ© »" 0ÁX ÐŽ"¢^©ŸgÚžcú04(ƒ5, þD…¸ÃÈÃ¥Nô/ß8.x1©}è2”}𘠽}è üš!p!€-…˜+S ˜‘‘=£ öª0¬Â) < á"®øJˆŒ© N›:ÀÂ/¹1Y\½Èj!̹ÛÑ–b—:ã¯üºÁ©€ÿ+ 4"H‚º*‚Û€©9ªˆsò€à"ªp;¤(úb3þ£Á®óTù #¨ÇϾSʘO xÑ6,€—XjÀsKˆ\‘Œ , !ˆ"8 Ÿ&#b'0›%{ºÒÄ£X1r1úÛ² äÈì/ÇÚAa@sÓA[¹£äxº$ì cÂð¤Žû¸G)?Î+ȃ)P‚1xFx逰Ôx´=óì-|(ïS³ ”éɯóûÆ„€ªx“âJÿ‚©:d ƒÌˆ}\”bÂ@Ž ÄÁ¢Oª-« 3¥¬˜zš¶«0%w2³/h˜AB®¯4 Ë2µ±È6ÅÈ-»‚S¹<Ô´Gc¹«X®åpK+hH/¡Ë Àþ(¹ÆÐÈ©DO¬ÉÆ‹É ‹ò;Rt,ŸÈ3Ô\²=+ëJÏ Ò«˜Ò i±Ú€›ÜD{Éù Ä0YÛ¨T²xŒcʃ€ªXœE…0ËËÓsÆ…ð‚Šª@Oˆ ˆÈˆ‚LŸ¨)T©H6³•4ŠŠë2Ô°¡èN»Lý8”ñsŒ<¼O"P„¨‚Šh‚}h‚•)ºgt 9lPxн Uˆ*‹ØÚÊhP°.ž+ =ˆöŒÌà eн½½Ð •PYûDÓ˜Fƈ öS@„ÈÉQbN¢ µ‹LŢȠÈù¬Ö­è$þŠ{2ÎÝ@SlŠ!ÅHU¼R\k·¢¬Jä8?9”K‹ K¤x77<ˆ+º6aBÄqÌó) Ì<•æ©Ìv2L30 &MŠ2m;òº 9LG}Þ;Ü ¸l”@µÕ<Ž9S“{“Ò4 •› ZŠàË€&qL〜1ËÌS3ÀÔ®0=Ë£ÍüŠÕ¥Ð­‹0KbÂ`Ô í‹l¬×Œ­‡-ˆ².ñìŒ ×¬ÛÈ Â<Á°e!îðÊ{D×Ô>´‚ÂN^eÌ N3)¦Äˆ°®,øcÝ€ùˉ¥tÓ…ø ˜(±aÈ&þ£Í{¸Vô˜ W™O+p“šOWô ë”ß|Ì/°¡x­˜ù,EˆÐÁWQt‘}½2ú¨­9KˆØ âT“w­M_ñöÜërTÀˆ-Úl’'‰’‘JˆšÖ,¨Ö9»lU;™ÕÉ„—V$ÙÇh½è‡‘L€ã›o¼£SÓCÇD')û™@©Ñ¢:CZÒˆ\=Qžã&Œ]èÁU<5BT·›þ¬¶Å¼Œ°V6.^Œ/èl \Ÿà+ZQPAU|8ð¯À‡è«} øPèýáOzùã9¾S¼\ˆY-Ì M~ ÜÀ˜ è¶â0YVŽÄˆ+¸#ŠO EH5 ÜìàÆ`Ýg¥áAæÉ¥LÉòUˆœëTŠk”^AFÓXs5K.ŒLáXU& O> Kâ¤PÈð3 R> ýµXT¶•MY…E;,êÝ…`dà£ÐR/]Ü®YGæe[ŸÏƒ<£@ºé¦TBåœ,èL}[8{fT„DT„(ƒás?i^“ i lT.Κ­äÅFeɼkÈFhºý>ÊÆìÌÖìÍæìÎöìÏíþÐíÑ&íÒ6íÓFíÔVíÕfíÖví×†íØ–íÙ¦íÚ¶íÛÆíÜÖíÝæíÞöíßîàîá&îâ6îãFîäVîåfîævîç†îè–îé¦îê¶îëÆîìÖîíæîîöîïïðïñ&ïò6ïóFïôVïõfïövï÷†ïø–ïù¦ïú¶ïûÆïüÖïýæïþöïÿðð'ð7ðGðWðgðwð‡ð—ð §ð ·ð Çð ×ð çðŸ‚Tññ'ñq)0ñWñgñGñq£H…䧘q©qGùpçñçñrÿq§qo”þr÷ñ%wòò¢¸ñã@ò¥˜rÿ¨òEQò''ñ&çò/7q/_q"GŽ,OŠ+g3“-óoó7ïò(' 4o 5? :W;÷6os1‡s0÷ó's*7r¯>ô?Wô@7ñA =—ò#?tEOF§ô ·twtÇ€ô9—t§ôL¿t(çñM¯óBÿNoDÿrQõwõ7uÆPõ…Àó"õEõD/õH/sTÇòI×õ]çòXq_'ô¦¸õ_Ïõ?7vbò^÷tfgŠeOö$uhògŸõŨu…°öGvg×v'çvdweÿtlöròsŸök·òuþgV/vwÇt9÷‰pŠoO~?õf‡ógÇ÷J×w[Çõj§÷3÷ w €ß‡v§xtçô‰_‰ÇóŠ_ ˆçøyOŒGxjùT˜2hõ‚ÿq‚Çw–§t¢ùAù{Wywùr¿ù?‡ù“Oùš‡uŸÿy ?ö…ˆy™ùmúÏyh_ú7ßyˆ9zsOz&Ÿúoú6úr‰ú%¿úWïúQÿz.ÏzrÙú|¯úh?ûû'ûq)ûwOû8{_{'o{ëù¹ws½ß{¡¿{[yû•çû¾×ûº_ò¿¯•À'u¾7üŸzÄO¹¼/üÁ7ø¤‡üFQ|›§üÆsþÎ?x„(úÿÈü güÍü†çyš/ýÁ÷|iWˆÐ÷ÑgñÖ—ü¹§ý¿|F‘}¥7}Ö?}¢O}¤÷ýá÷{ÔWxào÷ˆÏý’O÷ã7yèOØ—~£ þxï÷‡OxçOsóvÌïÿðÿñÇ.ÊÍnòGÿôGóÇnõwÿ÷/ÿð†ÿùOö¿núÇñ·ëÎÿþwÌýˆ},hð „ 2lèð!Ĉ'añ"ÆŒ7rìxÑ Å"G’,iòäD*W²´%̘2gÒÙò&Î5wòìéódΠ,_þ,jô(R¡J;Eêô)T“K§flõ*Ö¬ ©rþaU+ذP»Rý*ö,Zžd§šMëö­ÔµJÛ­k¢Ü¹w÷ò}˜W(Ý©J1:¸aaš‡)&&¹ødã‡iFîkù²Ý"' œ`Ø¡gÅ"C;ŽIZrÏÓ'sÞ§šg냯QÆm²vÈÙ qŸÔ-x³ìÏ }—ä½8D㑯N½¸Aæ0•»®I¡ô†×f'¸=÷sïgH^tÈó¥aªGͳ}sÌòçÓ¯oÿ>þüú­§Ü Á>GÄLý)¸‚“Ep^ ÐÄ>hc„Ô  .„( € dý1è!D H^ (P„Ä!‡þ&™hû!à„=b8†‡8¦Ò=î3†c€HaL*æ¨b…’wH¢‚XnØá‚T" Z Ĥ“!¦ØàŠq¹¤b¨ù —fzGe˜ß„¦>¨ ‚‹Ù ‰6A@@À7›T&Ñă>’‡FcP bJ~™§BFH¡f¡Mšè¢ÙæD”>‘i\NZéf§ŠYÒ@¸ƒ—¦2F¦ErÈå‘ThŸŠMùX¤ŠVâ¥pjH‘¬¼=Y,’Ã&ŠânѦjÀ¼öˆ¥²ÓÞ¹"§:òˆf*êŠ.žÇ2‰±ÊÊ”g‡%ûª¥˜j m£Òª9лñ–z£ƒ¶Q€¾­† ëjýšÞq¹n¯í6ø'–hŠËèaåÒxe–I¾é%¿äJé/…‘fÜäÆÚ9 Q<;-!ïÛæ±%E€­} jñŠ#fXà ê „RèâÐüÒkÜ´Bkh"¶<«C ˜aŒ3îS#”w>]R×Eÿxb*Bf¸`@!ùû,]®SíH° ÁTÅ\¸aª$Pt8ÆŒ‰¥hbó"Ó b" }Òxä‘ G¢  À³ûÆ0³ï˜ÈYdÌÄ1E¯Žd2”É‘4  ž1s0 !ùû,jM=þ¥ìH° Áƒ*\Èp¡”T #JœHÑàCˆ3jÔxãÆ vô²$È‘$Mª¤ˆ2åÊ—SÉœé¦Í‚-eÞÜi‘¦Ož@sκSèP¢0EªR©N¦M}.…ºR(Õ¤:¥^­ŠQëV“ Òüzó)Ù³hÓª]˶­Û·pãÊK·®Ý»xóêÝË·¯ß¿€ \R a‚I Þ7ÆLØÁG ‰BøHc( ¶|yòæ  ˆÑü—sè$O›-…4ßÕ¡«8ñk´íÐOŒô…mÛ@Ý{a0@üˆ”Ôz9GpÙÌ‘ç@0ALÐO¤û=RáH$$8òw ÃG€Àdöî1¥Œ‘칯™‚G˜ICøÌ}ƒ!ùû,^“Z;þSíH° Áƒ*\Ȱ!ÃTJœH±bCˆ-jÜÈ!ÆŒCŠ”øäÈ“( –4™²eÇ•,]ʤ3æÌ› kÚÄɳ Î=yJJ´¨” H}þ<šéÏTR€6Eù4êÔž:¯&ͪ5(Æ}+»bV,NÍ6…¨¶­Û·pãÊK·®Ý»xóêÝË·¯ß¿€ L¸°áÈ£¸a(Ž1Ã4òÁ#D±|9€1›9<À³fÑI—6 &4gÕ«“< {µ×k¯®âIJnÛ³s—>ÜoáÄ X~DJpĪ# ðlæˆõ#H“ž &Bé'Ù}.W8‰€ŽpÄñ 0éùȘRÆd>ÙLÁ#L‘jáŸA!ù…û,«º BSíH°àÀT D˜PáA†BtÈpâŠb´(qcÁ ‚ ¹odH“(Sbü8’¥@;f<財F›!þMade with ScreenToGif;altair-5.0.1/paper/000077500000000000000000000000001443422213100140745ustar00rootroot00000000000000altair-5.0.1/paper/paper.bib000066400000000000000000000051741443422213100156700ustar00rootroot00000000000000@article{2011-d3, title = {D3: Data-Driven Documents}, author = {Bostock, Michael and Ogievetsky, Vadim and Heer, Jeffrey}, journal = {IEEE Trans. Visualization \& Comp. Graphics (Proc. InfoVis)}, year = {2011}, url = {http://idl.cs.washington.edu/papers/d3}, doi = {10.1109/TVCG.2011.185}, } @article{2017-vega-lite, title = {Vega-Lite: A Grammar of Interactive Graphics}, author = {Satyanarayan, Arvind and Moritz, Dominik and Wongsuphasawat, Kanit and Heer, Jeffrey}, journal = {IEEE Trans. Visualization \& Comp. Graphics (Proc. InfoVis)}, year = {2017}, url = {http://idl.cs.washington.edu/papers/vega-lite}, doi = {10.1109/TVCG.2016.2599030}, } @inproceedings{2017-voyager2, title = {Voyager 2: Augmenting Visual Analysis with Partial View Specifications}, author = {Wongsuphasawat, Kanit and Qu, Zening and Moritz, Dominik and Chang, Riley and Ouk, Felix and Anand, Anushka and Mackinlay, Jock and Howe, Bill and Heer, Jeffrey}, booktitle = {Proceedings of the 2017 {CHI Conference on Human Factors in Computing Systems}}, series = {CHI '17}, year = {2017}, isbn = {978-1-4503-4655-9}, location = {Denver, Colorado, USA}, pages = {2648--2659}, numpages = {12}, url = {http://idl.cs.washington.edu/papers/voyager2}, doi = {10.1145/3025453.3025768}, acmid = {3025768}, publisher = {ACM}, address = {New York, NY, USA}, } @article{2016-voyager, title = {Voyager: Exploratory Analysis via Faceted Browsing of Visualization Recommendations}, author = {Wongsuphasawat, Kanit and Moritz, Dominik and Anand, Anushka and Mackinlay, Jock and Howe, Bill and Heer, Jeffrey}, journal = {IEEE Trans. Visualization \& Comp. Graphics (Proc. InfoVis)}, year = {2016}, url = {http://idl.cs.washington.edu/papers/voyager}, doi = {10.1109/TVCG.2015.2467191}, } @article{2016-reactive-vega-architecture, title = {Reactive {Vega}: A Streaming Dataflow Architecture for Declarative Interactive Visualization}, author = {Satyanarayan, Arvind and Russell, Ryan and Hoffswell, Jane and Heer, Jeffrey}, journal = {IEEE Trans. Visualization \& Comp. Graphics (Proc. InfoVis)}, year = {2016}, url = {http://idl.cs.washington.edu/papers/reactive-vega-architecture}, doi = {10.1109/TVCG.2015.2467091}, } @book{1977-exploratory, title={Exploratory data analysis}, author={Tukey, John W}, volume={2}, year={1977}, publisher={Reading, Mass.}, doi = {10.1002/bimj.4710230408 }, } @book{2005-grammar, author = {Wilkinson, Leland}, title = {The Grammar of Graphics}, series = {Statistics and Computing}, year = {2005}, isbn = {0387245448}, publisher = {Springer-Verlag New York, Inc.}, address = {Secaucus, NJ, USA}, doi = {10.1007/0-387-28695-0}, } altair-5.0.1/paper/paper.md000066400000000000000000000115411443422213100155270ustar00rootroot00000000000000--- title: 'Altair: Interactive Statistical Visualizations for Python' tags: - Python - visualization - statistics - Jupyter authors: - name: Jacob VanderPlas orcid: 0000-0002-9623-3401 affiliation: 1 - name: Brian E. Granger orcid: 0000-0002-5223-6168 affiliation: 2 - name: Jeffrey Heer orcid: 0000-0002-6175-1655 affiliation: 1 - name: Dominik Moritz orcid: 0000-0002-3110-1053 affiliation: 1 - name: Kanit Wongsuphasawat orcid: 0000-0001-7231-279X affiliation: 1 - name: Arvind Satyanarayan orcid: 0000-0001-5564-635X affiliation: 3 - name: Eitan Lees orcid: 0000-0003-0988-6015 affiliation: 4 - name: Ilia Timofeev orcid: 0000-0003-1795-943X affiliation: 5 - name: Ben Welsh orcid: 0000-0002-5200-7269 affiliation: 6 - name: Scott Sievert orcid: 0000-0002-4275-3452 affiliation: 7 affiliations: - name: University of Washington index: 1 - name: California Polytechnic State University, San Luis Obispo index: 2 - name: MIT CSAIL index: 3 - name: Florida State University index: 4 - name: TTS Consulting index: 5 - name: Los Angeles Times Data Desk index: 6 - name: University of Wisconsin--Madison index: 7 date: 07 August 2018 bibliography: paper.bib --- # Summary Altair is a declarative statistical visualization library for Python. Statistical visualization is a constrained subset of data visualization focused on the creation of visualizations that are helpful in statistical modeling. The constrained model of statistical visualization is usually expressed in terms of a visualization grammar [@2005-grammar] that specifies how input data is transformed and mapped to visual properties (position, color, size, etc.). Altair is based on the Vega-Lite visualization grammar [@2017-vega-lite], which allows a wide range of statistical visualizations to be expressed using a small number of grammar primitives. Vega-Lite implements a view composition algebra in conjunction with a novel grammar of interactions that allow users to specify interactive charts in a few lines of code. Vega-Lite is declarative; visualizations are specified using JSON data that follows the [Vega-Lite JSON schema](https://github.com/vega/schema). As a Python library, Altair provides an API oriented towards scientists and data scientists doing exploratory data analysis [@1977-exploratory]. Altair's Python API emits Vega-Lite JSON data, which is then rendered in a user-interface such as the Jupyter Notebook, JupyterLab, or nteract using the [Vega-Lite JavaScript library](https://vega.github.io/vega-lite/). Vega-Lite JSON is compiled to a full Vega specification [@2016-reactive-vega-architecture], which is then parsed and executed using a reactive runtime that internally makes use of D3.js [@2011-d3]. The declarative nature of the Vega-Lite visualization grammar [@2005-grammar; @2017-vega-lite], and its encoding in a formal JSON schema, provide Altair with a number of benefits. First, much of the Altair Python code and tests are generated from the Vega-Lite JSON schema, ensuring strict conformance with the Vega-Lite specification. Second, the JSON data produced by Altair and consumed by Vega-Lite provides a natural serialization and file format for statistical visualizations. This is leveraged by JupyterLab, which provides built-in rendering of these files. Third, the JSON data provides a clean integration point for non-programming based visualization user-interfaces such as Voyager [@2016-voyager;@2017-voyager2]. In addition to [static documentation](https://altair-viz.github.io/), Altair includes a set of Jupyter Notebooks with examples and an interactive tutorial. These notebooks can be read by anyone with only a web-browser through [binder](https://mybinder.org/). -![Seattle Weather Interactive Visualization](seattle_weather_interactive.png) The example above is an interactive Altair visualization of the weather in Seattle. The plot on the *left* shows the initial state: a scatterplot showing the temperature and dominant weather type between January and December, and a bar chart showing the counts grouped by weather type. The plot in the *middle* shows a brush that the user has drawn to focus on the summers; which are dominantly sunny. In the last plot on the *right*, the user has clicked on the a bar to filter the scatterplot. These interactions are achieved through two selections: an interval selection on the scatterplot and a multi selection on the bar chart. The selections drive filters in the other plot. The code for this and other examples is in the [Altair gallery](https://altair-viz.github.io/gallery/). # Acknowledgements We thank the many contributors that created examples, wrote documentation, and reported bugs. You can find [an up-to-date list of contributors on GitHub](https://github.com/altair-viz/altair/graphs/contributors). # References altair-5.0.1/paper/seattle_weather_interactive.png000066400000000000000000005574141443422213100223770ustar00rootroot00000000000000‰PNG  IHDR’/øKsRGB®Îé pHYs  šœ iTXtXML:com.adobe.xmp 2 5 1 2 †Ò®$@IDATxì½|ÕÕþ?³]½Y’-Ù²ån0Æ`:^HhBw ÂJHÞ H„ä!„Þ ½CBuh¶q÷&Ù²%«·Õö2ÿgöÊ£ÑîJÚ•v׫Ýg>b8÷ÞsÛwÆZݳçž++Š"ñ"        H†ôGA$@$@$@$@$@$@$@$@$  ÁŽï ¤ìÒèap($@$@$@$@$@$@$@$@$@ƒß        H#4Ø¥ÑÃàPH€H€H€H€H€H€H€H€H€;¾$@$@$@$@$@$@$@$@$Fh°K£‡Á¡ v|H€H€Ò”Àé§Ÿ.‡®¹sçúýþŽòÙgŸýú׿ÞÜÜ9b#C)x½Þï|ç;?þøP £Î÷x<÷Þ{ï'Ÿ|Ö&þꫯ†e"yÿý÷oß¾]Ÿ¿aÆûî»oxD/¼ðÂk¯½¦¯yéÒ¥ûÛß0µ°|tñÜsÏi™7nDÚåv»µ" $@$@$@$@$@’ÃuÚi§ RsæÌñù|1ÔˆUå™gž9õÔS÷ìÙ3b…ŸýìgÐ|÷Ýw…&d\úäˆ ¥€…Í·¿ýíÇ{l(…QçcrÏ=÷|üñÇa-`⯼òJX&’XälÛ¶MŸ¿~ýú¿üå/Ã#zþùç±Óׂüá‡þõ¯ÅÔÂòÑlZ&KèB»\.—VDH€ö!ÈOh»ÝïxD#¢ÖO~ò$ÅoÅÛn» r]]݈ ÂüÍC9DhŠñ‹_è“#6¢WèííE#ÿ÷ÿ‡L§Ó ùꫯÖ+Œ]¾ð Å8Å]4ˆßöúÌööv­£_þò—(‚5MËÑkΜ9SËׄ+VèuV¯^-Šô™>ø ¦¿cÇÍ›7OËÑkB^¹r¥VDH€H€H€H€H`dƒ]ØŸÔHrÉã{Ã%SŒ ¨F$@a6oÞŒ›ßýîw"_›¾ò•¯hjpàr8ZRêëë5‚ø9ÃìZZZôõ²¾Èf³¹¨¨ ÂôvÒI'iÊQ-€°ÊASÓÊ`ömÐß´i“VQ/hßWé35ƒœ:u*’o¿ý6äÃ; ²Ñh„ _¼A˜ÈúKTANOO&ÿèG?‚Œo€¯eÂ'dÜà v ·TêGH™H€E€¾ £&Iß…Q£cE  v\2‰·„K&þk! TX³f ì;GuTd§øÒH3Aصk—ÐÁÞY-þàÈÔ’~ó›ßhÉÿûßz;ýQ4Ù£¨ˆ|T„|ÑEáŽä¯ýkØÚ ùæ›o†,.aÎC¦¾e Ø^-uvÂÃî¼óÎÓ2±qí¿ÿþZæâÅ‹E&rfÍš%ò‘#¶£bÓ®(wØË  Yß4e‹EkIÈË–-C#×^{­¾Jyy¹(…ÂÃ?¬/ÕqG¦f}ƒŒKd 26 ¹»»]àBR«"”á½ ó" ´%€ß]a}b|Xô]ˆÕH€H`(#ì¸dâ’i¨W‡ù$@$TúÒÉ'Ÿ¬æàà†"Øz„†hkEp€¼¼<Èblš’CyØ ŽŽŽ7ÞxòĉÃæ‹ò¿üòKÎ 8îÐ)((¶ÙB€‚@>ÿüó¡ÜÓ`–‚ ¬‘v(‚· \üTíPË<ò„o~ó›hv=Èz·µÊÊÊÿ÷ÿþŠ„,Ì`‡|íB`Ô=å”Sá€Eqijˆ¤fãÓò!ÚQ…šf?E2Ôj³7Üp’z‡G$à vB÷ &è;¥L$@iB€¾ âAÐw!M^HƒH Û ü½>Ô̵¿§!pÉÄ%ÓPï óI€H án½õV±‘S|]rÉ%èrNNNÈgëµ#Ž8I-Ê'"©ižnb0¢¢£ìàï~÷»¢A½¾6a»à‚ `q6/¨¡/ÜaÏ‚bƒB¾ë®»" ƒ0#žuÖYZË‘»3Ï<3l´0ÉA_´†€§qìt´F´± #üð‡?ÔëC>üðÃ…¾ðFÔêe°gÔÂÑЬ­­ER\ZEDh]ˆ-ºúî„û!<ø4e”j;X?‘Ä.3”.\¸2žµ¦IH€Ò„}è»&¯"‡A$F6Ø —LX5qÉ”ÿB8k t €Øpš1Hú;@X¾|¹È©ªªÂŰE¦£ìžzê)}Sz}ýÄ‘o0¨çªÃg ù®ºê*Üo¿ýv$/¿ürÈaòaÝ™ðq2# vZ(7M'¬)$á6(ú… aÄK˜üjjj4MTí#Ú׊" vÚá°<ð€Pƒã¡¨¥ à²Ë.9Q$tôÍ^yå•HbÏ¯Ö ’šÁNË„ÐÕÕ…¢Ù³gë3)“ @š¿Ùľ ô]H“×’Ã È1ýݯ±à’‰K&íe @$@É# ¶¸Š£TE/b±ÜîD&¶jÂÜYï2«tÂj!Õ`'ÎLÀþM¡ÖDƒ"©ÝE›hV2k2™Äxà@«îÍ7ß k:G}´Èúc1ØtÐAÐqÇDÈ$­­(ª Œkš­Mèèëêe”†ì0)¡ Yâ"{1áá¨/‚µEŽ>žÈA‘f°ÃÉ쥥¥bR}ôŠ£oŠ2 ¤ú.Ðw!}ÞFŽ„H « ¬¢N›K&mí>\2E}I˜I$@É €_¿¸`ÖÍKÈboæ×¾ö5$§OŸŽ¤ÈGïÂ`‡­©ßûÞ÷´Lä ÇžB¾å–[Ä¡®ˆÅ£?tBÔ…ßÄ´iÓ €–#§#Ž›@©(ÒœË4Má‚Õ©¸¸‚?Ê“Þzë-xº úbƒ-¢¶ýùχNDzص··C»îºëDElËB]­Ñ/VPȉŒa'Ôôwè‹a<òHì®EÑ7Þ(Á=Ì`wÅWèëBF`>MYa H"_‹Gvî¹ç"ûgõµ£ìDX(äüâ¿À—ßï×+S& 4$@ßú.¤ákÉ!‘ d0 v˜¹øKšK&ÁK¦ þÇÀ©‘ ¤Ô€Ã^Åï^Üÿô§?iÃ…Kä—••‰Lá(‡Lì‡}üñÇ!#ÝÓO?-4…LHH¾ýöÛzƒŠ„ EçœsŽÖK˜ oGX¦´pЄ½©¤¤Dè Žž¨+â¸!ç9è«‹M»° e°Cuͤ…Šˆø&Ô7‚œ¨»—^zI¨éï¢ú¡‡*2SOäˆ{˜ÁNX0õÕc4Ø¡5<&Q1ò äk;hŠ@ByçÎúñP& 4!@ßü–Öž}4H€H 5~Õ—Lb9;—LC½$Ì'    Ì# ô]è»yo8gD$ÎF6Ø¥óè96     Hú.Ðw!I¯›%  ÈÐÐ~ S         Ø· û¶{öN$@$@$@$@$@$@$@$@$ '@ƒže        ØÇh°Ûǀݓ €ž vz”I€H€H€H€H€H€H€H€H` Án?vO$@$@$@$@$@$@$@$@z4ØéiP&        }L€»}üØ= è Ð`§§A™H€H « œvÚi[¶lzè¡ŠŠŠ»îº+«‰pò$@$@$@$@$@û‚@šì¸dÚ/û$ ¬&ðÅ_¼ûî»Á’%K^~ùåÖÖÖO?ýôïÿ{VsáäI€H€H€H€H€RN  v\2¥ü5`‡$@$@Ò1Çsþùç˲ ‹/~ã7 ¼ôÒKW^y%é @ÊÐw!e¨Ù ¤3t4ØqÉ”Îo ÇF$@I ¬¬Ìn·kSëëëÓdEQ4™ $•}’Š—“ À8"`J·±Š%ÓE]$Æ%Sº= އH€2Àí·ßþì³ÏŽ8¯;ï¼3Lya9LféÓ§öÙg0‘4ŸÂGQWW—æƒÌ¤ááÅΤédð\Â|Ä—Ip÷6 W\qEOœS# #^;.™Â“$@™G€K¦4|¦yyy¯†®•+WÞrË-¿ýíoKJJ‚Á VG‡v˜óÏþsýàa¿KÁÓ„E#y½$µqU zIx555Úø5¡¡¡!j¾¦0v!]`éÓËßí„?÷¨O0½¤¦‹¨³KeæO~ò“W^y“E§>póÍ7ÿú׿NåÆE_ô]‰ƒ$ ÔH/ƒ—LI}ê©ùspŒ|ÇB€‰…’¦C\Š…Ô°qÉVà’)’ðÕW_-2/¿üòn¸aêÔ©+V¬(,,üç?ÿyúé§{<žÈ*Ì! ˆ‹Žñ©¯¯«J¶)Ów!Ûž8çKYH æ‚L¢š^;.™2éÝâ\H€Ò“—LÃ<—üã¢tÆŒˆÉ¯;øÙ £Ï" Èp‹Ãw¯¿þº8œñ=ðƒàmÈÿ׿þ%P` g~~þYg…_¡"çî»ïž7oÞSO=…$êŠÀ ø^D´ƒ/ìE £Ñ(~åÖÖÖâ+´¦¦¦êêê믿þ®»îÊ’X¢ië»Ôo4“Ú¸þŸg :JA˜Q zIAâѤ¦£ô’‚.RF,©sAãú•ûD_¾ éxè–L³fÍ‚ –L>ŸŸßf³yŸ[†5wï7ß|œ?ú裌'À ’ @² Œ/ß…45Ø…=$.™Â€0I$͸dÊæ§Ï¹“ À¾"ð‡?ü]777ã{›7o÷ÜsÏË/¿Œ¤øÆ‚ßïÇýË/¿|àðMÛíF2òºæškùÍo~w˜ê>ýôSÐÏÍÍ…pÇwàŽkÍš5 ,röÜé»=Ïš3%ˆ—>)²Çwa|ìâ}„Ô' &À%S?\NH€Ò–À÷¿ÿ}Œ ºË.» †¶{ï½Éo}ë[&L€ç8íBV¶ƒ:9ØÖŠdäUTT–‰¯£´–/_.Jíè»öª0I$ͲÊw»l~Õ9w qI€K¦qùØ8h È8Îq¸î»ï>1!áépÜqlj bãõzQtî¹ç®^½º±±Q¨áÜm«V­ɰû7ÞØÝÝýØcáK)xO<úè£a L’ d•ï v|çI€H`\à’i\>6šH€Æ3ÓN; ÃÇ Gy$¸ÚáûÚÖ­[—.]zØa‡UVV"ö´Åb¼xñb8â]zé¥bÆiaÑ¢E"vÿÍo~sÝu×]|ñÅ8ep˜$  È*ß…ô:%–ï @,°dB„o,™„²~É„{Ä’ a†P*–LÈÄ’éá‡F–LO<ñ–LØyÙ–Lø-VJbÉ„C'"Õ˜C$@$…ô‡Nè?DfΜ©O‚̲eË4>8›2ŽMÐt4aÊ”)šüÿ….­VUU•V¤eR   À:eÆ pÍÆ™Eˆæî <¬€L&“8Ôh(wïƒ>8’$ܽûÛßÂÝ»­­ _ =òÈ#84)R-e94Ø¥ 5;" „à’)a(Ù Àø!=¾ 4ØŸ·’#%       ,&=¾ Œa—ů9§N$@$@$@$@$@$@$@$~h°K¿g‘ d1ì²øásê$@$@$@$@$@$@$@$@éG€»ô{& @à¡Yüð9u    ˆ“@_ýK¾ž­¢’ÁR˜7õ¦¼ê8Û : Àh°‹I€H m pÉ”¶†# L%еæNLÍh-3Ö<]¾Þíö­Om ç~?S§Ìy‘ À>!@ƒÝ>ÁÎNI€H`¬¸d+AÖ' ˆ“€øè)Yøs}½¾º`¶óvo²ÏÕçS&  äÈßưKÞûÖI€H Y´%Sá¼Ërª¿š?ý<,ŸÌ…3îv,™’Õ+Û% ÈbŽúW0û0krðdÊèØñj$›o¼ñâ‹/îêêBѳÏ>ëóù 8Î×^{ ÂÝwß½|ùò‹.ºhÇŽHò"   `5„à p÷¶•‚EPÐÛ wïÞMÅX}¼¨Ñ`7^žÇI$@ý¸dâ«@$@$zÞžÍkIÔ~ fùAŸ]_:þüK/½ôÑG---Eþ<àõz!ôõõ=ùä“®»îºM›6=ôÐCµµµúŠ”I€H€H`Ùã»@ƒÝ0¯‹H€H  pÉ”ŽO…c" , `.˜>Ì,}=Ûô¥V«õ’K.immUEŸ¯—¿ûÝïZ,“‰QzôT(“ I «|h°ò=` ¤-.™ÒöÑp`$@$Á‚Þîaf‡£'ô¥+W®|æ™g®¼òJY–€V„ °a&<£Ñ¨•R   ad•ï vü ," 4%À%Sš>‹H€2—€ÁRˆÃ%¢ÎÏ×[|Sþ})ìtÕÕÕ/½ôÒâÅ‹±õ;d—-[…믿^¯F™H€H€â"=¾ 4ØÅõbP™H€ö=.™öý3àH€H ûäMý&3a#§ÞW÷ÏÈ̵k×Âf‡ .uûï¿ÿ=÷Üsâ‰'"yï½÷F*3‡H€H€b$=¾ 4ØÅøJPH€Ò…—Léò$8 È&¦¼jlz…“}ËcÚ¼ýŽÝZðo-Sp©ÃÖW\±¦:lŒErÁ‚Ï?ÿ;gÎè¿õÖ[F£ñì³Ï>÷Üs‘9b T }E@îúTt­øº$çv)wÆGRUhc ¬žñÖ¯_ßÑÑ!¦‰oÖçÍ›WXX˜ñ³NÍé»Îì%Í \zLí'Û;„µî¢#x²Mš?./‰ÒË`Ç%S5›N2gã;¢‡€»ÝÛµÑR2oŒ֔挱VÏx\2%öÃû¾ûªB³»víúÉO~‚³Þÿð‡?,X°àË/¿DæwÞÖc]]ÿ‘šaù‰M&µ—¤6®qHA/ ìbúôé ÚàõÂPùz1ÊñvQàkÍñ;D§½-{ÜÆ˜Ìmñö2ºI ß þ­ý©½…X¦–‚^RÐE,3EÈPËÉÉ)--u¹\kÖ¬ÉÍÍ=ä(!wÿþ÷¿Ÿzê©S¦L‰¥eêÐwï hà¾P׿˜Ršc6Òã[£B!뤗ÁNàç’)y¯a þLAà“‚^âíÂÔÛltö‰g×Û´+Ðeå9ÆÛK,mFꤠ—ty¥ —tù€F—Ã%Óè¸E­õÙgŸtÐA·†.„á‰'žš?ûÙÏ´h ?ÿùÏõÕa¿ƒeGŸ“ ïdòzIjãô’ð.jjj´ñkÌOQó5…± £ê¢Fl‰Eï¶šcbèz‰¥áA:±ô2Æw;áÏ}Ðö&RÐKjºØ;¡1ý_|ô{ì±úVÖ®]ÛÕÕÕÖÖV^^®Ïß°a~—^zé¥úLÊàïÂ0pX”…¦—çeá¬9åØ dƒïBzì¸dŠýí…fjþãß±Ì+]'2]l‰ÅJö?)M&‚a¤+®X ÒÉŒ‰`ƒf5Ú—L£%½Þ§Ÿ~úôÓO‹]±>ŸJƒž#Øê…½±QÝF¢7Ä\-¹ùÉÛVႯܵñ¶¡Ô\>b¹ñ)ÉY¯=rl‘'¨¯)ô[sj•ò¯i™²“ p˜x˜µ9pÀêÕ«7nÜf°{àŠ‹‹±y¶²²òŽ;îøüóÏáÅ Uð ö†n¸æškðÇÿ…^˜<‡šuzú.$꘨³NjãúSÐQ ºÀŒRÐK º&5¥ —t‘2b)›‹þŸg¼r–ø.¤—ÁŽK¦x_Sê'œ€}Ëc~g³¾rÓo‹·ñ’…ƒ¼o¢VïYŸ·{³ô˜ ¦K…çGÕ‰š)¬æ¢9ùµgEU`föà’)áÏúºë®[´h‘8nâã?Fûn·'Äz½Þ©S§îر#á=²AÐ}°Ö‰¹ùeeâÙúÒ±Ëjœ;o»¤¸q¤Šìï* ¼/Iß‹¥YÍwOrÕÇ¢OÌ&ÐÞÞn³Ù¢ÎNÊX;y<|Ï¡)àÜžU«VÁBWQQñúë¯ÿâ¿@¨;¬'L˜€-´0äálŸ+®¸‚;XÚú.à©%ï+ù¤6®±…‚ŽRÐE&M$5sIM/©yô0€Â,Æ~eïBzmÇ’ +%,™piK&|´#yå•W®X±bì–-À0ü®VXë„BïæG‡Ñ]âÜ\-Á€+¨î6sÇë1¶£ùîùz6ÇX…jL`ø%&Ž%“~úX2sÌ1bÉtüñÇ¿ð ³fÍB#ÐÁ’é–[nyÿý÷/ºè"}•,”W®\©„®£Ž: ÓÇÆX`D­uYø2¤~ÊÊÞ tj×AÕÇ3±ë$I‘¤Ð×´JP–ü£i_U­ÑôÄ:éKq놜vE˜vË~øáÈ„ÏòUW]u×]w=øàƒhêòËGv k*³“ð]Ђ0hîÞâ3îÞ™ýè9; Ø ã»€¯ÛáîÖ”æî|¸{ŸwÞy---BîÞ?þñ·mÛöä“O†ÕJ“dzì…K¦4y3²sН?n7¦¯¼ ‡ ø=°Ô jDpK¦à¨ºà’)áf6È%Ó8|h2 MŸ î’¿×l—Ô-«‰¾ÊþG2"jqSÌeÖ¯B;>”þ.·þk¸Îª¿=P*'n[FÀ¡næ5 Àûx˜QçåE8e6«ùà¨XíܹsáÂ…"‡w=ú.èiP& ¨²Êw!q{EeÉL_ Foï6“m‚ÁÛn)úŸ„=oêiÞ®u~w+Z6äTz ÔÍ­Ž†7½kMùS f~k¨‹öûßž ë/MÜ’)èësÔ¿hʯɩ:~¨®™Ÿžµd:á„Òs‚ dO›Üò†d› ›]@Î5Ú×Êžfeb‚£èãÜ) ê^W˜q¹dw“¾TO^1æK1ÈÓW‰E–Ÿ©)}u/åO?7–*ÔIØîŠ3a£ŽDäE-Îb(úÕ¯~õ»ßý. þô§?ýàƒàyU?›3á» Ÿ¾p÷ÖçP& Ñw'ÑGR s÷F”jÍÝñ"õÓ!‡»tx CZ8÷ôm{ÆR86;,QÜmŸû» fÇå'ö èãÜuÕÕa¯+ºCuÜý};õ¥ú6 –¡Šôjñʨ‡*ØpwrÉ/½}¨Ï%Ó>„Ï®I áä¾µj›ù³û-h÷ƳKx_éРšb.Á`|½ÛÓaHCŒæÍ›·fÍœ ‹S&ª¬[·.,GŸÜµkâÛ !n0â3 çÜsi®Õs¢L$@$ìñ] Á.¦‚JÙ@ÀݪI´”Î4ÈZ<»Œœ¾ß¹ó2ÚÊMÏ2fä43rR\2eäc夲–€RrŒìØ.õmEøPçÍL Y6)û"Æ‚b)‡åFöuaŽ9NÁLÙE¢ ®4"ŸvuuáLXœ2!šíééùâ‹/ Gž{bèBÑäÉ“TÂáôX‘# yZ  ž@Vù.Ð`7üËÀÒ,";åo÷Fo§ú±"«ÁV,%û¥`þiLBˆñGnÊXëpÏ™ẍúTH\2¥Ï³àHH`,äžU’s»Rù eÊ¥rïʤózw­®05KNlVýP);.–ÆÕFú6Jå'+– #êˇÔöVž?_™~¹ä¬—Ü»$k¥’7gÄŠ‰UPwàöm‚ÕÎFƒ]bÉ&¿5l –"„ô'ôiFZë´¢Ha¿ýö»à‚ ®½öZ ï%Y—9$@$@YN «|h°Ëò·ÓW ¸›?övm,˜uaÉ?s·|V8÷û»·}f¼=[¥†7ój¾ &4âéX“W{ž)·rDý ¯·oû Fo6ºz»7ùí;a>³”-±bbл§ý ƒ%ß\8#±-³µdà’)Ù„Ù> $›€Üú:>~Ћ¼ûq°”¢ƒ!çû¾Tüê Î’«Nvl*´œªºäîRïUl~9¦`s¡àqy¾]rW‘Rr„”[+ÚÙ÷ü¹û Sv™å¡ G—âLX8ÜÁWn­ÂGoµX…H€H€²Êw;¾ðÙNÀ¾õ)Ī…îu†K|Ûoê]æö«ë(XÓp"Ĉñã\MKÝ­ŸBß¾åÑ•¡Ö³^=AÂä¬w6–äVŸ`)Þgëë„1^ã‘—Lãñ©qÌ$0@ d­C(7us¨§M²–©Rpprˆ”}= ”Üé²³N¶oP †s çrHöµ v¼H`´°#)jHïѶÇz$@$@$+ìñ] Á.Öw‚z™J@XëD(·€³Ù˜;qðLB® Δò´«GzÁm¥µ\ ¨<Ô¥“œ}ž¶0Ø ¥É|‘—L#"¢ ¤)ªsl¡Ü"¬u’l°Jˆ17âбm¶ýXë 8¼µNUГʎ•YRÔs`G¼¿ìïQÌe#*RH€H€H€R@ K|h°KÁ»Ä.ÒšÕy»6ˆPnÖ:É`°H5žÝðWnÍ©Ž¯ÂZµá­uPÐË›rjÐ×')Aœ;|j©â÷»;M9#kRƒH€H ½ (eÇKy³e_ÞÐÖšsNM©[n[‚±ãDCã“Áê ‡™‡’[+Wž&y;”‚ðS;£ÖÂ[¹ocw»#§gµÜ±:²©@©úVTåþ̾ÍrgHCBì9^$@$@$@$2Þw»ôxÑ8Š}G oê–҂Í3ñ’òb‡}ûsXËHA_Ϻ{‹æÿx˜1·Î03×ïl±U:ŒšV„m³îöÕ=íNW˧Á]êÂÌ`).ÚïšB¤àéøÒÒDQ,»n#[` @z°U+¶êð!åÏ–:?¾uJÀ^‘V¬U~b¾”üyÞÎÉ¿7‚˜ß>|Ua­ë×ñvHúÙ Œ¥$@$@$@$4Ø%†#[×ÌÓ$ü ¾`Å34¼)ò‚~çàÂ()S~ ~¢ ‘e›pP°·.Øû‘(z»‡PìÏÖ¬uH\mÆœ°hGÃ×f) ŒŠ€µRò´ô×4Œª VÊ4›]}~1+«Yž5)¯ ǘi“ä|H€H€H`_0ìë° ˜˜ò¼0bÚ?S«Tß°dútsøYU×kwÆ÷|8zÈn²·NÝ` í<Õ Œìë@x-» NœÐôµ–5A+ ”ÊoH¦"5sÂ×à VkR `ß®z¶¯ñO:°ÖåX “J,ÅyFOY×з¦~WÍ—^zI?õçŸ^ŸŒ*oÛ¶­¡¡!j3I€H€H ÐÃ.ž2çìl•°´í<íZs§šð»ZM¶RÄþ Ë1‰'D ;¡‰Í­CuÖTÁ¬ {6þ#èéÌ›v¦l´†•ƘT‚þ § ³‹ŒÖc TKX2a0X2ç™\Þ@·#€%’ k‡sÁ’éœsÎÑf%Óù矯%£ X2Y,–šš8H£¶ÃL aÈ ÿ@i9Îoõ}O2—Dµ£ zE‹”¢ƒ‡iM_$RãÓÈAE}³zY¯)+U‹#3ãÈñì14¿¬xšÔ*¶©±÷GTMñÑsäœ wo§w;ºþ»¯¬`Ȱ¿·Þz«þÓgÉ’%#~úüç?ÿ)))á§Ï^Ìü? À Ùàî·¹a!&H ãó™ÕÙ¨¾Æh›5ZœÐÁÔs&c›xtŒ ‚¾ÞžõÊúfõòðMÍS´o__ƒ}Ëc¸£KáÌØû~T,Ý'¸dÚ'ØÙ) $‹€c ZVÌ%’ËahýWôƒ&à¡ë$Ù³GêY)Ål°“ÚÞãDZœ8vÉšÅíÊ-oH^±©Ö$½²}ƒþœ!*1; lnr`XaÖ:äÌ›œ·v§}K“3²è7Þøøão»í61Ÿ»îºëì³ÏÆ÷@Gurž|òÉ={öˆ¢³Î:kÙ²eú¤È÷_þò—'NüÑ~¤Ï¤L$@$µ²Äw»¬}Ã9ñ(¼ëk´•Kξ¾mÏD=hñã cΟêëÛéjþ(vƒ]ßöDã8‘'N †]”$3 3 8v£ƒÁ¬¼ž¶•ús6’Ù3ÛN0.™ ”Í‘À>'`„!À †»bä»40´Pü8ÕZïe,y;û7æÅ[;úÖ ÉÛr]÷«žé¦}1†LƒMHv¿Í¬ºxF^L-ÀÚÉëZLñvnºé¦µk×¾üòËÓ¦M+..F­n¸¡££ãg?ûÙ¹çž{Ùe—tÒI^¯ùS¦LùÁ~–Ôz‘eÙív755APq"‹VHH€H ëdï vY÷rsÂÃ0åOA) j¸¬¥Q5Eü8X뢖“i.¬EË¢q£¥pÍ$s«ü0ØÜÁ Ïd0,C¬ “Ô=›M.™Ç’-‘À># ö·*eÇIy³%S¬ø°iÔT”Šk£ ñãE®ýD”‹kªRr¸ìï‘\;±‘VÊQ÷¶çú·Ê oCˆkwêÖvCP‘­&eZ‰h/öK©ÿÄâ@‡p¬öµ;v,\¸PÌåŽ;îÐ&UQQyÒ¤Iï½÷^aèÒ'…Ú‹/¾[žËåÂöØÒÒè›i R  ÈxYå»@ƒ]ƿϜàÄþÖ¼šÓ,¥óaÃRŸc—!(˜Õ¿}#¬>âÇ!ŠœcÇ«°èŵG5§êø€»Ó×» iÍ…3Ьѱ®kÍóâÚº|kO (åZ N.TYذ‘,œ}Q¯×ÛµA6çc²æ¢™‘:Ì/¸d/OŠã$¨„µErLJJÞl%¡ÈfÄwóº9÷6ñ쨵”ÜZ©f4±”ò“ô æûÖ 77 #F›]›CµÖ¡_†\ž§Í®úý(_nïp§åZÜëbœãâÅ‹ámwüñÇ ý°$2±Öãñ žäûï¿?Æf©F$@$©²Êw»L}9¯˜hÑè ÿ‚ GIÀ‡ønNg_ïæG ç\µKñ\˹Q‹†ÏÌŸ~®^Ád_!åæ#ÈÑf·£Õk.§'yZE޾Áå¢ý¯Q‡ ã‚—Lãâ1q$#ÅïØgôÅX+•jÁ ö!ÊcÐ0„äTvξ҅€Õ,〣¨£ér¨ïmaî •Eeeecccuuõí·ßµ¼çV¯^½uëVQ–™_|ñÌ™3yä$¹%6*Ff’ @¶Èß…A«Ùö˜9_# øÔhÊâB”·½bý_]1á° «ÁåáŠ)žKЇÂ%Sг;H8¥ô+rçš íTɸ¶»´0: {lc»* ”n·kÔ!ÇV‰Z™F`Ö¤<GŽ3aqÊDØÜ6ív†å ÙÜÜl6›ý~ÿÛo¿ýÜsÏE*œwÞyÈ„w¼ÃI²ú$N™@V?l­:ˆˆ‡^$@$@YN {|h°ËòW=Û§Ÿ;ùçnõøçn'âáTÙ¼Éù3¾‰½´šÏÄKª§O×rÄDLµ3k™BE¹ÕÇ[Ë;rNôÃ"„޹hN~íYaÕ™Ì0\2eØåtÆ#¹ùÅþaÛ×J… q¾xrgBÖ:ô"7¿,yÛa­ƒlP<Š}µ¤3ØI=+o›tKJ6;ÅÛ¢žS1tä;µ5q¹p*‘K=bïÕo‰C²k™T°`oö ÿè¸ê0‘¡ÖtØ}[šœâ„>m–Cýq¢)P   „È*ßìòΰ‘}F wÓâoOÛ [åáSøÄŽÌÛµQXëÐ,‚Ü\-°ÖAVngË'zƒ«ù#¿«Y¸•`@6}ŽÝ8§b˜ÈwhM Õo¯úú f5¶¸„%²³ñ}ìöfú¿¦ãëÙ<¨€‰ %À%S†>XNküðÙƪ „Br v²âè.±Aîô-+p†0Ø ôH‰tÊ Ì0ÏyýAœ ‡»¢ÁqëtŠI€H€H ñ²Êw»Ä¿@l1•‚ÞÞî>)Ùo´n™”à wAýbL'L "À%Ó LÀ^ííí@q¯öf$åÿJÕùrãÓ’ß.ù»¥€'ÞO+I¥ìì`•ú6:òŽ«ÛÝ=yò䡯ªäÍ‘;–ŠR¹S«ÃáÎ×®H¡}¯‘Õd£"™Äý2äÉ’"5>%•ŸŒí‡†ö·‚E‡–xVÊ]3úwòÚj0É:Im#à1´<¹o½äíRÌd_»šo)WïQ/8÷íÙëlU!†ÌÎÎNlœLö#‹a T‰ƒ€Åd˜Xl‰£UI€H€H A²Çw!Ùæ=63 8v¾O±‚™ßJêØ ç]Ö³þ¾ ¯×ïjS°—G*Ž«;GÛRП7íØÁêéX˜pʺÍsçÚ¸ªoÐR¶À±k‰ÈA;T÷;š‚žV)´ïU¯Ù/Ë&Ùh’e£ÑŒP/A 5¯öWL%NŸ¥~Ï—0™íÞ½ú‡jD¹¥JÙqø‰ª)ÔN…è^¡ô®‘NiÏ?eÙŒ|¹éÉœ E²;e_‡$›%×Î~kÝij±=V(ÈAŸb0Ëž&éûìQ1—³ß6R?2§¥¥Åív#?é,²oæ ŒOYâ»@ƒÝø|=Ó~ÔÚM"*\’†Œ­£ÖòEž¶•saï–Ç f_lÊÕ«B¤{ÙÇk©ÓkÞTÿ¡)oʦM› úC)G[?¼š¯ã'R9z5$]MKÝ­ŸÂ‚Ù»ñÙhEN÷†¿%«§­62Ù`öõnÖ:L¡wË£BA zdƒÕ`߉¢°¡ ¿L9åÃ+è•£ÊömÏúûÔ…n²YÔÞ™I$@ã€C A äN—uŠ{³ÁN›iÐg—M%í¾)’ä®(ô·ÚÍG+MŒ`_ß?H˜í$·Rx ìÞiPðýRM¢ Å\"ûºdÇÕÌg›$¹wJê>Y³¤nõMú%¬uIï† dŒ÷]b'EÆ=HN(ƒ x:Öbvâ<_Ã(fôv£V›ºd’ªËdÜûúúFÑÎ0Uà+‡R Þsè®ÿô‰€zDÌd°ÖA0ÚÔ=GÞÎ/q7åOÅÞê-˜Š%“°Ö©ò"¬$`2™pˆ¡Õj=ýôÓì&\¼x1îaçf%žq3éa<Ô?‡¢ƒÑ&¬ujË…ÄѾ¡ßβñUÛ¶£n«] —››G;±¨†ñÔAš UgÀÐhr(Lª­J*Z„6`­Ã=Xr”Ú,°BŠ‹:I¾ “Ü›'   qI í<ì°d:á„>üðÃO<ñ7ÞT,–Î?ÿüçŸ> B—˜³oÐFÛ„€;ø&ùsÏ©<Úµç}o÷&te«84Ž )d 3Ø*PkrN}Gׄ¦n5 KIIIíÄ š[s*À uÓ®­º| ÙæÌùSpHEÀ݆dîä=í«„ùÌ`P·/™Ìùýö»:µŠ­üwÛ磮Ί$0® À¯ö‡?üá=÷܃YˆšC=ôjjjŽ=öØÿþ÷¿ãz‚Ù3øªª*‡Ãk~~È&•Ì™+88ÕZ-»wÃm-¬Ÿ=½²_‘§ÃòER™|±äÀ÷4A%o6¶šìë÷7ìl5Î(š0 ûCêt´ïèvz'+ˬŽ”òf†•Ž˜TrkåÊÓ°uWö´'œ‚îÛ9ݻוO˜*åN“:>MùJáAR¾ »_å®å’1W2H–2ÖºÜÚþ.ú6©xKŽFƒ#v—þº³X,0Ûåå%÷ب¸FEåá üå½­kvõÒ<óÇΘY‘ôqÉ¥$@$@$yÒË`Ç%SƼa…s¿U¥ Aß’=)[åaæÂZ_ïv[åa}mÝãÄ)fûO‰þGdÉ‚ë¼]$%h)×z#v³‚ôÃÌj«8ÌRŸHÌíS1¸dJUD®ÖºgŸ}vΜ9höóÏ?‡µî}ôQ¢:b;) R»¬Z–²°Imn3ŠsÊa¥ýɽ¦7Å×+9wàÛÈÊÀRE* Sno«ït!ð©¼S:bNÇû’¥D1‡wV%J^äÎøúGÄÚƒ%Îeª•rk´Pzrç•ÁN­kÿB½#æµbÀZ×µL¶‰¤öw¤‰ç U'—sG…¤ôÁ%bðYÛÆe©_ïUZçW·öºÖ6öÞ±dÓ¤"ÛmgÍ ¾‰Ç÷ñ±ëg&}²çYs¦$@$0"ô2ØqÉ4âG –’y)­1§?aÝ}º¹ÿ»_GÎ) +IÍôpwa_ªA–J]ïØäð@Û·®ÙÝéÆÞÔmE¹;_7Ø&˜rT§¼¸.¿«VET±ö0à@îsÑt-”žs÷[Â`wë2܃>‡1·J³Ö¹ßw·-G>œõŒsÊ"§Œ¢Ñ]ÚpŒ v£c˜úZ\2%œùM7Ýtß}÷ÁE+á-³AŠ€Œ3p/’ºWÊ}k%i]—Ë()¾¥Ñ.Ww*Š\MRü;¹wlm±ÄÚ“qò,4C!í$ûZ©¤ÿk0ÇÈîmAõ(LœÁN;¯qE@|ô<ø½Cô£¾û-ë{?ßÑyÈ´p£³^M//Y²„;=!Ów!’ sH€H`(Ùà»^;ñ$¸dêd~òøz·¢qÛÄcÜÍÁÕN’}K¼§Ç´B©É.UµyKsp Dü;wËÇèžqØ ‡¾aÇð;ÕBi‡M²ž¶âYäà[­Øþh°C³¼Æ.™ÄóZ»víw¾óuëÖ‰ä 7ÜpçwŽâQ~öÙgtЭ¡ »óÂZТ1D6^WŠ_V!Ñɤö’ÔÆ5)è%]LŸ>[¡µÁë…¡òõ:^iB@êw²khßâª×„l –{6J®¥Û¤©¸ë»°íN)·Wª’Åê­ßÕ=_éŽ>°°fõIk`F‘w™ä‚5Pj•ФP è¥Ìc0íB³uï|+\ ?ð´³ìÚ›i ÌѵP"ZÐw1”¬ŸK¤Œã8q[bEÑèžàèjEføœô’‚.†Ÿc,¥}_ ¹f­Cε_›ý›76Üÿa݃ƒ v¯¿þúgœßÌ¿ÿýïñ¬o¾ùæY³f}ÿûßÇ/Õ£ŽRc&._¾^ÌO?ýtWW×m·ÝË2[‡¾ ™ý|9; Èß…Äì¸dû›—š?ÔRÐK ºíXzñ¹­¾ ¶©W]]‡†º¼Ufçκ· ÐU0G’uQl ´ùŒ½Ê$xäùšìÇJ}q¯Ò †ýÍÎ÷Nu#IW_™h±x²¿ß°kïâßêì“ðƒ#)ŠŽÖ2 ¦ÍÝ{[pT`ˆCM',D\f¾ÁÛ,jiÝ…52|rÄ.†¯ci zIA1Nv5.™qÊÊÊÊÄ‘šF£K¾úúz¬ q-X°à‹/BÛú†8¸èÓO?ÅŠQìŠõù|(¼âŠ+î¾ûîk¯½öŽÐ%ÔþóŸëëÁ~ËŽ>'2ÞÉäõ’ÔÆ5IíÅëõ"ˆl0h=Ž]¢ÃÚùIË—»>‘¼mJå™a:"ÙëÁY%R‘ žm5Qt™5’¯VvÖ#Þ٨Oö䩦f³«ÔâP  T|àÓ¦I…¶oì«+“:ïl§Ä:릖:¤€ ¡rrò”Y—KÎí²âWòæèFy-üéS¡ÍV­¤Üm’»?S*N¯ÉÕéîÕ{VI®z©üçTì½ôsÙ›þì@w:°Ù.æ`R_-m¬)è%5]h3µ°ª¡»¼ ü+ ÑÚ/Oßk§.§·$w@áÉ'Ÿ¼ð áLœœ—lmm5 ®¹æšË.» A«ñ{ÕårýóŸÿ<þøãßÿýQ-“*Òw!“ž&çB$ Ùã»0Vƒ—L yÿRð‡Æ™Ô^Î=öºç{¤YS矚&Ã4¢Ÿˆs÷;ðG+œ}Q¤>–Ñí½Þ "U üí©¶7gzÀ=Û×½Y „'õ]ìhuɿГ:z»[&åûl•¿†jÝõö¦É%yß>lÊÞFüÿô€ëwó'ÞžMe…=’ßÙQ÷Vnn~Éa?ïoúmîöÕFk©¹@u¸Ø{Mw6Êî=Kóg~ÓRÅLànþØÛ½9ÆyóÀ¡{ú¹ìm'âÿÓ¯òvoQ‚^ké ×½è1u½j¹)è%5]Ä1ç!T¹d,ïÿô§?Á )àŒˆ?ÿùÏX † “¼îºë-Z$<é>þXu†}à¦L™‚ü#Ž8æ¼aê²h€µnÏÕ%Ùï÷wvv––ÆnÃÓ¨å=ÏK¾n40p8¨!²­B+Lu1_楨$L{k‡)ˆ0Irz'Í)¯Ðš»áuõ«šííî=½îóöm_'¬u¨(·¿£(ýaõÄൖµI×]@IDAT(ùó4¹_èZ&!†Á5†Üõ™ºWãÓR4á­ N'þ`ÜÁí3•Xˆ[7LƒkºŸ;(fÈ /¼ œé`¤ƒÝ»ï¾Vý¡‡ÂñÜ]tÑw¿ûݰ¢q”Ìßü”¼'’ÔÆõÃNAG)è3JA/)èB<šÔt”‚^RÐEʈ¥l.úž±ËYå»0Vƒ—L±¿X¬éw¶Ø·<Ž šœŸ8͹Õ_KÍd{7>ð¨®sÁ†ƒ";P‹©®¿Ž\0NœÖȲ­=b³N¯³øÈ9SµRaÔßÜÜ·»ËqýÉêáz±\W ¬uÐtî|M ‹Gujð¶ …µ‰vž¶å²É5†³ñ=ìŸE•žõ‹J#¬µ°¤)/,ŸÉô$À%Ö~Q ¼í„Å-jé0™+W® +ݵkWX“éFŽu,>ðÒêëëK™ÁNXë’JCXë†é6»aJËÛÕœâEJû»8ÝešŸƒÝ±1_#İ YëDä;DÁS,á!ecã€@»}¸¯º8'l žd…¡8T_„|ó›ß S¨¨dà +Mÿd–ø.`õ>Þ}½ñ.%uâ]MAé3‘÷7µ<µl×Õ'Ì\0e8;þðÿгШð(b,M±¤vÆcœé0jYå»`D,EX2E:8 ¢X2Ååà ºÃ’ mâ_Ç!K&$éàËãØW:ˆÈ†®Å¹¥žŽÐ×ì)аÖ%µ«½¡u†ì6»!Ë" <897)Oñ»$_ˆP.C‹a%q~…^[Xëù™" ž¾”r†õ’ Nd8›ïë_ÿzñ¸dB¬-ºÜƒ>yëV5%¯ì!PX¨zÀ{Q‘z¸\,ám—t¶Á®mÕËoLWD ¹‡˜ËŠºA[½<{`¢¸==žRÉáj­î•’©X6äúCÞvR@„„‰s'ú; 8$¿ûU½…G Gùèaë®Zêë‚áOn¥hâEÈ5Z­u*“̽JóÌ86êüÖîVƒ{ÌžXµ1°õ¡àUaüfÂwBk¿|°ÕdÁÝ;®©Á­ß9áã —æîýÇ?þÉW_}ÑãjÊ$<Ëë;`­Cû÷¼·íÓííÉëˆ-“@T#ú.„ÕŠÝÝûƒ>«»o“cõ°Ãè±dÚ¼y3>œ cÉtùå—oÙ²%±¡dö-#ö>"[Å¡®¦÷p–4s*Æ=è³c¥a°¨Ë§ä]¦ü©þ¾Ðùz¡>|} æüš±tÙBa®±ÇáQWMÕY aqñÀ2é«ó*cï1·úøÞMu8×Â`- úFç&)·@RAo¯Á2°‰ ÂâfÊEÈ<õ0ƒ†ws@2IMRƒd·«V’uÊÉ¿?cþç»ìyã¼ÊÜXï×Á®Û)˽kÕ8t†S°AöûS‰Ôø ìwJÕ·„š |¿•ÊÓ íï*W¯'§¡·B±VY¥ýgÕH…ª·”6`É:Qò4ã£Hò6ÉìÛë=ŽÁS5m\qìŒ;–l™°8e"lP~w¸ïK®1ì÷Gû`€_àaÕÇor¨º{ßgÊ‘ÇBà“mP;ãÀª×¿hZV×yäŒð]J±4B5Qû.Œ;wï±ì¸dõK–a±ÓÝòYwÙV¹¨gݽA¿êï`0åÍÿqòfZ0ó[¾ž-AŸË:á@l,b7¨¨µ…ZÃò6g=–L%†=]k¤ÎNõÃéKÓé~ïØ·µXMq9c×mñ‚ŸxZ—cûªÁ”gèÛ0¸á׳áoKqÑ~?IþÌo;w¼ ˜]NËŽ®RSA­Y™wмI°BS°)o²ß±[ ¸ý};Œ9“`°í𞩸d“…Û¤I“^yåñ”§M›†Ã"àƒ€æÇw\¦>zÎ+*˜ê::Ô_ÎÂZ§¢ê'&3wÚ‘;ÞÃ]Éï3ÈJÙ ñ6>¨d!íµ3Ú׿‹¶¼mî *Â$]èÇA…‡ÔŒê«)Ù,åNUzV¢)Ÿ¡Ìäo—`°Ãå·«wÌ¥Oõ;[…µɆ„T==R¡×6[ýtÙ«QêY&Ù¦J¾„vUŒê e_‡b.S‹xe"™ù“Šlë{q&,N™SÜÚb¿ó­Í#O}î¹ç4 8YBÈ¿úÕ¯ À• ÷믿^SÊò¥)¤­@ß…´}4Xò,>´fÝ«ë`­C“×[&HYå»0Vƒ—L‘/PÖæÀçKqÔaúÂZ§’ÇÄ\4;v¼†;öäÂû rÞ´oÄÛ£¾d!IÓE ØgZÚ8—7R öb±äûëÖ¯/;zÿýãíú²Ál*š%79h© x›Å&Ö ·[´†³& 3ànC¬:%dú¬ï„¯¢ê6â“‹å¢j]oÐÇݹçKáLŸc·$›„W£ßÕjÊßAa´ÙQˆ$À%˜À— FºH8d™ÉH£ú¹ öŠJ!9îŽô-X'IýÖ3]3²¦:Ø£áEèSlùV«®,>655rjè‚ÛxXHðŠRÙ×uý²™’Á*=û; ÙôD|ü©‚I²þR›Ûcaï ™íô¥”3ŒÀmgÍÿ|GçýÖ‰`¾Úì"­uZQf ô]ÈìçËÙ E ªØvÓû¯ÙÕ}òþ•VSh±4”*óI ѲÊwa¬;.™ýú±½Ñ€{ª‰½¢BŽ·} ¦üÉRgx£Õ,›=¯ø y%¹ñìEܘQ·SØ`´ .”Œ¡½±°Ö]óßÙhƒßÜüR×"Øëö^8L bävc4ľ¤Ð¥ïbo=þ?£pÉ„#}ôQ8øÕ¯~UJó§ËáeÿÊ]öES·£T‰*íµÖ©…ˆ=±›µ¯}Íi¥–JK§±Ze|çƒÿ‚åÆeÍÛ2©^uõ+ša»A]…‚â ʉHÈ-oHÞæP¶I=÷Ö¾A)è&t3ïäÐÌÈXô]ÈØGˉEèèó þòŒ(ILF¯ËçñË T·ƒeuÿøo½Öîž7¶áb¦µ@!ód‰ïÂX vxð\2eÞÛŸm3Öºþxõ/kÓ‡œ0~ýþíMÈD°Xî>ÙÖ~ÔÌøBB(:­MÄž‹ÜÍÚÖðéÉ…– s§7 X 2œÝ”` TQ-kˆL7¹dŠÔµ}UCW˜ÁNk†ªOF•û¶=pîF‘ÁhV^OÛJkù"$µðygh‘µ23I ÍÜsÏ=W_}õ%¡+lhˆ\þÆoœqÆaùL’@šÀŽZxÀåN•];ÂFX‘g®÷`­C~mYx…0å’Ö:¡ßÈwÎuË*‚¨̸#2ϧ:ÿ•çymf­<{$Å «¡"%SžèšwÈô]È€‡È)ÄBàÚçÖô¹Õ£²<óçõ{ ÄR1Fg–ïzwc ”ýAÅd¿Ü­F÷þëÃ(AÍlìvWí£0ÆÑPmüÈxß…±FˆÄ’ O+¦>øÀét¾üòËšƒ–L¯¿þúøyÖi¶!ðÌE3#AL,T¿êµ÷9Ç«%ÒZ'ºÃ’É´(Šº`K&“I]2!27 ® * Çúù:„¾CSÁ€O6ôO!zçÆ»ï¾¡¯N=õT¸× ÷7ñü£¢¢Â`0Ølcý÷2î€d瀛šš´n×.lùr8PÆ5²jHTÕŸ®ÏbÔj8&UT”|]#VØj«’òfK•ýµüÿ=ºª$Wýnõð©…]{Ö….¹óCÃÆŸ6\«v´ã^õ^wî†=/„õØßŽ{'ÜëäæW´ÒN«êÇkî!×þ«ã˜:WUžÕÖè)›SÔ£ÈÖ'vÌ}§unC¯¡½·çá=ßøê¬"1#Bn[‚d׎׶ìÚm¨Û….œ{+oû¼ù“…&òÖ¶ÍXÛs¸ËŸ/™Š¤üÙJÎT¡‰;NÅ5q¢¥fÓ¦M›7oF|dM ¤?ø.<üðÃ8 <''笳Îjnn~òÉ'ÓØ! ÄE@XëP¥Ãá‹«bŒÊÂZåm-½¸[ͪ™¢ÇÕß×ÅG©KÖÒ·xeõ¯À±\X2]sÍ5§œrÊ-·Ü²páBD!Á’é™gž¹ñÆÛÚÚþýï¥qÖ/z7=ŒCsMÊÙ¥EÛªmÉ~š"žµüÐÜê†×Ô—â˜TûæGS8÷û"rœ¾4LÆHD/¦ü©–Ò`Ss~©®1´þâÔy7¼ðE§Ãwì슆­ë×ÛÕcüMëëÛö\á¬áhL?àé4ZKÑ:Õw!Ú÷ön3ÙÊí[+˜ý=Qêpf®ó]ÈÇ~;¾8²ü5ìèµÍ.ìÞd¯x¼~ÞÉU…³¬õÇS=ßûÛ©“ô(ìÛÿé·×·zJ[ ¡…cíŸÛ‰Æqî­{Ïû’M‘»zça kš§úíÁÚœU¹6Ké|Í4™?óÛ«nC­²E¿Æ}éÒ¥Uþ7MF¹dÂä¢ý¯­ñNéF ®®«ú)S¦¼õÖ[ú±xàX,Áf§Ï¤œ‘ºººpv*¦ qvÁˆsloo:0 êNny©¿AûZ©p!Žî±ý~…=ýå=/ ³†®¬×Q¬U­9çÔÔÔèÕÿï«j²§§g×.W¾jzS Ë}kS¡l*Tü½8£V‰Ð¹]Ê¡¯.Yöº„{Û`LTÌe(õŠ´~C¡ñ¦#s¦$]+I7¼^ùÍFù-¹Z–¥«WYÚV÷7y³$—jMk“æI8æÈhƒÍnvÍåý ø2á0tÊA¯ctï\ÿù›’­F²Uo VÏŸT¿Yü),ïøºBoooaᘾ¥$øüÍ-õê¢WneáI5%G—hgœÞéî=N‡¶ sT—OõW˜ß•»¾Cýœ_U”¶ÃæÀH IÆj°ã’)If5ëj|Ö: F+“}¹$©÷siûCa~Ò,bCéÃ(ЇÍN(àô;p‡ßׇý®E‚­ðÝ76Î4Ôî’j=âXM¶Ê"¤ûÖ'ë_+ ¬“á„ÜŽkÛ*[ùÁzeÊiE Ë—L“'OÆÚO¤¾¾¡ë8â“i¬jiõ|9˜á ˆ§o6›…Ùnxå¸K}ý¿êÕŠê^Θ vq÷S%ôðÚ‹¥¡,¥¿Hý·€³ZÕÿé¯à€+„âÇq±ªÁn˜ëò¹n‡g{[ Ä˜œç¯ÈóI>¦/ãÛ+¼+µ¬"†0PchI› ¬í˜\–†ÖeÉ>&ð¯ûÔ?HòŠmå5ùŽnO[ƒý“·å—ä÷Yûxdû¢{ú.ì êìsÀREìKMR 9­ý.:ô±Oê77÷Ú̆{ßÛvÅWj×5õ~²½s>bÆaû€ »$$HÀÚ†K¦$?£to^QT¿£­\5Û…äŽX=½T»>)/¬ÖÜhmE­²Xº`MeÄZ#L1ã´ÅçT—‚á®+÷—:zd™-P˜Tè z–IJÀ©UV” ná¶7¬FµdÂÉ"©«;¿œ%“¢_²îm›ÿO\2i¢6tiI YB ´´Ôn· k]\¶Ú’’’)“ΓŸîW3Åãêe›,¹w£¢~ãçˆÝ¨€1‹UUMs±äïAŠ1_2äI–2Éí”dõRɛ֔RµXìKUósjÂJÃ’Øq«j™¤C'ôù£?hhõÎÈ-œ–ßÓ‰U±U‰O›Òå6ªçºV5VpÌÃ%’Éh\[;³nÏÀ' ^Y³.æååM˜0žØ‹ÏY«ÕË3Ò7E9eÄGÏiW-Ð÷¸üõz˜íölëž4³XŸŸ 2}²á)sŽ‚@’Lu^­ý_ž¾~„}ðï{žøÆÂjM“ Ùà»Hû—LÙù/'wòI8´A8Ùù ŠBnõñ#*λ¬gý}BÍ`ã¯@sÁtŸ½ÍE‰ü¾qv¶lÙ"Æc²•<]ˆ7g0Í[,qr(V·µ4|×OÑ~Wˆ ­ê kE Cݱ)EX2^éòduÉä›5©j´ûIQÅœ?U6¥ÛcV?½fWrWY‰%“$ –œüiN?hCÎaDÔÝ-ÅóàŽŽ{‡Õ¶t©;×ßhRú¬Ö²œªãtŠÓˆ—Liô08”}G@ÛÙŠ5óP£hiiq»Õ¤šòPšƒòyÚ¦ÑAù#%”ŠSGR²\îYUáZ*7 tÝ·þÐT8å„ùóû?Yi~4ß¶!›œH®«Üð6*è‹ìÛŸ—Ýyø®gþ¡_ÇÆ#Cã“JÀµ®UÚ±µk¾êŽorìªíÏRªLõï;¬uBg’éK%è‡'£ßÝŽ“. z (Ý=èëÖ:T×¼ÿF×k¥˜ÀðK& K&ý°dÂéÑGÅBäcÉTTTs±dBVJ]Ä’ w±d‚ –LÒóZ½zuyy9â^a^8ì(=ÉQí+ú8zy_g¸~ÕHyC^Š *ÂJc(P‚Ь`«!?ˆÆ RÐdð!΃ˆ”‡3.Ô|]\<¡†=Ä ¶¸k]EQä]Vý‚ŠT`N:(ŸZ0ÌðZêt‘LðM§ÕzÉ%—àÓD{+Ö¬Yƒêýë_aÈÃßÇ<ÂPâì‘Oø`qú'™UTˆ{ãârI'€cëñ—™öËöhDIÛ±cÌcì[Þõ aÛ­rKd·1¶Ö_¶0#ö†®IçâæšoË0œÆ ÝÒú¬äØÖ_ªûŸÜõ™Üü"tP w9¹Oo .¹ãC¹c¤ïu&|­_»ìJsì²ä¯ë*—ÊþG2õ‡YJ†&ïþøTR#å)~Ù±A ¸”ÒcvvZûTû>®žŽ]-;–Ëîü ¹~ýúPv”[gg',z}öNɳGòã 0Y©¾0Š³Ò˜€£g8Ûq~Yÿ‘Žb+W®|æ™g®¼òJü뉈ßÒHâæ¶mÛŽ<òH¡‰0”ˆ`xõÕWßvÛm0çÁ‡¥,wÚNð4F¡‘ ¤”@Vù.$Æ`Ç%SJßÐñÖ™·s«é=%Ðÿžl4u­¹½sõí}u/Œq*ØJÓöñU½[žc;úêˆþ†}¸"§`Î¥*ü!g7ùvoý»·kƒ^_È®Æwq -|ÖP o5wûjMÇÑð¦cÇkZ2ª7íL‘Ÿ7õ´ y˜Ò6µC6XKD~ (7ùö7š¬FDÊ úÜ«qØ¢®ÝißÙÖoClÞ½eËÿñõnÃj}ôQ¿Ÿ`dMMM0êuu´»îN¿£ÑZz@¤sÒ™—Lâé\vÙeÓ§O÷xÔ_/øf ÷aÞüt~ [ ôöövttÀ(ïËI“&¡}lă ÏápÀlËݨ{”·üRµ‹ùºåÎÕ˜n‰»”ê ZsÎÁfXŬ:ÃÚËós VWž©Wòî‘;Þ—}úÞäæW$ûZuê®GQªu~$;·ãÿêÀ[$Çö¨#ìïåÖj –̹@±Õà65 j4¶¶û`ÅRYZs”:¤Ò¯H93$ÛTɘ³¥ÙåöÉ].Cc¯úgäîúµ¸Ï,mž_ÑXS°²ìUkøãÓ‚Òô¢ÏPf/@¤<Éîô54¨f>^ã‚@n‡KDjëN5¿¬jÐÆm檫«_zé¥Å‹oÚ´)¬âÙgŸ —:‘¹dÉ|ûyÕªU•••à®YÛÃ*¦g’¾ éù\8* Œ$=¾ ‰1ØqÉ”‘ÿ 2)wÛ*Gÿ¿K6Z f_Œ6Í{;¿ôv­sì~{,;1[–^îí\ôtºv¿9–v"§Y´ÿUž‰— œ)§\-íø°¤Ð†%S¾©×çØåØùºß5È;þå1wÛçØ£ÚýåŸPŠ®Ýÿv= ÌÛ¹G:D¡èYŠçjØ}̵æÂ™øM k›ö¼s¾ìZhÊ›<}Áס™;ùkñw°©Ó³©Ñêëרºý*;žÜ6­Xá²eË´f5®%øNÉžMûLÞNƒ)ÇÓ¹ƒ×t(¤9.™´„åì/ZB~þ å¢¾ˆrV€ÁóÍÍU}Ö´0v‰# :;'ýò¶)þ>5ô—dÀG8óAq©¯Ë«îWÌ%ø>IÝgZ¼HMömPˆSÒ¼™ðé›d°ææ—`Ë9š‘]!gñ"÷ÿÏÞ›ÀÇu•wÿ÷Î>£‘Fûb-–e9Žã8qö}% I³HZ”²¦…Bá¥ÐZø¼o)ÿRÚR „¥P–PÖZH !¬NâØ±ã}—%YÖ.f_ïÿ{挎®F‹my$Ëö=ûêÜsŸ³ÜçÎ̽ÏïþžçÑJLØeщ<ÉŽº»M¼â èl‰Jƶ H?”æÀí¡ìV9KÈ[»‹PkojaU䄺¶õNÓ¸eË0; ÌÖÕ«WôÂ2X·nÜ…âv–ÉtJ~ ŠrRñ¾GRép§S×ijþq—…0™ÒÑ^ò<_TÝA9êéÐ$jœY 2‰!¸xžú+ÙM o›óÙ©¼ k‡ÖíÞòÊ:rí1`jLXbL7ür^·ø „Ój®†‹>N½4+’v¤É ©´V†ÙF ñ;wvƒÓØW²‹µ]l°L&uE eð‘&L6?Vík×®UG­Êé¬RRrú$`[V–‹¼–S‡DŽG3ºÝÏýàxF8Ú¾%í6wuV|²³d"Ò¥š夾%íìê©ÍYmèNmtƒ8Zqé ÈèzöÙ°VËËË—,YrÖYgQ—3eçˆÊèÒåÉŒ¸ûT—LÜÊC{þ›–¾ÌYlbØÉsì ŠëRîVG=ª[•E®Šz \/$;rª¥÷D¦ þ'~œ)ò "$È+;Ê`v÷ÝwŸ¸ôÒKeû#<òþ÷¿ŸúÃ?Œ‡¬l\ü[‹»°ø¯‘µBK–N œVÜGQ.&h&£Y&SQTzÊ âkº1ÒñËä¨ðƒðÔ^„olþÔt‡Í6‘Glçëp–¦3QA:˜çâª8 _W#Ú ¤¥Û=6W¹»ê\óœÀ§ƒaçðÖóˆçrVø¯3ËkÌŽ€\‡"9_ìv»Á]{qjlSxtÍ%Äé7W‹¯ž,û_ü÷ Ÿ6hœÍ®z,?(þb}S¦cØßÂ9œIj6`:8üKÍbV}1kÀl2‘˜O.“iÝÿ öÖ¿ÌÙÕ¦&“jÀdRue2a5©F*˜Lr“ÉܾØêÍÍÍÐv b¬_¿þÖ[o%XÒb[¡µž¥~ f&˜4Ì¥K—¹K¥R¬‡Ç•£eb†wÚ‚ë³Õ7knÁ2“Å([«Å» G¹QºZó¯o>Ú¿"ò«&—·!cT]?µ›ÞÿkÍYeT\ªÙ¼HêeçºK³¹Œ²sE¹Ãié1Í¿ZË&ötnKÚV7-ñhahtñCiWãƒCŽ'}5š»N¤ùVÛÝÝ V‚–Ðy<ÕJ %Jf¢j•TP·Ù w£Q}ÃJÝ>Ë:íZ‰ËІžm®6ºõÎ`¥aó”¸c-¶×µ×›ß§hOK ¹komoo móµ6Wei™@î ïqH#y|jFâÁË“À«j·*'\×¼mÅá½£„ô– ZÏÔ[:t:T,îÂép•­s´4`i`‘hî‹?ß ÝûâÛ—,iº7’_|ñ,toÌtï;¬ E¦)˜kav‹ØY&ÓÂ\­“q¸uö•Uñ¾Áì@µpêÄI6‹K©ŽOg£«RàJG,‰ÁͱÞgJÛî±ûD"YÜu—ÛÆ:ìî wÍ…îêIÚ¸Èl‰|çð5ZY6]Ò*¾%´÷'vo­¯ñzФ§î Ýî²ÙÝžºKLjlÇ·³ÉWõF&¾yÇúÑ„í‚öe˜L¾¥wf³žæ×¶v4Ø®¨»ÈQ²$vøúŒ¯vwíÚÅs$†åyç§ÚKKKÏð ÿ²I]@*#~îð·x—þh^†U/Š,“Iª‘wE£££Òù±(е9•4€c¦dØ‘–„ŸY3ÏîhNSY§…¶<é}ÿ«Õße¸ªé5^ÛÐ:ó.¸^PB½-zdááÌ‹©ý‚·-Z¼Gøæi°Wx—’Ñ»¾£ÅŠe„·'2ZƒÃÙâL§†eËõ¾_!¶cëÖVý bÆÆ‡½¶ÀìrkV#¨¹TGga@Ln7ì*X%3QyY½.ë$x (ʽbOj#`Ó¼UöýÉ+ôÔpí¥’¿6ò‚fã SPçþž¼{êßìÍl¤nÄ{Œ’TTfg¢ ªÒ" `"® ˳’ÉŒke±ümh/¿µ½œtää„-©pW7Y4‹»°X>Ö:, X8 4pZqŠØY&Óið½˜û)¦£‡%ÃŽøn@]žTøèKôÐïÂÍgl÷ƒDÁsø„ù¡BÂeƒs@ëd÷Xï³6Ýæ(mKnÚ’h^’»÷»iŸ?>Ùÿ3G©@î“Û•ÌèÖMæ;ü!™Ñjuço*Ô}ÀQ»*¼÷‡ˆ~®1ý(É #]˜L@“rÍj5’Ô!Óa“TVV²; „;ôûøÀ+ÈC²#Y„Ý[SW.¸uòtx^>»A˜LZj¸Æx>PY;ô¤M…"„ø#§#…ËV¾§4.ÂÛ¥ÃÙäjWå¥O*æE·= Ä¢½Lço{ u«, X&×âßøĺÅsQ¬•,* H´ž2¯CH@ ,ulË‹ìBÞðµéÑýFü°–ìŽm„™¥u]^³–)üñ„ˆXš_eÖecCsè™”áµ5W]±Í„g Éèd:֜܅ŽPå €w3y§Êä³R:ìB2ÆN..{fåÊ•úØfmÔ/Ål£Üe®R9´NŠÓ—ŠIàZÙ^°•ñ þ±<.\ÁQkw‘hÀSâ\º¦ð“°HÖ¶ð˰¸ ¯skÆÅ£±Xêc?ÛÜVãûÔ‰¨V±4°8}¸ "ÉñL&‡ãÇá”Ô@*¸›ó’‘Ýd¶c:ÍÄÐä§FÁ;¦AfÎÉ£6kd úfrëlváÏ«k†® L&¶Q—p):¸Ž­tbÅØÈfŽllH“Iº ‘ÄîÓ–ÄÐ&Ú¥*Àì¦Ê`2]vÙeç­¬ Ê¥XtÜY¢u²‹ì[ 0u4V‰ÍÇVfž*cµœp H“é´%8<ð€À”eT²ãMv¯¦µ€âj@Rê@ë–:t-,5Þ *7´?µ€x½Z'º”­Q§­è¹ü‡2qḊkjHn„e¦“³ÄÔ¹ûŽ®çLtIˆŸÜ‰â„¾I%ç–k)ƒtg2+^Ø$³âÍ+h]4åòÖ²¹î ÆÒ?7@½ßûæ–ʸÆ-Ò»T#1+Þ­þ3¤ ÛÞ¿Xÿ‚®¥Éî]R³fs_r(¸zEÓÖ=]åÜZ‡‡—x3].yWr™;ø\Yü¥Œ­´¤ó[^±8À¢žÖøÑ>Ôµ¿Íùlxw´Ì1ÂÂôL4ݯûÚD\<<‡žÑÒC%tVié½ç¬ÄhûÎB´DöjÜãÆq71ËØÝÓ#$v I‚ñ±;~"J ”ÌÎû-.ŠŠ*ˆo,íXç\ŽióWHó–(ŽÏ²ÅìZk–±ê–æOÝ{þtk¼È5Ð="’;5<‡ƒG¦u/òs±–w2jà”§{°³L¦“ñý`kÿª8÷ãñ¾—œå+Çvþ—œÈSEbèõh×c+ ¬þ€ÍY¦=u;Ë–A fR¸m&‡·’5N5*¿N9!Ûë3‰aþÙ]L2è*köKð;—Û_ÛzÑÆC±ÑLåMç®xiýRRì˹µöô´»Seåµ{Æê³ý¿õGÖeíeúd‡SÄ–>˜LðvnßÜjÿÃþç*sއÕTp§3pfIëÙøP¤ó7éx?ij¾út|0¹í+t¯¹ä 6w±ö’£ÛµlFw ÀŽ‚~âýë‰=gÄ‚âm|¸ž:%0¶û{¢ƒÀýž†kÜãQ•*|M7ƒx:Ë&â‘Kùi·œËŽ;ä¡Y|{§ík5Z˜ƒþó?ÿs½¬.§àjÏ ÿÐñBÐCZZÇ!|H‰"Ê0YH:.˜û몚êþ©•L håÝE"[ëÀãZý[ZG­£€ÖQJVÑý6#iؽÛlTä/úÈ‹T¥£h ùŠæ¿;ÌÎT¹“ÈÚK³¼"‹ì]sÖû9ÞÙÙ)к\Ἢªª»Ø‹„õÊä6Ð:yHK“Ýu¼žk’ Ä;w6;×ÓʺӆÝîiÑ"»lFBÄxüL‰­nÓ³Qhé:8]&¬w|Õhÿ”8SÇ‚R¶¦ðíä®(9Í ¡LF¡ufw Þcd¢(˜Ç¼«Ð:!NJæ YÀª[˜W XÜ…yU¯5øbÖ@ku Ë“hÝ’r{1/ÚZ›¥“GÅì,“éä¹â'h¥dW­¿"›SÓÇÈsZ…BëdÞb½Áç ï{¸ìÌ÷(I*k㟹…zAŠøàkBÒS£F­£´Nl“Âp¢K*¸Kx$iNœ}²™¨Ã9a›E»…e‚£(Ü4çèï]mïe—f'+r%Ð7–‹³<5bYÍ3ºóê+þ––mÛ¶ÖI²»‚bkš<ج‹o­“‡8S›+ ër+ RÒ4ÚE\¹”áÎvOi[jdK6‡XGš]ÚAëØs÷Th¤ˆM§ÆF6~¶ê²/r¦6‡¸Yš 9yÍ»Ô N¤@€ü ­“Ae÷Th? . †šiW¡u@`Q6ØLòV»¥ãÔì~ó›ß˜™3¿Øâ®»îÂÔþùçÕ÷Úk¯•#¿ï}ï{Ç;ÞažÅªŸ\0óæäÊAè¤,ˆ€t¥Ý”`w Dlö |`óþÚ…&“Ãß”Ü =Iq|mêJ\á ZWp—+EÙ˜)n7] Ð:5†b2QïÌpèqh ™èá\”"‘CÊ8ìž4A‘P•½ÄÈ$Aë¨;ÊÚÔE¬ØÜ1ƒìnÈs)³(a.ÃY}, L§K/½”Èô¤óÄO,[6—_90YÞ„÷|—Aý>|ûí·ÿõ_ÿõtÓZm'X\&˜q$‘t3ÃUàn o2„hÁëêêúúúh”þ’õõõ¼YaÚÙB»ãG¸¢¢¢ —Ú%DaâĉölBÏÆ G¹€®hÁ 8±—p¯Oá$‹,|ºÔˆŽë«î€˜fØëD¿æmÕ€Àrbb*¾ezÙ9ÚØëZÉòp¤]°ãrEÏÂÅ‹iŽÀw_éÝÑ]Uç½û܆þôõÑGì¶l¨î½þœ§€BÁÁšÊsb•5™]Úšƒ_÷¹t­òJq ›Ì°itåÐSË›:–»“£.›®Ù ±‡ˆGüVv¤­~ð›z²Ç ¦^ímÚà“Z¼“%ï HŸQs“Þ÷(76­á-rXóvddDfpfUnª„ês7Àž“a[‘Ÿ8 èõâËå ®ÂL—cÚä "}i!r^æeXuK  ‹»°J¶¦X̸ïê£òÅY̧`­ÍÒÀâÔ@q;ËdZœWw¾W>…‘€QAä8Hmøªâb9u¥í÷†öþ˜v™Š´tÅ;‚;¾•M —®¸¯Ohwβvï’ë¦v”-Ñh¤ãª³{ªðÙÄÍÖ<Sà׉¬ÍY’Ž [·»ðµù–¤¢‡]e+õ\⹆‚[—©é‰¬w•¯JgÏVk€ãf¤£xž~õé=›º‚ç6ÞvQkGêÍUÁ_xÝvûÊII2ö÷4-©Ìy$å¸le`öWuƒ_ø\¾æ›9|rU˜< œÉ”\³²aË–•Éð°Ëe³;²¤ª`©ñÑu»\áÈkÿ¤…8}þö?‰tüorlŸÃ]Xý¡lb´dÙ[Â{ÌÙ•®|Z¶ªtuuuww³Ëª€$d» Õ‡ó‘ÿŒw‡w—F¥êJ™¨Â»äjÙ¥`»«wleý„ç²<ЬÌ{KX1‹ãP 1kw>4ðéOÚ<,l)  á™ÛX‡!ûç.¢‰Pˆ&ùØcÝyçT,ãÿˆÚ[`œ X¨ âÆÅbv‰ø¨Øg¼-Ã*Xf`ˆ£9t^ 'Ùyó® Qg3ò¿‡úÐÓZd2¼ùÕ2HO‘ËP¡z Ì.¸Q x6Oþõ޳RìoŠb G@+¿X S1Ê/ÑøG‰tÊvùO¼Ü.°?MÛÑûÁ«‡>xEãçŸzãp4­m=xe[ù«+—jOhîƒZÿØ¥e“ƒ€Ùi5÷陈vèGZü ˜Ý#À.ÒêôõÎA§\j8¶Ç.6<—jñØÿ³p:Ë®» q=zn ‘c,j-÷É‘EbŠ(Œu‘þ‹t*˜QטLP^ î5ìrQ¤&·ö78Þj‰5 . \‹Y.‡ºÊS;NmÉ/Åúci`þ5`qæ_ÇÖ –, X85PÀÎ2™NÃÏÎ"øZ6½cããç7  <®´éŸ¥6¥ËJ—ÿqfˆªf†8X%BðÈ‚“ìxuš¿*ªš^òGòp¤ãWÉѲΰt/Æxï  xÄç–bøÌ&‡7QOí$ž§áZÙ.·ÞÆëù'êû÷ËúGžkq•­ ²¹+Iì½ÿ–Õûpz(’Ò¶m~êº{/n:\’z´Ú¾WëÓFbíæÓ³ÓV~àද'Çö20"»wïJ«Ê¬+w 8Œ`aß%`ßUÝ©èÙ% ׄîðùÛïE^ðþAsä]kï§‘‚NïëøHÙœNWù4d»Ú8p@2†h`e`Qjrcw ð(í\ð¡©9"¦*SIE%‚on§n…®+Pˆµ»`tc.<[׬Ys¬“‚eík_£×O~òÞCÒ=þøãúЇÞõ®w}ýë_ß¼yó9çœs¬cZòó¤Ð:F–N¬r sà3ZxRÄ©Í!ê*’/hÚJ1x­;Â, u¹¢§C†ÍO¥ Å\94bâ=ÊlE—øÂímóÈþŽá ­Ë•ç÷ ÀnèY-• 3gó‰@uOj57Ž÷Ëý%²žHbëT8bê!  µWÖSƒšÓ¹wì  G·Ë–L8†’Kª|ÂéêœÀþd;9\A<Ð: Q& N)7;ÓÝØ #©š ç@.ŠÜI!:PÄ Í!êœ#Ohš0ÈZ7ËD uò¨ “'¢hSr&S69rÄEÊ@xj|ð)°ª½ýÂ\h]®<µ£À.ÒõX6&ÂÌN.› øŸ‚ÐoÄæƒ^g#‘«ÍÅbætcH8I¹Ü{‚í$ôy‘ˆ}0µ¤Ö-L ¨s@{2Œ]n6‚ïMŠ‚ZG»žM¥ÓQ)`ÞB¯c wÆ xùÕh¯ êíl\™Þ¤½þõ×_¿ì²Ë̽f¯?ð{9^ØZñjÇÈwž?ðž+—Í.oµ40˜Jj˜Z'—v€ã¬:™âùg?û™lÈSà(»ÿüÏùêŒöƒûªe>*ó:˼®´QÄY€–+B¸¡.=ae]ÎxœÓµµµ‘ÉAå΄Ɉ?û ÇŠ¡\{mnW ôKÊ]µU26#.…­G|ŠH.Db±ÈLåÔª{&“NÄ¢_x|g4&p:ŽFc¿öìúSi*ëÀÝ53–Öíá±T8–_¼\Œ?å)Kgløºfâ$e gÅÍ‹w9‘dÂ`üûh,U:Œè"£y˜µ!¶UwvªeÐÒßCœ>þ‰B»7÷¥ ÎÝS”½ŽCÜ_ØrÉཥ|0â³;2™¥¡˜–Öc|³ŽéJá¼Ì×S^hÆ—þ¶Ç4‹™[Y€Y`й»Õëh4`qŽFK–ÌI­LÖøä/¶ÈSøösÞ¥U>õ ÿî<ûtË8¡Îý/®i»°µf~R_kkñ‹JÅì,“éø/êÂ<¨qùĬ§]z&a‹÷DSéàÀ˜æD£¹pÔ9ŒÏ”µ%ÆœŒœ<[²Zžˆû(æR2"z´¬R€03Ä[}V;Ó" ¦ÀHˆD#ŸúéK‘¨°9Ê®ºîì:BiÆT&•ÉŒYGd,Õ?ùô Ÿ#Ñ2ZQ)`‡3ö4\&“ò;‚ƒ©Ààð“¡…£QØe5¬P-#Ý0)rd â›GÃâtr%›IÉ5Ë]¹•$‚è³Ë\{Âd²£·ÌÖLfI0’5Q\–¦v4RP×cœû3ÛÅé—ëÙ÷˜F(ðèw`–˜âèÏ×’<¢¾ùÍo’ÞDŠ‘¿Åì~ľ°ê€”;íG?úÑ/}éKÈHüN ÿíßþ­¹øÈ޹e>ê|&ço–y\i£è³H¿Hé°LÈ3<(™O|œd™tZ×H^Zð[ÇUæ‚‚ôMõÙ—W_¾Zšê5*¼;¨æÆŸl— ·q ÆOü£zÏÏ´ô¨¸š}ô%Íq~6´àÌð­ð4½³Å^2.9ñNNO~ÈÞó—-ý¦óÚÙm ¦4Ÿ×õ‘«›®¯¶h°íèí.׳E°ëÁW¡ZÛýgºjÿhŠÝ€€Ó,0ªïçŽÐ¥Ýº¦t®²U¶x¬Þ–öúJËkÊs'k÷o'°EW×[j¢¿ðz}DÁkñŸ)§“z§vøç8ùvŸ½êƥɗš›ÍYh9‹õë× r#ãó8W¯Y«ikõþŒ7Þ[£Û‡Òµ¾\N‹ÖÖV!“C`¹a©¤—C)¨··ø’Ì×Tô–šÈ\Y€Yf óIYõùÓ€Å]˜?ÝZ#Ÿ@ <½£Ù/i«l­ôýôÕîG6ZÕ bã€ÓõŒÆ¿ðÄÎ/ý ¿ó§cùÆ3û¿mv§ã•_ sγŽs6ÜúŠe2ñ¬<“ÉÄã5ÏÆS—ŠÉT`DM•9þ–y}–š×ÁÕ¹}­¬D;ÜVÖAü8œ(™¢µ©:¸ýëLjö UkHŒlw–k6w’¬ †ævŠÇtsIÇú ž<”Lü¸l*d÷·éÜŠg¿vž2_…w§a”,»ËUžsSº†Åæ©Q¹è(ÂäŇ¼ WQzÊî®H ½fdbÎòÕåk>bs–š ëJWáHllûW¼NãÍ¡Š@IDAT¥Ìý|«€ê(Ÿ¹}uK¥ ÇÉW+Ê-4¼ÿç‰Þçp}rWSºâí9ÙI›éVŒÄš²u¸Êºü­ûâFÓÞ²òê3kG„'¯ÓXý—àåçí°´´¬¤åwÕ¹jÐtl ´ë;ÂÉ×á÷·Þ•‰ôø—ßS×Ͼ\ý48à5×Þ@ßО¦#ÝÝ‘†‘L«Ëa\¸v¥ÝU®;r‰Øt:>ìðÖÊ)p4Ã[VZSjR*Ÿl4CÂù}øzá&¬Ôe–)z}f95¦(ºæó€ÿôOÿÄ­áŸø„\äÅ_ü«_ýŠ|ǺæßýîwÿøÇW®ÌÿŒüô§?}÷»ßÓ:ã“zâÑG½ê*ñRP¬»OBfÚ=áß,~BåÚø´HL–ÄMMMjÁ*2˜¯Lo}ø¥l8þã:^š&»ôágåddt6vEè·ñÄZâ°ÈÉ@‰”qè´úon@¢ÑQj,y«¨˜ŠœâÕ-»3)Áƒ#¦éEçŠå¾¡Øò*qß)J™t"ã#îÈC"ò]®lݺuü vÖYgM½pT¸ž8¤¹j Ý)º8üÆ’{™¥4³ƒd^q¾R“yùøÁpàM+λcÿ®×JÊ—aD¢coU—ƒ°w³ç/Z€KZ€YN)rðÈ W.+NÍÓ¾bùä'?ùùÏ^>ðó°÷‡?üa%Ÿ42$èû�ъí@ƒ¯|å+“$vg*waZ›åhEGI÷þáX Ï™Êa§Ò½ï¹çžak×Ò@q5°½?öÃ×vÝaÓc©ì­¥Ë*ÜÿýÚ œei¹û¾KòæCqç]´£ýÝÂI–Ͻ±y¼jý=²”!dQKB4)FùÁ~P,“éÜsÏ}Ç;Þ!…É ¯¸â i2Ix¨ëµÆ(ަVF µi¡:¦ì{6oöôú\J‹Ó®]ØP«QQê|ÍÀRç¸M¹‚2¦Šªff®!ízŒ$ нQ#úM%vð5¿ÇRð/Š•µgÓÑÚk¾Üú*Ám_³¹Êgý¹Zƒ¹òØï^H¦pê¹Èa·ÝzÓš[.Ð 2-(¨Nöò·½…æ êðÝÆu(ø” 5mÝ® –ÍkÁÆËÖ^!Ç?ù7k!íŠò‰l%hÔ6'ž4õ¡Wî·{ˆG´¼Õ8ãŒ3´ÑW2±Þê’ÄȦ È&"ßTr¸vì9=e ½J¾ŠJwÍÎ’FåAŒØø¼Ñ»à°ÈÅÈíý·¬2ïZõ“Q§€ÉDÄzÒPbÀ|ùË_æ`Æ@°bw—ã†nØ´i“¹ãw¿û]ìI(BàsÓ<šU?1ïä¤o&u3q’ׄjyÄúÌvÓ¼T2²¢Ð:±ÃY¡§FlƒOeÉ š+Ôs‰ž v?ñãòhM„„›¡H´ŽƒÙ ÌmQŠˆÖÉ ¶‡Câý™ß 'lÔJ³R-PÞøýïïï'ÝŒ1㌴ž‰‘‚VO5—xÁ¦¥Ãs6ãºJ{g.öC~6P$Ï…!yUÓîÈ&‡ãñJsØAü8 ® áŽï WÍbý9É5ÀÃÏ,`ÏüyŠÞ•ŽSχhŽÊö‚Ñøè*ªæÔŽóÔrÊÓ½çSž×ÁÍW|&Z€)8£˜eêxüï.~·E)qjºå|*‡’û^90ÒZåûûÛÎÊ9æÍÔ‰Žyˆ£è0³üÛ½KˆèÇäÒœ)¦=³˜h^§`ðiÏËjœI…ü¦™äfiÇdúÜç>÷ÿðT(–ï¿x¬$?üá³ûûßÿž>«l%ƒƒõ·¿ýíl)×]wbóQà.<òÈ#gæ 7VèÞ¸oÏa"èÞ’»ð'¹Â’»À;TÔ¨Þ¤Îad«‹¥ã×ÈÔ7ÞqþWßvž¢,ÜwõrêsFëŽI'p|9w¥Š¸kê¹i@ÝŒ¦í®nF冂ÓÀ´bнa»Œ¼M+9çÆ"v˜I–É4ç pútt”4¨“M9¦¡LCÍSŽ’FUŸ½b–tÚÎÄðgwOÄð‘õ<©!ç­cwWÍ>ì‰:êqŠ‡Ô¡ TÈ:A—¨óƒ"]„fqJ…fƒœ¬¯åøqüs”4Ñ×Õÿ¶æbÏ©ÚH…s*Ü’YD T[TF*”k˜tØÚ9¡°L&©~ E<§0ä¨È2 ¢}B¯˜5ùBk€L°€½{÷öõõúÈxg¸Á®Z•g›ãÜâIî’ãþÔâ§X”º[åß©[£î *Ñ^}¡ît¢+Äd{HôJ¯O±Mô‚(l\¾5|Ý–þf-9¨m’ž¡ôS•‚ÁõÔÐ¥Õ/zÒº‘¤Â8zpcLj —Õ=ƒÓ<¶ þõþ¯¾ëS¶íÅ?1õ ¥;(ÆÖLhcN²¦Ä¨ô Ì®ÌcP§&‚_ž†[ªÓÂzÄŒ¾"&Jôñæ‡7C‚U'jpju·çãÙU^¹µ¿‘ÁÒ;ÄPÞ-p€YÊÎsêqsÁµ HlŽ ÑÑÑ!Ÿ*A…Æ/‡è:{‘Wœ×K\txy³ [GSÆå'—A€–äP wPÌ(ò%G_{íµ'žx´÷³Ÿý,Thà9™ÍùcûXÁìàPÈ“Pø0’SpAy!dÑ hÁíæá‡Mþַć\ʨ^`xЃƒWM¼õ—‡€ðhäc†ƒ­.b¾‚Å](¢>­¡³ð:òà!eK'¿ä­añs¦yò;Vås ´L¦cUÚi"½ 7ØÁ—þ6´ïgVó/ñ5Nÿ™Wæ×.[9ÁDÀ‹³¤õNlîÊÒŸÈI*ª´ý^Y™ºEyÚéK¨;ü[ñ{M‡ö£ÙigKó`Soë¶Èu¯õÔgc}ñþujpU)œhzW7¼âNг *ŒC½Í!\V_Ù#hzþ…v¿÷™÷ôþînþ±[ £v·w…ç@УZd¥¥ÆÛPá¢^Uê¤N¨NýŽÀ;0{$±¦Þðÿئ#‡pGÒÒ#Ìç Ô4\_QƒøšoÞØ]ÿTퟰë,[NÌAM·yk/s–­ OaôÀ?¥Ìž­ú“ë£#{™tíÚµÈMáÝïS~gËÇEtüêhºX2sÖ€e2¡:˜’yZ‡,ã>æsVªÕñTÐØ ¿œœ IEñµÄû•_Qˆ/d¥ Q€CK—œ'Á:illœxWd´¼OÅÜK $ͻƒ?2¾eZjÄH…ˆRG€)=)h5úî¿—’{k O‹Ç_Ï=noøLq¨ï—¢—)4žyLQ?ü 6kë»/)ý¹<”y5ŠQÏú@dÒ“›Þû?RF½¦eäk˜rÛ¦ÅìÒY-’Ìc‘†'à 5%Y¢×á Kè: 1%ÂÂ]–“œ%¿ n•T¥ -e†»Uó4i¡-¼4ËËTа[vâôù×y(O¢7çsîÙú·ØšÅ^] ”O]^/ TÐU³Ûr~ÌþpÅ!añS@á¢CªšÉr†¬æcÓÀ%—\B‡»ï¾ÛÜí¾ûîkhhl¥‘ÅyçGå_ÿõ_¿÷½ïQ•ÃÙ“á‹_ü"»æÇîO~"^. L8[˜eþdă^xÁÜ>µþàƒÒ¸lÙ2¶ÿþïÿ.$¨S{g‹Å]8NZÝ- X°4pL8­¸ n Ǥ£aL&nÉØK²÷U‹³] ¢Óp7‡"urâÒÝ2› —4ßÄ?© 3T§”ã*?ÓµVØ3ªÌ„¦)*Uï—»™ø þ­`O³‹tÃ/.d—£;ú*x_âuŒöíÜ:s¥w$´ûÁÙí/f/Y:[§ibÑ®gãþeš­ÙŽþXk­ÔdÛõ=Y‰÷½¨å™kZbp#J˜: 7‚Ѽ3𦡵Ëò9ÃÊ‘ ¾DeeeÀ™ô`M 8’Àä^Òà:+u»'1°ÞSw‰ÍQB»¯é¦ðèw^xe§ÏîνµKZ©¨€€ÔÍE*s ûuìÏr¯Vî:J¦¡Cš»¨: Ní5Ò±¾t}­÷õÄÈOýå2aˆ’±*EÔ€2™¤µ#GÆd¢¢L&ÙÈïóÏ.,L&¶˜Ll%¢!Ø*“ šÃ,&–˜ê5S¥Àd’Á°•É c¦ŽÇÚBeœ8Ö¾–ü)¬ù!穨ú<žov¥SæC(Èé§æÔ¼[|LËds¬„l2—=6ÿâg6äδbÝH¹ÙdÆ–%‹¸¹dSrO¼³/|E' åÆÄ—ðnÚT'Û!¸™Aµ¦‘_†ñQLÁ,hóÜ„ÆÐ#ší"¢)bóíG÷‡éŽNp)ó…få¬!«©xþ°r<³¶1ˆf£æùõ¯­ê<'TTTˆÐºSŠM’a§ÔjkkI{Â%&Îõ§¤b0ËCªU»K–ä÷ùv`ad@ Sùƒj1VÅÒ€¥K–Š«¸ XC&нå3€¤{ÃÈVtoBTó‰·>X’î G§~®U¼T¹*I÷^³f ËW¿úUµTFf 3Ý›wW^y%¦ÓC=Ý› rvÕEÒ½wíÚׇY¬i IN_kkë1ѽ ߯ªiŽ©"M&b7ÈòÜsÏSwKø”Õ@àìć8;aKÌÓùª°tbüÉFB,%l‰¯Ø¦2Âb12ÉàŽÿP“ÿh9bÁþÁdòºÅ·¦Àbb4Ù=kpDÙÓZLØlí s?Ó ZÇ[]"¾²jDÐ(Ч‘6²M7™2yÎæÊ¡&CÎ4ÉŒUï/xü"Î(=Ã2™Mº"3ˆYÍÇ©YL&¾w²|á _0;SóAÚ²E¤(™ZŽh2ÁaLâMíknY0“ Ø?û³?{ì±Ç¸5²0ó¬úi«h>êÏ3ÖBÄASiaùqΑòŒò+¥þϨê¥2<&p«e9WÍÔ¨–ž iOC…ƒª&KÙEò¯ÓY’¶@ëØ­+ô9‡å—ö´êZþ¡Îð¯6êß”o7ýqÚ'ú¶Uª[Õ„„Dë ÔÏ‘4ˆDátpˆòrêFc+ÑÜK0ç¬ÖÓcù£Ž2YÑÇ^§ÒP[‘o?º?¯n£ÞÉ(®GìÊs9÷JHyHrÅ)êpľ–À4À››ïÿûp0±: ºoKsà¯þ꯸X8Ü€ùû¿ÿûÏ|æ3×\sM 0áZ!ûâ:@&î·½ímCÉ],"ùÛþ†7¼¡««K¡uòÑèÅ_4÷â3pçw>óÌ3·ÝvíòÕ‘Y`þêÝ{þtkliÀÒ€¥i5 ¸ 棧ÝÛa>½9×¥É5ñ_þå_žzê)îÐsÊêxÊh tå{B»¾Ù3â#asÏ{¤m•–þ¥l}7¥Cû©¬®ÚmïNR+jFÙfClU™J…s–¶¥r}½õWe¢"ÕƒÏW–±—ÇÂÈi«› ×±8ë>F ¡/1¸@º»ú‚Ò3ÞI¥ x]ã°iaMIr•˜æÀþ¥G‰l—è‘1˜t[þËksø‰[g¤#šÍe$†uoòJÛñþWÙmmnèQùRÛ¼6ÜùH2jíÎüÃÃ’™5{OÜÍvïL¤#],ƆEgsš¯Èì}­£sÐ&Ó½÷Þ;»Éôä“O’bÏž=˜L0˜L9Dƒ¬`FL&—f1™þæoþ†.3™L—_~¹PšL¼;âŽ@㼚L Dgåd‡`.h˜jØ„j1VåÐùI¥o¦rŸœý¤xwz”’Œ05F‘q¦®÷ÿZ‹÷héÑÚ”Sï,1sâòp›gÉîx½G8~áö69‚ÄÆvåkøi®½ðDû¡i™9D”7nŽCOk‘} bÓ¨½޳~øa-Ù¯5¼ÙpŠ «+µ¹/èßÙ÷ÚÕ™j¹y[zëuxvƒO¯>ÿz½ï-ø²+›ö´\¬BÆ*]µ·æž~c&CñJœTf‰+–Ÿ ɯגit$¡íT<¨ £µ{ü×»ÞN w `4žU 6HŸ`8œ©)üà ñ†$!tøð»q†½ñÆ9*›ä¹âþûï'†÷ŸþéŸr/ûÑ~Ä åï|'^ÏïyÏ{°Êig~UÃÀµ×^Ë ?þ8¼<$ÍÎ_Ý¢{ÏŸn­‘- X°40‹fá.œtïâv–É4Ëgè”9ôãWºžÚ! Ž2ŽÃ[3Õt&mðŽ”Ç,>H^xáL2¡½?I‹¼ ÃîŒkdÌo\‚e€t? ÞýjÐÔ )Ñ.¦xÃmïaüදeS!|f‰òÆ.ÑÖ’£; 0;ÿò{ð±Ûù_ u¥+ßíð ÏÐËÆGïžQáuÇÚÆ;Îm b^”JŽîÄñV’ °@ ÁD z|ìuq"rý„áÃ)X™Lf´Ž.çb|CŽ%øä>¿¿oW¿0™Ô™ÊA6vUœN?üãëo¹7´û¿£½Ïè™TÛå7æ-È\P?z¡«Òö·R™©tg®ÑÆ3+bª]vÙef‡Õ+zèiEE‚bTÂ#ûúzòŽ~ÿøëí´È"Á2ê˜L2‰$h»­£RÒš¿(åÕ´NÖe<;ÕT'Ñ:Z~µéj/¨D:ò¶yä!sê:ŽvíÉ£R¾§GX†Ërñ’e»ÚŠ&2ØC‘djÝ¡(ö“¦J’–[7tøI†kËŒe“#›Ÿú"h‡ gÍT]¡LTªF.¨àö([@ e˜v˜}2ìfÓÐ:ÙÎjujS»X-EÔöVŠúPQ'«£“‰Ê­·æÉ,ØK²ÏĤÉ$C<°+M&`â°‹½D]ކÉD<{‰ 3b/QÁB€/¯”d BvÁdb—òÆ7¾‘–“‰v)vü[¬zFgpˆA0‹™B ÿ\Ö'DEü´L]¿\¾˜i"=`A|hõ‰ˆ¹Á²ùoñàº`½QâS‚ÃI÷Ì¢¤AÆóx–œë˜¶G '@¦õ ¦ý[0Ò~{þ+?ËäP²B³‰ˆuB Dg0rÏ–îÆ¬æšXḮhWéÄAU“W_˜’5í²§½Xh˜›W^£F¶*–æU’»€õøôÓO3‘¤{ÏëŒÖà ¯o<³#¨èvPqO E.²s8VÜ‘§Ž&': M"×0umVËé Ù¹ ¸A÷Æ6‘to "I÷ÆÓHÒ½ T݆8S$$ÝŒKÒ½aÕÈwH<¨@÷ƺ·y4c°§ {ÅUÒ½ÍGµ^ÀÎ2™ŽUï'|bŠRÄSã<ë0æ´æ´òY?oç ¹E¨øqÂdÒòÆL<9IA9¾ÑÈõžËf$"\kçVf:A5š˦5™¤U™M'ív&“ì2É[‰®Ê³eÄ:§-Mˆ¾tN.\ƒm"þ·,JWìæU:~Èüò»8­ŽM#“ˆªYFÔÇ#å™ÛÃáÐèèÈTK³ŒU·4P, ‰îß cÁý²X³Xãœ( ð{HjHù‹Äêëëi%×T‚àÏî®2ÈÁꦅÝW++串3ß=²ù_iñ»’­²ï?ÞµFV¢Ý¿[Uq`ÇÈJ9¾Ù*1¸Ù]}.bÑÎÇÓ¡‡?oeÉŽl©àQ)mSéh_ÓèÃk¼þ-±Õ4–ûfûm:Tí0Âç4…v休`ömÿª·áÚØá?d“4 ļ¤ñ&,Ïá¶(ñz0P)SMS4ŸÛŽÉ4Ö»}­oÌe·Δ|âMW©µ­¹ðÏ?ûD0i °xúœ3üq7‡¯îŧʺ®¼BèŠeÊÕoŸòÔT‚Û ¥ox¨.©—cIæìêÕâdeéŠ÷Ž$Îlòû=q YÙÿpðð†”­±. ¦ƒ!èw²þS˜L”bxR›L¿ýíoq—㣽X ±ÆY$—‘/ xu šFXÏcZN x¡ÒE~<l7mwóMaðN}ø9á7 fçnN§tWz„_X-9ˆ++1àTü¸/´Lê­~HKr{XS¶.Vû§*‚ 'Æ~NOöYñ{Í/ö¤ÎìÔÜTØ”Òzÿ‡#÷.Þê®Ð•Î Øm»L7:“šç€Mí:­â mäu#Òz~`¸4»?æ¨ó´RË¥Š€‰g†÷ïߟ_ÞXG‰K»`i7£Uš¦C~íÃ#]%Žp&›dg¤õ„ݽ#xvIf4Dlnî[·eÏn½Jœ¸£Dø­VuQr†ù¬¹ëÌò$ —œ-%`;àx8ù¾M…2”T^8ÖÆ.¸½x§ÕÜÜÌ.ÃÒwÚ0æ­º¥"j@r@ùì}úÓŸ†Ùaѽ‹¨ÞÅ0TB¾ Ï-e*-`1¬p׊ ÆÀõ~pLY_à˜Ù$о°Àk°¦³4°˜ k8úé-“éèuuÒIÁ-¼÷Ç,û/ÏÜëkj—×ÑŸE|`cìГÈí~°ôŒwMÛwvdd-Úý8ùÀìÀÎâi[6!¼b3ÑÞä¦Ä€Sñã¾½vÒðc;¾Mr i29ÛÿRzð!!ÃÆ1&ÿÒ‘îlT‹Rhù—åÙ¹£b“‰ „vÊ;Ï'ʽ ×¼Ó3ÁdÊ{“ZéëÝÚùMë¼7Æ­Ë&£Û¾â,i¶¹Ê2®¦ŠµoGÒ\pTd¤ ÈlI¾öÚkRL¬(±_]ÓyŽ@ ó ïç`ÏžRg4¥§0™²i[Üðlió¦ö„‡¡o„´hèÙç²—œu³P¦³4|àç¾æ[ÜUŒ1Q‚;¾Z‡†ÝÎ,Œ_y3+¼@`iI¾Øßë^`[†ÏiÍGÙ¿Ÿ#ÃÃCš­ËÊ¿ú¯åV‹e>1U³4P$ ¼ûÝïf$sº€S†¯Q$ ÄÃȰk¬àUñc2ÀŽ“ç‰ŸJ@¨™»Ù¤ÇD(Ã×®ØežJŒtXsLê®` RÁŽ…ÖɆDýŸ¹{¿O´NlKÎÔ31-1 ¹Å»¨™ŠïæákÓ£ûË’» Ïe3INm×Ç6ŠÛù[=†3ÀtžLçT1ZxgFŽ;‰Ù ENJÊÔJÌ­rj½½½"È`jl‰ç@µ«Ë Ë”f &›§¼¡Œ?£Ù]vAÙ¦Hejåh£D»É€äqsË`X¶2ý(÷AàÙ]zÈÊÏô”[W™Ï ëácvRoÖva4`qFÏ'p–Ë–Wÿ×órŠp×3ÓÔç66w9ºzIÙL2EiÿøÏüä/¶HÖ!õ¢ŒyLƒÔ” ‹I–|ÓÙãUëïi¡ÓŠ»PÀÎ2™NáoF¼_)óÔ_ï}>5¶ç˜»¾èN.20¤aêÍØÍ®@æƒTœ `—ÅXÒʲÉQ›[0¹f/æT° ­“]|«>Ùþï¼½—h»ò#„¸7˘©±}•§ã o xo˜E¸àP<§ ̈H‹'•ª=¶·@Fî^pÁ6lÀP¡€ÙaÆ@=‡dê êdçxõÕW1D±L°O`ˆèéia2U:;!$Fv8ÕÒ÷Ó%iÃîväM&©Ìü î.ì$÷‘úã@k½ë¬K¯e.‚J~"õ ¦ÞTåêJ#é÷DÆ[ŽR</ë@Á³ K l·¶–æCŸøÄ'ÞþöBÔ{>&²Æ\x Àªã…Œ$"C+Óˆlˆw§üíšCw9—QvŽëÔ£¹ßj{™–ÉèŽ@á»i—åY’OUa:Ê/¹$ýÉ FgT·Ûã u”YÑ:Žeç꣯€Ö áªkÄöE§"Ãn‡aGZ‰ÜDäãLïtÝycåvçγS" uª®2i€ÖäAºÐ®Y½JëÛ£Åsñét[uÀVÙpþë›^Ч]N[ "•)к\] (+\# Z8v¹5sé)ÔåK,*@u°í¦~–#@Éd™¢¤¤¤D6Z[K £‹»°0z>±³¨ü¼1Øi‡~DÔÒ&¿^Ztœ#uûBÙ¡„c•£÷?žMäê¦ÆÀôöôÔ6ð„–<,(s¡-ºÍmT^IõËÏuwçbüã“_¸½MN*·¼Ë¬Ò‰§³úÀÀH¼öü•ðíÊDäV8v¹m~£‡¶k#/0¸Þ÷¿Zý]†«½M I#Xž8»x–Axw$t±¦Ûê[5©Šš›1úÁ u®¦ÃÙæH4dËqå8ÔÝ—ìÖ\íÎï{½¾ÌðKº~+—åƒÓÉÔ"ÜkÚÚÚ$yXv%GhÊy¡q"!±9¶åååøÏr dÌgJ]ÅF䦆¢ ¬]KsЀÅ]˜ƒÒ¬.–, X°4pD ðôX„‚ÉÄSõ‡?üáxàÉ'ŸTÈB†¶†X Ðížú+æ€Öɵ۽5³£u„¨­C˜¨j‰¡×©¢{mü»ÎÈ®;7lúgZ˜5„÷þ·ÍᱟK½x9Öÿ:) ç2oiO mÂÇ“mª}tÛ×ìZriùÐꆑåË—ËÇúž‘ä@Ô¾<ÓõÙG·Ï’=ëg…©HW&9 -Úý[9ì?>ºý©}¤QÿøC›ÕD²qÀSRns–F™xJ;ÔÓ¿5$À)‚Z „‰÷éüµ‘ŽïÔÅDƒhll4§hË[ØPáè±Å÷íúÎÈ`Ÿ–MVëBW8z¯t廓ƒñ:Šõ>_ë:ÈBÑ fv‡öt ¾²;Ó³þ‹Ðý‚»¿—Ú”Ü0ðü_Æ:I…ö%ÃI {Íõ7CVtöá,À wìØAw˜}Ÿ£­s÷~LäååÝË–-ƒ«²&W Nм«b#réù˜YuKG¯ò¯Øñ©¦ËG>ò¶Dà:úî–äi¢€Ä‘1VzYª³æ¥u‰ *Çut¢B€9U2“ .Õ<{Eï@ ¢Õ‰óè^èu¸ Ĭ4[`Ï  zO[ŒÑZ6®9š ~_„¤˜Dëêüâåkרp&UEºŽ” ¬éјËç½G•˜¬äŽ Ä7±.8:i71 )øëâŸA¤!æv™bDTMºòe÷JUš!KâlK2¥2½‹‡ÆPvâÈ.Ñ:`;1é•|>î>¨H½õY“2bŠ ×ÒÜìh]%(/ºüÌ0žÕ\d ðáÿã?þc¶§äk¸ o{ÛÛx°nÝ:âÙQ/²ú¬áN? ÀXàä§Ÿûõö¾Ü9gMÿË;¿õÜx’¢9bu´4`i`²ŠØY&Ód­Z{Ók`ãÆÀ=/¼ð‚Lü§„TT5ZRÁݪ½ B€9Õ’Nä µt•øàkFõzžÃÛXŒ3±ÂÀù¼*Â1³Þ'¯Ã‘ì`$yekn^†M¦â!•lA _}H‘’øB 8?7§¶¶ÑOÔ…¾€›s÷ ÙΖ)Ø-!ܳ…ŠÍ-/÷¬“JA1rǼ2@n¼æ’‰éé1ÙOëÄÚË¡-\‚´&” ÀÜ‘g­te¤ö†ÏÃê:¼~|Ô-î Qþ)9©b@bX˜d¢ƒÑßïìÿàÅ+œc-tßÕ~yÿð‚-øXWhÉ[8I5P—ØiM&r¤J±–=‹2ñ!à*ƒìFÖæÊ[fùlbDwøt{a,s'oœ+y²‡E¾QÕp5-ˆªÌ”T؃6À`ÔÌHåMÍ€”ÖïðÖ² `¦Dÿz‚îQ‡”'Ñ+µhhmj.*x†r“契Óf¸¼Î†¥-•¾ŒtVeÁ®ìÞ”¼ŠWÿfÑŽ;Ìæèi±nv²†¶Ç÷Tšƒ_/q¹tG¥8/ë[p G‡¤ohm¹6߇»IËûmûÿͰ¹¢±÷žÆ¦Vuˆ kp¸ý¼4 »¯d—g¶\>¾Âòº(a®+·îÂ\)$ù@N¤HdÙ}ß¾}¼aâwÀœ.I^˪aêxb#ªq¬ŠÔ€ünRçrÌ¢,ÔŽÛçŸ~É“ôä.àÇ͇º÷G?úQèÞ–)t’^ÍŰlÅÀƒ‡’°0 K¦§yõµ0S[³XX ðÈwÏ=÷<ôÐC®¾äÿÃÔA€¤?;^}Ý KG!5ŽˆþÖÿrÐseuM©¹Œ`4£»RÁ]¡]ß•2([rÄéoÕmк’¥whÙ”«J V²Ô\ø÷{7><®ˆïâýü ?L¦ºŠÒŒûm§¯¾Ìûÿ½á‹r.''û!®W…}×’Bt×åç|,ÑÿJ6qV:JóϦï<£wUúÕá¸óê%PÕȯ:Q0±*á8x\vâb·Ÿu6Î¤Ê‡×æ¹J3™X3Åû íûY:t€AÓ™ì^ÿ©Ô ~¹¢Äíð_cWÅê%{»úúzºô›.\{·š] ¾|¿ÍîC†Í»rÕÙêÖàñ•'£ñê{*ëëöFzSéðذ#3PpGÆZ¯—ÖÁ?2lH˜=Dõæ½lä'R„‰õïöý4í¦7ôÊýŽÒ6 6•IPŠaDÁ4•ë|\±Õ8VEjà´5™ÞûÞ÷B•ý»¿û;ô6Ån}$Na ð›ÌËù»=Óiòeì€r”h~Ä ¶I¨ŽCâ‘Û `‡™­^-èqüñZF’—E]–²s“£FIçÒŸ–Íz&lè.™P¡îDˆ·lÚðO uHBU®n»·D²ó’œú/~ÛYåK¿åœªŸ¿>ô¿[ïöÃwÎÒîÕ†ûÌŒfÝiŒëäbܰZ½Ii®ª‰C¹´?¦Ì?†tË5œzj¤,ù²¦MÜ£E/W•1¹»hÌ¡uü%¥<½…mÒVáâá15¬y¯kjkݺ5œÕP JÅ®g"†£,œpg  É4´kÑ9WäxÁ†òõÐëšv—tJ•W„ËÁµ0¿æáŽ#½˜%ÄÍÌŽ‹Èýˆ7I’ÃÎÀ|0ä–ÑZÌ'IvŒ@1ÃI<OlDy.Ö È| DT(êi«‹»pÚ^úy:qÁ80TÇø ‰Ö GU~á34çÒ8®îsž×êhià(5pÒÑx–*BÁdúÜç>'-%Ëd*‚B÷Y˜TÒ‰2çA9íbc‡ž4·Kf–DëhÇPÁè)£^‘ÙÀÖák´{*ã¹v²c½c PZaËQÒd‹Üzj/ɺÀà\«øZ'Û³É l²L:Ì.ì0òÃR1&£u´`È­¸à`8Ù êMoz˜Ô˱ºÚRï;/–Ørá¤à1†¤½'åu›“Pz¾¦›ZG;qâήŒçÐ:-+ô6Âÿ”ñE¹òrÐ:é–k÷ˆð=ŽÑßËaÕvÚx­C&6“4™z9 ÇLlä«Ï¿ÖQÒ5je‚<9Z65FÎP:Éêh7U5‹\ƒ·¬1£ûSCëÉÈÑ;,FN¦µ¨QCæ>ê===Jž¾XG\5|‰MG¥"» ûJzï†üONÒZG…ÇÆî…Ö)#J±,ó#_lDµ<«¢L¦ÓSŸúÔ§ø¶B¤bË×íôT‚uÖ³k@B`Èpëa+‰uŠ^G Ðì#Ls”\¨¹bhöÜ ¤¦TÇ?Þïxê§vᎃs(l>p(É1GFzÅ‚ÖQ¯ÌEf˜Úñ¨Z¥³‹vA­c›Ñ \0”d»Ç ÑËž#.a®¬ƒ¿¹"Ñ·hÒv`´º#(HmǤ( r ÙÄ ‡2®Bš¹t€öZHèÇ®p›ä}«w¹…äa;ê¼'ž#i[ Ѳ0¨ýRÌÚQò+Z·øé sPÜ…ßþö·Ý{ª³ºL«û®^þáëÛクôEod¢¡pÜnQ&çàÖJ÷w]¾ôϯ^öÙ7M¢}Ö€–¦ÕÜY¦=jn<éßÅìPe2™?V}Z ÈgqILfvKk„ÈÄÎeŸÏM;޹ÑQ’÷ Â3V]6›â(™(øGÅáÏ[æ.@H gçŸ>Ïô¸fHûmI©ø.<øR'Ûšãx³4a4OM]ž£<ß|¤¤‰™w¹‰r0š³å'9Ë–IqØ‹T¶lÙòúë¯Ë ß‘¸¾{ °w°œvS¨E¹†dDäµÐ<…*’†¨œ’W8Ô¹| ­«)9º“]V%@¥/çMFåÒK/EÏçw[·ÆÞsmißg´¾ÔùG«*‡=7”–Šà ÆA÷L]ùñ=!¬G=lpŹÅË£ ¢è§iíÎYùúp ¾wS¼ï¾û¾ô¥/ѰE™*p²·¼×â.œì—pñ­ÿÜæò7ŒêY˜ÕÁ­ xó·’9Ìx劚‹–å)sènu±4pœà~Ä<ç8‹­û±¿Ržá xNRGàà€¨]«²85`d‚•fä}…d01ÑÊÎ|ïØÎÿrB^Ë]î «f¼²ú._Tpë ŃXdÎâñZâAÊAiùKz41²­¤ñ ÅÆG#H×ÑÀYPS+¥+ÞÜñ-"¯•®¸75Šk­-é!ˆÜ:â©ô-’HÅ3 Iðl/, 9ږͯ¦ÒÙvW¥+»?]n/«}ËvßU}rxxèã6ð&ó¼Û8”_ªaøšoTÁûˆ¦„ùõ“%cE`x`®”–-÷Ö_ )ÏYÖžÒˆ©7Q²xà&³¾\ºÀ‰VBµ¿mxãÿ£«n½+jwŒ„Û?Ÿ°¢S¼’%™Ñ ‹`”•5‚ù.‹Þý wUÖ¨Ev7V5h5gGÚÎ 6öü_ÍY¥ÛýºÃ_ž—´Þpé°~.·xTíryÝÙ`‚ûpâP÷þAïê+¹î€nz¼óP:½ÌoôkçŸK‹Á\êŠPb“”aêhã“@‘—ûˆ¼”âŠÆb÷95F–#H@–…rýÃDÛ4òËÀß»w¯"ûK1k{œ˜òÝ™~¼o~ó›8GÿŸÿóx™ÇÓàôB'y+Ü QL®è'ù)YË·4`iÀÒÀÉ£ù‚P½Á=š…›!¬£‘?!2Åì,“é„\¼ã™nTxÿC`O â*k/À×ÔÈF:>ºõ?ä.22¡n¡´8JóÜ.%LÅp”O;ph7xDëT(kHíZÃÂÛÔ<Ú´C™d=°JX/*0\Íÿ1¼ñ³º0™ìÏ<ýµt],ÅÌ”r‰ÖÁ°‹u=N¹°Tl†­Ê[×èIÞ|³°’ä‰hAÌ¢f)X›¿ýÞðÞG»Ç?)ßOŒRIU3›L‚q–;Óþñ`FŒMd6wþ岕9ÖF®>ºå?#[Y F]bxK[K‹«‘^7²é•L¬·ï¹8<µjºÓïL…Ï)×úÆ\`Æ0µ×çw¤0™léPÇöMýkîÄbÁ\Aá?ËË´açÅg7ä’ÔaZNsxãÞÜüâãA%©K¥±‹8h)M™ßdâðÜֱ˴}¥ì5<²×äh)»ž}ùÔÞ$©ï@V`s<¼®\¹’4Áð/ÈÍ÷ØcÉ 9…µ=~ œ¶&æúå—_Nb>tø½ï}ÀŽ»µØÿ'jþFŽ‘ü)2€sì3󤿤æ fȲر™„=‰ú©®¢‘CS(™#VÈ ‹Œák#Ū¨ÓÜ©ž©}%L ŽÆë“¡þa^Ǹì©Õ #/®E'ê~1µ£>ò¢y£êz%c| 4NF¦>¼vM‰EʈhXÆ4`üiáì®Ñúè† yyæ™g@(ª«ÙK8õýœ©q˜±–WoÝAÕå(+0ÑLJa;|VZ§U!ª[£¬Âœð(‰áBS6¬@ëÔÚSq52DýÐ(ò-&õåMX÷wÓÄ%wð¢6ugeÐZĵ‚x¦³8TpÊãÆ9ÕõX “‰{qØ:–|ü‹I­]‚Åt¼`ó^BÌ£DÔ¯ŒbV uÙ¬S¯ Öc±ãÿË€jƧ‹Š[fæ©ßYÎþ|Ñl‹!êví+ :æ˜W‚´\ÊÊUQÐ7}:qÙQ O²à@DÆ—Ö0¾´‚™ÛLLHhº h’~ô£ÅE øø„pÎ0ÉôÀ8{‡tß~f/×nÿ NE²]9#ýþªòs3)™8Ã{±Ñ:ïÿøÿ`‘ðç?ÿùž{î9ÊæM `—™Îü‘¤…)E MpýfΚ6Ò‡í¸'£M5 g(-`µ Ï“òagˆv'b*覣Um›¸T“¸–50"V6ýBšQœ™v·æ‰È$¹K‘"ñ­EÿN·*Yuyúô"“8ìC1°O«‚cdµ›E÷Ã4«ª ñH7g°‘Šôžò̦Ac’~ÛXJ-¯ì˜WÜÍ%R"“I çe[í+5Ñ`‰E9÷ÐÄÊZÃ$"NëâÁv«1*"PÒЯ«+ Èñ"b#,ŽF'ó~”ÞHc›¹ÍôÀzà¶Ûn£_hæÕµ×^ (L„ðÉOªŠ·™p*{€—*6‰MóOn›tÖ—´‡-P)Æð…7† e(Þ¦Š6j fo@ÞÆ¼0%RQ¡nZH€XÎ2’[vD¨ÕiÒ%…8A¶úË ø5·´*¨ †æß`Žjh{Œk~øECóo ]ëSz· þ}8qSuœKŽÇ7C"2oå]Bóïêÿ¼ªLø 5‘ŽZCü2"œÒÓίq¼¶Ø½ÁÐò`?Ûp«+áþͤ³Zq/Qs]³’¹ç÷“¥þK–\¯XSŽDó×fç¿q$Zßìö4V:ß–º8 MÀ,v\ˆ:þ óâà㓪ܤhÕ•»ãr¸„d%«ûA\&Kß§¦³r(¼\¸æ+)19÷[ê-¡äZ.šÁiÒVQêhI9 ‹Ì®)ø‹ÃØg¶Øl–diyµ<_w¬¤)Ë s0S‚À¸3Cød0Fl³aæ0O@¹ÕÐ7@=±‡•gd^‡BÀçF\€ž ­ ™³v¶¬ÚæLÈôÀõLÔ½3º Ô£6Ó/ïS¿#rÎì½/쟦.™˜â=0µÔúÅìqö¹&2i|X3¿ãŽ;î¿ÿ~-19=uâfŽŠ0Ò´dUñìþY"êS•βޫ5³MÌc¾£5ÏV´‚óFÉ µ½]*ÍÞÓQ~ P”¸]sx”óìùOhvb ùꫪude¥° ´¼doþÓÑæ fc{ôUUfv–¬É®ù¸Ð ¼fÏù;_ýcú*Ò‚eh2BöX>OðEkþbWÕ{„8Ôþf¸w·!³¸k"½{ÑŒú›,®ië®ûÎoó4ßxo¿; ±iz‹p!‡ájÂtµ¢ìUE¦PÑûz»ÍâÙÝ’³ð3ÁV+W"¸É“£-Э‹ ul²æÎ§g¤j¹òÞ}¿¢mÎÊ«l…UëŸß—g ZÚçäÖ÷zgQ×u×]·qãF$ Vr«}u£¢h ø”™ß‹ª²¶7ô‰¹åN³êÓ»?œÿ-éF±AΞûa2@Á\3Þ‹ZŸ½`ƒKJÉ%÷Ë£eϾ[4Å,×’5£8úzÔÓ`5Ùç%w&óÍ;º—-.‹)6kÎBy ma #‘ÞàÑÚs¶üű@=ŸŸ_ t?Ýaºhõº÷¹0·ÇZ³¬ñ Ç¡Öèü¤¹X´&e[#¤äîê˜ ÷Ùqž7gÁ¡C‡p~‡U,ÒB”Ãè[”ßà;˪N‰ýšù/ÓcévÉšÂÁhPX|,dÊŒº@F„%b05~ï¤ ÇŠN™ÆIí–ˆ¦•|£åŠ)«FÔB–É¡½¸C.µÔ Ã;Q£& |‡W®Æa`$­Š†ÞJêükůj£§^üû•‚uüoh{B‰v'U7%î7Ä}Isö¡¢¥¦w]§øjÕ‚x¦+nÙÛÙª\%‹@9‹cÛ]VôÒJ<`èæ5KõI— †£Ojë’¹«þ ÉÒ›%ù÷[ŽÖ…s¼ËbÇT­KQZ’Ê¢¹sçö—‹•`³Ä ­$Ën•øŒ¼ãJvý”©ÿã q’°à"¢?¨èaš½oµ³2{FMÎóðIK*ÎEy[RØŸ)™{ƒð’µ"qmȰ`#V!á\UàWö{ï€çˆøj3zº” Àdz& zX|D@À-¥Ø@bÎdö¤‡3׉êt˜¨{33êÞÕ«>ãï‚,õ³u¤WÕ _X‘;~†£ç‹'ê:|5¥ê—:2=é1÷ÀÄv‘iÌ0á­ƒ-`¸[¨c‹:,¸ÍÞ79O«QCå´Aë¸úÑr³föKB†nYš6Ì^°KÄüáŽMJÜ«˜,œÐ œI‘ÀÑ ÃvФU!¥ô×`ËK`F¤DzU=hªñîØyk Rf0Ú°ÒLƃQïp=ñ¼™Þu]¸«ÿ,4ÎvI²æ²¾/¹Š%¾=°ÙmseeeÓ]tò¾ upè;ð;­7—ñGâÀàž÷ I¼ÿµúÆx~Øe[lÙà «ÚmŠÂß%«VõËZT¡{÷>àž‡\:Cgj¤«0.õ‚BŠAÉ=´.›§£:³J¢fwÜúÊïU]¹HÊ4ܵ$›ÿƒ“£¢¿^ÍjC‹¸fÜÀŸÆ\ @0»|eßæÍ*>Û_—VgyKsÂÓ‹ÊÛÂÇçRÒQ$bÍË›°:)' kƒPí6÷,(jÊvº£žZúÁhÉÒjÉD2=0†àýƒì½ÿ~QÞä=oÞ ¿‹1ðÌ™´NAÓ&„í@&zæLƒ4r5T(-kT·©“Çñ—wô…ã>¥ ÒqÜhTÕ§ëfƒª-§Ë:–!ÿ{©Tá"Ýó‹Ú’U×Ïïj3ø½â„ÔOâñ¬“ŒÑBJTåÙ”Øà%U5@-H“´Û!"I“Káot—¿¦P™ké*Wh9¡žánQOÑM¢”à냊@¤~àFdîÆ—Z_Û´[)Ι8i%Žº%Ÿø×¢^G«äVé(¨ÅG¬:CéÑô@Fwa4½”¡9õ=°¤2÷†eOnkYZ™sÛJu já×þ¼Kêúþ-Kò]ƒ¸P85-ÉÔ’é©ÞØeD¦3v$£}´MÓýJ}Â[?.$é²'ÒjÇ§Ž¡fô3ä)ÂþVO0±©f¢ÇHgàqg4YTñé¸vÚ1Šcÿë{cIyWÞ²ãZrþÆîH_:*E6ö=¨Þ€ê!bfQ–Ò{¬âÿWµŽ…xj€ŽÝ ù¿ÑâFc”{ñ(="S¡Ú{gC&+8¦Z{JdX6Móeø*‚ÁÀ‘Î#ÈáF%êâå3r¥= ëK]tÑEœ)¤$ú/ÈEçwx ‡Ì²ù<ÛUŸmïßjÓ÷ƒžC&žéÑ÷ÀŸþô§o¼QOR(€¾ J ÕÕÕüvPÌ‘zn½õÖGy„i<.ÐG_ÍÙד|4vJÀYxb`»Q>÷ˆ§+¢½". ŶŒ—ÆE5 ?Ám5÷ö£8Š„¦°vàr2­Á·w_K3ÊâÔ÷ô…,ó+§Iɲ› ‡¥ÄùÎÆ’æ”]ªdä¬ÐšÔO™³ÜàápÕnn9×UŸ{$<7Ç·¥7ê닞m(»iF»WÅ_§ÒpÈÃÉ„®,ýÚ³ ›û––ûæ ”—4»µ/d²\UÇžÀX¦YCó”í=ᣉµór÷;-Üèñ‘N,àt uTjè~UÉ?þ &…Îç{¤ «4Œ¹„+Xôïe@7QÄãÄÏœ‘e20Çô€A.¦ÐäÒ~ÅòC›cV vÇgbnG?!õÌ3ñL ÓÝ…a:'“uz{à†¥eüâ6ZWSšUÛæ»çÑzý‰SÜ’Lu™˜ê=p|™;ž'ɈLã齉-kÉ™õ€'f›\åëBío¡€F<–sÂâx˜z1§&—¬ùóç $#”zwÖìÎÊ«]f[ÏŽÿÜÑ£uo¼ñ†èe°1>gÎ)2ܹuëAžKÕ­‹ùÛ»û¬çÍîÏuÏûûÞÿ®D½‰xÔìš.E¸bÆ«Å%ÂÙ¬hJo¤=ã‘È\SÏ«½‘D¦õ‡ ?´Pí@¬k)ˆímŸáo?{ÙœÏý~놺®YªH¢³âR}ñC')¹ ÿ?}Öøã›7oÖNº˜k>Ú×[³° .Ëç¬>øc (Í?çœv•¾R Õx¸×ì,Ñ'JWtwwŒ•œFŠ»¨„bñmÙ&UøqM¿VÑwKÊÕW_OD&ìÑ€ê$"†!J•ž/&ÕèÖéûA_]&žéÑ÷ÀwÞÉ[±|œ8`;Oo½õU ăç/ÌñªªªÐB_õµ×^}«ÎMJ< ê\4­GèXí=dB€­+' `ó(æ´z2å˜1•Á!è ˆ“E¸hÐÒ‡R´@ÄtÜG€]¦ P‰¢_,¦”YØšP_àŽêÿ9zýž¶œ?qAi_ßˌ {›Ï+MU…òrÊÔÐþÅ»CáÔT£Õ`¯Lz6Ï%ËnKR¼à)xíS¬E‰é'¼óqf£7ÚÕ¯UcŸa}gÏsûâýÔeŠîØTM£ºØ-Æo^3ãíFo"ë“ J\êçg@·75U¡–>q· ubÛ~èõ:%‘´×†ÞeMX±Éµ²ÞLyÎ¥ûÞ¹·°û¥Îú-å7|O\òYÑ0VhiœöÓ–t>p‚ØêÉ ãYlázF€F0ï1ÖžàO‹g"™˜¨Èè.LTOfø ß{[½›zúz{¯Ï žùGCÔ”æØ ÿD™ÜLdz`ø˜À.#2 ß˧27«ú&}uxŽÿc5VÌr÷œˆª™ø†=Áq8N,ØÞWû?bN«çÓ·ÿ7ø2#EŒCÌÒd3É¢ ½È„-§ž‰gÁ ®„ߺٮ-ƒÏh/6⃷÷©B߸"¤±Î¶µýFMÉ©y°û¦m]âŸ÷œ¦íÿ[¨¿[—2}3MÒª¾ƒŒt¼4Y-®`süç‰%)OîÙÅ©¸&GYþНÂJ bợU™õ%_ël}bklë×np)ÇÍB5â#‹é§\þÚþö\÷—N\SOLwʱ^™­{Ø[ŸÞÙÎÁ¹û×:MÎ .¸€ã, &U)¬úKùmwomyçü»HŒ6?Ù»ó^©HZ(qÌc;—¶x¹ÎÕµµµ{}Ó4Ã[¡¨ô}ï{4 "êÈÀÀ¯¬e…F®rè­>%ÏôÀzàK_úz šH?R,^Júâ8^­#…ë믿®ÏÊÄGìðAÄx)¥é@IY2Qg“·¯ ÉyѰ^hÚ¡Ò ïnÄVA h• 1*]8AcôåÝ:B[»c`WðО6‡t¡Þ£ }ê¾Q²ñ~ÃôOJ)%pÈlL&|XÅ*(˜ëÎWÅ´õQU¡ /xaõë™DÉeó£N–ôû“•×uÒhÞÈ_lïšž­X{Œ±xüküÚ•Ç7Ÿúkö¿Oäfßõ½ÍÌ]Y™U‘sªÄ—\yy¹4³£Ëë²Æô.é人:öù$kW«ÏªÄ¦ØíöíúaÞ²{DuŽ2à©xÓWÎ,Ò–Lì4"‚Ö¡ì*É8ê 4f4TÇJmèÓˆ3·£ì~P¢E =!ò¦,Ðí† Àa¡áh çŸÈ~ݺu¿úÕ¯†)5%²2º Sb˜¦z#ï{éÀöê4äø·¶ïÆÖuÂUçðþð§ö£ñUeFÝ.ØI÷ÜWUý~c¦¸”ü?ëŽo§4£LLœó=01€]Fd:3'Ò¡vVS›‰%[{N°[”k¾áð&éÙkÍ›Z'Yb/)q²­ã‡qîšIú˜¯‚Ö%cþý½s–ä¼Å¨¡ÖW’H8Šª´°²:Ï]à §þ=?´qÖ@’§vÛaî…êü¦öƒÝªuRç–ï.ÿg"„Hï~¼×E#½,@ㆨg¯bvaŠS?°'ž4æ? §â㡾ýÿ›=÷#©rÇœ›,»c%óLmÕÙ†·{Ѹò…‡¶ýè¶eB3ÊkcWź÷}÷ü’5³ ¦¨<•!%g*³fÍ’J›Z:²ì†¸9±„%5ÝŽ½ªdmiì1"2TÏ€;š{—(÷ÒK¾zn98îò7>íš~ÜnK¤… JqÌÖì$®]­ãœeFhZ®D„FÑlÞu$§¤|^‡ômiô™ÛÑôÀ¹,2}æ3Ÿù¾ 7{œh4ý6‹9åÇ{l`–¤|ï{ßKËTw,fü·“ZËd0ç…2¸0¢è”V ކš1Ú!Ó:J#†JId!h4#Z©YZŠøAÓ& üÑ £‚ò¢“sH¡q'§Âv º“$ ƒÅh‰%ã<›?èï>vú­=Þ’ñ™1“Ê7Î3Ç“±ˆZ\A‘-;Ú– UNœ“ˆÆÚ;ÃMÜh OUŒž€-aMP["®>l{oŸæ .E8Âå—Ûý]Á~òùÝçw+Óß/M×CLãrRU¤•x‹‹Rž…Dµ\uàX~˜@Ié[­ºÆŽèœ„zdx<åI#ð±w%£Ï4у¼ ¶ô+p­ö3bm¸‰HŠ´JKOk$4ÒÒ™T4Œ/&5ÊH›@xƒn Å6­–ñÜž‚*ÆÓ¼e5­Fz Œž£~ÔÓŒšµR8ëH;ãå_"#{vÝm¬3‘Iê ;­{Ïâ2_o竇£Àa+¦çUä"@MLØt¨û¿_UeDƒÍ±Þíß¿eéØX_6¯¸<Ǿ¿Ý¿¤Â=£p„ïÑØª˜Rÿôȶހúå ôsBZ˜a’é‰ì2"ÓøgÒd,Ô:–@Ôd1&¢ cK«'ߎå¢úÖ‚Ù{ÄÄA¥©à=Òﱩç– šFž)Øh>–ž :OdrŒ¼ÿÿ´*Òrå– sc\•jÔã“,Û£ñ /Ѝ’Òzèjo ‡g©ç¸™b,å¥[ID„9Ö, rˆL>¿Ï{¬1¦`ƒ9ÐgHÄøýNÆ ˜ƒÆ£RœáIÍá A­0û‚=­Ç ²j§®˜bêõ[ã®xÒ€¤¡¾¸Ûº=£yi×{_oëàX¾Txrs=E.Ë]kÅnJ£JœTé…ܳ æY¸Šô¢Šº „¢”*Zuu-þâÇ7ñOu¨'"“Œ~ÒYcK¡K8¸† Æ­ÁD;…J4h4™Ø1bóFS‘Couß;6ÅÀ­ê*[ùOfE{´Ÿ»8zÅ7”1¿½K¾D ¼”[wPBqW§¥| >^¢Å‰I,eù,ŠV`ii© wú"Â\KåìÒèÇ95UŒ¡aCýP­£øÕ0.ô°–’V Àãj@ÒÙyÿûß/ñ+¯¼òàÁƒ¼ÆÓŠL­ÛŒîÂÔ¯©ØÚWtÐìÏ^:kYUû…EŽÇ·´¼v ãçO›¨Çùå‡`å G–çl;ÜõJ­ZãɆmM½oÖwÖwøÖÕß°´_íúd™œzO0*hÕ~N¸ºâ©yŠL-ôËùÄgIÓ÷¦s¦Æ÷Äv‘i˜Ù0š¬IZ¨¡üf­ ¯°(^Y“3X-35­¨¼…WCéK.‰¦Ô¬\3ÞkÍÕ˜!Û.‚éÊ0O4Xƒó£jkIÆ-NÃQƒ!ÆbÍ.Ah1yLPòÏVüGXü)ù·:c¯Jùª¢Ü.ôÛ¯uÿäöóö4¸“†˜Ù¤”Í»ÁV¤oäÖˆ·'™ŒÍ.£Éis+3Ný²ª¡™Ù½­%Ü[Ënº5»\oï ñ^tQ}eî ÄbI#ªxøôºAš>DÒ®O ÙírZeæùÝm½ÓH*GâÙkç QHE»&Vgs{Ó¦MÈ6Ë–-Ãñv0îE(òÔ b3fÌÐ$4´Ü×?mGd2›¬sK²J–~‘©§/WQO¨hSœY¹‹?)ƳÒx$[d0à ±e¥Î©¯dIûåAÂ9·?+ôC¨Ñ·Z>b8p¿Édtf®;ï–ìHãÌDô»ŸP]ݹœê.\gDa¬Ó<ÔNxwISõ×SS…¾ÆqÆ3"vv€;œs2ΞDà?tè^oôSŸúÔ½÷Þ{×]w}7ÆÉÿ,>”Ü®uEAšöÍPdZúˆAúÀwXºQ„W ,:ÒAˆ‹›3(…'3Š7^²ê¹ýç*åå7þ·ÌÖݸ]ùiýåÙ‘»kÞVz6*öiâ¢2q 'ôê­S”º®àïPaåN~Àu]Ð4·fñǵêó–þcÔsC'šw=Ò½þ_B1‹wú=ÿùZíL‡w–±ÛjLš ±GÜd±Û¬ö,‡¹Æ½»gÛ›ñp·-š<Ú܃röœ™]ÂP<×û¹;p¨U1§,mƒÊ ÈBxÆV÷›½­s³læ{? š!?´©ùù=mwüæ4ÌNkj&"=™>ô¡1‹î¾ûn&’ôÉ—¿üå1LüàÀ RÜ×x@<ØÛ®^½úÍ7ßÏL‘ÓØ²Ñ  ƒ©#oE€®¥I,ãä‡ÃªŽ' 6D®¤7;pè²iá¶>3g”ßR¹ç]•ƒ¢î-‰‹ºtâ÷ÿ½AõÍ7«Ð^×™û£Wƒ¬5ôAëHôG޳¸eIu–Í´è˜RøÑÇâ!Aë¸ç¬U ³SoûöÆb=ɘ×çàÔ”;_­ÁW+d†ž7U´ŽŒªq¥ÿE­¦LPÀg9¢þf·;+͇f_ æšc~QQΓJ Ï*±>âN­;!ðý¯üúŒc§¡¨BJ:‰Ø­³Ú&…8cÇWbÊ Ä&;pBÏ4¿‡$2 ³ã)‡Qˆj¿œ} s@ú„Ãé½¢ˆ©©´8Pòô¶G_;x·þ–8Ñ–2ÌíâÅ‹ÿûß ï^n‡!žYg½îÒûÄî1ë‡uR™ŸâŠ&ïY®ès öÛ¾»qÑÞƒ /7FØä¾v圙å#Èzúhõ„mfC>ûEƒ…ÛרU£±GÕT–Ý:wö¬ÁOcÝ.ûîÿù.Ë˵OliÙ×£ä—NËu¦¿14“×cZD­…×õ“DX53œ3|Ð*„ùè¯Ýþ‡êâ וò«´¡%¶¡ålÓ` £Fß!£¡­ã)[ƒ|›ðcËJ(Îj->å4¾'°ËˆLƒÎ†L"ïèNeGà¢îÊêxs¥'‘‰Ó uzÊAã?xNÕÞJ.®|ý©ÝÀ@;} u$z‚ñ­M½~ÿR·ÃrɱÂüJ1¿ uÐòz”0Ô±0.êŠGz€±RªJ¨k{¸{»Z^€€‚ÉD„8 jÇ*œ°ÿ×®]‹–¦IpteeW&\ܶ;gšžW”+¥_ý£‰R®Cë$Ë’3ûà†û­)[e“Š~zÝžmŒ8]V Ç»‚k*ä›hô’U+¢ *ŠÁ“šx(KVÄ{°ïÀïäIq1. ¹‚Ç­X±‚ˆˆLˆl§K.v"&ÕõÆIûöMýKöÛί°Ó8œÞò'Ö:Ò<´Oo{ôµgD¦þð‡tÈ~ô#­[ÆØ}=4&ÑŽ­LKÏÜžù= o¹ò3èõM ‹G\"¨q×nÓžÎTw¹K³mê_$qš:F3 œuªÏÐ!KØiéžpB‹§G’‘ôí>îWâ¾dL…äöª‡»×#‘o”í´Æ;ÚK&´듊ÿhJSð.ÅߤF†­}ÑWëz6§œŠ®žî^5Ý=Ê“géÆ#-Í­õ›\œu”(4“íáŠlEõB«gõ¦àª‡ŸíX5=qó’B@QneÒ4¨Žs€å¹\åUÌ•"¤0š‡€á“Ï ·|q0o׆[Ó›b©E´öh§T$P†…‰ŸXÞÀSÈäÔ,sÅÉ#:Œ ¢§·mR;ý†ñ AkÏhÜiÄXO3pÞZâÔdt¦îØM¡–ƒ.ݼÓÐ0GoZ5cÍlõ-:ÊðÒ¾v(Á›ðÚÖ¤z:U÷þ²‡ëŒ',¤¾ñÞEC‚ £»*š~yã`IW.e¨ø™¯ñ=1€]Fdjœâô¾ƒ49Š)¶ñW<òr¨ý-ƒÉ™5ëfç Û(™»çßHdt+@u¡ŽH_=˜ÑâŽ(;Q4³¸ª¢þ&£-/°ëÍf—ÛYÙ¯¦UŠl}µÅ›èó÷亲Щ¾çšÌܹºD¬yýDÄãÖ¾úGœå—Yó¥¸Àáce¯ù‚w®U”ü«Rß’\Wÿä·šÔ-÷÷qåpv3Ô«w¼ ʲᄇ&ÅãøÅh;þíÁôW¯ŠÄû¥/Gù:ïÁßAŠqh΂Om{æe¾ó?hþ[OË&ï†oM_ó/‘ž]t4®é×¢Ÿ}œï`±ÃÝAÍ8y–Ìu5E—Ì)åɳH)öïÛ»õù¹y¡H²È`Šu„+ •~Ò’3w{üâÿy¨á’¹Á\XsÓ¡ÖõêHá¿ÅXé~3QrSqJ¹¸2'ùó›gv¢‰¤’†Öi<÷wñ_“cOßÃÐÌ`ö„øh_¸$ÛÆí׿UfÛL‡²>ü¹_î$‹}­ÿ~óÈu /É‹«òXm‡©æ˜Û;­–¡"lïØÔ”%So5zù+ͶÜý®iCÑÒÂZëßâö gF$a·8‹k×$™O>¿¿÷ù”ó^”î)/Ç× `£ƒ½'oWör@iApÝ`‚[Et ™W’'’îÝ»—\(IGšW"¬R% b «5LPZnÙ‡ÇAß ±«Å4Fl`e ½x<ÔÊžúˆ4I |¤vœ<ÒB Í<õMX#?(>è²-‡­1½:&-7»ZÊ’%KzívªG2º S}§JûPãe8sæI u<;<{¸z"¢VÖÇAåx-ȰC‰Œ¸>tô…‹²Õuþh‚?¬JEY“‹d¦%“Mˆ$rtJX] ¢Ž‘%%™ Ûü,;éÛ ¬è,cŢ޵„ØñUâGÝ™¯ñ=1€’™NûÔý),L#]ÛÇïÍWÿX¸ãí˜jש$÷ÿZoøyRO*c£}ûm0ZbÁvgNÑQéÚ†¢YþНÏÍßøt°õγÇ]‰òšÖÔö:²Í‰ÇÚV?Ö¦úŒÓЧªø_nýM´-—¬¾º‡#íçØ½~£azÞ†ì9«,Sw§çWºÿÿ.ìòEâÉDq¶ú6” i†s»§µW=h½¡é ”ç:¹ýêÜé˜Öv•æª{ˆL?xnßõ‹.²Æ6rË .û¢T4âõ×_O%”ëk;ø«Ès|ý†ãåPLTölþ+¹û»§Å—Ã]¶êâUˆ+BÏîܓۈ¿º¿=‰Þ–ó(Ó&‘ˆrÀnöÜ›ÿVPXj˜qçŽ;,¡î+Ê7ääæ›Œ1ƒ±%wÉ?ÍV”çŸ>B”2Õ†Þµ|Áò¼¬¬– _Ub˜Í*XÅî^ß‘È<”œË—÷„T-:wÄQD*ãõŠqnÿñòYœ2ADëRâ÷}ð<®§1ÈÁ¼Çµ6,Z´ ;µÄÓ9ÇE¦?þñ uÈŠ EHéïz×»Î& ðôN­1Ô>!òZÇÛƒ d4Œ’Ñ´‰Ü`Å/—w#€Ý¨~¾JÜJ˜£Is•Û˜×ÙÑ´/¯²VUOÉP+o¿¤s¦!PoðnÑ;%Ô ôâ\6+;DIs'"ZÉü!úܳÚývãí¦¾]­¾‹ªû µR# u$Xz›=l‹å<ÕuDh*sðšò… y ‰©$»°™GÆG4(ðºù—¤TåäÙ¶¾(Yz¤r`cÔ”pÇ¢âoØÞr]•‹.Ó“îåÛ¡¼gAþ3{º÷U»Ræ @*úÂâ¯M¿+.`œ6¯€«H§„¬Èõx¬dÚ€åAÆp3‘ –"r’CA@Š„”ëÂ<Ñ_“M©íÉiø_¦(ðåi¨ûdª¤‡Ñ³;™g-mFwá¬Ú3æÁ¶û¾û×}Յί\»à‡¯µ&6ôþüC'¬±‡o)H¶®Û«XAÔÊþéªy_z|'â)ß9æ—†øÿV»©®ÍåTWæzi‹ÛYéÞöW¨ýƒ_:¯D£ùí›»ô–¸ËYZÖè#¯íïøÍ›½œ“«ÝŽ'òV}ׯšUäúÒ{æÏçÍøÕ BS–3ZsxžsÅÒÝùÞ@T¼þ=ðZd+g $Τh= Fú]7>L²~Ðh†œáßê‚xüA™`Eï 2Ÿg†Ã˜{@¬GÇ\\ F½‘^£Ñ‰«‰x8ܱy< c¾&ŠÃŠk,ÔåšvQ4žm¤gw<â ÅŒÁ„cz®Ñlíò…•²„•*¬¹ó`:ú†Æ6Öw(íãA²Îb—±ÐÔ¡e)ȲêÑ:-ëß®š¶vŽºsµµ)e@¤e ­#'ÏÔÝÐå¹Aáƒ*„3 ²jÛ|=>uÓÉ”’?9ìb'$ƒ[ ZwëÊJ¾”üݶRÅ[z‚zHë„2º›x myeÇÌO®#Tl?‚‚˜†ÖAÕrÞwËŠ â;šÕ/7’,W³³Ô^Ø‘é—àÀŽäjóJô©Žô/Ghí>'H_Ä!@Ï®µ7G“$–.]*í| ZG ~^yÆåÙ’Ë•ÛÑè¨kô“5ÀÑoÑLFFÃSD&Q ýÙDó¥/}iÍš5b“rÇ£=÷ÜsgÓN­gÑC-cn9/^ .€¶Qº ª:Qé…pÃo&ýûp¨‚I÷ƒ9ÇiŽ;ŒÁ&oâP´¦$ÛZÛ¥¾*Ýö7;Ý‹I­ãšÌ»è8ÜpàE¨$ŠÑª¤ÈŽçˆå:T}º;ŸíþÍ;m{ÛýëëŽ[ Å®ö¸ò Ú‹æPdõ”d[ʲ­ÃJ‘‹fæºÔ—÷ïhк§÷¨âž‚Ö¿xV®†Ó¡"—Êú‚;ºÈªÊ鱸Ùâ9!\8]… Aë¸Þ¸XýÈ 2‹ƒ6=ˆvB™”ž¤ÕÞ 0—æ°†× ÓxNtÐq^ |¡8C– ¨M)ð˜@x6‡þq iµŸ–[ù)‰žÝii@¦Ò1ôº ÌC¦ôüãõë×C¦H¦†ê}­^.u.üÎ ]ö=£‘ô YZxuÕkg‘tèˆÿß«ærÕ”éàPY€@T½F¬·khç½U×…ú¥¾ú§]Â|ÃÁÎû^: fHÄ%ñd¯Ô®Gë(މèˆMM-¨³ÖAY×á‘!ÖÇtšZצŠ1Ðܸ\•ÎðúG“øµG„Á1°:§ŠðùFÁŸ;ôìБG›aÄÇGã[[HˆÆ7+#<õ'.:ÇZ¿ˆL/¿ü2+ D¦ÏþóˆLW]¥‚2™pê{Àœ5}ü•:J×ú£¾X°MIDÍÎ2[ÑŠñð´Ÿ<òÇ5ÀÄ`4yöü‚ˆÙ5òÛÇ^¼*îqaš 6ö&'VæEvµð´Š}“¤ŠH¯Š”9+¯Ð²x8ÄÃ]ò iYƒF8Õ¨Û½ùwû-&•?·ïž|§(­ˆËfÖ´”yƒ³Q“ÜÿJÒ[–cï 8óœ*hEñi…6L¦ØñçÖ5ÀÄ)'·¿Ó,‰ú¯Â Kù“/ʈÎGMÎR8ä:"üy¬éü®šâ-ÞG7«‚ëß­ªR|ŠÑdOÄC±@ ¶±RõÀ«6¯ØóGøAo…^–Qê%ãôŒFÛ¿^5}g«Ÿ½eYÐ/¯Ì6$TnÞ‡böÎá>½ù%NCËïâCÔRYS4ª½¢WS(¡ÏÁP $Òžçk»¯˜;‚F•j·ëÝŽ#Ôðј¡ë%%И´W(EWŠ&ýÃÅÏîë^\æZ9MÝ¡Á}Î"›`Ñ–e ¦1À挠(šañ ˜Ä`m¼™Y[‡ß#ÖŸì1˜3 4£O\Ðjá‡3f@C _ì7Ç09µÞ˜ìˆê 0åY‚FÒfª“_ÖhÌN'»mþƒö€¦»Q÷´2‰ãìLVá°rFÞ; =Ç<ñŒ…庚AðˆšR·ž—(+¬¬týãõ+FD²ê;Tá®<×´$ê ­ž”jï ©Y9öÂ,ûCï4Ÿ”Ç=iVû×oXT‘§¾ ë;|ß~F•õÈÒKIBR×7ëT Qºt”Ç£IÀ÷š)ɘ–“:ÊܶŸ¿¢n \–ïÝ2ÈúAr3W} Tô)gMüÈcÌO•™ÆÜuX;ÍH÷NƒÉáå¡»玘íÍ.Ûç.›;JÇgúÆØK/ô‰zêð17"ÈEÁp×ۣÁ¾è»²k>.¬‚-/†Ž¾ir–gͺÅ=÷#=;ŒšV2Œúq4cÎ>fîÜl[Ÿ=óV“³ß\g|Ý>¿ßÇA¥aÛšy5Y{âo·Lûûµ…Î*üÆco_S²yÁ4„œeú6Ó¡£o=Û”óôŸ}Š¢¾Ç¥ÌúpV¢ÙdËþÓÓKüÁM >ÔËåöû·,½ eÍÊí´|'àÝ0Ÿ„`ÞÖ8¾î‚jUWyõÌ‚¯Šè¯­)69½q°3ÛfÆßªg÷ÏÂ6SÒY}aš;bøÛî6hý‘H{ÐiÑù¨Úí[{ÚcÀp¨Lš³gdUßgšô/×ÎÿÓÖ–åÓs/RÖ~1Ò³§ïàŒ–¬Æ¦†Vo‘ªñ꫈=˜¬ní¾`®=1ár¥ìÖ—ï/‰7‡‹ ;êrßÚo:äp-,ªº=êk¶dUb3³{|o»/üØÆŽóìMVM^3Zz=ÿÖ‡Þs1(˜3'?à6l÷z.Ì-p¸ÎÜwëœ9sØ~ÁQÛ5¢r(3êÌÜ~q^•l!̋”FŒÚYù¤gøC Ž ˜Ö|+ ‚ µ!Œæ‘en¨b *l㨑D$g:ÐCQªâ–ÆpK;!ƒÀŽ‚Z;I‡CV×ïåÛv¥ÇÝp$øÁ3ÊîœÑß–;§ýW]Š¿‡4wr’÷·¶éÏ×õ«§=òÒÛó‹Uµë†#Á ÖRcyÝß©©Ó¯6[Cå6ò WåŒ~öÝêÖé:òA¢`^úÔdå‡ Í¿5´=¡X‹ÎÏ«Q…mÚÎpŒå×è)OEܽµ¾âà½JH•ˆ Á:%Ø(=V™kÓŸ§ÁÌdghÀ§´A\dqÅj•0øþì2@78 ïdͦ•)A"£% ]pãöСC ‰2¤¸èÙŠ9É:h$½A#A3õE5u}b&~&ô@FwáL…³¸ —Î+~ts3hϘí˜ù}¨îZ;§èo~n¿ç¡cѶ¦žeUøW$Ì,R¿D¢ö±5Ó)ûgö¢³FbE®Ï,›…ø]—ŸôÚìžG·K}zhfQ–ˆBdýì培ô¤Ù O®hTð˜Ò¥ŽZúdD4äæt5L-˧ç?ðÑüa2YçZLÌ>#2!óÆš¯æìN¡umž`{tæ}óé=ÿzýÂiù£~ôbqÏâOŸ2T<ܽ«w÷}Ɉ¡$Ðò·X°#oÙ=}û ¤zÁó5ÆüÍ“ÍdSßõ¸K39J¹•ƒS-/ˆ½­wÿÿfÏý§[®ÚTz Ÿ6–ÚB¯ ݹxѬŋúëÿû²§œÎ¬HO/³š;9ÉCdzzO‹Äúû§–TªÙžº®µk¯TdúÊ;zÃÐh¨‡(q–yµ;ù•UÅ4ýVئ]µs|$=gÑ?xvýÄ[û? e+ŽD¸g›ja1›òÜžVv²o ´íøª/¦"€áÞ½QÏé±…®»®˜«5Àš· àüoF}Míw[ÜýB Zr H>ûš•æÞý3gÆb-O8ãí¤Ìum*]õ¯›7odË/ÏÖžŸmÉšyó¾þ´t' ؼ'ñÏ,UE&Kvîïö„;½¿~zý#ÍY®$»pb;çZSOey 5¤zÜùéë7ožþ6?-=€oÁºº:ä|døo~ó›@«×^{íwÞyZ“©Tßò²•+ˆÃ;=·áã‚ÎPï%f‚ 0|Ah$Q $ÁÜEcKZ%<)KÀ—,Áò¸z"ñùúPÔ’³ Z¼Ñ ·*u šzTüèƒË‹¿¥]hàÀ[΢ –VPÐ:}bm‡ª@±¸¨¿–éy¶Æžp‹'\1Jw9F»Rv“Òú¸éPº;TuÇTH%OÊÿ(6ŠÑ1Üy|UÓ{tXMð5€T`SF“×/]Ç82šè<ʸc1‚ڠÛtt¹B^L²ÐÅ#"ð'_4™Œ)P ,k‡ÉÓXtôà ˜Ûè'Ò0¥Æ“ Ió8ûH˜ 8À÷h< 3e'µ2º “Ú½cf.GÃýŸu£gÆ\Ë)(h3rû²ÿy£¡ÄíÀ§Íÿ²Ùåλã♃V‡î/>¦.\?´º «šAiô‰Ðs\,¾çD€ºJsl›£bl„X„ê?_3ov±ªÐ=0€ ½¼ïh©Û1¿\ÕÔKó÷ì®¶ùeî1莠37=Z§U-˜ÝÖ&–2¶È÷oYòð;‡‹²ì7§ÜɈ¥D–ľX°±+ˆË£•ÓsU¨"êÓ;Žljè6 Tç_½¨_meD†‚s­Æ ØeD¦3vÆìès”*‘¥É7:•-½cìFÿhá®í '½MÉvŸ8;@ÙX Mõ‚g²pü«z8,>m –ª&«É8hÆ<ܵ“8¾çÈÂG«zF“1Çëñô²sÎq¢¤4v†ãJygûÔÅÕ¿Hy% ˆì̳.TKVÐ:È´°ûˆúö_YÑZÍ,rÖwF¬W+n4;€9a#håOKOµôɈ°¾ß¿¿pF/LÕý]°dU- ÐbµŠ*‚ â2n€jìmÈ+F[Ýë:Z->òüÃ¶Ê JáHb<ªéÃW¤årÊ;€X‰¸5={¶–›‰œ!=ðÕ¯Žp|ÍÒ멯 @Ñ^<©ö P ¬D³y´8xŠXüi¹À"clåM@IDAT j)¨% (Á4RL)¡¡:yσõˆ–‰4ž+Wn £FªQBÃN²4Û[èc&8!ü—ÛU0ŽpW…·ÂfTŒƒ%O}#èÂòŠ,È­3+ñ¾>/Ì“x{B ê§#Wfª‡BèS0kÝ{4ørSx]ê]ZGîhѺ£¤¥@Á5ØdRÌÙŠkfÒ<‚ùª¾ãŒ´‰Æ¢ð¡#ɈsÎä 9h- ïX†Fôàd”e˜H$KAâ|˜¨ˆX‘+6° =‰rV™~L]á ‰!µGU‚¨õ1¯X! ejÊçÃdá&x¢¼cL}‰MDðÔT4æf³¡Eï1ô9¿t®cf5Õ ftÎÀÔ6Ýïh¯ùä™ðtN«ù3—ö/A¯ŸŸ7ŒAëhóïÞj*uÛç•©8Ú0Aè1ßᬃ\Xe›'<¯Èñ‡O¯#Žû9Î[ÀƒÞ Ø™°Õ4‘VÑÕ‹F¿ws¼(~ôŽßLZŒs'Ì}.e5…á­Tôù?nÃ÷¿ÞúÉ)"€¡òpMÝ-¸*úÌ¥³ÎB“qÒú Ãx ô@ú‚xNÄÙ¼mYÑÝïÂÛXßµjæàóñ7<±iOO rµÖìÏý~ë0Klü„‹/¾xü|&Ã9+2†üèG?JëØ/|á i)™Û1÷Œ”ªäkxV¢´ÁÛLD2ÉÀ²Z–†J0™5ÐGK¤ FÉIׄ!¯ìR°µ@#ùµbBHk€óÂv¬ÁŽ’·¥éÀ\ˆ@CJ +® <ñ H íñš¬Òó½q{m{é9Ö«œöDYpŽ;'Qv‹VD"çUâÁøÈŽÎÛ‹ž1*å=Ag$a5›mTÑÐÐAEú"w^X¾»Õߎ­žÑ_ûÒò¬7·wT×uBYæFÑü䃣*éP%®1‡Úö@MʤWϡի*; dàØä¼øÕ†.=oºÛëRlÅI×[è!2|tÐàb¢Å C¦ÃáAÀOž&sƒRlæ1^jLÒ©,e‰ƒ)°`6¢çXYY ¦áF.HY°ØA)¯V™3ðœ;WUK¼£¸´Ûs9h¿JFQF]ñ®»îú¯ÿú/¾§œ5O¿ñ3¼í¶Û gžyFÆ}ŠvfFwaŠÜm6Kañ‘=bûoXVÇixÃv(ÇÁJ\¹½U×)€ž9¾ç´Rõé“ïVíu±·ÔŠ&›ym[U¬U7ÛBå´Ž”æžÀ~hNÜØ€Èù³—ë~úÁåvË n4ôíäÐöoÿæI±š?ÿ»åúÜLüìë ì2"ÓdO LD¥ ÷¼¿Q ¬]Ô£ÔÓ –hÒÄ¡?,4Q˜b©:˜mJƒ?-/…;Tß7½j˜ÖGÅö¢Þ%—Üß·ÿ·žîæÂêuö’5}us<«h/™ÌšýA9³sØØ¾±io"‚­¥ñ.Ëß6nå`ñ\óÊo]î²—¬–¹Æý 8Ÿñ†ÝÞdqÉ‚[=Škgêh¿éŽK”ßõFݾ`aA>¢‘ˆ"Ó¯ßl¼%÷φdywÀŠš­6'Ks,Ä<Ô%ôEþïUóp­ê F5'¬çWçÿ÷«õí>ÕuPVŒÉ¯Å]ÍŸ¾®“ïlî]|lãE+Ûœ²ºÒnF­cHã^¿ríìRc´¶ü~Qj`ÁPÇ–`Ëó‹Ý‘-M¹€¡%yÙuj÷"Û`Ñf>ú(ú’j©¤MÕ—´æ0.¹ä‹ÍÍ3cÿcMz|‡ 4?›mÍfª²L»©$×V’?ïOîr)æxž™ƒÛ› îñ9?¸u)Ž¥ö_.g™‰ ìøÜfD¦É›¯‘î]0GC m5ßÁ?ä,úÜhê¼Fä ~7T)œ;%û*?{ù èZkhÝP¥@vÒ;(³ç~¸£¾Þ^¢~?@븪¸Ù¥0 $ÇbŽt£UBÚªÊw„ÞXN k‰úb>PÓ3:%îxÞôé¢N¼¼*÷‹ûºê]o5aî.ˆ«¬¼¢Þ[•׳í¹-Í&Ud²F-î f)[èx¼Jì(>P÷˜·Þ7»QÝQØhºdîÈÞ6c<)œÜÊQàYß¿ÿïIõS}ÇŃC¼§ÈÕ‹l8Qz&3œI¤:RÈ"Â-†zõZ–R5õÊ‚Œ*ø’RœÉÃŒ(¤ì@5OÏ K ýSÓohåëS´¸ uÜΟ?ÿ»ßý.ßÖ¬Y#¹çŸ>+Õ‹.ºH#žrdÅŒî™9jiGà ÚÈï?§~h€?€ð×Â2(Ùd$þ×zu­+U¿ÓÐ=Lk[=ªš‚ÙdøÉíç¹ý?×wô ®ë-íü·Kd”×´Ð~øÔªâwöoÕœb´NÚŒÖá¡Nÿ êc‚$".òéN/½wÓÏß  "í^µ¨ô…=Ge¬ç”¸ì˜ubéµ¼*ÿÕýíœJÁI²Ã·YÐ:hn]1 ñÉsÎv,yã—Vÿ)çÛMЯ ôý9å4¾'°Ó?ÿT³zcÈOݘ´fìÂÌÃöÐVxž¿é¯JB•|TgvΑWÌÒ.v;ÑMIizE^<©îŸ’ñ¥±}•Áךç•„oY^*¹˜ÁŠõkÖìÛIùÓŸþ$é¡dþËœ—Ì:R¸üÿžÀ5(_GHb<ܳð3žÝ?ã6gÁ§Ó(kjjjkkA¾/š_Ù¹ïp–ÉÝÅ×êi(Ò¾ã>ÅÈ–3'wñ]íGêÊS |G?ýDèâ¬üJ³·*×JúRÄõžøx„é¾oìÉKXK´wFc ƒ’ð¶<Üý+{S޲Kí%¤?Å·þ†'mm›z¼Yîyw tr÷ý[–¾UßõÀk‡67öò§µmõÌü¡Î„Òhˆð’BœàFàv )–žXâ2èfW%Š‘J2Ƭ{瀊ʡTŒO¢Hdõá×ÿå@ﬡx¾{³Ùñª‚u*¶EkÂ^ÇùÓûM`ô6Ôž¾`Ns“§ÕÂ{Œ§ ¬zÁ€ô]!4’¢ÅÁï*SC²#˜ [8à/ 24êØ6àUÉt’êÔ5@2Y¿ãÙ`L]#}¤ºêUO^Cwxo|Í¥¹­IƒÙÐò;ÅQ™,ºFêÕ\Ή©±åw¹ŽˆÓÚ¾³ÃÚÍm‹:®(xʆžàüb—Ò³Q±W*–±l¿Iuã¼zßVüuÂÄÐúHrÚ'ºÇEŽ¿ÞM^LI/œáJ±Nß¾ø­°Ó™Z|øˆ6è²qeÄJ¸‘bŒX1FÌI!fµÆ¦º`Fúâ”ÕfL€ç˜oD g äÇœ„'é¢4§µSæ$óGÐ:X gŽ™<ÚÏa4_UŠŸ­Ÿ(Ç¥= `úú»žàÙgŸ½æšk8Ñ{åÊ•x†‰ä‚ÊX뉧Vœ‰1ðí4µá\nm©Ûv0¥­4Þ“Ý-TÍzXªÆ0e˜êÊRÇ…cº MCg?Ž6 ýø³¦çÚøèüñó3‡¯\;_öø¹Þ~Á´ånT¢·îy|K‹ðœBÕn;_@3 œÿrÝ‚û_«G}¡®£ïå}íÁhÌa1OËïWC9Ò«ŽìÌ"uKi”á@»j¦áw£,u6‘ñifNÖñt|»Y ¥}ßõ<å4¾Ç ØM"û{š€æL?º§,N34µ8Zb0ZÍÎ~(ãGõ€ÈðMÂA2‡«²0M4üÂЗD$‰W|^ó‚‡Í£wß/#ÞƒW[”«««{vÏö×57yZ-bWË¢–g²®ÚÐ\sÃòjÖ|Û)î[µ¸9»:{ÖûO KÝ GØñÓ²÷<óáO~~rô?•²/î8Ü»~.ä@©å*hוuÏ~áרªý‘êÙoøŠ8N´Î|éåEq0€ ¾ y§C)»@ž]÷dųìÝÛÚ¬]ÑÜ£ ×幪ÉçÁö>TŽƒ­/[r°ëK:â ‹ï„mÚNoMÚ¯FfŽAÉ} óƒÝþp«'„åu—/ÄvÊtfCÒé=vçî ׆T ü­™Î• ú ˆCšDDÃP«ûÃáל‘ÞÆÚ {BÉ÷["çæõ™¾÷½ï5ã~æHzZGß·‰n2ïtÖˆy- ›FMσ¤‘@)8œ5/xÄN L€™ Œ¢'þÒ-؇ €µíÝ»zíåF«ô^ðÀ4a¨þ” VuKÛ`ˆ«Amx›ˆ« ¦=~uÅl6Æí¶ä3e©ÞBµÒ4Þ}ªáw›r]\æÜÙ þ¡%ÉÔ ?VS¼ÖÚê/¼¡Æ<ßîÚÑžømë_/¯uÆš ¡–äéìïvÜÊUoðPúö*îEê“ xÐ;Y'z`vh¨1mèd>©ïȾ#%È0áOµg|Áì¸23ù$1RÌfd wš²¤~V€ÓñAa‚1%( %q™uÌR¸b×)A#_MeOÒ¹RS”‚dÁSƒó4‚s-ÂOrÐ_™¼14h^ß->øàücú_9zâöÛoç n_yå9ÐCOŸ‰gzà”õÀ?_3ÿžG·cׂœ‚¬á,~&¼I_¼f>ÊbvZ]•flä FCÑxIÊ•›Ô‹,€IŠ&ïp¦ÄÀöõF²ìF—u_+¸ƒAåÀ²g` 2 ÍÞV÷éµ0¿,û¯¬ÑnÏüˆ&7t8aoc½ª¦í«ëŸG}FÃñ³5¶«€Ýðè[YŽý`@U<üÄE3"ñSä–ç´@¤-è }`á:(`7å4¾ù=ëŸsÄøÙ$2ih݈O=ÙZGE¬\£í‰€Y4oûm̳çÈÆG9ºn”( K[g¢Ù¯$ÕsZÑ!ÐyÁ# ´–dÌp’Lâ›Ù¨'Çóµk×òc@eï‰'žèõ‡µ—þxE¨é)(­¹ó8éÕÒû²rÌ®QÜØ ýUÌ%ØMÍê bžN8¡ÞûúãþÐlȶ7{Øv(̲–ç:sûöµ&Ôýy³1áN$nk™ýž•ܶlxHjU`ÀOŠxâ#ÎÉÙ‰˜jÈc3'öùB»¢e·.q-1än9ùCÇ5ßšuÐnä×ÓØ…Rc.ýx®io¤gW¨c«½X}´ÕÃÉ:Ñ8£“ÙU`ÈE[m çáSd˜ª«ûå úæ2yfdÕ'c.¼*Æ‚íf‡j4Dul›Ã äÃa &K„QÆ—üV:î|Ú“eŠeEº¬Æ¸Ç¢t„•Ë‹üÞ`âÙÆX¹)6ÓÜsÛ{.—‚Ì7ägÖôL6IÑ®·,¯8Pãx¬’ÂÂÓr—Lëç4‚s-’™Îµêy@uŠf”é !B¬1d«FÀ®|( £ü1EaEó¸jÜ$®Áy´œßû íçû¬Æz >¯HÃä Ž#f‰âúÜAã¢CAëx~Àx[ÂÀެƒjK˜ø —•• ÊÄü”GNÐ:-®QVg¶ú•†>Ûjk¢]&¿SQuÇÓpjZñIä‡_2(ê¸,ÙýÊÕ§ßkK†‰QÒe\´Ù(hŒ²~.ɘB/³ˆù*1Ä2+àqr™rÜÂV()7Ðg)%° ‰Ì4õ§  ©j“DOyŽÄé7;l8Ò¾Ô²b´>þñßxã +¹=ô#òå/™_½zõ E¦JâÙ¤»0Uú|ÂÛ‰]Ë„ó%C=vÆÉ¡¯Ô¶c º©¡[°˜hæ¨È\¿Óü·Ým‹éãkJ/ZšþaÒ¤3ö³ÓlP´,‚bÄ3%€Q>¤’14-½¡_¿QßÞá;Nâg/ƒ¯·I­t2˜_8«àͺ®ÏýakyŽýâ9E‡:}¼T„_ïn¸VÆhD;ßo¾oÑÔŽî]TáÍ¡%“ñP§§¬0ùš l wɘEÊÔÒøžzÓ}ÐN?ã¡.ƒÉb´¨ºBã hŸ¡'LTA%ÛóòwmI’÷„œ],-Óš†ªt(/xxÄ3ššc18]Ò`TP 0»ºÉC~c˲ y†E0šÛúŠîüíæ¾[UC­ãš4¹•d§ž``>$‚ãXcªXhLxw£ûçä0Ö›–Wl<ÔÃû«Ó¹² '–è÷íKÑÈMRвTJqñ&qrVNxW»rÈk]抛§ÑçHzØg7ZOBßXã6±kדKžr Ç2G¼FÂMEHÂŒr‹?RdVN# 1¸âÖd=®eÃÞ8 8ȺÀòÒDfC_P¢ðé%Ö®NÅ:§å¢2k¯Çã$즤Ûè·š kçV–¥ŽâX1ù ÕŒ4´VS–#«¼,yøÐŽWQÄT”Qþ TÒ³.dD¦³fH‘ü/ÀöódJ RŠwõ8õ0á0Á¯ß2 ½´àƒ T™ C,×`¥Á”åçcͨ`…ÏO‚ôñŠ÷zÖüœù p+p ”i’?láƒG9^#ÑŠâe"( eaË Š7•À+ c%Í£"ly„´Uê"°€ãµFSyÏÙÐ`ºÕ*iû®Rךiu×Îvš­än°Ý`ˆÍH–ÞÜe¿ºRy¶á¨Nü=óó»QÎd¸xV.qÉ24ÝOdõùxü©z²V&ƒÜÞVü¢Âneý!ÄO} å4Ïœ@NH5ÆÑ¯Ú|ê[¢Õ(s˜%,ÚyŒ{N¨³q…LTÞ˜cL–Ì )+Zuäª2'åT8Š3²F–8Ó¸Mè­czË-³Tf4€_)ðÁ&W e¾1g˜¢0s])N)É•ÛsêJGÑ!ô'Ÿ~üÖÑuü ­ªO´.Õ:Š…%¿S~¤ißz`ªDÎ&Ý…©Òç§·ú“"hÉ¿^¿pZ¾cœMêDîyd‡0y«¾QÈj6Î+M*ß¿²’?ȆߌG- °Î`ÒøÇ·4OÀŽ–#!~åZÕÜgJ‡¿_[H&Ù#žPa¶£yœp<Á©VŸøõÛ…)I–”ÑØùþÛUÓ:QCiêA»eٴܪqO¿©Û±,êÔnL->Óž‚·¬EÓÒ¹rßÀîø "ù ^°bÅŠã©£‹·u”d×|lt…†¤­cÝÃÒ×¼,‰Ð­kÙð5[2D·;§bÑeo¿ý6qNÿÔ „Žäújõ7Y\Óh†8•;æï/†PO µ-ê­3˜ÔC<ñO—ˆzñˆÁhTõïLoÝÙsþNXi×åË—¿ùæ›,ªÞ  bçòÀª:.ñ–z÷‹Ù÷]~~¨c“5w~,±(§ò*Ïžÿ$wKs‘Òü«ù —–WŸÐŸHJ],¦1±¬JþÕÛµ?ßä¸ffäÂÕ‹-/U¹7ݰ®eÑéñ¤+7§tYqíowT!2ïz=r`ƒ×?Ï=ï‘¢÷;ýÏP…fÂyóŠŠ_Àîò%Ä%Kåúk>ûçß¼CÖü½9ï¹DDE Q äzº-§y†h—bQU-¬¹sOWK´z¥»œ—ÚŠ. 1!E91a&ṩõšRåy椣tíko¨ŠŸ„U«Vq…žÅ7ÊqsæÌá–<Å‘µÀˆ;¶nË5ÇIƒË”Œ¼fEu®Ÿm‰YâˆXË–-£ˆüú½À¡˜ÛˆdÈ`¢O!¨ÒRò¿&­I©s³ËˆLL€k¯½–Ùòä“OΞ=›Û©†’r‡"^¤Á@yXËeD` Yßð3”RüTùb6ȯ"üÎÓ;±r…€eqíqàÃM69i-X Ÿ=Á0mmÑ08È(«}òÈËGš!-¡I¼š¨—O•ð䔨‘®A¢s!¥*ºN6Éy|éÕºº5¹ {g\áÜ“é6ônŒÊ4¨î/{ºß9ÜçÄkŠÿüîª|çñ•FƒW»G¶wüþÈû>P]·Ðu8霩\"Í;Wšç=ô†½dŽb+>ͪeÖgÙ#€3`ޱ ¸8)|e˜N DZ›Ÿe2ð±€RK× ˆ™½ÿ?{ïŸ×Qåý?½«W[–%˽;n‰§’©„„à ,¡¼aÃ.Ù…ÿ²” „}aÙŸ¼„…]Z 8=qŠ[ì¸÷.Y²Õ{yz{¿WGß<Ͳ$[²­ûÑçjž¹gΔ{fæœß=3Ã;„åEC ýаœÆ+9¼VÈ0ˆ!È r‹œ»@~’"18 †)­ÊwäH1¸(*—L…#Èø¬è›´³¼øã CÕñÉÖ¬ÑÔÄYÈq²ñзÀs{ÿüÎjÿÍÕû¾~ëœÁƪç“´Ž=În[8¡­7¬ŠÄjÛ½œÜš@9œŸ u‡šµE—3JFßka89OÓÞeÕ[Úú4_Ï_7£±Û÷êæn_ØaÕ>#-˜”óà{4j0×ò)ùü †òb ÁM˜}˜Ä©¸Reá¼óø>¥_&ÔäŒ~^0&ÓÐ,q.“‹ø›Ï¨é’‰¶ D ß±Èõ.SÝ¢å7ȉ­µqãF¬&e½´‚Ö{OVx–£äò`ÛŽpP[Œî>`°xرNO\ ^Ã:GV;JL{Ìù±®•°vÀMÿñFβëøÓ"««AýÈwͳ¿šûv%vijž<í9xtÇQ-¸Àq,h ¶j¹cþ*÷ÉßlкÖÞÒ,±îŒÅ#œ Èé¥þú×âæJUµ§¶\¤ÍŒÌ/Ë~ô®ù…M —KÑðAéWkW¿ç#Skf;ëly³]å7½C5jÿ)^÷þW³*æXÜgìS3â…Vˆ³¯þuì@¬Œ÷F,¬ìaûÔOåUTlÚ´‰£ö¤›7oÆïTŽáRï}0wî\!ðåp¿¸+¯³/b ú¢qsÜ`î‰;攸 `#îèýÜArˆ `Ïà {»²²ãñs È@²UÇHÛ±c‡ ÛÒ_H8²µÆ¤'GF|,I,½‘å?|nã&Ó‚ žþy‰¯½öÚU«V‰—ÍðÛö¼à 8ýB@Á5δäô\pR>pGÎôLèø‚ÖÉSý£Ó†)Òii2ЋI¯` ÄgôHÜè$‰RSuóN¿6ÄYŒQ–Ú† ý¶MLûd%—:h‚Ÿ‡Zü®©[TæþÈâ’wžŸúÿÁ…Eü •#»òôTC Ìåç­øi0å¿cø•—\¥}øé‡ÆäŦ11 +¦*¼D¦ aƒÙ1¡(b…ó~ù,3Bghà'Ü€‰'_¡Qpž St(¡§ÓIñ$áHÝ™q¤j”Pn8“)5Jg{ŒTÖgʇ–ä"åT x¦LÆéÇ[àüjAë¾xÃô9µÏZâ»ÀyjáêªóÏÝG*U»9¿=§dùw^e»ïß½€G_~jg—/‚ÏÝO?ª)Éé.V×Êñ©É… dµÇ^:,is~]º\Æã‡ÓNGLXØ¥nÞD']VpÜDþ©UÏÃá|Ѧ-aÖæbúF}E`eÒgÞ”/ÐÉ-#:³>M˜Ï½cÖã{d»‹Üd2ô+ñœ Á–p¼û8>kæ3Û²Ùèä²ç1ê£|jVÂä3MVçʾ¨6i‹O‡| p‘\¿¹éÆBšƒÃÀîw‰kÖÕ8P`¶€å§˜La£¦Æã§Öäê˳§¾ç«OïY>%ïÓWM`¨ûÇØ? ^¢‹ý`ÌQuîÑ:Œ̘ÁTþŠ+®Àå±f±sÔ±­2ÒÉ)¬ÄÂÇÀØ¿¿x¾ÏøcŠÉqÙ¬òºúÆöÖž¸Áh³Z—@†ý ˆÉ$ê¾rU¸ä’KØ…[L Aa€ó¶oßg¡d fDÖÞƒ©×`hdè‡kK’N¯ F Ÿs@s‘›LÍÎE;ÿò—¿üò—¿,U?üð£>zÔ³G3¥y(°iÃ;†þ%xIèÅü¤¿#ó<’ÆDìq,ÂÏH|Ó@™¬QŠ!Ø=šž‹dÒOõfñ‘KOŸBùÛÍuµsB?•ç|þº^ZNʾu¬„-Ïwý]*a5~?Û-–ŠõÊ6‚?}£ZòzäæqGãᶺ¤ÌõЬ|OR1§ Œeï‘ì.r“É–?ß[÷¼ uHÙ¢uØ ,$TjŸh„¨h|^æ‘xŽ à¢DnݺU|ÓQò·V¥Æ|lÒm±f¯nšå}ãèUÓ‹æ–å˜í§œf-Ž£=äŸVv‡LP5¥²ºæ8É=®LØeöìOê¾' äeMÖ²c7=Ž( Vqß×6ùÛïŸ÷ßy1šhvî‹{ß>>ÁÙÃz¥Ã´ÆOvk3_ŸØªó¥}MOm=ùvMçeU]œ+ÆïúçM‰ÁŒÑLîÊ;¼ÇŸÑK¸ªÿzóÍ7‘FŒöãÇãûFZ¶ˆÆÉC2ðzìâ PØ?ïfΜɹd½k×.tz›Õ‚á„€ãƒmÐß3pð_„\päÁDÁhÁ›0Ï;lb¨O æ Oׯ_/%äŽU¦Â#À}U¬8²Ãv˜§R(*À:¶"Pæˆd4²L.Z“ ÙxðÁÿë¿þKÚ´—…xƒ?øÁF¶…Ï67éPÌjA_rŽBƒLUÈSÔ°I©&Bƒ<¨åŸÉ|ˆ¡»‰g+°`‚Häôð‚GÄ ó??y$l…•Ê]~r‡—Ñgù0ÀE¥„ôwÆE +@USF2⩬¬' =¼P`}}¤ð š¯/L¸TÚt  &€N2° 5Å‘ ˆ*صm’3fˆŽù–†cV“ÙÆÉpkêè-±ZšÃÎÇ®1´âW_5uÆ¢2Ï·_©ûåÛMß¿.bl2µ*6].žx-‘ÊŠ+(Ó7¢"]ˆƒãJn 7Ü ÙæŽ Kˆ ïš¼/Eh 'xx!{¤b¦`š£#†¼ñ~áÃ#bÈn¼h.rGé"G)†ÐÃJbøÉ…P[Ș » ‡”Jâ‡|Wh#€F–ßÂ-‘:r!¥zir^ã G¼.vß…oÐ1ɰ/¨g.›æÕ{¶¯\Í€òúBQ_H›zðÉå‘°>k½ïñ;Ot“ìaÇ#6­;ö­Ó×ñ óvÖnÝq¢kj‘û–/°ÚVud f–¼ ­¡‘ìx7ŒÉ$ Ú$ÎAédNÖZ²¦dM½GhXÞlÛe0YìùË…†sH³gým:>ijû’,²ÀäÀyG<P1BÀ>xH!Žü„^¹,\˜x¤‘»òvþ {gøîÚVÛõÐM3f–jËT;ßm2QÂÐÎÖœ™ž)ï'Iï±?Ê¡«íxô…ƒÄHBrAðµiù ô©»X‹þ|Ùêc]Ûά²«²¦Ý;@j0L™{Ý”–Pq)&«§à’¯þèÕ#†vÃJCí—|iïž=]n[s¯uAÌf_ûê “\±XØ[XŽY,V'»aT×Ü^d¶¶FÝšÉtdMhò-7Íw锂/?µëÇkŽþô6‡·î9ÈÔªØy_dQ²ï!•{3¯I ivô³-Jñ…çP½·±¹µ³7€ÕBǨÆ{…´(¦¬ÇxÆÙ¡E8q:ÀFR6l;z|ì–¸yû¾Ûßw¦RÍCÏ t×tùØAŒª]­µ¡¨9ã˜Kv¿2ìÛ7ໂY……ü‹ÉÄO.ñˆP[J. o) 1C[Õ.¬äÎ^2" g)Œ,¿%æå—_¦Ž4àcrïÓ3Ÿ³À©%±d‡e¾zõjÙÉîšk®ÑŸµ}Î 3œŒD€á€Œ¥ã£>"„t(™/ V™°L(è_à)¡žÃÑ£ 0 î@˜ B~R!ˆ¡_«ìëé%•º îÆOz:ÀàB2Š¡¯)l!V©˜e Ñ'„ \„€ª120D0ª€Üé[@æ´X1˜PNÀ.) ªžpgC,˜m÷çØ½ÝA·?Žg°v˜ìÑÆÎɶà{„?ð\Ü=#ûë­ã;¤Äì|áØÁf¯ÛfžY<(OçÓÖbìÈW¾ÜPT`b6žCJõnrxt ¸†j¸ZÿiÂ"êƒ%4¼G^–¢d—Y¶ÌVòe´ø)‚]@@BC˜ñ<%­ˆäE¤pÙ#»‘–n‚ðˆÈA^”!—†¢:”„@ðÊð`®1„2Õ É|œìÜ´ÀEî»pnùŒrÁãìW»l–­.˜®òõØ5ÃMÅŒH (ËÖÚúõ[u[Ñï»ÐÏTNÒ[R™ÏÇP€Ö°”•$R€oß9¯4Û¡ü—æÒ‘³âñû¨·À•3Šøõb\xP“æVµ‘ì.“IÁa)hÈ»ï=ük o±LÈI~Ú OÁg=‡~%‘Ñ@[¨ó›¦ÉÏ„;fT‰äM½òŠ&ª-åÁ§ Å=DýRTOü<Õa <ùÉ–¢yÿ•S~±®† ’¿´¨=ËÂ݇¨ô‚Ö¨—·»èBA¾Û޶{Ÿáéß´k÷•®Ë1wš ¦x,è_bô?Ï»°€ÉÅá”x °ÓSêiࣤ ®¬ü¡WaV#0é@ÆÀ+#(‚”É!‚^“a%-#Å#÷ î„’…ªõ¥0rP=Q€u˜€b(:&=”„z'D~Ü0°À:}»ZâöEßç¸áVÀò úyEõ ½Ú4¡!7ý_0.ðXµ"ùÚÛR&‘6sdú)Ëk€ð›/ÕrH…JuÛœ|ΖU?/†³ÕP,C}Õ …†É…w-/š×ªOÅÈ/B޼AÃ[ã)ïWÞ&ÈIãB‚XÂJp:ñJ2¤îçœ%õÅP ¤™ÑdCwñT¤‘|‡`- ™È§ä+É HXò‘vCþÉ‹§Ò tEމ@6. wï1ÑšÃ+D‡7ô•U»…ÇÚíÉvPfö²jšä„—Våo®îËwc¯8â²Ï\U•™aæ§ÿú0\{¸Åc7ß4·¤¥7øç$Ç9`×$C’„€þ)aÁìp°˜”çÌœïøÓó®jÛ}Ã<Þ伫òxi3†WR¶Úi2¡Æ¡®%×—-êTdº½áô4²ÃJ¢Èn\8†ÈÒý# ƒâ¡>b~4À'ååË—'“%ÄÚ!Á‰3—VØ%<ÕÿÜ×”Œ˜ó£{Ì›¡O«šf;­,8ýÙûCS·u£%ó8A/ÞêÎÖvÊÓ$êèÑ£²ÏÖ;"†`M¡ƒâ²„Ö«r™à¯.)·7uÇ:¼~«Åd3›Ì&­‘W®\ þé«»dbë‰.mÉ Gp‡Ã;&SjsT; ¶ÿ²[¢+%¤»á÷;ìTć–Nºaîé×O)ú i‘¹.ÑØ©æõLò¶VÓ)U,y}BÞ‘X)©˜FÒkì·?Ä’:c4†§O¤®­‡‡;â_šR_ší<Øì ÇLÖxÜe‹Áž ;ÓÞæÓ{ßûÞgžy†ŒÈ¼1hy£ÍÓÔ„*Ëa¹­¸³Þo±{½·¤;a+Ì0rǾš÷’‰|RÊìHެçÅ:âR••02ž"ŸêÑxàÜ·ˆ*¶ëþç’µ8‹Ilés_°¡å&G^’\`‚>Àg }ȨÇÿH9A)èkèÔ[j‘l+ˆT`´" #ÐÇAp£ÃŠxˆ)­°æ=….¯É!–¾Cgdï^Ráø¤’m™±Bb¨ŽLR¸C5UÁxËTH†Æz=©h (iIE Lhu>, X½Ž 0P•"-˜#4‰W + #/ùö@QÉä…òKñbö GÚýk¸'è±3E" Œ»­Îkz±uÞüüÍRu_ænS᱈!E¾½IäkGº­[>9‹mɶè[½¿cÉäl—5…¦¡ø\^R„Ã2’À»£Fœ4¥¾¼AŒ·Æk"Ì»†aÞ¯ž·À‹J"‘:$–A@d•ìxD˜»àhx×ÄðSf(ÂèW¬Õ2’È×P2E„3”’5bC³¶Wö5É.Û]7«xÙ°ÏëüÄ•On8þüžFþT9ÿížS¾ *R"Qm”+ôœ²§äÑÍ &>¿»TqÒ’H*IB`KMÇ–ã7ÏŸPY¨YãרlŸ½qô­cí›zíVÓÌís‘×çýÝ熅ÍšŽ—*] °»ÀL¦K/½”#/¥±D«Kh8߉—LŽüh°ý‰}…{:\wƒ·ëÆRÀ5tGV¶² ¶k÷$­­`Aù ¥PûÐÛô4¾ú×BmÛLö‚‰Sï®®Ö¾® Ÿ¡PêѺ@ÓÃëFk–gê½ê0 a² \ûb?ݲ{ýs¿ýP¾ÝZ¸´sçç¤ùT£rqMÿôú×ÿÌO£ÑÜë mؼwþ;Œëâ3¢f³£diå.êÜï­] vÄ"½FKVîü¿·˜Mhºõ¾ù­ö9Y;áÓ±<ö|ÃÒÂØž={D›ËÎØï M‡t_ì7´a‰Œ4¿‹x_:VÚÞÞ3§¼àÞÛ¯‚OçÎC>Ÿ]¼¥+j;þW£1Ïâ™| Ùï´Fºýv“Õˆ=GÖ‡¥¶Ïôrûü……‰&Ó"ë©©.l.Ip¹znw“ u+§‚ ±Kë¶ž¼|Z!¾ƒªq.È€˜Llý†9!¸€òKW_§Íl³í®œ ¯ÛëéŽpP¯æaǺ<Þ#Nvj)¦üÁ³ÀÑ  ï£šZÇõËí-9k±)tsoswÀiÖDQ³¢+®¹ûµþ´¥Ãi ²> z£Õnð{9i$ßêëˆäU8ù Ôˆì N µî „Mv Q0¸¼‰•Eëk)Ë.fƒ\"·áh™"6øZb·ÇñþoĪwÆÁyP!ƒ²Ê/ V¬«þKÚѽ.Z“‰ñØ4¹ñÇŽ5›\¶ 1غº¤ò!K™—Þ'˜zÊŒ “ |¤H1 '”‡>Å÷!Á8¨éÈtjP9½[“ž7½dxbÀD:ÉNJ ^ư ò"lÁnè¤âø&aj`Á%È#w:µ‘‰ÒÒDÔZj* ô²ˆXfIR °Î D_$(‰ä)Se£0LF<‚9ÉUÙ¤Ý@ý¤wÓñ)RÀ:dÈá “/ Yr–#àwÃãõ·|fÅD­œÑø?=_SeïÍuX²KæËˆZÇ#Ê&S!ájXÕ÷oP²/«ÈþÉú†'65¼peqÏ#ÚÝÒšVìþêûf-‹+¦ò÷‹uÕ{ë{Š<¶ÙsîZ|¸M6ÔcéSBŽ…nÍ¢ñ÷ŸA‘ðHý¬níûîóåçöº.z7=E6ÙÐ7û È8‰‚ÖQÃ).0“ Ó"Ãz:µŒôÉ…‡" mÙ>Úðn_8;À¼,˜¤\‰©ih6üTª¼zÔ{ô÷Á¶­Ñö4`YÄU3êtN!c;9½H°ƒŸñÿʞùI³ó]+ḲúøÑ·†>—Íh7Çm“ý'_6Y— ¦Âæm{¬Ù ÓpÇ@“ƆñßG˜:ÞZÞŸÁ°Wªñ·€Ö‘ÙYœ%&k–¿qÝŽzÍ%­¾¹=Ïßý@‘ÛÒÐÒžk vúÎ~'8Ê l ¶ÂO4fb°—N¬ÿ§üü‚ºoU°§1úž_±®¼m×9`Âc¯}Åë u˜—eØÓ³"âœÉ@åÉ  +£|£¸í5ÍNCÔðDóݤm¾Æ^ŸÿíŽÉæN ˜8u°)‘òR0A|óç'‰W£ä53‹˜±~øÊaŽO"þ¾®¼òJZ{@Ð:ÔëÓú8ÐK¦f¦.½Âˆ% Fv æ –†Bëh^¬#±©H"(dyý«[‚^CöU9ÇãŸ?4ñ`hbÜä4š,¼”u6UÌXámÞŠ9ýÆré}o3ÚG‰ýXOÄ{O•ynEÌn}MoiyÜi6j;º-ðº$O"p:¬tŠÊÑ:²zËß{áÀ‘͘÷5‡š ÁŽGû¼1™1<°ˆZÕµ%!6€aÑ9Z÷‹Ùd@d´Z~´òó{˜0bh†± jè+Cz.1|ô1E&4 ¡Ç `¡' ã0DBZ )¶¢! c² &ÔE Ñz™%)ÈEÐr‡@qÓa× #=‘Qz(£eT3³S~òˆYI² †€T„&ÔBhðcœ!!ŽNÔˆ€¬F|¨"òØëuÇÚ_Y]-”ÜsÍ¡ ÙN¦Q‰a²Ÿ"yò,/yZ«žìð”È õ.-){FÃ&-ÒŒ`¸H‚Œx Dæ'h4ÄœKP0y§´?ïøL¦$ýgQæ yûDJa”_˜ˆ™¼úL€õN ’—P0AÏùI^\‚¦ÉÓ wPoéƒT„“)i¹Ka¨µFn‰D2…u["Ñ‚dÈâl?’š²e„jp:. OERŽKŒØìÂqçw®ZµŠä´íý÷ßÿÊ+¯Pkö4à˜ï³]æ³Çÿó]dCÉ÷Î%¹Ÿ»fÚ “œK2Ö¨þ|m5êœdúñË+Ï(÷Ê×ñvmlùìÕvSrrÌ ‰T¹p¨+áG®<ãýR^?Øü?›O°‘î{É¥‹¬°¾o;=Ͷh–ÎÓ ô‘ aAëVN/ZP–%Ç•>¼j×÷îÖ9¡$$ÿ9ìhê (fbkdæúÿýUûò·­®Óm³T¹;}áNoh_Cw®Ãx¬£ëÚÇ^ý¡k3süÓŸ¯=¶¥¦SÑãÄ7³ÌÿÏ·bCz•fT( ÅÜ™SP)SNC£ZÀaeŽ]<¬‹ ­"ùÓ1Ÿxg›¶bî¶…š^^Ý”4”KÔG¶ôé#}µ‚Ö™LšûCaž»^×ä§¶}^¨ “Å‹Ã=Çô„ ÔÉq»-&‹1±ôÀj$’‰iQè©o=© É®|DÚúÓˆÇ4 Gv»Cße]*_¬n\¹ ³Êf1õÄìÇ£yÔg+v’d¶@IDATqDoÆ:ÂÂA¹¤ó _â²Ä‡h•K—O[{‚‹/÷ãmt’|¼Ä°z…Àûßÿ~ÂlùüÄ«jêcÈã´ŽŸÙÆ[6ÈѺü-\¬¾dÎÄTy¸Ë¬œ’àBФQ—iœ+Vàö8øªaóÖx³œŽúÁ~³°œ;Oei#ê'FüySÙ§(Ëu,)oͲj04W4nBÑ;ª¡©µ;^é7–àß*OÃQ@_Ë1ïÜP|Æ‚DÉ7$ 2…U¦³ÈWÉÓÈEpgò¥0"T’…bˆç)ÉõÕ‘¾@¤t(½Ä¦ËèìÅÓôJÞ¦*°Ê‹J–ï‚*R×_=Õ|ê©§^ýõx€È'žx‚A€^üä“O&Ÿ_?Ñx “¯ó«gTÚ}õݲ:£í¾²j×¥=7ĬQ}äfís>'º~ÿîltFùòŸOûü-­< úÎçÛ•AŒ&Iø­º¾3ÊëíšvÐ:’°‹Q]‡¶Ó&Ó‡?üaå=B ªÉ7¾ñ‡zèL¹ ½ÅSšÃÌh_Õ4}ËžƒS-¡+J4ÄgS;©¶¬ý?›º+<чª>ª8Š–ÆB=‘@ËŽªpW¾ßЀÆ/Ä*géJ/§@ø›Ø Ïâšà(I_Ñ`Œ-Q_i#YË Ø»=/°¦¶3+µeG÷uîÜÁ^uF“6úSY¡ñTÞÖ¹ëßBûøi²jH%ÌsMY-Çm¦H(f~î¥W1W|F[u4ÿý“œ†P;&40 €ŽlýŽ‚ŽV*¶ ¨x'Úˆ×wÐëÇÅ×lŒë7P`›&4ÂŽŽvÒâp'6³”‡0}’0Ç!1Çüëó޵jHeøÜÕU®`.ö|âa'Z#^’6ùþÿ¯Õ@ÌåÃÞ{"™óØŒA–¸†P6°]¬œ×¸HN¯ÄrNà#1 ª ñüä`K‘¡ÌÓPhÕô˜˜gÉÑî– eåe¹þöúù9ž~ÔÒ‹6.åÙú0÷ ¶Âç7í D¢í^ G«rmÚnH†h$ÌR)ê‚</ËW,®wž$þ' ^bEMéë3’Vl°›¦ØŸ<ªÑ¿±ýà´b›0&[˜‚ê³NÌàìÿÎl21Q!üÊmDGL&­vo¼Éôûßÿ“IkÐ~låüòqÀA[ ZN‰Uu”ꨟƒ0—A¬ÎóÁ³òé§Ÿ¾õÖ[ÏáìQ‚@¡ˆ€C°X[bxÑOª…ß'. ‘²Hô_Ñc€®Ðl„ ”°e¬æŽÆCrh¸lr&#fdâ!Ð&@¤Â†×ÐGè§¼&PU LŽ2#Éû" ,y´èI²ÃS@ jÁç8€ªÐ=ÉVèa»P’S~è P´‰ rPŠZÒ&GÙàÀ%¤1™ª¤Ý(ƒ¼}Æ7øH˜ŽOOÏÐÍÿ÷eÇ;?ÝЀöüäÛM@x ’´?¹pÁA¬|ø’¢ßíhÅ)9Y éë_¯ôÑ%%B<~O×¼J$ 1ý°†,1Ý3Å‹ (¼VÄi‘`^àó: äŠä r\¸à±„™—=¯W/$PÂ<…³„% }˜ï:ˆ«*ERÅH@A) …¤ÃŠ„ÐéàÉ]j$üES2ÝH©lº®¡YúrÊ!×4þg>óŽ\g†ºçž{„àÆodXcH×ÓŸGae°œGefQ×mƒŽ «w5¨­Ü†ÉsÄ“óI^t¼ç¬g(8—¬µºfFÑî“ÝÇÚÑcÏàÚtLÛ-A®ý Ý“ßKÞ‰Ëô_N«€"¸É\ñc-š ú£XOÊsœì žQî*ùx`0-€Õ™ˆÊ²ÊüUÛNîì_Œœ!-h f“ñŽ…e_»y6{:}é»Þ8ÒÊdá²ZüamIÄð/%öç©>~øÙ8TJx¢1¢-(æL1|úB{Ô«aê)}ìc(ù8+üÇü?Q ?ô¡¡7bS0Aë)ÇBx¸€ÝEe2eM»7ÔuH뽋æõ<ûŠ?)pÛü]-k×¶àwÃ…†š…Ä :„Õ[)ß4: Ã:á¬íõšµôöó²dUîîZltÙ¬¶œÞ~×½]DGéf÷¤P×A³=ßQ| †c9­ÙYì*»NO`/\lyÛš3½³ñÔ7J‹9Úp|gƒ¡Àmî©pÉÏž ‡ú‚FcÉu%ù9¶Üµ­_xf™;wÁá=óao=9Z³*Ê›¾—å¶—ØÍGZܾhV8_(¥`sŠ~¿¶HP9ÔeTRÚølãÆè¦J©síWiÌ©±Ð¦cщƖ?£¨Åœ9_“oku™Ü>㤺Œç~2~mé{ô…ƒ˜L?~í(m`GãsI³cãé N99—ÖÎvX½!m°ûôðNw’Œ.ì;6 ~vÊêVÞpÔYb *vuL/[²yâÂòœ]'ºÿX}I¹µÜ` ]¶hVsc°­»¨ÄìcŒüÒÓúÑÅ|@bÈõ»Øq¡0p’‰¹3âènm)2Y£ÖàÒ‚’C}¶\›!Çg+’`´Ø  GÄøJY.o"e~?yýè{s^[Pàu:mysŃ£; )úæ¿* ŒÁQ[ª6n2‰ù‡MËБùuò)#ã•’í_|‘·Ï®»îº dsL„LoÀ¡1 ßöF2©)¸åžC`  N0£+Y3]šŒ¦’8 š”>è1è($dІÁ2˜ÅˆÀ"´B%ÒkE¤mI #ÛY*‚„¯Msñ冓1(c>u7‘¼(1”DŠÁ€C-„ƒ¨\â¿Æ·ZJ8ðŽàÀ”Kàqº$4J‘-ÃÉNR6(…›à;´›”\ŽéA‚-ˆ^1µ›š˜TŒ>Z§~‚Ä}ÿ¶yl±*@!©í/…æ’IY5ÁMµ=ìÈ)1ÿxÃd•|<¡˜€¸ ­¼>Þ#ïZÞ¦zJû hKw®E€Ûx.b„L0bÔd¡oJÂH”èGÂ’\ž"Û0$’ŒKÔ3b‘}!Uyž´€BRx r¤—2À±”ÂÐòSz7©’Ë)FºézJÓK¦ùô§?MÙµk#Œª#º"#Ûù Ø1!!ˆ“ô}Uë¥HÅ&0f}¤ð÷.›¼õx'h?ÑñS£t4ì1·æ@sOoï~[Î¥#³ñì[ÇJد9rùÔ‚×i¾±‹Ë´éxð×MóJw×|¸zfZƒt QYôêav¾‚â,Ûw?° ±Ä/šœKŽG[½s&èÑ u<Gë2·Û0ŸÒ¼@¢/îmbµò/7TÃí²©š{~vœB€E¯Ü|jÄo?»ŸH«Å8ob.KÁþº«‘¸¶×vĵï;†î@Ød4乆‹äp†2¹p !a¹íO»Áì8ÂeÌ®M/YãåÛ丒Q»»ï¾û¾ûîûõ¯}óÍ7ë[ßúú×¿Ž®ˆÀôÊ`®4}kŒnx¸¯y,›L¨ï _@Ÿ[¼x1êÔ0› EiS¸ ÇÎi§(úX&™xr`,‘‘Ú`+e¦›ªÛ££Ù¿®ùG[4’_m¬½Ã¥™…“-qËÔÐç`ŽÜ ÌÉòUÅ ÔŒ?õSKµó{Üñ µïÊ]ðzð»~ÊS€?/_2u÷¦½°¥<»Ãå*Ž…{ZYÖX{_wWu6;û4£bÛ±Þ3s„h 1=ɽ¤|Þ‘6K¡wO—?ÖåYúѲÉ×ÎɧÍñOÁ¢AÐG)3 ʬfE§A`P²ÅŒ¡1ytuþÀNsœ‡ÀOјé0WÝ|??eû0\Rñ3åZ§âûqÏ«8v@ì=”õ9s樧89÷pKß›‡Zñ%–øÇ>xš‰MŸüb³*™Kµ@¨co°c¯½`¾-g¦i‚bª¶>dóÄÏ\rÛ‹áîžNöö¶ÙÌŽšcA­'º1tÂþ¨Õ]Ûáýú_öÜR¬á×–ˆÏh`éµ)·cQ§9h4„M×MEMKò ó,ø Ä›æ.[II¬7'Ù`\Ɔ§ßaO™2E2]`q|u®¥§(+×[û¬Éâ±dU,[¶ ±Aü°è^xá…“&üW¬¼2™ƒôît_l’éÏR̸ɤ^ÖïÎÚÑ7\ ˜×]w„‡yÿýâ¿ÈDþƒüa9rd0<R”LL˜ô ƒ*rU¬A.ìyFHì[ cp(Æ:5<ŠTÐ Èô™A8eF˜ÊÀðª ÄåƒSè™+y2"‹t®4ÉÌ3h9<’a™¦ ;^¹ðî=”K?ÉNئ¬õâ)æ=5… ¨#Ý–H~ª–§j`dA-ˆ‡†úr%Mp¹‰GäCõš¤°"29¡>ß:µ™ìÓÓHø ùk÷…Ý …I;…'ÓÇ$´¯UÀ8â„JDBO&â$rÅ s ¯˜$èåÀ‘Vˆ/Z¤bÂz†„å‘D¢WðÝž$@F!D%aLH®ÿIø‰RJ7D8K’#‡jkTº!1ˆ.ü¹ TPGümõ Ï}˜2ó"ÒåKÉS>zöÙgqXþMÿE³£Hÿö·¿J <¾p§Lu^D^T¾ òFØ=í«ï›õ쮆ªâ,ñÚ›zqoãªmšÅä D9˜•ŽöÞy§¶ÍÏsœŠS&0=ðÀ' rÇwáŒÊ0³4û_µ¶óæy¥³æé¦×ß_¯ÙSƒ;n\‡šzà™ÉU3ŠýVèP)Kd_ q´.C‹È£oÜ6—w„cÂð¾Ë*~÷ö A눩n=å¾ÃOÞ þøS =øÙ±â积i眑x<Çií â*£}_zópÚÖØUÛ¬ `Yˆjò‚ªæD`—¡Ìø¦{Ц( Mñ6L)èel<æv”® 4­g·;ZÃ]¾mÕi7ùƒ±¾@Ôã8õE—£0 sMºÞçíŽÄöãUV˜ïè·ôXÔ @R”6LJ4cjE8£}"×}$W…ÄŠT€}Ä £­ªUi8b€ñÑÂÊ"U©|¹ŠxÀ—„§úŸŒüµö9jÖy†»Zèù\´a\;½ÇŸ‘ê·‡ÍëR?£oÙúPä*ر/×Yó[…Áèò›ö†&ßãDÂ&Äc‚)ÔîŽø‚ì¥j3šbq£/nwCœZ;‚Q»Íd°Ý–5NúÌw®˜±~û¾¶>m»n‘ $-³©¬/^®YûiËE]"¾F;@äð¥—^/lîmÞ¼Úv¯5@çëÄBSDž´Œìz†ç>BL<#üå'5R 1ª^òTÑ(VÝKZôÿAC¯—ÕôBzM´ ‘’\1QˆIB-(64°Â4 Ø+Ø gimê•ÀA  îp€§ü¤`”YM¼„a„¡J"îxòÓç× ÿù?îå>1ˬ¤èSÒ¢ýԃ瓒ù #ÇB.Œ« =(Cá•xóZy)¼;y5ˆ\‹ÐŠ\Ñ=(…­H5“Tr%B£”²0¨+ä Žüú®‘’89Rò%-LÈZ¼ £g‰xSxʬš(U—A3T¾äÉÌÏM s.m˜./5%ÜvÛmTÞÊ ¾‰š·zõj¡Ù¾};*býyôs,û.œ½fAøûRùƒÏTк¿¿~úÉúúU|ü<ï;*‹ò?›ëZzœ­ùÅeªó¾&æ:nÏR©, 2}ì¥ÃÉÞR …£‰%–üñ´8)fü爷͎?«Ž.ŸZø‘KËáfÊý{w-(ðØä%žèð—ç;1pŽ´ôøÃ1Ð:¶ï`ÂcÃþ¦ãEi7{ãÇ;¼>Mçüʪݧ}ãe¸2#¼$”•¹8Œâ£ÌSR2`÷‹_ü‚£õ1½2)³³êå—_.UCÞaÓöQ¬QrÖÃì„ãX3™øX'S<È]QàÀ¿’[a1¯½öZÄÀ®¨òb,¡uÁQP4OÉN”Ežr¥ä_é mðú75Fæ»:Yþç6ÄY»ÙÜÍñ™Z×E1ݳy3š ZÚ*ë³U/CÀîë«ë.ê 9{#Î=;Ÿ;ä÷\Zî¹}ΩU$À̪e Ïó&™0 è¾j9~MÑX¤ÇZf‰tãgÛä N-‡–†vÍÉûËv™Â-ÞÍ册F‹7b v·yì†_Ü §ÀH?—Ô$¥6ƒ^»§É÷û];8\]™WiiÇ/.ñ˜€§j^)…þ'Ùù៽ɽ<צ$Äéî6nšÇƒç“†Á ¢Ï»\ÌÞ½–^Í#4æ¨8é-ïéj7Æ´ö?дjúìŧêŸg÷m¹ ¹¦ú|-b`cäpy¬ñ™‘†]­Yå&m6ºÔ³ÕmémöMsÐs0MjmSý}' -A“ÑnëÿÞȇ$R"Q–&Å—æ…Ö¼µ£Ød÷{̆!´áŽîòùžc5Õ{ƒÎ,³ÓåÕÀb.NäÀ›•®líËæôômë%Ëö­·Þ¢? ‚Ÿ(èòtTîã&“4ûw¿û]NPáAÆd¼áÔ‡“á¿0+š±wéÒ¥Âðá‡Ös¿Ó»2RÉ —àD‰YκrT.Üxƒd7„J@%E‘1= ó!­‚¹@–ŒÉ¨È‰÷ j¢‹=L0<í·Alf’ DE¦àü„'1 ݸ`ΞKÖ Ú0ç' µƒ §B e\J&M˜C­aN«JÉùEQá,Å€/uù¤„=O£ñ7¦?á)wÖ Jyhm¤eþ%Ì¥ŸßùI^dÁW^ ˜;3‘žaòâ]@#ñÔŽT´<‰¡`Ü•ÃÐ0¨u|ÄÂ@7AC:'µÐ×Nnlz_ÈÖÙä‹ôM…¶X¶µwo»{ïºF䈯¹uï“eá&Ÿ}–µøJä[s ±«b‡Õ–Åöý¶ìÙF«Ëî EìSkŸ€g¼òSáÎ=ÿ>wéÝ—n FÕßµ¿õ%CÌëÎ*áÔ‹VÃB{¸ÛipÐìb2)«‰w!}©¼ ÖWó´»â6[Þ»¨’ ÎÀkŽÜ®Q`ScÌk äºl®þMÊ…U@‰Ò©Wñþ‰ò/þa¬ÜvË÷ï]$­1Ì{BÃä–.ùù˜KçΧ .OΜÏ=Þîk²ºKù0Þ\·; òE‚3 Ô‡Žø”ol|ó…ˆÁÙw¤«FMOáþ³ŠC¾¡Ñh+°tØ-¾{ÐaÚÝͶ__Ø9)°¯Õ†c]a; ÇYï3mk·ZÃ¥Žw \Ý¥Y“\ •%U¶Üªt-œ.Þ^wcCó&_,첂- NO¾ì\ÆY¹r%èÃÑu;¹{ü^<û‚9y-}º!#;z9¶"KìÓ1ÏO™ ùT@F)Gê¥ 3òl(€N`C†nyÄÌÅ\ƒÍ¯\À$ÀÐS {Ô©4ˆ"Bl]aÈ# °X/%y*w`nú#<™Â ÒÌb,»P*ÿÁ™L»5n ÷ξúÛ­k×BŒA‹QŠå#ŸŽ}"W­ZEù{{ºwnyeR®ÉP»: Û òH]â ,ßy"Ñøå®ÙoðÜ4Ùè?ÙÍùè(ÐzlN¥Ò8>B’ë#Ó…ÿ¸õäËûšÔÓÁ'TI.ê@\Ó~Œf§É–½ÿk„±í±¢Q§ÉÈv 1Žá¸7i¢ýG͹~Ÿá3JÜ"8tõðŽl†®R©=ݬ¸fâÖ"“6 D£¡ö¸óš+ßÿÆë/Y»Ê,1Ûͱޠ…S&êü–éî@wÈ´­Çseq$Ï|r’½¦(¿˜Å¹æ™fg‘dzÚ;ˆ 3ÐÔP -n „LvKÜào ˜4×9÷áðÍך‹ÍYEÖ>‹1â²iK–,f—v†ÁÀ¶•˜(šT ÆM&ÞÈ×¾ö5v1Ç&ÌPÌ8ÉpZÄ â”Ȳ:ï“#?%YºHìR*‚‘8z r%p‘¨ˆ=… s=¿Ø´L*ž0Þjð„ƒÞz§ÌüÔǨ$ús ‘L1Â\ ÉS) ùr‰Z#ha@1º µ Åàs¸É&pq§l <äa.)L?{¿”ÂH<Ø+%á'¥â@Œê›ÂDîó¢™zø =o2Þ0ŸÊ…§„)4 €d'wÉJ*ÂEäiÑ:}òÓ†Õ&wŠ’˜/\UV–“zŸAE6@ª ðjªË Bå)²Ä‹†™HBˆ‰D0xõ¢Í DKYEc!ÁPf@XÁ‡$†|¸ø)¹Ð™Ñà;CòePæÜé"QªG ž;²Ç#²SU#w„™,dö”’#À°M—©Êë(êó©*³dJ&±8å YŒ)ß…Ì ²ëD×¶ÚÎÇÚ9'aIEÞÂòwáYéÒ¢;V!O—6]üßÝýË Çw·ÆA ørÿÉ+*«ª¿x%¤Ý\Ýþ‹u5½´üÚY:O‚wiK M·fvµÆ ­^^.$g£ïÎ\Ï´¡8=oÍñå»Ð1|ö™³'äü{z/„„\¾_Uõè 8þ‚ÂŒ”i“EB5Gðç¹Éèä2„,>uµûw[N<{T[0àzq¸]Ú¦Þó=Úîu &iÝan÷wuûÃÞpìÅcþÿ»µÍŠ[ͦâ,ûĂܻç]–õ½š‚œ®@8v"œ•pà‰ª‹ÛÕÃ3éÌe€yf‚3zšîC5s%|˜•’¹Ý~ûíïyÏ{|ðÁüãl€Ã<ÎüþÈ#0³j'™~ÔcF°S&VÍšfICÿh›öæò9ôÊ) 'çqöo6Õkõ~õéÝ7iÚvzž! ï¦7ä0š ù.Me”‹·‹µ kjôÖ;?±y”«Ñ;)ÞõŸ½ºÐô\ù‘¾{Ôg÷‰2'ß'ñ/@§DÔPò”VgBµ3˜Ãq“ËbË\b¡nöþ·åÏóÊ.þnwÛŒ¹YnôÑ+*<jûNÖ¬Ÿ‰’ÑÁ—fC¸Ëir  RYJŽ[wu­0™­£©½§À*^Ö/Ü<²x*¼ÝGµEŽ**í[^ýËAÿôMÑüGŠp÷ÐtV#ÊlAÿ!¡¬ýF…¥ä¢¹¢Æ¡e²i äô×°ìœà16ÎÎ>’Øéåd§Æßj66ÆrøûüUËÐô4à ë·x>Ä|ýÖ9ý³úpX_i#>íãÉQ °i¤£ÆdŒ³žš‘Émÿô—½sŒõ³Üû Ü9í1S‘³­«g‚5{Éë4À´´Ðmíìý‚hNÐzçãžÛpåÏšæþN šwÌ7'jɳÄBqC|_Ÿsª;ä6Ç&­¸Ën)1V³¢HöÈ ÷K ØýaëÉWtà,™^™Õ*žÿšdŽâá6ŸÕa¶æÛé÷>Lý_ä»ý»öB\è –zz++&¯ßÝis˜&O/Þ=8 ‚Ì`ü“ü\ã&|ÿý÷ãíÈÈÀÃÂX&`@ÕsÐø)³sš$aðGZ„^!‰paë‚€)0&#“ÐT”ÀŒ€Èxã)fâeÔ•$jècXñ\€r2A óð„9¸Î/„Á¹à)Ù©„Г#<ÏM&&ª&ã<8‚pfò’²1óSâ!£ï;3ÀA"Éæ ˜LvÿÐÀ–1„»°å)ÃS艡l‚°Þ·o H- €ŒKÂæbN'@³ð4¥ÒF¤I¥ÖR*rTÕ‘Àºwë¾}óYãÿ77jñÿûÚúq?»A¶°(Ha¼_Þ,‚0„Ób ox^%0äm)ï]Äi'žu.|SD& ãë­›HÂ÷HIHZÄL/ÐÃq¢  h‘È3w.ôÉ™D„ùRNy 7äá¤Àt%´±| VHâ…›‚D[õÂÿð‡]x(À.‘zp¿7m;ÐØsÍÌbvî\Š3£’Óó8z‚å¤\þW¹³»FÊàÑ7Ž¿d®¾ðCOi+“äšW–-ÇV¼1þÿܵÀ{æ”L+ÉúÝÛµ}Èm 'r4âÏÞ8Fö\;õù=v¬ÝøÕ÷ógöþy»¦&Õµû˜QBÑøeUˆ°Ýà‹ËÊÖ›÷ï®›þØK‡ÀC;á)†3°þà³8Ç”œcɌɌ¬¦cUìSš+åŒùÌ3Ï snÚ´‰Y•¹•$LÄìŒÖ:×ÃR¼‘ìÆ”É$«xI ›ÉΦÔY}ˆ&Èb[_¨ÓbM¸è[)Õú<‡·+”Ûæu”x¼Ñ@›Ù1ð MžzÓ]b0ƈOwÅcGÁ‚¢žêê6àrSoØ•S6•JÑØ6l؀ء ¡Éɘ@Ǻó»cŽåÖ:‹QÓD;wÿÈÓ 6?çEXت`";ÇjˆŒÕµ‘ìܼ šËÞŒ™Wn7Æý¾1ä—͹cjéMþl‹õîí^b6F-ÆhqnØïª¨méZ³î­+Š ñX8ؾ«ÅgŽÄmó²›]6{O_î"kãöº˜ÑdžŸåŒ„¼yÖ¨Õbú¯ßýqB±¶¬˜ò ¨–¢1£¹¢b’5'Ý 0ß=ÑÒXÓaèˆ9â9—Ÿ|á¹Î,e³hf'ßpÏöIÕ ¹(ìæ'],:Á_9´¯¡÷[ÏîW"‘dügB X܈Ùt àwòf9„÷’K.yiõoº|ì·w™ÍÛB¿y2»¥ÀÒäõú#±Ò†¾üˆ!â‰à9gÁ}»Ú;µŽS9ûêΛT.¸½÷ØSÀÉá°!7G:CqÜë¬3ÜåqÛ`¶.˜Kï®5ÌiÛÏI)¤u” | a[®ö¶f°ìüâÇ6vÙáBS°3îŽâWŽ©fêßœc¼&Î^rõ\ûžoúN˜Â¡pÔY×úÝ_A&ßx!þÇ;—mÙä÷†œJá±åEù8ò Õx:pò )? ޝq“‰ÇÊÅ-nùòå 8ç¸ý²c”·bLÿù€ÁŽÞ °u™­äÛ ïV  å'PÖ>&1£+BN$üv‰‚XAf÷¬kP 8ÀPŠÄ[à6JH˜Âulo(y %¹ÃÒ„S*`> žC=MX4y£7Eâ mtøì|·UÛÉrÑß}4 -îz*ö}Ö÷u… ÙÄ?n/ËqvÎ^ùñû³k²Ùoò„¼åŽjNãt›l&[^Á¤+CÍkÿvòŽ …»ºŠv žo¹±ÌRWálŸåö8M!oǑ©Ké-¨¤è¬b2ñ¥–ZÎ[°,7纣/>ã Ö¬ÛsÍ5zs讟þÃWðÝ@š ×ßiÅîiÅÙw/)“˜‘ºão+¾"ò-Qxþà 3¿õì¾Úvÿ¾†nFÆ‘ÊèÂæÓÙÕ‹äZŒ8Åa±„<é‹GCXÞ1£¶¿û¾©ù93ègÕ­iër½T³ Ùh±–X;óM‡úN¼2qÊ 6›£·«yÊÜë ´,â v†|­­œ•\9ùæ†ãlUì »íÆ˜Õ ,³ç/ðu4&ÐGü¡¬†øe“'V:Š—‘\Žo÷Öðó‹Åkj^esƦ)Y*«ÙQý‹w|‡›M;üù;w7Äã&yò&‹)Ø×Ò ,êAÉgØ÷Ì.!`²ºýÑ©û»‚“fÏgi1™­ã)Ý“û¹.Z“ g:ŽÔ¿FK±Ïõ‘ç,Ì‹`Üc¬˜¨±ö¢§P†™â1ÚAµ2R¬zX¡…HíÐE¸˜ô  ¼” ­ã*Ž 0„?Ü(¡,¾£ l˜%(C:å'kRQ;Foa"Š|€9¸sIIOACàÌ#y/„¥‘µÄÀŠÀtƒ„ú×GXÐ@RæAia&i8S Jñ¬§ dAQIB±á =#† }Dè07îÐSMرoôêiº@O òíWì7hî˜WpÅ”AM ­Î+§äüagëÖ½ã€]º¦NˆçuƒÌ R*¼G$Jz1¢xHOä'r%IÄ›ðê;$PAAåÎOÑm#$ä.h‚àÉŽâ \$‘’ˆjD!e~¢Â\›0CIÎ!õé—~-œéd‡À“\ª– Ù9~‘S¾ )Û$ÙqÌŽ?1Ëy:âŠwÊb¤Œ|þ`—«¡!åSù髪>÷ßÛA눹gÙd¯,«Ìg¿#Q-«Ìkìð¶Ód³sÿöº®éEîÙ58ž“:­»´*þÄì_½UËÖ=·þǺÒl­?Êñ²bqD>ü.­ÌÏP¼y$hÝg¯®Ry‰<»»áÖG$‹q&Ò ðâ硦¾O<¹ÅÜÿÑŽó@þùö¹éZiyUÁºÃ­’Œtí>ÙYšãäØ#-½^*ÃÔBÝYZ™Ç!阨ø„ * \ÇÛ5sÜþþ÷;±w°z¾w÷ÂGþ´»¥7ô/«÷©„ʃaDd[±=ætfd>ýÊþ¶’Sÿq­‘*`7ÖL&ŒÌÔ&€¹E‹ñ Qß½ÐÚùô¯‡ŠhA¡!€øÎ»ui†6EBažqÝ~»Ù7›b½ ÀjèúíêÔžÇX  uÚ2B[OÜfއ'åØ,}]0ìü#8ªÎT£ QCåÃl¿’jÈö¸Pæ"Ç7ñ±Ù3å.kÎ4ƒÅå?ùr™»¾12édïÇO²±òÇ—V¬œ`aˆø[|†â@Ôè0Ç ÞF³C뫱iF|ëÝæ…oÖ¾E²&¼5#˜´)°7û§ÇNîw#;\»)6ÃSÿr[6»*°Ià5O·xgâêà±¶,š7õàQ Ðeé6`¨ª”ˆ6Ç"r¸ ƒ‘nÞ‹yW¬X¡ÚgnYηî˜÷õw»‡nšqÚ³¥IÛå Yçέ@Å6]@¡uBðžY%lŸ±áhû8`—®ÅâkC¬7l+ËéÍ2ìéˆLêéÑs@/ˆô„ƒ#ÚBŒz}Té›!…€DªGúÉi˜‹‹UçEH„¿ä(a}©‘ôÔ?J^}Ô_»ëZÍ3{Û9Jâk×g‚\S²jèÑð¦)ù§‡S&¿#T$×ÍQt•Ÿ8Nâ>I¤¼ei~J@àfèãäFCãBiDx€w™/¸.’ˆprg^@¤EäÔèn ¤C±¡âÛ-‘¤þê8Ò( == J¦08<¤ŽÒ’/•"žn¥¤Z˜Š+½@ “\øñ˜±Ö¼Ê1⻲e^Ü«-O6¿‰YàéhväÞÄXŸ›\<}]ð'b•ÌKûš•ç¦óGËwÛX-Ûç¹’öÃËÒzÙ虋¿¡Äܾ¨ìö…¾úô~ªs.›ZÈwàtlÝÃF¾þ³¶xO}×ü²\ª¹äúŒ’ÃøOÈ<ÎïþoÞÀ4áh îã—ŸñÌ’Ì\óçÖyÕŒb…ÖñSà/;Æ;}S 7Ì–‚°VûìÕš{‚wÎt9N¸0~*¬€N™Ç¢IÙv§"Q &å5÷ø›{´ï¾¡HŒSb9Uù–*ë¥óOïõöõ¿ìiìÖÞ<ËøôÙÉŽ‡¬ÏsYß;¯ô»X@ñ~øÊ= IH¨³afIÑåP ÐëÆl9‡\°ávcÓdçzûí·1'Ôñêè[ u6«•ƒ?p|öbßÛ·lß ÄÙ§¿9t)0‚Ï VÅJr_\ó;àÃ>hàüùóÿÌ®‰QßÀ—ÝÞ6om¶; þ¸åã‹ccÀ–씺)º™ j€ ˆ‘æŠå:M]8ëØõ¶ŸàE†Œyè£%|šÏ‚ uÌ6íû¿ÕY<©ƒÅF‘;k=áÖÎH¾7VÀ£¦Èvu÷ÃÈh`}H ¢íÑÈzÝÉå3Ûš¢áÆHl¢É`¹´@Ûp,‹‡5_Պኗãó»K‰*ˆÍ˜+&œ(ÊòTÌ6˜«kB®ãŒæ¹?ìn?¶N3bÕÅQ³/íkúþÝ UÌ 'ú=„9xdôãdqK^Àhèî;™å1Ù§çÒ&ݶ¥“f\½qãFl})þõæcžhõŒ"ƒ;k ÷éÉÜ•·;#7„»„lþ'¶[œÆH•¹ƒÙ´®/nšXRˆ=#I(N®P Ý–-[ăæ†nèÜùÖºcyFxºïŽ»>)4 $¬Û•0c§èaÝY/K¡‘UŒ|‹Í±þHk¬/T¥+˜C?V>Õª.ºçãÁÑlŸÿüçdÿÜsÏI!XŽtï½÷&x=Jùð£ÁtibdôfÜÊÄM•3FrŒö ‹XóoR‰"‚å=Èv>ÌáŒñT+ùä)‘”ºà54<‡ü3zƒÂVb±YFJôhéÔT„rR/A܈dîc=©ÔHhX6ÎOÊ GFè8T¶¤¥äô8DBÆLG€xù.J+Qj-l™ï à¢ÀÄO‘¸S~r4„ÊÀWêvÃORq—ƒ'””JÍþ<ÂÅ—ÚÂ.§õËKg—h_&Žw~º¡¡Ó=ÚæŸV¨ù\d¸Ø·Ž•°Š`]5߯ ËM?H×€°555¼JdF¹Cò•ˆœ$H‰¨ ŒÛˆ2€ i$ ÝÇv$±äBÂ{Ä’$Ð/ê B…C@–W‹à©¾€˜!{”VHp‡ QG†ékð¤H¯KŸ¹¥Ä£‹Â¢² š~}åGñƒÛø5ö[`¬ù.ŒýK.¡øþ€ È*‡d‰aå,PZº§ÏY ÝÕøÖ±VÀ»Ï]3u™éåé¶hs‡¬·EKT¹è÷üºqnñú£­lÑRQ0’C7å|ü¾%Ÿý¶ã ×§¯š’rµ¯<ÕßÔÏ 9Žo½?5¤hPt ß2?…*®hÆ#Òrˆ u0ùêÅ|x4aª>Ç…åyòSPར­_.pÛk;¼ ¹²´V¦}ªäðë[­Kib³=`âUÛNΞ㹢§$‹ªªÓt´äLG=&ƒ<êeN5„eÈ`ÌšLìg„-‡µƒÒƒŠ_’m7:óÞ8¹÷a 3ê±tyƒ·.›‘®šè¢ö{Ò]Å!ªÞ %š»üx“¶76Ê$:Ùå—_ŽYü¥À]öw!zæÙ3îëÞ÷“X¸Õ¬z¢­}ÝÙæÈñhñƒWîßýV{Go¯Oóqà³ê‹/¾HØ0TÄ ÒñåÏæÝj2[œÛ¾‡zvïg^4ãÓ°æõ7Œ£71;³úãµ›Ñl7ÙòËbkú:Bžü*[Þì`×B‡–ÔwZóòB |°5½ÍqŸ¿i¿ëcÄGioï%‡z:¿öÔäfÎÇÁ(d Ç<•ÎàÆž¾©–®·Û/›áac&k[d¢BONòL©˜UìÜôÖzò•nC‹ùƒ{ÆL•ÂíQlÔw7ú8IçÁë¦-è?يÕXÔÌìË0k‚æÄžábß:VÂ*ÙØnó¢Ju1@Ã_{íµÎX…ÑrÉŠÅ•V¶$¡³ºˆœF”çd?ÿÛLò·ô¿9ï­ŸW/wGßZ¤)"Óf^›²õL§½`Á²òy†Ÿ¬z…ÓQÜFÜOÂV›¹ªH³X0œ@@«¹èb#xíí­F£ÇÂsç~*zäiˆï¸ûóܱ|(@=DƒÍƒ°¯víÚuûÂ…¨b³-Í Åëîóýþ©§Ù#¯+bÞ´¯æ+ÿë}ªÈ°`%*f<0FZ€aØK_˜)SN³šFO|VÃ`\d¡†1S€Ð!ÆâÄ#ÆsÁ¡ÒáЄ»ÀalG· àáBÁ#~Ê^Š’@ò¥Ö„‚€Ð)˜™ Á(Isgê$[Úb€tDòˆ" pÆOçøIa@:ˆá)œUlj wZb:#½¶DC÷äN¾¬0Ùq ò‘´ž”„0¹7A&liR8P<&}Ðs§M $‰ÐS¤á‹ÇŸ÷h¦Î{gç ZG¸2ßñÀʉ?YßðäÛÍß¹¹’˜”'Kp&lÿ)'Y [ÛÜT«½Y§Õä¶¥UR²º˜#Ä^°3%¨¼ké €Î\žÈ ò óä‡KÐ·Ì 8mÚ4’Ó,æäˆ!“ècð-9–2P*Èpš#Ùƒ¿€q$á¢SðMž$Dæ‡I&¥‘´R$¡×wœÌE•§È9|%ˆ¼ô sV´ }YÐ3ÃhvVK2ºÌǦïÂè¶Ér—•zEYšÁð/à6…¸ ’[i¶Že½-I€KdÑ+¶yùñšC]¾Žo<мóU)ŽØ, » 2·D2¸èá’ÄÇI¿ƒmçA}tcw@ }dB˜M‡¶ÕvÕ´õxòÿÙ`{÷@8 d6¡ÿ,øŸö­‡˜_¬Ó>íd¾ÔƒF½bÖ‡M-Òìútá%3üŸÍuD~l…f¦¥¼€‰ß;o®µ ›¹#Z¬(7úÞåÁ’Ãxä9kávcÙdâƒ'M‰Íƒ«Ýÿ^æz|ŸáÕýMøš*Xç˜×v¢W|3x ZÒ$o%oÑÃ@ߘúèjxs ¬ˆÝ‚{ˆ^{oøpƒ¶DˆkÅÌ´ŸFsæ> 4W®];Íç+´nñT0£nV|¸‡½œ×ªmÓƒ>‡šˆJÇVˆØ?Lõ¿295wƒˆ¹”ÓW ¸¬Ñ°s&zâî&¿!bÔ½½[¶éO5§<´xÞðy»Œ1£¯·Õ•U”7ÿ6îjé á§}€`n0¼h(2øCá¶Î«Ö{o›Sâú]Æ>àÇØΛ”ë\šœ`îË·…nËo Ç4'»žPÎúÞü9ّɱx^nÎîŠx}]oÖ…?^úÜ´¼¬ƒ-¹»7ŸœìÜßå› 1L«ùÙMS¯û¾T|È÷ÿÙTKÚ;— ZG˜3›¹yÖwŸ?øïkŽþ,ýAW2üõŸ2±•°ÇÚ¼oÒ¬/—ÍÌÆ±C.ÏÅ–Ž€Øã#@Åý±\¤Ae54v1X;?4> H@IDATš¦M0ÌRÿVsë}O8ü¿½~w§/d³xÜ6‹5ûôÊw߀‰ÅÒéöo»A‰ƒ-]±)Àmtð?c&õ¶4ÄÝk$;»Rºy}x®ÜÙßC)Æ=”/E0¨è°;vìXfoD†»{½ñhk¿§ãÙh5òÌá^ƒå_ŸÞôµ».ü›¥ïS`EÏ)j+"yö³ÉÄ;•CÎ^ó“³øÇÑk°iøÜ’À1NˆÑÿ”äÈ­>RÂâ捻Ð:Hù€”œPx'g¶Ò’ô)ÁPÀqî›îFÉéïLC<SàgM(<-#@$‰É‚~vÀæÂ™Ãà ddD׃ ”bxò¶ä(@wœ©”ò¢´ÐÀ;¬`"å‡dD8†œ¡$E –ò+øSà oÄ´×MÓÜŠÕU‘§9Ý‹§"“߸±â_^®å” ö­“§ÙË?ÝV“Næ0C ðMH *$Sß &ÀKg: RÆ;EðqËðZõ| $ˆb‰€!„å)Ÿ‚ð‰#ŒÀ#u<%Œèr'/Aë„’;"Å ¤ù'ÌEZˆI ˜ÐÝ@…Fq€Œ©ý)RÜô”òÜþp–‚z¤£Tî´-8’*p³Œt…†ÎoÌú.$T ,0ßo9yï²IúGÄð“§úÈsf‡¸×öjŸpÀ8p ~ÖÇÛ¼•…î7µd;­—LðW:-Û‡ß7[á#Bì¶›Yi´öHë ÿçÉ.öüf×b€½Nvzå06%ó8,øå)þj9¹©ka¿KŠ$p¨©g0{þè“dÿËšz|d鮱ʿ­üóžïÜ9?]ÂU…v¬½}B·YÞƒ€Ò1O׳¢âçk«Y”º°<§Û<º8˹¹ÿè6ÐO—Pâ1ZýVíÚà ‘€kƒ_´ÞÜ kÃYþœ!#ÊJyEƒk*> üd•ú¯w÷Q õh<0Š-08Æ7™dKi,“¹ Ør2ápZÿ¾9™L& hR~6Äß û_ÿ=S¡u$ †cv0€Ó]—]vx[ã9M(ˆ|¼g9Œ¥Y¥(y(jŸ ì ,"Ð ˜•4ãçÙ¶¹Ó,]aëÞÿÇÞ™À×}T÷þ÷E÷j_-Ù–dy·eÇ[â%vöÐ4@ ¤ ¼´^R ¤ÐB¡PZ %ôñ ¤´¥@ d!±'±ã}ßW-–díÒÕÕÝ×÷ýß#On®dY±%/É>ÍæÌ™åžYÎïf&2Ù–Wò¹S¸^Ö÷w%s¶÷™[‚–úÀÈ èœ&ÆvØI˜LÆ VfŒC}½Ý}¿ôxQrÙñÖ\ú™èí¡ÐuîÍ!ÇE≓ û{±$»@$þî•‹~»íx™¡:æ¹ÛúYxšnŸ•÷Ü)£ƒ³à r]µ…¶Î†œVûÜòÞ£.8bÒ’;7/r®MZϱˆÄ2kש¾IùÎQîMßߦ&¼ûÍ›êkKtÎçT™¾{ïŽŸà– Ž€Ç]´ÿüþ ²ø>ï¨g}}=ÂFÇW[ÎE·I™ ê-ÁøŽØüá`çšÿh]Çùt%¥SךsÞt€Â(†2Sã:Ñ•0øâ…Ø¢#ôh:©P¨î¼óΞ~4‘4»s,Ŷº¼Æ]Þ¢}ó ÉîÊÓ9(áÓC!¹…†B¢S=гò´$Ç E´ü’üÍlóöE9‘d^U±o îݰaƒTŠäØ,ÀψNå¥bÑèP,UÈÄyÞá*?%ñÄ5ïùqFJeÒ!¹ÌÀIÌ à_ê÷"êœ4¨ô¨Á‚ /Œô„V Ú¼ŠÀC©<ÃS¥‡€þ%é¥õ ”¾@8ýNrä)è5¥O±I(8PNœDñJ¨8_¼+xUJˆ¥eÀ)ðÀ„„ACv‚_ðÄA °…÷½0ŽŠC#¤•?õú–<»OõC¿ ê¬–CÜãFw´nN…{ûICè·ûÿáìøo“ìëĵÀ8v—§Ê÷yÿDÎaæSƒ‹uÏÍs¦ß<§\ šÄ–¸­ÔœuU!ÉYù±ÑF}Íø1ØŒÍÅá â|ø xüÎü¡-ßcAëàÆÂ…“õ:ŽŸÒ/eŒ9fg¤¬Õ«ƒÝx¼áxq±®Ûä„rŽv‡:„b¹¡¤µÔè;ØïúÕ3k‹tÀÏà2DçE+ìѦÊÅ.ëñÎÁï<{ä…v–š9¶X•#JjaC¬dUÇ2+Ï]YhÐJÏ5 Ä ÉNOjòs98÷.‘tÛÌ|òº~Véã[›|I[…M?,&Ô¯›mi öÌÏí;m·š[ú%NcEá)¢Íœ¨¯ôžöäöú¸·Í—tŸ¹žU¯Ø0÷·OàÓŸ >Û”Æç@£Ù¹û‰ërßÒ×*n^‡-ó·Lð{­¬+æðN•cÖ3öX¹r%¶B,RYšc€¾AZ +…ÙYÏ2⬺‰¿±s†R¬V£Ô€~кiÅݱb¬ùÈ‹ŒÐ|Ê -m›¾â6Ú …‘°9h4…µÜÆ^,óÎp“V\ˆÁîÝ_|©Æ- %æp@¿Ân”#HvçÎx¢¦m'þ8{hÝúW¡)6úúXžéº¢dÏ(wõÕW¯… u *ìÁ—XÚ=£ˆ.`÷W™0lTjððŸf¢C ¥sQh±¢É‹Õ ¯ S„ò <‰¹2ƒ,¥ódˆA@†ÓÑ„A–±ŒcBJ‰•’ º3O;üóbÜÇ!nHµä« zýV¨Ù0!/¢Ô”‡Ÿv¦›ƒU+Ú¸4>Å“2PTJB ¼‚NRw9ÕN˜^…|ts©€80½Œb"d°Å=¯€ïU´¢çSHò’úˆ_R‘St©MM•Ž9¼M$wž¬ ­ÓçµPˆ*À!ã—â%“ÝÏ Þÿ?‡f—æHÜ?¿Ò‚¿ºp´Ï~BÌ“ a³wªÖ8oÂÆÏŠ iG2…ÏÙ~µô\D60RòOï£CA[‘j„EØ1º¤Èªˆ1Ù!–Âb¶Ÿ„ÀMõG A>ÉNdcÁŸ$3 ¬^lÀñ ÄtR^ÕA“äBy$S©‚2â´ŽJ;°eÌŽ%«ì/‘$ãþ”ñjÄ>Â7f@ 0b,%Ù±cÇÒ¥K¥ ¹VuíÚµTmÍš5>ú踗ób2ä7b꿘9ž_^÷.­Ñpðócx S=¹«í¹ýºE…8¹}|„WYÛŸìös,—X6 Msê~Ì¡gÿZG$×2pâêÏž–~¶Ê²hì ÿì#˾›:SL”Ñ(åddÙHB±]HÏ÷G¯ê²ÁÑC;šú!ûÐÕSΞ¹~±ÀžSýn‡…“ÅÎF†CÔ ³2§Ðj»#jæˆÉ)¹›ã¿Á_Ý2Cù³žñjdòîE•Ü÷Šêª,.Ësߴ̯¼2øpZ!ÜÍ’>ü•óî‘óã]>ŒR·5êr%·44X¾¹¡¯#uÙÅðT—[ó/s1³­*¨|ºS!W´çB»K«2jñ ôO÷ÃBëˆrG÷'c ÐÄL& •;æo<þsˆ]uc¹OÔ ¢í¨@@.–MO<ýl8àskI-`«#ø§«¡½´vQmY&Èr?¬{úý˜q²ô&{qî¬?Åc³hV-i7z"¦ªã‘a=ÊbÎ5x¢FO2/?ÚÄû¾ñ_µ´®0Ç2¥Äµ·OÝm-9ÆP #¸éÿzlÅœŸÝ:‹ÖW—œêõé—›¡YŸCѾx,З;l>ù«pã¾YZci®­¬ö–Tn#<^;ÆÑ˜ú¹bZFã½þ(ÓIÆ/%ÉVM/~rÛÀ»¾ÿš2›‡ °Ø1ÞÁü½v„ßà-. 3 Z^ƒtÌîlü¤ÇÕ_t¹ÜJþ¾ÜÐØDgÑlQžûú‚yöœ…åMcU®mfÛáÃh5´¸Ù·³+áuµöxeÖdŸgî¬2&¶¶øÛÚR4Xâ<ýŠÙ˜,5³¥N7hÊ”)ˆŽOœT y´Î×øTtà(}cÚ´ûY¸¿Õ»h„ÕØŸY•éÎÄèfJæ9"J8¿º½ J„ 0#Éè®H¯L@L%鿯(ÞH£ü”D1Ëdh¹ ¿x¢¾P‚[A‰Š«5ቺ+ü Nਞjþ‚ƒ”(Š'¸˜À’¢‚2”á`ô<8p1bqªpð¤gA Là€é "L(=—âIÿ¥ä|…b.VL !Š„‚@ ¾I I8夲øjª««ÉˆX•’±‚p²¦e¤¹g£`Hƒb" 3žš¨Tò»dÐÈë»çþr§¾Uädo°¶ÈÁNÿŸ8–›:ÎüÏWŽ`5"“lิC:|À…‘L~»Q~5•Ð 3$ADEþ‘OAë £—1ì3kˆ¼!«ÈÒˆp"EÈ'½ž„H&â¤Fh@àl†‘CXQ<(Iˆ=`cc#ᢠƒ‹à!¥Àmˆ·lPuJE7¡`ˆ4¬ÀàØOZUMÒR ©Å£óâ‘a'…¤VÅ8?}m”á—™‘ròmÄp=”Êô‘G‘6¡îW:`Ç/rš{«¦V¬®^Â)Ã*Ïü ±ô·ä?tzàg›š—ß·x2VEo)íØ‰¾åÔ`(²zzÉÜJÝhhóÉ^AëørÏÙ=„?9­æâÔz|Ô'„ë)ÿÿýß;9ɇWÌß®ŸË­µ*jO]™›X¾ha¥í²ê¶ s&¢a”Ôã =¹«bl †ª¢7Y6•¸tŒ´ŽçÌ²Ì š@å¸aàÀé!c=9;mR*VyÐwÖ“—'Ôá 5÷ø¯®ÕïÄÖñ”CÓR#?Лöµx¶öùóÊÜ7ι;BF.ÊÛ=ó…uúè}1ÝŸÜ·ç”§×ÙÖÔ·hê9¶„7÷ê+.l-y²5°ÃÜÙÔÇAa}újóŠp¬eFfÎeö”É §–vWDEF)ä…v£ÌÙ£ä:.Q|‚PhÝ]ÿ¾KØrÊ'çwá·äNsÕ¾/êñùi,ÐÎë®]»äUUQ¼§M›&há`jÀLŠÔ‘UK~x¶²ï€pVoÞÁ>“AV`úB¬£«gÖŒ:n[-Ô:Êòg¦3Á/hò29†GŠé?Ì´’¯Ð,.WáÔÁA¯Ó¡“ŒœAÜå™Ì¦«JMÍ}Z^2Üë‹ìÎåï‘Û— &Ay XíÅbÕùGmæÜO}¹Éz_nOë¼\×-+ë>õ“µÝ1»`š“çdcðY."/w›ûþ0oÙ”=4íŠ6óí(6Sî–SžÕ†F³–¸wºñÙc}e>Ÿ›fšºò ÁÉy¹¤b-HÜw^8²Ìµ·./4)?ŸqÿO ÑâjÂUº§Ý³pÊ»"ýGmÅ F}8Ñý׿S„+„.Ý 2ƒþý‹«~úêQ·̪Èë÷‡ßÿ£M\’MÈo›Aœ}ÐÀš þ|ágµÍ^éM£ç(4yñC‘˜¾ NKÉ?· ZGÚöÖ†ÒŠ©hû,#Ž#AýžVtsÀ'T”AOW›¿´5^×+™kif†E‚98•(úú‡oxø±ß9Ñ=ºÍ Ypùò导ò : ½K#I#g¸>¶­™ÅØÜúÏo?:Ã2˜-VŽÇÒ¸ˆ=jóæÍ uØ,»€Á±¶ªšX¡Úèˆ% h]c¯»?h×Z_Œéç1D°7–؉pY•i"ZuŒÎ} ØœZl«´çíQ'^Á»0Ò¯[#Ò‹¤”Î_ûü­3Ø[óÓ¨ŠpþÏ×~@B–ÕlkìŸ7)Ûq¡Á¨MкªûÑ€[cßM³ËjK† ¥Ó3?{=±³L?º²ÕƘ] Åâ5ÅN) …Rlë`Î^«¹“ôN1ŒtúBÉÿøÈRÅùîŕݾ€ÝMsÊ0ÜSážÆ¿ ukf–#1ÊI¥TÓ‰¹+ï‘W´¼tT®ÎûɆÆÝ¿ˆ5m:Íè~ÎWGN™½œ[@uÀ¹“r?só Š*"ê´™ì~¹íÔ-sõÑ0â–>}Þá(*1Yå¶@þnÿ· nK‚C¿ð®‰ÒbÆ«UѶ˜š™U•œ™k˜:™eº%/Öœ(k£\&Q Ø]ÂjÈàû—]*DÁ_d Z´D×Àm:yOfvêæ £¾0ZTÕµß;Ê6?$:‰2:qèì0ò%ÆhÐհሊ‹)ùÕAÇX]±Hbżȵ’Ð&ôú´LÀ“XYK ç£B’qýÈK€GèÚ8‡ÓµÏŸoißË5³Vs Kr­„Á­±ÿÊI}G:C«ÿçÒÕÞÞo_/[ø$øB÷¤^ƒ)Ä겆‡¬P‘{V~”ŸÚMr™4×òéÔz­†(ô·öé©XòäŒ0Êð¯/žÑó›œÁ“þpbãÀÊO.+bg+s‰*Ù)§ÎòÇ„Jì•$Šs1÷¶|ú»o.ìï -´Ä»ºYÊˆîÆºü­í‰p<Ñç YÍÖïÞ{UMñY?”È!8.- øû}écáÉIp[·nõ˜ê]ñQ]tù?ãICÒ`Õ |> V€ôHia»»ÏWdò[Œ¾X‘‚J,–p|ŠÙûðc¿'‰Ë ÙŒ‰Ýñ±14?õÒë\(QfÓM™(¤7Mj:TW£ßp«Í™R¼û”G.!‘Ź ÿÀÙtT#4(4ô4*‹vÄ Àn> CÒÒSx¢µ{{&0ËXTÕ¨¸ïàÑfVðl°‚ó¨tQ'äÂ]Veºð6¼@hÔ8˜ *óD°è¤#0¢ÊìÀ“p±ð°\`ÌD?GœX[ Ð"Z,#ˆB:dí?“ˆô&È”BK83Ÿš`‚dBÉD”ŒHN1ðà8 2$zÂ)€ n€¬e+át%˜ aÈ+OJBˆ?Ø ¶d «Jˆl‹NN#PeÈ(?”$çI,…^#ãIeaHyç5Å^“)•S RE-`Ë« ð$©±Á GÖŠ'µì?%ÌXŸIS 1È <¨Ç:X4…@~YñbO÷Ë=]l°ß‚LŒ~8q6d‚Z€_PÖ0ȃ’ðô¼è"JdùžN#~ù ƒ º„ ·" H”"Æ/]±aÆ#"D8P$APé_È92ÅC¦œDs‘d2Mi¤Ïrò)5‚±‡'4ÔG*Vª” lþ.}ì¤Ï²<Ã+Âaî =°2,R=\FqÒò_ÿú׿÷½ïI wË–-=ö˜¼²}~þüùôÙ×1¶€ÔLgñcçÓH’;âNtù ¼˜Ÿ±1á‘ãá‚Ñ”™À‹~v2Î*Ùt²C¶û–MaC„l-dã'€þ`DÏ¥ µ‰Oñ°[ô¹8M¤¾ÆªàLØ=ÐJ™o~ÿöûp!ìöÔ&Ö¥Õrœp#ª˜º"_W[òœúÂ@BÞÌòÜo‚žT ‘e¹v;LíFLùé•å;Û›ØÃÈ{AS_e}ÿ’*ûFL’ |Û·€6GæQÓ·Àî•£:ZÍ‘v§=ES %Òá/ßÃë⩺q¨80è!þÚÂÚÚ‘!‘3´—þ?‹a 1­S%c’ŽhxÓÏ–^‡+¬ǫï„Ä2N//¶flz­Í7öD^ë˜;¹°Ù’BëÌ9UŽÊ!@óg]Å/ÇrŸ ¿"k) $¢Ø£êÈ›ìï9´å@Ϫœm[‡Êck­¦X~Ai~ýgt,¹jjjX9‘=|ZyáÞh™¹óhŽæ|P”–)3íë;¶î(¨\lÄîÆhq™œ•l—ƒEÉzçž=ZóëVëšYÎÍ ü¡%l44z+÷ùÊý Ç‚’hŽÝúËf}Ⱦùªr‹ÑpúH»ÓŸYQÂÚŽ9˜™ø×;ZŸÙÛÎǜԫù6cÞ{ßÎ&§¡õy§m2k¸¦C[›°q(ʱøã¦ÖˆÕb<Ó¾¤nñµeË—³˜«Õë¤ù‹¨}óú[^-õb±A8aíÈ¢—¹¿ÀÌ¡u ä$â~‹EÿØËºïÕ+ë*¸tõ¿Üb¦]3Åð¯7‰Ÿf,0•XAVl(oòú©§S©ÉÖfýCšîVM2£[¢ËkÆΘ¾óE뭨Έʾ^´àÛ¾ …Hµ¨Ö \“2ìoô…ô•Öò™CÇyœ¡yÓÐoÿ(dHˆÙ{ÂdHØÜå­º~žþ•~°ååÞA£~Ÿ²%a2&ŒÉN»9´.ö¡;«Bô‹£m}MÐÀéÂñ$‰;F‡•û%â]q—Ájôvh1]½·¢w¸ŽDúêàuÕK§Þ¸©¡wJaÎ]‹* €’{Éš:L*eÊÚ˱ uTANiÜÓ5Ó¯i@– ,fw á´Ã¦M› ÇX€„ÔQwÀ.«2ÑÂâø±Ðuϼéÿ›ô׋àg˜b°â§Ç¡3£Éãð¥ À¨›nˆ¨D?%Ú8ª5ƒ-IàCZ©ÜÐÏù>$W5•õ²Ê #vpƒ|AI*Æ‘&,å' bá I( ~4hûèü8² !KBO¿& ~ÈYàL.Š*È9Q’5Ì™U!æ•NCêCzá*´ŽêS:šÔ†°‚€b0õðJ!áC ~I%³¹€Í1ƒÐ88˜P1qR…A0Tk g˜ vCÕÈ]v„ŒÒ‹Éˆ&%•´ágs÷-Ì<êûl”Ùð‰k~,~Y¶R"ü@zF$r"! wt±ôØá~½¹h‚X†}häAI‚!ÂL¬H2=Y•©S° r1²-}¶ˆ«H $Q¹#·$'-!ô5¤bºOè¥3’AªCò%PI:r=Sá$ÁÁSz–¤R™^¸‡’À„æ• ŽÈ|SÚ*=V &£Çï¯cñtOï˜}Ž¥¾òôÈjKœZ‡öý{Jð~üÚIlÍÆÂäBhVÏ(åjÉ>Ÿ½äðBâ¶ë”‡?,×ี¡?<,Ÿ²pJ¾¶òTŽ›ð¸!—ݯ Ý:ˆÉ1Ùk÷w±Ñ}‚*áÙ<€t×>ŒˆÖ‘<¿áH«æO8}ÁdÄßm±9eµŠVóîRCÔ`έ[’õ$¼:Ð.‹9<Á¶—Ý9ö“måcÄ” ȲR %Ђx¥^ðI&í¡¨)aÊ%ƒVs¤¢8ÑÖggAÆaÖÁ¼z2Xár¨Ä'ÿóulJ˜7Ô7¶Ö«ÇÕZMð€ _vÿäÅϼø²'Í«Y@*øèz6Àú?]UÃ3ë.m Ì™3ådß¾}ô …&H‘z¼AëxÝ×4X_9Vf”a‹zOä;µEÀ½Qm9à2$eÈƒÅæ²éjXW¢¢Å_~2VÄ÷Ôä‚Càପ¢3P †©]0[] ÊálÝúÜÀŠâã]Ô’S˜ìÇÙD¯r &—̵-«ÖF=žÁ¶ÁøXWR䢜XÌXÏ HqàÀ4yQ~­#I]^Cc¸}Oèÿhqª¨$5 ¥…öÁ ×^H~Þ.«2©¦»êª«ð?ôÐC»"¸æ{~bLÉ‹Wù¹®ùª!êýðbÈ(rB¬,à#z8 àuxB"¸ ÑØÙP½$¡×0È   ˆñ0h CâH¥„PH†zaŬã!‰¬„|7™S(Ø ™Å˜:©5;ø¤¾$' e*ð‹@ÙâG+0V@&UÆ7by…ORÁpqxzÊ ÚYby¦ÿô‰ðšô¦¿”J¥J÷@@,-¦0¾ôجÿ²m¤….]¤¨JxxM—„ôŠ uH&!#r'”<á&l3z®@iB¯ø+âLrú|¬‰Flv ÐCžC@ DžAëR ­dzEHH‡ÅI„I­ßèt¬f¥CI.°å[²tü´ ÌÉ(£"ª¨çç¡qHHEF™Á‡Šœš>üuïÞ½pÃ1| ͆Œ¥Ä´êK·Íùöþ!ô“T?üТû~¼ùW;Z–L-<ç‘ócÉeà³²\+_衹ynùœIC_gGI2z[m /x`y5P gɱ‚ôýÄÞ>ô½‹&Bv†½ZÒòiE‘Äqo8"ó/Ý6[`ÑMÊrmãk†¶²®Û…—t Z7¿Ü9Šíˆ%”@eÜ$Û>¼kqå(Íò‹í-½ƒ¡;TNMÛ Ç r?&X ¯…ŽÕÂéó_iUõ¡C‡Ìýþš0 3Œ»¶ \7³T‹ö{¿Z›{"·˜rê\B>~¢~°´$cÁã[jçÎm÷êßEVa)6œì‹æzÓaÜ”Ÿ<ĵw=ºö¢W­)©Eâ¦XÕ‹}¿Æîþh&‰Ò‹§;Ð:žœaÁÆà•uÀ(cýèq)'Z–Zkž ~ã?\Ä^œë\¾àÃßH“õ]-€ÚÀ·ñ ´ŽrÉtRÀXúKZ™;Â=µŸ$'ìóÜ[ G¾ gf¹Žî!‚L©l8娸DÒˆbÖ†>$r?¬"Ïhé|KÇt§oNÌ‹'jA¤ú™ßï}× t‡ÞÃÿÓÜÍe1¦CýsK’椧ÏëïáëJK¯òKLâT;A Çâ|•-¶yER¤›ë:•.ìºsÙ¢+—¬+]¥²gVt ð¢qeTD’Ÿ÷“‡´Y•‰F¸îºë¸ÙƒÍVçݘ˜Ešeü¾(ƨÇô^Öh¡È•Rk17c¶"S’é€@$9ŒØ Ñø „§~GȈŸE‡G^E‘ À,#B"àL ”ŒÜð„@ž¤%/UJE7†rN™å(.$?Õáˆu$ZSxèIE”nRq¾ë i)Q’_5ñB*bñ@Oá¥`pÃÂ+@&”„ËkzƒÐ†X•æOÓI*žÒà¤UVHéÉG$Pi³ž+±è\j4 ùð* rŒŸ"{†t^ñ3ÚKÏb^CÈ%-;*ð@€ ãðÐkO$™péeBI”pzžLÜl†‡^ yAÃ+eà+i‘O~8ÌW$È‚XNl¤Ç‘#d¬ •“ 9¤bº_¨5ޱ:„˜\¤.xÆÑÑ,ä•nÄšÎpŸ×tð1=¿TO}}½ògÐ\‰¯ßvAv¡ÖWžY‘¤ZMNŽ?íÁNS{xý Î úص5ל¹Et"û²ÏÀK^˜ío@©Ñ·¥œìéÓO|Ó4Î’;ÞåÛÛ2°·ÅóOï2 ²/¿{ö—žÜ×5áºX áFÅ?ü‰ÒHq´s°Â4X?kÌ3²n»ð¡«'†XÌ¥Û.d¤þJ9¹uwKC‡ƒß¹pÒ(h¤ýÀÒÉÙdCÞ-p¶ý×ë¦à9„M¬dÇvr®:áäîšb ª‡žè¬+Õ•¬b—õ[w×§3$¹p p[‹ï›v¨ØúªÜOߨ7\nN@•ÑKÅz2ƒàŠ;¢a|»K®2qnݾÖõ»‚Å‹þºBKGÈj0ÿÛrÁ¿¼x쯟اdÍŠ9uîܹ²À€‹O¬ì…ae4,÷†Nð‹.œfÝÝ1Ók3'}6[Q¼ô:C¡%zûÐöÍ7ýèh¼³ƒú¼ß´jªy#k¬¨!Ïç÷¹Ð«QŸRwÂ&í¦Ò5¬½­î`²#9ÿšüõ6N'ˆ¿Ú;oãÓŸÏï|¤qž5Ô^b×Ú;»Nv šÝæfgîö†Km-²$6ì‹VîQ&€M½yÓ ¶…œøÛoµ˜ï¸·Þ`r:­9¡ˆ¾ZÂJBlŽž~úiVE¿Ùr¬ÈµiQ»;¿<×ÖÔÔÔ—[êææMc¾1n»§åõÙ]”Å_a´ñï»ÃÍ«5Öî:I”Ìd4að‡Ù¥«…,U[ür{˺CpÂéäêØcþ¾Åo4™ôꮬÒ*臰[DñÏz®¸(ͳžìÐ1Ü¢Ú7-(%Pnõr7æ¾¾¡=ÿu_]KÐUëŒæXb­í‰ÈP`è­Bß¶i-(G$fòG,.#ÊRÂaÁBÎbꉂإc±cIÍèÆíFpÃd•3Ùi*¹çŽëàƒ~uÒ;Õ¨u£6—e°cÀÒ¹uÙ9É`vöø"[}…?ýÓ%;¶`(šÇá`\ fxCI3Gx/^¼…><Ê»9ÐøK¸Ñ L&@¤š€îjÏžY&Úd2JàG—U™¤1}ôQ~treË#ãØÎgcÅ‹ H,£«(ùL ˜"Šú*ƒ¾ =*º YÌA¢™3%Ž»f¾bXª2ånJ„þäC4sI )´ÀO ×Ì8ŒŸŒ®„C áB +^É É‡’†BŸ§Åˆ•‰O@¾ZAÀ”AE¨ Éè™´?IHN¬þÝÈL°ERfˆ¥¦ñ*%¯„ƒø1£†‰’ &(ôÔ‹(îCp¦$49‚DPlº’t")áxp¤þ´h~¢à+šBÕ]hxyˆ9~h3"LF**ë¹[`¸ ¤×‚þÈ+R§d ñC„Ä!¥‰tXI…ÀÓwTw •ÐÓ !HÏ‹(B ÇXᆴ ½ ŽìèAÒq^èI‹À“„WüÈ9iÙу±„8Lu4„ ^²¢£H$Á‘V³› I¦LpŒ8¾NÉrš|y•Ñ,½AR%zG<.¾íÂ@@ÿæÁuª}ðÒq΃浂 ÌnŸhè<²¡qF™ûl† *íÅ÷üíSÚ†¦QÀÿÑÕNlô2Š‘cÕkNç ÇNvùz|aìãØñÍ»ê=èáöz#[©äÜ®Œäé¯Ü`Ë_Cƒn:A.ÝvaR¾Ø”ŒÒmÆ’/VŠn¨8–Œ²4—U |ችÜsB‘šz}n›¹È5t΃B0Σ´tRq'ò×ß3ÿç[Nm<®M¡ïˆ Ý( ¿ùì¡ý§úÙ<ÈÀ_ØÙíkõ~ò±Ý÷•2£”aŒQ̤ÃMF2Ò2¥f„0™Jó&+Rü—ù oŒò5yK¯—Jeâ°€C§õVƱ™¡Nkç ‚–Áx8i?ªéPQÆéœ?…®B8‡Ý ’…zlÄ÷ù¡ÅM.OÕ¹¹~.°§Í{;”­^më“/C· D¶%ýË&»ÐÌ%-˾РüÄ2 M= ¹éÉ»=<¡qÓLpì¡3:+Ž7uXb¶ùKoý‡umQÍøàmß_¿~}0\ZŽí5³opu¶ïM¾ÆÒeO$cV·¥¨øOn™\èóôÍÈOKú®ÿêZ¦Þ‚²°Èï_ÛÌ®[ÎÏ"—­¥Í|;02ŸªV¬X¡¾¬._¾üåW7èötȧAëõ¦M®èéh}(,¼=dìvÙú§¸O»“1“³ß8æœú†ÝÏÎÙ4åÚo¢@>÷Üs±È€É·Y’¡˜…¦†3ËYš©Êé[_Aëð÷b¡A/ßÊ* rØâ*±êÉ%r'!¬#‡Ÿ1œN€êÈvK•6ë¹[@]7báÿí¥£„ÿÙÔ—7óM§fåVpТÑÖ™ÌW h¢×hÉ—+ÉÃ婨*¶toŽ…úµDÌa6¥®gŒŠ$½R(ïžÊÔ¢m –…’–b£¯Ê4¸zª~0R ¦ÓÊ4‹V:yjbà0Ö¢vCCÀ1{O´¢¾<ë‹9´ªXb ŽDcQú5[àéæˆ%b¿ëP»Û­8S C¡;à§;‹?ªËæÍ›ñ¤#‰¼Ž—˪LÒ’ùÈGð€Å(8f¼Zxt>°€¦AÃ`ˆFÁà×çw®’´è±Àa8<¢ü#‹B °¯bÎÆ‚?ˆ|4Ñ´ÑÒEoW« ø’´‡€I¾ðaÁA1$!$É€ ¡„ ¶S&/ &K¢@Pûù$8=Š [ÉB˜„|yRY*E¾ kb‚G a.ô8ºŒäBGF`m$ÀáÒzÌÂôt2’Z =E"až¥F¶4/O Lá…?%¤<ä N8$2 <#D’Ë@„>®B@÷”?ëy[¶Â†!Ò¿Db¥P_^¥Ç¥ ØÄHš#lÒÁék| FÎé²é)E¤!FæÁµI(}“'O¦H/±$nc®¡_G øÉ†ô²#•'<‰•„àÚpfè€å¤@É´…2r!¯‰“dò¥š #¸t ¡ÝÔ·“ôðw‚ÿâÛ.ÔOÖýÀ«®T? à£?ÛÁæ«Ù„†/F7l•Òú«_ïM7\8¿ßâÿürÏ oðÑÚÚóK><GúZÇîW®vÝÕà|G+s 厦¾%Õ…lø•{œãF0’Ø9¹Ëj ÿüúºŸl8YUóeCsîð\†‡|ù…–gႃ g+!ç´]8[Âlø;¼þê×{<\kéó{ƒ1þ1# É]V?|ÍÔÿÞÒÓ¸ã?¢ +6–}TêH ðD cˡٜ:©¬½×›ZÛ­ } €>Ça&¤ªf¡ïTk<â0Ïí W±%béÒ¥ð÷ꫯƓ†˜¾óÈ®H$L²¦îÁöû—ºýQ‹É<£L×І1´|:ªBÂtG^òÑ[ïÒc³þ·_ 09™{Ës¯·Ä݆ȼ|O8–¬²û;ƒ¾¨Íiéräé[™èMJlšÛzûúPfÐFˆBuä¾¶nÝ:䥾*ozQÍÑvï²ÚšžC›Q*èѨ:ôPú5c7=7êï¬-´—¯ ulÜÕsXÓ–-˜äD}Ȧ/ÓƒJŠ‹OµµÓ‘H%ÂYSSÃ0‚šD¾ÊÜxËаråJÊÉè²Ê€¾rÁaš7A¿]Ve¢a„¹z˜o0ÔÈ£°•mn,ü!C,ÑÏÓÏåDlG&yʪñÀ/X’¬L•‘R$népüNaE*r‘'O^eÀ‡²Ç›b©ÛšÁGEI ƒ6áb1Éy¥l2ÓIm”ž$'y ²†bhá g*‚£H$gÓ+á†TäBQéhª t4’H‘àC¾”“@(Aú@Ûù`&S Q„ÃP L!Yi Éš\€8a>HrY‡‘J L§®Jì(d!Oá“2ƴ驲þ+¨|ÉA~déYø DÀè• õ"òD,Åô•„8$2êË/…䓾CïàK0Òˆ¬r!Y@)=‚@^!€ØÉ{4ä–.@8Éá =â‡üã(¢Ž“²Á?…dޒŹóJ^|±€ƒX›ÙDË0í@”–2P/šBæergºKb»ÀÙRݾÐ?¿xôZ>B­ý¡?¿~ú7Ÿ;ÄOP™¯[èä§.-½À_„õ<P?†/ìÏ›ó/¶µV!¯ìJþ|¿ÿ{kB.?zµAã/å@ô0âˆÞl#ÆG‡Ú½\/&¥âØ8ì†8¶OˆGJ¡ǺCe»Ðá‹úû=v‹©¦(ç;ïÓNÞÑ‹—}Ƕ€ u"üÅî˜ÃbÐàõ“íââãÑ›eÍÌþÎFœM_{r§>eˆS›^Ïdþ—‹eo›¥PîoþhÖ·ž?²áÄåرäoELUyÔ «B†{˜Â$ð2?¢á LgxÆr U¦§>¹êÛß~½ÏéÕl¯ÌL+§•< 7K"VB KÄrÂ;ZQ}ÿ?9‹-¥z±Ì‚‹¼tÝŒõûÑÞVÒúú»spcm÷x»¥Ài¯·êŠJ¢ýð+®9Ù1µ«a¯f“Á•8¬ZKÀddÛPÒè²[rÍÚ ?šç¸†íNÑê`WüðÓ»lîxÌÐúÉ­Œå¦T&(0XÀzÂɃ§Ù:dìJ8WT )Qñ믿P¹§¥·¥åD¤–"·¿aHñÃÆ>,qTœ'ÅÞ¸q#~bITeqwêªižÍtë|Ϊœô½uÇyût(|H¢! ¦QYÏÛ©¦:NõõùÃ3 ’§=Ú³§«çxç–pßËѵµzþõ7‹`È“ž‹ÚC Ðy]´:¥…¾FxwŸ§7fÓö6ä›ÂS*JçåÇo;ûPÏB _wJq.]&hhså´ÅZw3hió+¯ÓÚ[Nù.^‘pÄoÏ©þ@ko®Q·ÂгPœð£_ÖA jõ6Æ‚ËNZ¿¥΢JýDË0%y‡«L¿ûÝïu™•™¶ió‹ï0i!S~wžjüÇO‘4 ¥I`F@r NŸzÒK+0dü¦ ­"¨à!áWüèùtQï‘aˆ™e( (”äÅÆRù:Å+ƒ*=½V”–²Iô 8 ð‹ªO~RNì†àCȈ2À‡´¨åÀj¤â¤&ðJ™I Ä, ˜€ˆ¥cRZh ÀIE(<i7ˆáC¨ˆônRIeág’ó*É!¦•s„ž„8¸‘5儉˜öÐ#@ôHÂrNc:àCª?ý‡cý'þìóíÚXn²Û“ *ñæqE¢ð †J/ÀÆ ±ÄAƒ P¥šù$ŠW’ðDüI<„#Ó `ä™.†üË„1ŽX²|Y‰q«ô&™ãXŒAC¦!Ð!“ ÃIè°…ŒW2ºhã!ÕQŸ ¤$ïØç%±]øÁ¯úã‡7tyÚYãÞƒP,þì¾6¶Ãò+üýóä·˜Zä ŠÜØzÙþ:SòÙ³æ• œâï°¥/è´šþâúi˜aއ^‘ËúÀòü¾°ÞsƒQ×èð9œë‹Oî[<µ0ýžQj*°È(çµþHg¯/ä°šúƒ1l¦§Ž ûâ“ûÉîüfS½CZ€¾IMé§R_¤œý]žk§SŒý<»á-vãì2þ@“O{W×Ê5²ÃÉÒC¶§®pYRù¦áB€¶Ø§S^~fj° `¦EU$YòzÑfC•õDxư»ä*ÓÍsÊÐÚ=!öZtUµj)t®¥3ÏúÃzæ•wçºkòÊͥ׳VS4é®z`ùŽ®ÂO΄e˵Éθ?ŠuZ¼8…bˆy«¢£§:÷÷Æ.o²¤Ï;x{qŸYKÄûêçÌÜs¬™“%JÝvTzY¢iÚò«çέ¶nÚ¶¯£yߨÔ³š½•á Q»O…\@IDAT3[ ênÝ~B×^|½­sª&×”®(/°½øüK®„«9þC¯{yA Ü‘´™ ó+óöžê­²v[´XÒ`O­¶ø )94Ï¥À|¯¼òÊ 7Ü`œ ´¼p¸µÀj.³¡U%â-ZéÖvž …£‰kæN&»ö¸ë„·¤Åïhϸ-ï%‡i ìg=ºß»Òh  >aÐÁ+kG¬i%,AH¨4ÏÓm­óË Vû²rÃÇo™MóÓŸý|(É·ž?ü7¤‡ŒâPh%V¡Ò÷²˜Ý(ö6ˆúÊsoüî+h]±¬ðTw8ç@Þ í¦õ=p ×f6Ä\óÑ4Da@ÞD]¡÷aÙŠŠ‚ê‚~‚r±lg¯Íiò€gT9›âÁ‘pn$¡5¶éq}­×ë1bQÍê%=­÷,2o8FÇàh¼òÊéñ vÞ§[L÷,´ $BÑ`LGêѦž[û27NpW¬Ýja@˜ãÜñþ®|·uÕª›¸“2¤œÀf_ûQü¸¥¥Z°ôÃò…Ÿó¿ŠœÀÇ;YezàP’ÕÇ Z™ajÛ:µhûd-à@­Øv¡‡ƒ=¡«3• ±xpxH*@X7¼Ì50„c¬Ôˆ¢x€3Â…-2»áè0$Œk€’ŠƒÇ‘… ×0ƒ£›LÀ?ôáñð„9Ìã@% ¦ßQXasÇ I‡mzr)³”žÂ–'eæ)M„>L©$¤äø™¡è¼’±8ØÂ'¦v¤ÅACˆ¼ò2@Cé\pcMF•éžä(xÊí™4» 9›¬„¨ô®*lÏFŸ {´‹îƒRÄ ™Äƒü#cH~A{ED¼E$JYÞA1²‡G‰¬JOñ Û"Ò0§—ѻɎ'I(€ˆ|¤0Ð@L.’#DáèÈ"ù$äS«W:)|˜á#Å¡¦üd:T¦ì¿‹Ø—ÊváXüþm¢¢L}±x²?)qÛÿ=Íâ ´ŽØ‰Cë¶5ôºl²-÷­¶7›^'¥,±—IÊÀFWáðÕ;榳âкÿÙÚr Íó½µG±?«ÆÀ5d“lŽÍ„ÑÀtð7::† lÓíìÒsáÓns¯Z‰ ó½ôð1úN.xýDÄå.Ëm §üùuußþÖ‘c·]cFY²·Y Hß”~JÕèÂv‹qRž:„ÍÈoµÊŸÿõžþÔ[Ð:Œì”Uݯv´¾x°ãúY¥ºzÊÙxr›åóÚ76ygÙ2é„Ü…ÂsÎ$÷ÙR]ÂpæDfCVª¸ôbð­K!éáW¢|»K¨2a2J»ß»dò´b;K”O?~€ka áx;̤§™z*M\‡EcoÍ©žiyVÃak²E«øÂˆ¿KL˜¼¡º  r¶Ý·Þ¸iÓ&ô t3¾½£c ío‡>b%tºÝÛê‡ fc¢# ÅŽp{r$˜´·õ¬½PN Üz o~îæÙE¦í…ó¾®`¾I NwÌ5÷Ú­œfâ´üaNé’C]e,ÈPÒòrôÍ€aÌ·}`ÍÊ¥íûbúÙúdÓé -ÍÛœgö ÌòÅLh^uŧ»“fÝÉZ¥Ï¯þbjÎIv÷VXÚ}QWY^U`±ØÔÁ¶XÇ©Dáž=¡ÿ;/ñÄþþ¹fCÔ›¯%xW”ØzAý‘|—K·ÝÀ‘#U`íHãà¡e¡¾Æt_ë¦yNÏg>øît•éÿÞ¿ˆYêDבjD'ðŸBë ‘¾7"q6ðmÓàé5E.Ö[Ûú¦p½ƒÝŒlÖ7[óS8dj²­ƒXD[Bl4ÌBÅæŽu2’ ê"ZíLÁbGZB$^ÇËQ`XQ~Žñâ™åsYµ€H8ç' “ô)äaCäè\t+:5~)°!~ä3cTG U¥Dz!À‘ɤ BH/℈JGC¢$Çt-‚®A/#&¶| ¢TxDì)0±p†¿d*æ,ƨdpfuJ,=[!Ë>/Z \*Û,_ûø5¿ùúÖc^÷÷Õ«Z³ÐRþóö€‚ýlú°öWœi,¦À¯^_¸È¥+;øóÐís8„î¶žzïU“VÏ(1_Lç~¸þäWž> _‘Ôº<N¸ÿÄêÚ‰é>±ºæ¡§xƒQÐ:9‡‹ÓxúaT ¹ç*‡Åˆ-iüùÎÝÖsHˆú“¿&YP) _–kûÆ{çò£WOrŸ¦DQ斟ΚáÏ Ç»A-AKç&>~Ãt¾tÛìÿÚ|êµc]c±]Î0òÎiÀ„göµ/ûÆ:¬J‚QîJžS[ñ­—€àïèÅ qK›H¯dSk%«g0ô©uT½uý‘.þΆnß¾ Àî哃ï>ƒ§££ýàå“p¸£~ÒX pñiX9“)"s%ó/³'kË‹_Œ‰Ëq|»K¥2}æñ=B#ƒ[7®{-i+pZ?\ãxýT¨!^ZG«åc u(ÏaO·?ææ>.>vŽÒš²Dcx"€Å„hòdIÄ‚‰'VxêXq¬„>û«½|Ba{l$am ç뉇MeùNŸ×ãtó¡†ûÔÅ…VS|QUw7™°M®+Ñòùq¾é°KD9„@™¬eWN$³*ŽŸÞ‡â“ã´sZÓÑïó›šJ­&‡9Zç<´Û»œ›h­V—ð'".W•hD,þrŒm u½±êR[k¾ÛˆkÜ>¦v°Ý×›Ì)ÈË zBØœÓ cå_X–·}÷^-žôF‹8¬®¬¬€Z³:dÑI­}0DŒ†`‹­ÕQM>M_SæZuÅìüü‡'dÅÉÒ“ÝO¬ 'Ȇ\é-ÐÐ¥Û¶|ý=ó~½£•…ÌwùØRÍjyE™^£‡HeÈ Ú0š¸6™<æ[4-gRÖât[22´»L†dÔh¶;§öWÞxÌŽY A;p‡tK[zt‰ ¯;”ðkÖŽxns\ûË›¦sõY@¯ùEãZœVÚÀÅ‚úþY³£ÌYyCÃɎ؉Ã>£®Åq)ÁÌ™3u¢¬›È`¸“O™I&oÐ%F²&ÙÃ1Z"¥¬xâG©FP™P(›ÄÊ$’ÉèÌ;˜² %< F ŒÃÃ0(b/ú9œ±qðÂ%QÐÈ“QòTfj`(7cÈÚV‚%$!e#€†T‚UQAزܡüã­“‚QAR‘DžQM²&!m"u'P<Ò&Rª#MAˆÌq”JüÌ)䂟'Å€ „ŽpÖ^„È:ŒX É7'fFÐL1”)zbÏÏÁŸŠPM8+ÒÔCŠÍzÞ-€€Ñéè,*I?½È¹‚Å ”úBI÷DÈå©C E !G¬ô)¤—Xž8ÕS„âŒU 9ªÁ†t iž’|(Œ|פ0J ‘®Jéãä12L]fz:„@˜dŸ¡.•í‡t¿v¬› þøÅOî>ýܾÓ\ VžkÃ<'Î^iGÅw;ü¯S†VbЉèÿÓJr8cŽUÇÌMÊw’éÏ65m<®›ºWµDî]Z¥’([ó\SËYuþÔE–„ÿë} UìpÏ5µEë>»†p9”ÏÇþ³­ÐiS ƒpÃÓ¦‡|õ¦*Ô™Tò<;½ajAZAëÄÚˆí<;¸9­fj‘n»¸Åv!½Yÿ;­¸çA®­c3l®Ý|´Ëg1 ÖºR÷ïö´­ª+ãEÏ/ÑN·,̱¹í–Ãí^®¤°“+b9àûÓ7Nÿöó‡wùAáé×Û|œ‹,~ôò!ðôôX€ì‰3ÔMÏè¼ý| KÿvÞ|.ÄC ‘ ,K –éîŽ%ùw^8bŽuß—ÿÄå\rjkéðÇÀïæ—˜JÌA_X_¾ô&|éìáàD•}£¡uÈeÓ@œ±¾¹Û+ö;Ü+šXáô¡T…’æŸþæ’}Ç€ŽyaÍâRÉ„ãžkçÆ5#fÞsM-Ék×®•ƒrÌ®©œÂÀ¬Öîíkêt†&L¶D<¥õ, K‘ϱV"mìæÃ*YòR´åD»m€)Ýw^8v´cpGO~›ßm1œ&š<Ò¥›‘›eü¨¸P\;š¦™,u%Þ’’ÒóVBÀµ•ímV“Ö“pQBBjKt;Û2£÷…íGúb¶¨†ÞeaW•­®®¦еx½õÖ[Aë dɆ,<¢Lâ±Ft+Ùþð9æHhÎæX¥"iŽç-c]›Ÿõ^ñ-ðž«*Ë,]ëvï3jñb—nÒâ°šÙÚÀjOV·Í¯J"ÏâAáa+“:ø¬Ø%ÌIÍ0õ c:<3mMUÖŽ[ßêÜun—n¼ z±³Åþ›#“õ,,ÑnŸþÕ@MxÊ“.Ÿ:Ò(VdM¬q÷'®|ðAnÿHw——âÌ0‹^4‚lðuš³8ÑðÖP˜E{™b(C„ö¤Xáav@ßFÓ†!ô2×`q#š9[óq e–‘„Üp€)ä ™ò ºŒÀò¤‚pA€Jæk ƒ…iy¥ä‚ô©µŽ $'#¸ ЇR‘DØJ 'Ã8ò„RªCi% h íii1i žRRPª $G;H½ÒÛJr!„òàdb…ÆôA4ª.é2üb¬bH©$JÒfe_ßf-@·’…+â‡b¿ÉÂFd’šÒsAô¤Ê‚V#ÌH5Äœà!è!ªt¡”~”ð—pâèdA7GÔ郊9dÉž¤Eþå¬F!ãI*J‡ô™K )å?|è×"öð©äᡨ4£ÞÓ%Ÿˆ¼®žØ.ð1;Ý]„b³Xúú3‡ä*UüÅ9æ/¿{6ù‚‚ìöZ7zE¾xÛìÏÞ¬žì D>}C¿¸,ÿ«Çtø`D‡I HÙ÷ï]Èó·NÖMíÆà°(D“Z{°#}cCúÒ½‰D4 ÙKp Ϫ;—¾‰nÌ/ÀˆÐVËÙIcN•%̶€jb—í«wÌ•›VœóÐís¹\¥8ÇöûO­Z1­šMúQÝcqS{WçWéÇ+_?ë MgO‹‡ÕÓ‹y>xÞOGáÉ-Ÿ¹vHƒǾÚóC±%yöy-0¦Áñœy 2q²{:‹ô׉ðcC÷ᢗc(R÷5Õ¶£›w}mûòj‚•†@·M—³¶xö787[á©Åq›{£bµP7~Îp|bøŽ]nsئEÚºj&•p³ë!TΫb Mz o(¹¹Ïrë¤È*WÏËžBì«ö¿×Ôv4È3„?8Å¿ Üþ®¯=yd¯0'¢þW ?h7ZòУú –ülû‹f-Ö©^hi?ÞÞ»ü®/„º¶÷þÏ®@,ä6µ4j¶ª5)hŒžöwû-ss"_ò•¾Iÿ•º§©(ÇÂóøÅ-Þ£wVKìíÍÁHÎéð¢Y•yáP«¨£}ñH̸¶§Â}ʽ¢ÜÓœ˜ûwï)bòœB¼¾Êgé÷'ó¯þèªra¦|ø±ßãÙ­¸}j²ÐDSbY $G­•‚¤ZŒ…£øÖÿ‘XPýêrE5dyË>ù7‚ÎâÃX#–<ˆ)"ùB•Îù,‰²ÁW| äµ>üÇø˜±×ÌXöÉmmšÍlÂ$–{ˆ¨öáw-ª”JbñŠú±iÓ&VŸ¾øÂºWsòK­ÑÞ#þ«£“QƒÕœ 4æÚMƒmÑò|ƒ®&Ø»lñÓ¥±W=†™¡„»k°0¦™Úâ·×7µz,¹9h>Š9ò¸†ÆŽø ^ ‚ü+¤kG–ܺDtÐU³Ú’§Ouâå«‚a}'E¾#jŒïÓâû&O[ºã¤™k Ó7zŸI1žÿéì` ”6)ÐŒÚ3˜þ¶ô?û쳪^?þø'>ñ õ:Ažtíš,ø°ºÊP‰ÂÌ+J52#X;ÊÄòK ®Çk†`—¯#ðAKW³'[ÑÛ,€ÿ°C\® Ai‡ý\Œ¼È‚(˜ –äà†’ôLpHa®¢œê ~Ò’n$‘aŸ±šR‰õ·Dñ*œá Ä”(ŠD¾äBûÔEUœÙPù…³`%´ HH˜®ˆñ@É'4h$vÞò»Ð’[A|mJO8¢Û%©Ž1"M6ðmÖ áÈ Ý±Dòù¸¢v (Ì.½Ê3=EúOÄ›¯˜ô5QEXáè ts8#<…˜p’𠚆p099b"G7!äÓq cOg$' ?± , éH/e@þp ¼8ŠÈEÁå¬-i"BèzìAÁØŒR¼·ñSFÚ‹YAù´IŽ]Y½á7zÎè2ߺ{>Êö«G»‘¨Óª‡ŒLftŠQÝ?Ý<ÇŠäÚ­ª ÿö²þå³Lç¶£lÃù¨$ßxï¼²Ü7v® s9Þ>=ÕßýîäÇ®­åŠï®=zè´þ5ôX§n¯0ŠãBB,‰¬f#ðFCé”Õ lG8 NžÜ±« –N<¿”¹©G‡í².Ûç׿Ý}º"ÏÎÆ„ßï= rW”cýÌã{…ÕÍsÓìQ¹_SS´·e@6ŠU¯ª+ÞÓ2ððú“Ÿ¿u»Ñ \=cH‘_±Ó|¶A`Dúlà„¶Àøv_ebWv®Ék³°A‚K}¾Ï·“<ÆÕ;ï_V'ßÙ@Ú±ã%2‡{´z»D´Ž$X,óüƇo@uojë4jú2Ž5K%ý¬Ìˆí‰è3ßSí:haÛ`o.ìŸ;IÕ­Ó§°{(Ás;wí>Ð6à2„¬†x©ãdÔrm­qW›ÆqKŸ£%RõÞ¹ù×Nn‹káèSO=å°Äf¹ÝáxÄfˆÏtî} TíÔã,<«ÃRc´´„#Û÷ö—Uëy}û} °)Ú²«ãåÎZ·9箩{̆pDsÔåë”ö·MFNÑ;2SÈc†ÿ>î̱Þóÿ6×—Z¿¶@¯î=9Í ï?OLdy~rµ>=Ó¬8YŠ¡ŠÂ3t äÀ²\#Éýw޼牌 û"wDrº}QŽ´$7Ê©–BÀÃ4:< 2*Ïð|Óc³þ+ºü§žc·ÍžkárdƒÇwâËç}Jj4â Þ‚XúôÅP,°¹ L ’nÉVœ«Û(!ýìýpú±o¹kë«¢¡¾–r—§+­–(‡0Z rì§:£FKNÆfU$ 3XT8Àº<TüX¤.^¼ùDÚ²e YÏ™¿Ô’÷& Ú^¾2î^صã%««²Ð´NªÀyvÜ3#þ‰{fU¦Œ¶½÷Þ{ï»ï>@Ÿ QW­ZÅØÂù§?ýé¿øÅϧœÊ‹o¢W«Ð%åGTd?)4œ‡:-ú¼R§¥ò æ¡¥£´ 0é¨p8àµi$JXˆ!¥ò)…XÄ2^aB.¤‚?Ìb`xH ñ[ò‚œ„@&žáN²–ëÑ…9¨”$ÇOe±~üâ>ä…‡’ඃÃ(ü%G~8æ^Á5z˜Qvþ¦‡€k°½‘R©)‰Ø±Ë”4O ¬à›ô\²þqo~2d D¼•Ž{.Ã"‡ žÒméA¸á4é!B€`Ðï(³ô#„‘†Üd±$8 Õ¡ò„˜$ð¡j* …æ+þŒWR˜ÈzÒòdå‰`«^3"âOǧ/Ë  xN´‡ŠÓ×2z Kný—v¸ÀAx¢Ë?Aü/²íGJEdÉ´AÓþöö9XÛÝö¯ä`Aë„üŽ¿Wª5¶4ô*´Ž@ «áô \ìFâf6î‘ h]C÷`m‰¾•A2ÅóÇ +uÓ¹êB®€(qÛOÕíìÒ]:(öåßP¹s¡T¼*z6; Z~Á¹Z\¯yÓw_ý»fqS`%ÄìÏ`wþp#×ëñúÂÁNŠg·˜þàUË“,~üÚI—ÍúÁ«õí\sÁÕ´ûµ3ì*â±{ÄXOÑîW:àr󜱢-*aÖóŽj¤ñÑ'»}_~÷B“шÄ.œœu*Ý–vb{k,­)ü¯5ÐC±‡8Þ鳚 []Gò…S†z"ýH¸½IÕØÙf)/m œc™r…»8*§„zã¹\B °&…lóèj ®Êe?)ë´¯Òª|v¡ÕÚïÏœ7RIxnˆÆ“ïyx#7B®rÙ ‘Å“¬·ÐFXPnëÎaÇ«¦é¬R×G†¢f}b`9%Ê ?#±Ä®ö kÏÁ¤ÝaÅ’¶<“gW³¥=dèöx_ó´'ˆŸ§¨døûäJA® Ì@ $9#@Ô§ ís’à‰}fU&i߇zˆÁJü{÷ê+㣱ü?ûÙÏ>ûÙÏ‚þñóÏ?ÿÛßþ–Áð®»îb¯“²ÝË@8úP´kž’Ÿýœ¹C´Vž„0ƒ¨2 Z„ ±Ì, {D‰1š` )PÑÑðùžA4 G˜ÝxŶE¬)ɺ ÆkòJ.8ˆ!¼'å‡U=_ 7Ä8ä˜@rÁ/ܤ„*ðЇ,è8ì(—:2e€w“±ø) p˜Ø¯‘•JÏEq Óòä‚¶OÙ¨/»\©Qº-Ä#:*Oö*Ò\Ô˜Oá##Ò¤ ¥‡GeC&®~qøóDÆÞê¯vÞCN„Ñ%_Ìå`…pò:bx½™G°‘O°i<ȧô&¤™çIEpDÁ G*邵ÆM*™€ ”«—9ˆ’PÒ’ È ¯”{p‘OüäHFñÄ2VHàŒµ¥‚^¦8Ž££¡)$;…‡³¥¦8ÆéÑ×]w„|ìc»ÿþû©*íÀ*•A|Ž^dÛ…u‡;i¢¥À&i+Œ¼DÙá•“ÔÔSlÓ8ÕŽÀ18IËóÉ]­<°bS™ŠÏ?½pXOŽðþÚï‚Ö¸ÔÏàŠ—qj>©þö©í!u&0Çö änÓ‰žuÅä"&Bì®­.Êybg룯7ZLFávï’ªÿå>z|ûÞQ˜ca®õ¶{ ´nRž=wxC»Oõƒ€î}õ†²Z¡Ð´O¬žvÆ«qíØˆQçô|ïÞØCÑ&ùæè»#9hâ·k¤Üˆ§ø“a–àÕ²éMª,xz,†±Ã[ Iþ÷õ'vò,­.üÔ u³*R¦E)šÛ`ÖíZ9­Xú×ð´Wn´Ì°LÕJq»rk”^òñì.¾ÊÄxM5¢qC"©ëHÏ=õÓpØ»®«îcSû¼A ʱfO} ++ªøêÕ_¨×¾Í4ö¯§þø‘3µ»£Çü1»;¯üS¿m]X‘³÷´÷ô€®5t;¾qsYNrè‚WÔ¿ùóçoݺuM^ÿѾîA§}Ž;äLF}1c[̾¹Ñɾî—ê»ÄÉëHÇ`k¼ð=Ëên˜UÆk<˜ìòD¢I£îû#šÅ˜¼&ÏÏ•M~ÝXÏkÎn0·Î>˜ošYn;ѱýWïûÒ' ßö5jÞ„-ש}|¡óÅC±cß™%ÎhCË ÅNÉOÌ­Õ,Øá«[r~½ï•Ž2ýîKÌ¿ŸØqÊÖçïî˜Ã&öµ‡:þú‰=ŸÛs㯠·äqÇÂ/¥ËJ|;vìX²d _Œ1£`uøbsôéÛ–T\󯔦’dx*òô“_×n?Ôqä;m+ëŠJÝ:Â2v‡QCÖ®aìÍu¥SZ æX‹&£¨±äÖ¸§ˆ%¢> x£uhjI¯#ã/š?—œ±`¿Ûn`¿ao?ÛáuC$:;6Jš¡4ój±ž^ºœ63š0ö´ƒ‘)#ÚŠâ"·%ÙˆÚÛ[šë¿ë*]Ùˆ:^ß¡kY@y,Á° ðµD©@L€ƒ¢ŸˆéÙE<ÇbÓÎI×Ñ}à€*+ú#sïóø›µ[éßÿD¸¬Ê¤Z•}ÇXGªWBAšTÈØ=ïz×»®½öÚ;ï¼S’ÜsÏ= †øŸ|òI~bØ‚$`†ž‰Ÿß‚' 6OÑ¥ Á¡"$èá`I¨¯BI j9bƪB%A¶EÞð@æ/U€ PJÿ‡ %!œŒ@©x…X4ydFP°q0—âAƒÓ tfÏpò„9¬P°1#•À¹~Ë$å¡ã5 @xÂ\ŠD85ÂQl¢g…DI(6U OÑÐPBaeZG,¦y´¤•¡Ò”$rúá£;² º7:“lìÅl¤ˆáôŠŽF`Àäç¾8é¼ÎD¶)Àˆëº›Ï)6eæ£ê2t.‘|džÞM¯¤/йÐIÙ²-U#V0]±G¼åi¤(øàfÏÖAG¶wH'GéÅpÿ¬Y³d8´NRñ¤$<'ÂqlGDëTv˜A@½Š‡F¾ãŽ;>÷¹Ï©pFÚ–a¢ÅTøÛÀslæTänmè;Ñí¿~ÖPƒ}á7ûÄ—®GrGˆ hÐ|ûîúá-ÌÑÀqå D?÷ë½.›™K*KÝCß8Ó‰+rmÇCºM7î«O0¥V_òšþTh]yž­c@—UNÍÿî‹G8>?êX¾Ó²þHçñ®Av­îhî—ý¤Ü’q½&u¾6 ¡0¬+ûÿì|ŵÿ÷ö®ÞeK¶l¹wƒ1½^hI€RH^ò€„üSÈ $磊G ©„B -ô\±q·e˲­Þû½º½þ¿«c–I–;}V³³gÎÌγ3ç7gΨYØ]+l'ç8Øb…9‰Ø²MÎqÚ-†sg‚!Î.tö…¢˜M°– ðH,Çe¥8«-ËUßíÝûÊSµß×¥@¥¬Q^qð/¶ MžðÑÙ.Œ²©ì©ÐZà¿NW­ê´ C x÷‡ ­cHMqÍ 2<çÔÚäƒÀn\T&†œ›SNs­§Å}¡H³Ñ$u9)ÎAëzó\¾ªv_Iš9³ð©{wªçá©”Oj¸§ÂW÷ÌÖZ{"ŽS,Ýù6{œñ‰Éùû{â;¼i =þ[^Vͧñ'®þœ•]W.µ³(Êi®ù–ø…>O ´Ð1¶ÕåÁÍ gÎT;k^©¹qÛK›±=¶¦}?“ª·Œ[°ì¬Jx…½yIwXGÞYú)¶ÈÚ>ÃÁ k‚©TŸ>wùËW&mÍ(dóÆ…–)SV¥±}EiáÆ–îpzû´ôö9®=ú¨ú¶ÝË£Šù„œƒ%Æ}¬êþݳhÞŒ¥ E¯ìmåX× ØcO˱ÖôDTuÞ€±œšc‡¼ˆ¾Ç•[®Fwk\ð1F>ìhüøÈìf§d[Ï.Sýâ¥BªÞµœ“/åO#óTÝó7s«7g¤ÏúO-]‹ XjCª~±ü¹ŸzyÝæ¨ÎMèŠí*<ÁÔŸOsÞ‚¡ú'Uh;l¶Œ´©+¦”~áÁ=eÑz¿MÅ8Úãö-±ôu•×¾n·dGöiˆ0²#"ú À½hJúšüø:ÒûÞŸ¿òÏ„òh_MïÛ;aU]eÁ‚\ßR™´VM6jÐ.‚¡ :³–A‹P&Ѻ5ŽO:d\ùàsE G©æJmœÑ%\…wûû'ª) ºO…'W‰ðˆÀGX+T"ŒDxÊD“y À†E2øË± ‚²P"äB4(…¸TUÌ‹Qò%‘×ðh@HaЃ‰ìP¨”Nåû«|X»²P‚mT•A눤I—”ä+ï‚= ®qã5A1€ð(w„€]2ÃTüƒÒt :­tE¦×DèǾòˆ§*ˆØÙ?¨0rM=E²èÛH5—õ|„G$BÆÛ²IççV^ D©ä‘$"kp†ž¥TÑK®‰V7¾R0,CXd'>ô|p43áäìcçÏá¹!³H.+É6Ø,³’ü‚ »òúœÚ)¿þðló§ÇØváä©9]_ fwά¼žØÞDMʶ ^†‹ž»>µHk4Á캼‡»º–.‘ØV’åèõ÷¶{BíŠ ±MÍU—ÿÅD‰²›ÏŸyÉï×óõç@X|Û¢±2(ŽBo]5Kh¤ô&uKü ð™ÒÞ—Í#½Òã ßòÄv­’åùÝ-üqÌ×·ÏŸù­Gwvû"F ™p+H³æºÌ}‡«ý¿φ?.º¤h¾vÖ4P†X®©jýÑ3#ìðª$ûÙ‹AŸTi˜k²íB¶ÓºdRFaºmúÔ£T ŒU øÃÑ^T;G%™íÝ¿ [K=/J·ìkõînìמ]œqÙÛÃ5šPDÖzLYýM®6SJ&–S¦¨Ö ^}P§äºš-¦ žX襵o&*øÎ´¬ëNPy–ôrïÓL6kÜ–4³’kWw©à…âWâÁp¾±ß†—DŽ«Y‰Û|››ˆ§µz³ö¾ô¯¼âéè²T²%K–h¹Æ<’R™’›ôî»ïÆÈ=P”äþóŸÉOÇ6~Ûm· `(j¹€nÔЊ8ª5úð ÇâFº¨îðJ·hàä‚^´YRä)‰’ÍœòB@"E[15…I×ò!Eri܈.œ…˜Òa%(°Pr…!O÷ïßψâÒ…§FF"Ùå‰`ˆÈ¦Ô™…tnÁ5ÈEÍA%¨6d$¡ñ‘ôä=§€¤k®¾9åà¼R@êëë$Êí‘Ò‡$>ºÄcP÷Rèø1àÊÎ7a¡3 ˆ3ò¦ýï.=VJ<7ú¤V%èé«È2K"ý™î*b%м$B  ôÒe%@â¼;T2j€)ó™ÏÜyç¸/ Îœ3&.=O>ùä‘°=ni޽í‡6⸠¿uÛûÎïo—„’èñGL†@Aº 8ìúû·ýþê…Z‹ᑈÎ2@«g'4ÌùѪîzãО&žéÖìÔ@1ž.œyÖ]Cw Ûiá,×lFƒ¹_åÁù.ðN*ËÖÊJFÄîßTá6è&l­ëö"3 Ó®ZZŠG#¸[^lxðËeÿwù<ƒùé¥ÿf(È£<ýáÌ)̰Þúøn¹=±, ³¡ÛŸn3ËÞXy²;…/øÕjÎüÍ+U_9kšVÄ :4o_J;1Ã:B»¤”íÂí™J|ÿZ€®ó"üµÞ.·-î u’Å]ëðúÍ’ðqR@ÌO(N{W‰xÿêÔœe! ›w,Ü0aœ"Ø}80»±ìh£ñR™ÊûŸ.}FEG¸Üšd áI{w§Ñ¢S¦Úuþ`ÌhT'RÌêÚz‚ÑxW[³Òg4u‰x0x fñ_uñû۽ᆎD$-˲²Ú#¿}O ú— =Ö„%Hè”î€ÎnJ 3Ùu‘W·ìkhWÙö¶7µW÷rü+›‚‚ñw¶T0#QÊÂŒP}lAÁgàeÎHcÈØÒÑe5êx;}~ŸË©ê<ÔS&mZŸ &Œñ€ç•W^a¿Ý®%Ö3vwwE œ*jà‹¥uDŠ:澸ùT¿×ÓÇ m"¢Kì>XŸï…-4?jËÖ&߉%΋gfv¡”1à ·o¨äQzÁáõ+­8Ô*Ù bðEȾ­ÚwÃß»|~u^8òùßÈ)µr"r J9E¼§†=ŠVÒ²ƒw}ÿ;M=Õ‡í$âO¢ÑEüÑX4ìíåx],néßÄ'Õ0*No¨ËP&fÔïö”G#ѳ3{íf}ƒýªÊtÁ$ƒ©+§ª¹Ö¬ôBÙ­]þ€¾úïëNtt+ )ü]yç?>w"Ä¢§ gMgcZ¼¯‡-·ªÅt ôLµíT£oyå#]ù éQrú‡^eÂGC$¿òXv‰|Æg|éÒ¥RÄÍ7ÿ›3ð;~tÌmxÊ "àq4jTt®<ÅVZ :VÿD½Ÿt· [±XáÇbqqª%L|œ5J-#ÌI„-þã æ“&M¢° @ˆ¡†ÐðT( KÇHA ¤¶P3"Pà% ™Z¡··‹ý0¤òäb´ûSÇ>lÔûkHDZRÃHÁô&Ù¦-0»Òâ#_Eâ@Ù´Œ£ X­†€\C¦¾8Ã1(‚²Ž‡RèÔ„ ÷?þñÙ³gcéO— “ðq“«µÉ0‘±úݥש¿Q ÛT…[o$¡»<ùKFÞć ó"ˆ ¢Á»@Ï#ⲤDœöLj•‘5¤qbòŠ¥*­!Ú̉ðç)²#Èèóðéwæ šk ÖŸäøcÊ" q¬F%ù^Ápø ¡ŠBöðÃKäøoÊÌV>#$ò±JÞ¡<<Ûãöé±·]ÀmVQú·Û­®Ð• ™¶%kª:AÇÕ %™ÕÐnjëtîkvìÐØyÊ =W|àrqä´3‡øÕôŸ|º¹¦çµ ßsÔd±ž;ë—ø‹rÌ+À.ðàßÂÄ ó‡´3MÀ‘‘˪ Åâ6½’aW—p˜-+˾´ß´'”r‡¿€$Z=ªN$‰‘h$±Ç§š¬âºŽk¶C²µn5ØíjR·A þº¡¼Í,LÇ8ã¡·GØ Ï6õ4ÕcÞZçV±/T´xÝŠIR(輸`kàiÓó<ðêÊö­u=Hb˜,Yîî®ZÛM®ž1-üñ•±ƒ¡v0Z§Uƒ)Ú3Òç³CÀôUFÆï}ï{œ-÷…/|µÆëSO=õž{îÑ8'‘±ìÆ]er95=‘:¿©Ì>1OßéÏÌ0Å9é•w$Fuáð«¥Ó`Ö/œU®ú\(»¡{ëtzo}Ó¤¢´7û‹óìæzÒuû––f®œ”©ÿÙ¿zÕð/NX180ª†Í½!åõfËÄD"ÃûùúŽâ Î`UæNŸò÷Ç_Ð…ƒõ~C_wÅÄR›ÉÀÁèNÕ 9‰@†9Ô·ìXfØ|«ŠB½1¥Ñî‡ØÎHÜaw;(6LXñæ€.…Ð7Ùi+vܳŒÑl›ÁÞÕŤ°dBY[moÜا;Í¡o‹EAš¡×ÚeËxmÏ„éèµx¯pFŸ;«ìäé±l¬kñöá3bOgbÏÚîúÞ°]g\ÔdlŽ«#q³ž½²hÒÜœÁ}¥Ç± “C…±ÎaWÓ ¦2e¬¦ÎC2×A)Ç ^çCSÊØ¼HÙ9ë™fà`1ùw/^~˜Æ5ýs_¶åþýñN]Ðk3Æ&äAƉ®\™¹Þ¼vñr‹/ÓÔùXýä皟 ¤ÙLÕñ¬y†N6žvÖ*Èb%ÿYã½çÙ½ú"{zKµûž‹ÉÙ.˜l´¥Ùb•}¹æOÖvûä ø ;·O\^þú¦j·•ßÙþ2˜Ì”üðäe—P`[¬ü4eÍ@b"X¸x¹À´)£Z)óúЫLk×®?>¦ ÷ÃQ<}ë­·°¯DÉ\µjÕÜ3™º4¿)Z1c?–¨è€\ e(¥,ÃȶSR¸…Œ©jˆ§M›†·PÑíÉ(k6ÑmÈN€^Ê"=ܘ0Ãàg,€'¥ã J!£txÒ7 '‘‚H‡¡ôaAÜH‘ @Ã#n\¤#N¢ä’yŒTO€ éKòJÊ‚­˜ q Á¦<²S ¡”ˆ T ˆž0äEŽØñRФ°J…q ðCcoõ»ßýŽS_p#nE§äîX¾5½î]‹K¦aV ¤Nå#cžO‡§«Ã yù"ÎDâdÁÇý¥8èçVD°6d„G¤P(n B#v¬àãÈs åS€r (hœ¹åÅŽ "c@åÜŒá ì¨ÑÜtÓMwÜq·òî8 ½òÊ+ïºë.R^ýu¾oå72.¶ .”÷+sñX´¢¡‹…y_0~× Û{ú¼ž`tkEU¦í°®··¶M?¡¾Îêêwœ?ÐÚÁ€{ì7wUæ9Í»‚,ØÃ¤4Ãò¶á‚º 8®ÿz«&ÃfÜ]ÝXjözüê@¯kÛ_«®ñON³Ëô†x5IS§lû¡h¨KìoîN¨gì)-ÝZn“³p+ÁŠ9Ìz6™¿0ÄJ$ŠÉY–ýíT^‰E4Îí½±ï<ôf_(vBíÌ©8;ÂzT Y\2÷ ŽSòûî~½’ô¢™&ó»fѲœRËr‘cPÊ1(B^üØt J9E n1I÷v6U÷ªs? žžž}ÍÝ?~ÆsñÌŒ-¬pèÿ3rm'æD]M€Ùç–èΜù?/7Bÿç—¶‰Œˆ¼ ‡ç¶ûžÛ^+ ‡™ÿX¬¢"2ê©FŒ¡ŒDŒ‰;<€=öØcL﵌|½eìf<ýÐvã®2(]q×ü#dçXœÁΰ.UŒV»9ðÓî:P];ÛhîVœ u‰h°wϯuK^†2ý´¯¼úÌ=±X4ÝèõvlŸ6ÅÙsUö«'¤gôìxãSk–gë|sÓñ„ÒÐ;­±Ï\zÁ›ëöm®ÏËwf郧™º‚Ëÿ\~â£kvÂ>os:݆vÓ¢½²l¢£ØÊR¼1‰ñÓºJe‚bVBúF—>2ѯXª¢9v]èâ €Šª‡à›ÝÙåSõ.6¢ÛÍêøZšmÛÛUàòJ³ª^„žEÅѿū”ðßW혚Ÿµ<û^â,åN?ïÿŸlÂ×Þ¶ºž ê.?§UåãG¶Ö÷ê”Ä~ÛÌ2Wn‰+Œ¹Ûýkš‡ìÈB“b»Ž­¬¸Æ %R-ð~·À‘ ºär“i®½ä|vÙ¨ÆD ÇúvSÅëKc-ZºgCSŽ/–=Ï™x5©õêædë®Ë6ÇÒ «Î7_×—~¶wïÝáÎ/•%ú–`©ºØÒ3X²ÌÌãÒ½1å¼¥366é=Õu}V“ 〉Õ]®7g6'2-žŠmµ&³Ý–5£!mè÷üà‚²mÛ6Ùæx!ªTÄ£îÜüÆ#g®ú\ò‹Œ2žR™¤Ÿxâ ŽÊecjÙÑ÷ìÙ#· P|uŸ|òI´b`È5€D'grX+z&]‚ìô bw&L¤{€yÒ ÙÁàd'À‚W”| , 8m&2e!/‰TbÒ¹% E [FTzíà–yŒT†8 °šØ ÁSªÁ®=©<¹%‘ »}Õ(…¸ äâH¤\0 rI‰4À® –4 Oì‚ä«Ú^ãáË,¹©ø1kú'H纀Ùq¼]š.D?Ôºè1«ÉÈ BRèöÐ -¦pH)t]í£Á+€Î‹H 1p›ö”MTåeùzÀJ¸bΉ@iÙ‘>¹RP2Rø2.Oè¹"\ò-¢†l7Ñ#}ô¯Áð€´‰V)‘O›^úÓŸrôÊŸ^ù;ßùŸˆ“N:iôµwãh»€]Û?îú „/’0Œ3Š2þ¾½›XÒݹE5%“u÷îµÝ»éìÅ3åà>­Å®\n|{ãÝÛ½ß>ú½¯í=Ô9aBG@ˆá¿^#w±xlAI¶'©ñè~»Ù_!8~çæNÌXà¼å’Å›ª»þ¼¶¦¢=ˆe_cÔ‰ Æ>¸uû„%û©ÊMáH ‡nd‹íª·ð¤Û…Û¯éLÈFšnH¶c•x J9EHk›‚ŽA)Ç ˆ![Œíäb¦Ê©²³¦åj}Œƒ•ww&8¡{¤¢·`ɲ¨4‡/À.z¹ÇzÛóûuw#& ˆdž5úýËÕÍ%7?¶›ÓZÕadP+q„j„”ÃÉ|xyÄ8(cb2%Ê##õ%—\rÙe—±/‡ 6Í Á9çœÃ„Éôã‚MUP™ðV3Éy«L çLM˜Ò%“ ˆÿå3K Òloõ:{"€rºÂì «.ŽçìÞ¸é û·1¸[Q¨Î®| Ïkywo|„øôBÛåâ C0ûø„¹N³Áªv÷¥®ÌÄ«ODÇ¡«?yjÛg”cÝ®Ï9uN©ÃlÌ·3óèk®FW Æ.G™Å¤?7Ï;Yi³GXŠ)±0^ír&œèÍ›˜¾ê¼sKŠòór²Ošh¿f²ÿòI1„‡«µ‚ÖQnmCŽòÝU³¾»jöÂ’Œ¾„MW0Y새g™»”X[ânK›>qÞµ?8mÞron¦µ`BÉÔ9Jã£"NxýX[žËÖí l÷Q»X\µ‡‡óÒ‹&¯¸bªÑ¬*>¯=BÀÙ_ ­;BÛ¤’‹ž)Æ^Ó3µøm Çš÷U·õ2 ,¶4ç9ƒiVŨ¿d‚×7Ï-´ú"*^@ˆ…Ý=ûþnŒ÷7è ìU¡¸:£uc‚)Ȇ£éAƒNñ†¢(Q·ã¶ÆÜÞ zZ™A õ†Ò½‡;`µ„D˜…–"{”JÒÚNè°Xt|¦Ÿpl.â/i^èL|²xÇd• kð¨Ê„! c6ŸP- Ó&GñH;4öHyé„€ €e¢«C†žŒ¦Š>O Š4) ^TOi|¤³ê‰â-ª8·˜Ñ‰B%?¢ö” ‡XÍsPÑð¹Âœ«ÌH¤ AæWØ SAvœ‘¦F\¹eˆ„V%"òˆ7".Xd@'0ç-x/ªMÞ”v¡'.Лl¥þÔPÉ.Õã–@›à)D†DæG©ëG¹èßøÆ7vïÞM7£ÒCD|ŽÏf¡Ã# Ôe‰ ŸSOs‰ˆhð^„äW c2™,Â9ò•‰\ÙÊ ‘5"-#åŠxŠHÒV|( ä]„Q>2È×&¹GÇW¥Ë:Á‘˜ üðˆº%üõ¯eýƒŒ|Ç0¯ã_€k®¹æ[ßúƒQ2å4.¶ 4ŽÆêEÄÜû¹çžã3¾nݺÁlådƒ\—%/Íòµ³§OËwâßzQiæçVLaÿ)ôOílá|<É8­#ñ äÑÏžß¿¿U5 €æ¶~wr—þa=h q õ…cõݾ æMʱãxÎi1ˆ±ý–DT¦Þ hª‡:è%”ëþ±å¼;Ö°ÿîó{ ÜÍbЧÛLåy®9Eé8ÏÛ|ËYR(Wvç}âU=!€;°Á–« u®~³Òÿû_»úŸqY6%çæó¦Ëƒ<—ùΫ¿ã•\Óíª`Ry ªÚ<8Ú Çâ´R²‹=É8ä•÷úæ¹Ó¾tjÙBC”JLµÀ([€.Ê߂ҌǶ6ÝøÀvöØ—¿°§¶¿úÄü²\G»'ˆžÄ|‘”üÇœäâ Ä¤tyƒÛ½x´¼nIßþ~ùñy <•œ“3Žo\fªïZ‡#‘гl|饗2Œ²äÀÂ×[¦¯95K(ë¤æ@IDAT·è„lßë³4a²·«ÒGe^(R×T |€ZÈV2§8mYf ­Ã^ßí© CÛŠulÄaô,Ñ׬ȎەɊ! ›6ç%‚øØBb›†ÁbH4ùL6vå°è„ò³n×Á†`¼!ê:}þl§Úu‹_¶Ýë²Õ˜b•Þ%äGrQ„05B•Â=?ªºyÔ,ž°ÍaÔ‡‚*ºáÊ.«¶EeÖ‘ÒÄóH*H"³>Ú¢ûQa6Qò PBlª¢î»ï>^áHMñ¾¦c4G‡¡ùʼn%ƒ ƒôg2"üp|ö¡êÂé¨+_cÒ…Œˆhzü²ŒhTGº~,ø`,Ã-pt\ ÍæZð'N%Ù÷Š2Aôy„³Ç';ôTÎò$B‹‹7xR:Ã.¨Ü FFÄï V<¢à“wA: Äó—°åÊ#6ÆøíĘö(ù¶} 0âë_ÿúÏþs<„ÐèØ|`5”ê¸j¦pt`êF=©" ÜÆÒ5B¡ £TyáEx;ˆÑâ¹%]Þ)“\òÝFRÈ‹X!JÐÓ’põ ¡äŠ´ÒÉ7DøÀ1Gù¶ðJñÙ'ŒI‚øóA€Û‘@@,é(hÈ…‚óÏ—Ó×el÷êŽÉ 5“q7÷þÖ¹3î}³îÕ}mè:¥ÙN–ü±£Y\š òõì®&f,<² ù‚¨úuþúÕƒ¾pÔe1žò¶‘Nso 7Y:)“ÅËŠfOu§ïOknøö™bÎsë…3ôì>a¸§©—ˆÝbŒ¶kó„j:}øù‘§€t˜ï=¾½ ñ¤¬«O,!=‹޼ }ñ¤Ì-µ=`‚8k …â´ƒb¹½õ‰Ý­îÝløä’‰âW®<ß•Ì÷À >u÷›Ø¢öø"}Áž€*VˆÃ37ªxñ¯3BÊYªÆ·¾ÿt:‘ÔaMUÇK-”|îìN”戜ìÌI/É’"ÄÚT«œ`‚4ô¨úÎüÐÕxHÂ)å¹|X»¿|ûùøüg*Ë€û®e˪ղ{ï½WRX&Ädaîܹ<ð€¤0Žs;€~Üoǰw• `W£:E°›ó÷÷™¦ØÃ‚µ›(õëìSØé–`é -š[[ÑÊX°ƒ}j¶‚™Ëkë;¼}¤¯ïÁ‹á¾¦e7LY—môÞ¼&'°?×»vr Çäͳz¥ŸmÏÀdôÏ×,þíju››?a¹xå¼µU]†Œ‚ކ=Íþ]=Æ3 ¹iÖ`Ö4G£ûÊùÅÎ+\³FÕI¨$ £¶¦þP4Ç…>oºûÚð‘ ’!ðáÓßüêÚ’ûÖt=¸ù° :)Çñð–F( ¿½jA°âçûÛÜ›ÜÓ¿÷”µ¹'x¢£`šµ673gÖü«!‡Õÿ>µqÝÝÔë084ÊjÄ~0Q æw<ýÐáu6E§ÚPêÓ§OÏÏÏï/!uIµÀ£~üÜ~sG]ºMž…f?Î/>ï¢Go­34»®·³ÈÚ˜oi0ØÙ¸¤è¢­z>ì¡x(n½»óÊÎhÎW‹zu‰¦¾°¹%˜ï‰åܽáÉ=¹ÿ½o|¸‚½$ÍQ‘ïÚºu«Noš\dÏÔ³×Ï~ üÙÜØ½áŒxBuíÒ‚æ<¦„ft$ÁìP½ª›£ ñ,ƒ1⾺§ùSÒ®}ã¦T&­ o¼ñF¬6®¾ZýôãÀWQªé¨Ó(ÏtºX飨 ^ IÞ˜:ŒB.>W´q8  Ó…ÐÿYË¡-èZ¢Û³?mœr¡©­­%"Š:Wp:QÝE‘¦>bnC2‰ v¨úÉ0™†ÙQ !c–%’`ƒk’°â•IA-‡ |°±'#ð%éI;3ÖðRT›ú°RE;°:*/ žH"laž å “lذÌo2àCuJî¥ÇOãˆP ˜Ôñ¼è™á@D’®N±É•Îd!5z;YDÌ¡g%VðD¸åãs^9ÙG 7ð’G|mà/¥“ z®ÒJ”H„"DÞ‰‹\ËÓÑ\å[Á—‘ˆE_ ù iÔ Q~(#ãn»2õzU;:9sþP$ö¯m 7œQÎË2‹ÉPšå@ÇÁ–íHÿÌ®f>ŒéÜÁèïWàïŽOÌïô…p ´¹®'Ýj4t˜&à-çª?¿)~{4´žÙ3'o0Úê`1à°¢äT×–”ù‡19×âH =ש®t‚Öq•"pò³¯EE$øBѯþs‡Ä1ºg}í“;š0’;ab¿9ƒ¢|ï¢Ù«+ÛØÔÐÞæíìë˜SœÎåF0˸bÎaš·Y¦þ§ZàÃÐëtZwýéSægÔvy1˜E$·Õ^|%ŽU,¨Üà·Åú•Ä6º§Puôت²{8¼y¨sgƒ:Ý-ÈPÅóø £#ìd°PmÆYÒáÀÞXŒìX#|úé§…×F mèÇývl»qT™ØfL#Úœé} ¿K>©È´µ­ /«··ÇK×ìŽ[––¯\¶d@C;Ë.ÓRVœñ?zfo-ÇOê"F½U pñµ?Y_ÓÞÊu¿sáÜ{7Ö]•ö8ª;†o?›[óíÝgßùÆ!Á§ùô?·Cûð~ÅBP§/øƒ³òí!ÕmÆŒôÓóC&&¦þ2w<[Sú;[Qž«U¶âwÿ×§Ôÿlk¤KÉžÕŸn³>ûû]÷ö¯Ž>ðS@ë ŸžŸŠT~sg1Θtá½­J—r·à ýìþ÷â9Àƒˆs^Þùs ªš»tùmV›%Ø1…2Š&.9¿ŒsD&yØ1‰<û¨Ö8©Hª´Àîf÷B“ÂÎô‚x_U›Fa:ÿµrR;‡yÅÎ*ƒ>ˆÙtŠÑiì ÆlM‘"õD¤Zã ”_7lJ®c]]'ƒÓg–O~ps]]kïe÷q{¨/µš 8pÁ£òŸÙ{V¦ºÊT¶ð“R:V²»ve¸ûÞö4Ü€C¡ô´ «UuÃ¥é*ØMD"9vsߌL¾Tê0iiý«RöCa5škJe’ÖcµSv”RMMÉø=š–×òŠÂ¬m…Ye¼ŒÄdû5-Ëàˆ0¡þ|~y*LÐÀ‰óFL)$ ³ QÝ…žD"BF^ÑÌÑÆÉÎ#T}‚à‰ÔDS×…WØ’-e@„âHUœ,R–F¯5»–"Ôx‚ „ÀsT†ŒTFÐF¡e”×Ã`GŒ0IÝ~[€~¾qãÆÛo¿Øó¦ÔÀvtHúùqÕHŸˆ'(u“8WýœÞ§UbM`I”<ÈŽ|Æ!&®É#”¤ƒ«ÆA‹¥¢é!|ôä!ß!¦¾ RÕ!¹ilßk„šãŒá% È˧r$~nÇÝvAT v ¸ý*ÎËž8þ¤å§å§}bÉijg­CÕoó„{ýa»É`Ðë3æ_øÚ¿nÂ¥þÁ٧İ8hÈv¨¾çÐbn>¦¶‡ŽCc¥h°BvÈFbªÙ8ƒn½p‡Wæ§™|É Éà¿î߆ö´·Å=«ÎÆî<íÔÚËwxƒvgÍÊßRÛÅá{7<°]òR Aë0Áã4[qr‡7¡Oþi#H„‰vFüŒù'OÍýß­Å7z:^â¸H¯ÛÔàåÔZ¬ü„>uMµÀ‡£þ¶¡Ž~&ðì®–%™x–D–þø•VwPÄS¶µ"¼ï^;¸­¾×Šˆà|ûü$FbX*¸Ù<ÛÙzð­9sFò‚iùª_ ã'°wElÀÚóà걺<`â £0SS†NÆ2Yüæð+†r«bƒ™Œ{ÊØvã¨2‰¡£ÅîÄÚÅhÔŸj~tV6 ;ú)'¤lyiÓ?;’&Æ2¾ãtt"›kº1œÆ'Q¯gpR{³.†G†)¹®·j)Ša &ˆ¢NÜjÒûÒ§[B{ì¡Ù¯D' žˆþ&럒K®  ”Û­;tøö¢– ëç÷ëøï¬ùÛ¾…ó#Ö?¤¼ø˜7¦ïü‰°a«ë®QZ¶uÕ%çhY]¶lšfv;vì#Díi*’jã¹€,6öØÏµE õ}|;pšuWVš}Vqޝ³Í7…bªnƒ_H=ÔLÞ¨²EµÉÅó C2²RqFœ%)ÙÍÏ¢n4gÀÃvp €ŒðM§¸x,Þ^Û3©<ûºƒ–@N<ׯ³.‚DlCô½o¢,±e=W'²cR*-‰Ò(í)xÖXµíHø`\™à¶’Å/NMøøƒUñ à ‹¼’‘`Ç•Môj˜ÔÔÔÐÍ„·hÜjvÐt?ô¤£«£!3ÿP5’þ}µÌN †¹(ö$R[ÌÙH'QŠÀp@õ`.3€ê1€4šQ%rËÛq•8h5ñ#Ù@1¾°&$4 Ù ¬ñ4¥hoq*|d[€ÞÎvÄÌNÎ~‘N®uæã¡e˜ÔiR‰@Q[j%袧 £VU:¤OCÙDÞ‹×d"É-šy!†[„‚ ›˜ÍBLaÑ$]ã/laE ‘¼|U‘S)}lq4Š  ¾còá¢A¨›¶X¥Uì£GÛ­‘ñ<•aÏÚTÝ]˜n]°“}¬N‹ñÖU³Î˜Á6‚áÂ;ÔM<¬îc‹7£ -ÛaÚÙØÛ· Dâ'—œðì®fÌÜp<Ÿf5~ñÔ©?±ò¯ë«“7–âˆìèP8À:iJö§Nš,ŠÉ݃p1¬Þ@ëšzü‹\‹3íìØÕP6˜€Ä)§ªUe»+[‹^ªhE;ûê™Sg°˜à©4tò ë9³ ¨Ë´›—LÊÚp¨ë÷¯¼þô©j~ÖGún:õÄA§\zù ;kWL²¤®©ø€¶†´RóÚ.?ûÁÙÎíiXÝîï@ÁOhƬÂ4´›ûÞ¬­ãiu‡ºÄ‹ØþìùJŽ×Ë´›ððxÕ‰“~¿º „ }¿r„¬‰ÃçwRâø^™ai JÅ0Tç:Øg£¡ŒÚZåñd'«–r\EưG• ÿì‡êmoJׇNt¾ÚîW;hÙ„)J¨#hÇZ-ùLÉÁMÿõkªðÃN³šY2#R::CX@“;ð©¿lâLˆÍSlf}cOàžjÕMã¿íÜÓÑ" ë¼á`$þVCG^Zé´`«6-C‰bædŒu™-étæp8.eœÀ·£‹ „ƒýž:§^é=ø ¥¼ñ|¹)M±ØLg}n&·{ž¯ÜZÙñPomß²‹·Ôt©‹·Eéªy*6á\Oš2ôz«R ¥ÐÌ1=‡„䨷ÅØn×®]tY¸¥BªŽÿ˜^àzu_ #͵*¹¦ˆ(KÿÚÕ9ߤdYu-]a{\oÑ»-z.çxv…ÿ´ò”ÞpX‹Haš(>S°Ÿ½ûÚ¢2]{Ïfæ¦}Á_§úʨ=lEÔœò+0€2„ýÆh$Ê`¦ÓÇcÄãhÔù¯¥HQqE‹±åJ1Ì.±{­ ç\2† ›R™üñ1lÏ£`%Ÿ}¾ðôVü`R__Ï7Ã1¬Ì†äÉ ‰¶Ï#ÈXãš‚-@gxøá‡éÉø—ÌÒ6~Ò™“ÖÀŽ Á¤2ˆ øHÇÞ½{E©-A'Â-b‚øðFÈ)B!ÀâmÇ+B %LàÌ•ŒìGEx³ãŸøp•Ö ·Ò,D(¬Ÿê!¡À]±½¾›½í6“©—Ͱbš71Ë! DÌÆ@cëÇ D„È‘,´BÇ%Â0ǸI`•1¹XP1ê%Ïœ“Ÿ~°âcػʄ:ñ1Ã˱ Û®Äs§]–5ñT~†ž¿PÑ@ó¶"õvp`UGN< Gâk:èµktütîKJ±²»7÷ÞúyßUgíßËýª{é9ÕÜWÜ.˜šçÐÖydØßæé ¨gÓâʱ>)°Û™1'›6uRºçñ@ÀïñlîˆNÍ,^8¡÷Ï·ÏS\åŸ › €ùÿP1“³ÑÓ4¥UñHÐ:"7Ÿ?ãºvoŸüUSÌê^–Þ?d>qÃbÎ<â¯,×5ûΤ¥[¶í©´…Œ'Î> 8í–91€ÖZJ*’jã¼n]5?Ä,ì.,1$:ë0Œþ˃ÿšo2›Z‚½¶ýºògfz¼¢-X·L¬öfl æ;}:cìŠy“ Ò­KËT0E¼¥üú“óåeÙÃþù©Juz¤Ãªˆ§3tÍž½}ûvvŽC ˜Ê$ÃW8”°›;uV%¨8ã (I7—Œ˜;3¶¹qó¦üYŸ¿éþ7½Qc†ýÜïLyZÇf©ÎÇc%%«jœ5Vᣩ2ñÖ WóçÏçì¼ä–ì×1’Þß8Ð`.Ð.ÀYù-85[C ˆðMk2ѺE“gªi3ê4u&Èlƒ®…ŽM Þ$¢ä—Ý©“'O–—‘í®/,ˆð¾/õÌZ`m´wÀ¶Nâ_8¥LK>rÿ¦ú×*ÛÙÊ Y‹;PžgjèöÕã/Ê‚æWÞý&šÝb,L35õo|p”s'¼c æ/E`Èv6»øGƒ‹¾î”²7«»3í6áôc‹²×u0åà|ð‘Èq,©¢p¿S›vÓ¬&6Ï>ºUµò»ñÌòÁµ”›ÛEüS'•h)©Hª>-0£ ]³Eàøfy)A9­#åïëØËžwÖn€áÖÞ€jÁÙ¿oﻫf ¬åÓ7žÂ DÁ„Ò/ß·qs¢Po^Çé´Õ}Ó ÒŽøí¨îôæ8­+§å]³ì}”8F4;<,3V—Ö4eÑš™§ì†ù ÿʣ쎕‰ÚÚZM&cZZ±Òõ¦ÒØ¥M»Æ³ÿžH_µ­ßºcFSæ1X“ɾéÇÀÉûÝ:кp4ZÇÀÃg~fgG´+ÝÂA þV8[Œ±Î¯ÌìS”ÊSWýT~u캉0>q>0°Ñ;ÉOË?,'áºûz”PXQo³ Þ8`±›2ff¦¸HnH´NëOÍU*p6uÑ;þ&ð´zÝ)“ÿ¶¾Îï ½ÑôœÃç+T–ÍŸ–qȈꬡÓÜÙ©®ÖI@"ZÇœò]wƒ‰C*=ÕG×8:]]ÙQ˜n9múûçœ9PeQiÆÖºÞ§ŬºüŦ&“’püf#Æ­ìiUÚ"S·yJÒ-J‰3cºS™oK,Yrú_­ƒ‹EÝÞ(#q,äðlj{IÒ¢êR-j  h˜Àv$2Ä|Ý],¬séâ ‡eFú¸b„u˲ßtÇ-‡Ú½óƒÏ‡®­ƒ>=VÓåç8Õr<•ÞòW%J…l  ºƒ—70cà‹JD@.^H´îd…Y4j®ˆÑ«Ñ®¡”QøIäQ²G9Ò¡ÑR ††tr–AÜI@{©€Ä!x ½–(¶?s†…€šHýR©*àæ{ZÞ!#R7pŠ!Ÿ2Ó"ªÊÉ|k˜qjH©Äk ;=ö§|þóŸg¶É-=œ.G‡“WKXa,øžÜøÊú ÂŽ¤3L0½¤ 7j(UÕd_{¤Õ\¤Xp4ÙXJ.MÒEj FàÔN B„‡Œ3q+‚/­zZz*ò>µÀ¸Û. Î°m[è/®œ2Âw¼ýÅÊý­ê~»­u=N‹aÍvŽé"ež½¤ã€srÝås2ØÕÃà“å0ýßåó¤1}hõê»ý¨OÙN3:¿µ©ÚS0»l§I—8ñ€tÙ¨ô‹—Tƒ-<ò¥åhdl‰øÌòI˜iéÉ ů7ö çLÎz¯SÍd>©xªŽÏ`;¼TìçW–MAßææÑöþÓ'p¸ÿçk–üú•=þps¯ã!7»àkg¿cУI(yÿþ¹¥¿xiÿS;›ÙQ+F¬C¾¾†ôñ=ÙÕØ³á`§p¤dEΧVÏ èòÃAä Éê(™”¢£_0VÊz6L[ê˜RÃã0Ëh»ãð•¤Jür»wG¼¹ŠN_ߥ©œ®@HÞj2ª›,Øà‹ïâ ‡?isÇœ† ´Ý¬ggœyz]I–ks핽`q‡ÏŽP‡4ÜØ%âªéÞ`\qòræp¾½Ï1°ùu9úX;V¥o¶©ë·«Ó¦ø‡­y4fJ¯?òGvj÷ר8öôÃ;¾ûÄž·ºéIÂ2‡ã;×.&þÈÖÆÚN/Îõ.]¨zc&Ȭ‘™î4r2ÓGfœ´õ8<ÒxÈš§?è- î‡å-p¬ð‰ÅΞ]0ò—úòiS!þÍ+U»š<ÛuS—;Ú²uê*+Álˆ³Õ°>–>Ϻ·D´«ø£tYUAó7¾llYñL2XsõfuñÖ¨¨°óƒµöîڹÈq-hÝ¢—oýç{ …@+0C4èÁ„’f޵†¦é ]F³:&½¶¿;Þ)¹N³%ÌzµÊ+ƨ´QyŒø=Ôf>°LЊñ[ÊXC ìÀ¼Ð™ù®nàó EºfˆV%ðœ(Õd„y DDŸ—jñ«=­Š`j`èöP¢ÃC XIYQÄĆ*iY † 3Ì`’Ñ:è™ô@FUá)5!‘¿À%DxSJ‘ãb¹%ºd’ÄâXêF í ZGq4Ž RTȑнœS·úà£ý‹_ü‚þ…/|™‰˜Ž fÇ,¶4 ²I—f–M?§7޼Uáð!#8â€4Á ‘G(ˆ#•¼)ànH5׆!éäôv‘)Tûz‘tå"„Z‘"RÈ+òB.h¸Ê»‘ÒY6à+A¢§‰ü`”rS×Ñ´Àqb»À+¼WKAëÐÏÅpA=_BÑáÊŠ=C""ñeeÙÁHto¿»¿mí\XªÚhú<î}D!o÷¨‚1}À¶pÃÁ5Á­ÑÖtxÏ›Sxz¿O½‰ýn¶·üþVÏôu68 ‹MËwrð¥8ËG÷¹`.Ó0åª'&"~½±žºMµÀñÓødd½~I†iï©VÁ,¨Y2˜‡ß\¹@ø¬= ºL½áô){û'H¢d´nw£›ÄY…Í¿~Îô}-X,½{øæ¹Ón±ê…Ý-l¹e_<äɚϘHß[5]‚Ö‘ø»­¿,©ð»Ü漏±r$”PšÑvÇÊQœúhw}{¢É_hh{‘YÝ0é½É…vÄ̆=¬Ó² Êî6~­-[¶ÈoFg­ë‡{±©y¬çüÏÚ^ ã¨[§{º¥pqf?luÝmû­†¸3æuLúšöKv^bÝÎÝá\ ^ZmzìÆ'dâÙ!©Ã“³õm÷z0Ù—kItGÒ3Í¡3&ú;”PÒ÷4¹5ƒUa8@Àî«n;_±}†ÍV·»EAµ&G˜O0e‘vòÛ›ª´§‚֡αJævb:¶¦Ñ¤"©x?Zàoj±qcjxõ‰{1œ->´¥qùÔ<ʼ¸xÄó™ ëbYÍÖ‚“mEçzÝõ; DŒ™¶PÖüO?ýÈÎ^_O©3/WʹÌ{Úzæäg &¦DXh’Ó™ Ë)‰X°ˆ%åu !¾à§—•Þ½¶Ôâ ×Úôh{‰)ÚóÔúʳN›÷|‹2%š°ó ,G›¶))˜Ù¨äÛu±çª,èK öÚ¼Ú²¿8CõüB°¬Hê:ú¸ä’K8‘ý–[nš*j3šêèÙŽœJ5J8#z;Ý]š¼¨ÓŒ8|lÙÇ-¾äE©F‡‡\”y&i,¾!¦«0TÁU&È…¢Î{ v&µâ <I# /¯ %WÐ.øc%$@¡ÐÃCº:&E,&%ãk”Emi7™ë€¾\C*#ð6ì”H'@‰ u”ËÁv`A/vyRâ€+8 è!œ0.óæÔAŠâk“ÔíG¶èð‚Ù]wÝu,˜Ó·ºŸÈÔQ7 ðy5Y@ ©é¨ï©ïáHD`q&~Ì3µ* ö'BgN^^^NDhDº™w‘ŽŒS4d2 ù,ðˆ X6âÆw¡&ÂS­ˆÃSrAgÊ6mšL€1$ bòVw­ž©Hª¬ý»M±<`›Ñys ÊóÓ~õr¥AÏ1ºÛ.[xÝß7W¶õYŒºÏž<é䩪=õ|¾b.‡•ß¿'+7>°½¦Ó›—†Ÿ!›pùRgÑ\‘}çÌ®š•ÌGÀR­8ÿóšj Ã/ž8£Ÿlr–T<Õ”xjg‹ÞB…1AõRF†yè×TuhgCsÚäI. Žcz’áÑVþCöð³? }ðmSoð¹ÝÍe9Ž3g¾³2ÁÙål?ޝY>%›s`oŸõ÷×þslñþ_žç E~:—’JIn÷ 'gw• ÈIŸ¸0;ü´7Œ x]_o·ZCCtf~wÑ’Ï=ñÄÜyæ™2•Á¶zŒ»¯]yÓC;¼Á(¿qÎ4>ôìoýÎ㹜yœã4/* Tyýg美^DPãã–L\ É»‡»÷øêŸýõ)J«¿yÊåçû,Sw5ô¢Ø;­€˜ð×å”þc[¬¯cÁ……mµ×Ë›†e §ß}N1&Ew¼r YÚÉA{:ð8̹á/Bû§CqƒbïQvù‚p^{ó‘Pì¥íYékÑo<¼J,`/žWôÛÕ‡8öe[]ÏÂÒÃ0„T2ùŠ‹:œ%¡h 섌™"º³afx[QQ±`Á‚d©xªƼÖõ/øüñÓ‹„ó‚‰ßºâW/W ˜¥ _®»âΰç 4±úÞX°ÛYvÙ¬•_Ѳ¸¬ÆÙŽMņfƒI_S±¥Ö¿;Yññe…œ)&4LOYU>Ðî.ÏKç^±r¶­}ÿ“•­÷Õ.ç; pù-ïžœãZmAÖ( àâÔ¤ë9­nÂsÀ•r¥hðí hàÕ ó"Dø)eŒ–ôLR·µ ÒÿéEýñþêW¿* <=ŠGүƤeèÒðyO}O„N@7Ä_»äÊ€ßɱ$"YÔ;YéäBàúyM$Wxò”ú&"z"ƒ2­ÁÄU2ÂJäZ8s¥\i"ª!B ‰¤qR½a uᙺu ?¶ G÷ aÝ>õk\œnóEb=Áí^ÎÒ E"ø·:ÿ×kx”ï²^=/=­Sû=Ùá̇^H86=¨ŽƒÉ{röü¾mã ?uüjõü²߽vPuò3hçì7Ù+:ÈPm0DÀ·t“²mÖG" «LÛV”çΟöÇ5Õ‘X·_1ï›ooNúùKU7U>À‚Œ©jD ZwRYÖ²)Ùooªíô–‘w}‘•Órù’ììYYWóãg÷iøÃëûÛ“)OÎþÓÚ\Lâh29x(ÛßÚG}pwÙ‚âßj ™l3׸‘×ò|Þë8‚VÎôca/­<âi ã«òÏ+Á£T¦Æ°;T¦¸ÎÜ™JB§Â¹ê6ÕXÁ'´—Ož$‘ØÓêö÷»ÎV˜â˜ F½,˰+VóËÍs/¯Î2X³²²{Ò®è<ðŒ3Ö všÒÕåÓPÏ>®Ö‚­ëBÝ™SfÑÅáH\æô—çÚ«+÷Î3E»3̇|»âv½Õ©-½xעġ²#`²Ø¯\ZÂÀv°Ý‡nçWƒKgpG—·%öè"A½ŽÚJ¶3s7´´hï›”ãxö)Ór_®hmñ°û鈀ó6fopxóÍ7O:é$­Ì䈣zÖáˆX™¶JºF–ФZà´€ìÐL¦GRbÔ¯î1¤ws8^,ìŽô#wÉo¿(÷úx4nÂJÕ®óM4·}ç‚Zåo¯ZðÍû_„«k/u»u¦È•™]"¦œÍPÄ0#sÐóK:Ýé5­>k"b5EM¦hn®ü¢ÃÏÑœ‡)Öä³ÚŒ‰ES³˜¾{êäµ Í‚µà¢I™8unñ–*:UâRa¬Zà®»îºãŽ;PeAëXlÀÝÁXq!Q×¹2Ä rD—hd >hã‚Ê ÕóˆÕò JJJ¤\Âb<¹å©<\ÃjF@.âšÆShPïA4ä))¢ís哎¶À'|äJ¹D8 üQìÑö ”H.yð8†Œt¸¥ÃÃ\Û:,æÄ­¯ŒÉlÄ…†ÔÌBÊ‚'|xA~>xR©v YÐzµ[z>=ÞuÑE|òÉÒEI¡cÐaH§ÒŸ!{׎7ò¦,ì=õ=éÆT†Rè̃ H!@ðE„‰³ *Y’k5iÒ$ìpy;ø\6·"ÚÜBOõàC"‚É-mBºH«pƒiᆰ“K¾B2ýÊÔõ}jq·]8Š÷b†sýýÛX•,Ér|ûü’3úwª2ç±™Œ‹K³Â±¸¸±g#êU'–œ9AÝ|= ÀÃ[ÅOÖæê.X¡’h4°Â˜À ñ¯šeó¢7=´ýÙ¯œ¢iï%Aë4ý¬xÕGpC·•ç3ËK¹½óS‹„ƒ uøQÉq˜ÀC$3OÅS-p<·Z<Õ–>»(]z¸vëè+`8³'}Su—»¬IœæÍ³¡äs,¡­ 6Eýäù}X0°½½/È©}?zvï­΂ÉOŸÛw¨Ã7bú g”Wwx¿úÏí½Èôü´Å“2Ù„ÄÞùò<õZ±IzhW×é–ØÑ·ÏqÎal»qW™˜‹ Q•܈.¢S" +™5 ƒ¬dòÇ6m:Õ^«êÌÚË?ÚÙý­[ž·ÚMi6ãÔ\×]oÛû8-ÆXÔÚÝÝ¥tß•¡g§R–Á~xŸŽ½øtOeu°u|ˆ‹\ɽћSé÷_2=ÿ­MM~ÃöcoDɰ..ð‡êŸ{¹é“ï nBrÏ8¡Ð¾¯[9ïŽ7ðFGz—/D/Eb¸ÖzÁçg¦Ç}¾'˜Ÿf]t~i#7-íH5þ ~ýê¡-(9/Íú75,™¿Ä±­ÃÀ!€­(ΕÅ:|Å#;¦¿2GüpoÜ8©”㡘ØQ >÷#¯Œ±_$AëÈb´æ Þm Á¤Ü¬°§›DF[ñ„Ò ­¹‡=³j¥Ü~õÙëž{5Çè.0ÔØÔ-8Næ,3m¨~¹5m¥Åcð§™ÑÐ :“Õe­÷ùÖ°zØ’N—çˆöE”í5mÙæ„A‰Û•Pu§/½¡g}¿!aIæ‡Äõ©ÖnÇCä—¿üå׿þujrÊ)§¬]»EW3Qy_«öÄÎMŠÀF ‹O(å2 ¡E£K3$Ɉ ls`$b" *7¶3(Û8ÓÛ4¸‘ŽvÍçWöèqÅô†r¼4Èæàz eà\ wd¤P±Î¯„yÚO:^ºÐð)ˆÑá©!Wx’…€ë˜@À-OÉE…©¯@¡^@ÐõçSÁÉ•:(Wyz¤«˜ ñ”AY£‘§DŠ£=…­øò×hR‘N ÐÉAµèt]ÄŠ¥Ä .¸ ¨¨Hº.„¦ã¸ (j4<Áˆá€Ä!>È…tÅ÷Ú÷èÕ`dHÂB…‘zdA˜Îg¸ˆ$"&ȯÀtK#@Tye Ñ& ì|(D˜‰!}Ù¼;•”ž íÂK õçJ)BÀUä§È,ÂEëQ´”.R×÷£ŽÛ…£x¯ß_½p@.M'½¶Ó7)ç°rq$ïošWûÊ6ßšªvN¢tZ7œ>ujž:× Ñ3±o0~pñv5ɶ;¶»²4[Óé›ü6s­oi|©¼+´[ ³ÑŽöào8¡”fö+ñä{M§ŸGlÂÅ÷1)v“T$Õ¬à$V*¼0Iýa·)®ßÁß“_ïáßÑ¥¡¼í²ÃÎìäV°õãÏ*ë{¢ooh`Á€ûÈ.oÀƒ\ÉûßÌ$'Eä§Ùø“ ;âyô¥~ŸãgÍ̃ùþuÚ™ #o ³y–!(ÇKeŠG¼¾šÇ¨Ð²e˘—üss}»ß­ø:ºóJÕÍëÖ©€Ó2f0»víbêÃÌæP¥jJ¦›/¼þæ1¿}tµl¿1àWu~7¡«Icóìë¯ÇãîÝN“7×Þë,Zar©k;ƒ5'ã„ÿjßlJ/ÿχjIÑNMºúO›¹ãoÔw—ççž4'û­õ5:%va^¯ÕdÄuk;çEbslhYê†Ó@Óê`Ç[ŸÈÓ_ºK½­îè+Ëu½µºƒìóÖëÊgdNÓévÖvÝ÷,Ï6?¾öd|oá·ŽÈž¦^ÌM‰ŒÄ\¶C̦W®h"Ì ™îÞ½›Ù$·xÙ?ü8õ/Õï[ €;ãT‰ccØNQ,μ§9q5ع=h5§O ÷îïÝy»1mŠsò¥0!=Ðø’sòe±H_<ØaJ›Âé¦W£¾F×´k5‚ù3G<‡JÓ+Ó\.’Ñ—už¿NÝ 1ìX~vWË^ƒî }æå…áüD¬¦§,=¾l¿¯ËåŠaŽç´«›³Ü~\ZÆÂ1](¦$ôæ¾pܤDÙ±>ÓRWféryJV¾§WK¿k ÒqBŸw(«ªªð¶ö®YÆ„€ÅáÌ„¾-š³(ÒhÎÀ^èðÀ ìEÙF3˜ Ý›\XrÅA`ä«7QÑÉ(0Ÿ–Î'7pÉÕæQ2T!h5a¤#Àn3gΤžbbC*I€€ý¶ÔYŒã¸õHŽ"Pï© )Tì€[MÑù¹…€7‚'Ã+ ©*Ù1J®Û0qÐ *£Ñð^ =ÜÒD’˜Z+Ò磡§1Eùò—¿üÚk¯ýîw¿£Û¿øâ‹x¨üØÇ>¶wï^:]”^G—C²è¥["AG×Pô4X²‰TÂ$y¿öÈy‚*B ð'ò‹¤€AëHgjJ\ëÕloçEHûà-4^‡x%”øÅ#]ð5XIœÖ@*ID^¨°Vg˜ˆóÑ ‘8È A[@x¡¡2d‡U*¼-0î¶ Ú«±M'­ÿ(--å]#w¾~ðŸo5°[ ômjžK” rihÝ»r-]Èð5ô³ç+Ù‚Ç-ÇËbvÇØ/Ý¿•x¦CÝ{.)£u2éÒÊjéUGβÀâ<Šýß‹•çõ1A"hWкX<ñ?OíÁÀBË•Š¤Zàל2ߺÕûÛO˜¨jô”#®gü»“¸þ'£º ÚˆÌê}móK2s]‡÷m$s„@dÙŒ9ìê*æx|X0|í¡ì= GcEï0Iþq^úãW°lï ä8­œŸyýéS^Ü£.Ù.0Ðr"¹©øà›Üx©LîŠßË+y«3ä_´³3Ñê6µ÷© þ[z”»Îw¡ cœxâ‰(ÌDOð»Ãìš½ô3gCöúÚ &%27+:Û¡ÌK”>é ËhÁYã²;)Ž>s#F»Å2¹øÄäÔéM8¹ïOQ@ÙK[Ùä ¨NôõiV3&å;ÜÛ¾{ÎÏn3ºÕØþ>s$.w„•hìÆl¸ýœh麘çûs×ýÏî uÜr¢Ê²²†ÌM Ò­*xç²àú¡²ÕÓ®…Âñ.ß|d8bk»|àßûì2Ž$0!ÆÜ#yãîí¤c ˜3¥w$NÑ|ˆ[Ûoœ’¶yT† ]$·#¼ZsT¼›ƒ#Ä™]È]qp”^ZGº·æ1GÉ…Ö ç·¾ª¿Gýê€ÑõÖ­J£˜3gúªž39':J/øæU¤ ¾£Á]qN.;ä/~Æ=cÒ¶Pþ䮀®)jŒ÷t¥fG4‹ÚõõñŒr«—³:·†'L+ÈŒµÊ0'Vdy Cn£ÁxèàA“ɪèÞqØ,õI]GÓ÷Üsf„¡¯Ž†ÛóÊ"‡XŒ2š&Œ:6ˆÀ*‘¨ñ0£†bƒ*á ØqêLò¢Z“ˆBŽî­Aâ‘P Ñü݃ÐW*C‚ðIã@Gí‡4EpK剀ôáÛ”tà?L~Èj@:Y¤†`(PRR)¤ƒŒDìžÄ›Û‘Z†0€žºQax H1àiêöCßô@0¦ÓN;í§?ý)(3ViO>ù$½‹ÎùÔSOwÞyl£±¢“ÐôCú9}›þ9šÆJ#Ðýà#‚9nÉy%Ov4ÉS¦^²é[êަ`œ… s¡æè!-À-é|CÄbW¾’t"H«|"¤ eå°#EÐp¾EâÐ'W2Û/Û…ä·øïíêè £D°“îÉëWdõ£cÉCÆ¿ùÈŽ7ª:åjùÞæ^o ³2Ë€Ä{ú·Ú‘(¹^ÞÛ†ç_ô©9ÅŸ\:söÊqŒ¬I¯»|Q ÌqH‡ùDÉNëíý ¹dê7«YoÒò\–ÒlǦšno(væ/^?}z^²ã”Šf7“ë»ÿ~HÅ€J¦nS-pܶgÂrÊÄž&"¼ $‹³#€¹©-†;c^gxoµ'R¼µ¢jÞŒr£A÷fu",ðÈÕ'–\<¿øGÏThµ2 ò¹Xðð7D²“Èq´ìD<‘M‘kΕŒçM?ŒKj|R‘á[`Tsõ¸¨Lâ»Ê`Íe ³³mßêÕ¨]nQ×ísû#=¶â3æK%Ñ4ØÊ#Z‡gO‹ŽýjýKúÞP´#ÀÙŽ ‹)Òä Ò5s>^8²·ÅM\Öü™G¢Þ¤¥½²©ïV­:K³ov«ì?ù˜1òÌò¿n:€%é­ï¾jºµ­=moSwŽÎ0Æ1S‰NI4;["ÁXÖ ŸïÞò»>zѼÂï®RíÚ¾òàv†Ìf·—“þP4Çe-ΰ÷ú#ÝþðãןŒ|)4?ÍòãKæ"ZeFaRH@Œ¾Dû /15ð(u›j÷¯>·b2ìøæd"úö„sI¸áh’­ë84gÍáS,G§šŽ¾—f(&—FàšrE8׿*+c€ ÛaºíòyÈWm§ ×.+¹î[IŸäVѺ˜^ Ø MÑìž qjŸv=­=a§%‘—µ©ÑV §OÏ} ±Îi ÍеùÆ’¢<Г֦êÌ )ÀŽV³¶jÕ*¶ù G¾Õ|äÇŒûiÆhÜBƒ> ®ëxDÍ… O©'Š=·Ô8[v°¢öS+ÒÁ¤P*h´ÆàkÿK«@L*|[@¤CÐ:ÎSf®ÅA«ìpc´7îÙ³géÒ¥@`ªåXÿÖri%2Fßbt¿Ñ39âk ¬˜GñÁxNæTr`4"Œ|1¥dÉ(ò…Š|!¶ÌÍ@ëxS2’‹L¨3$—¹F|H.O‘w®`wù B+±¶‘§IGÿ¦)Gjñ²]ÐêƒSÐ:´˜N¯ºzÞk6ßr–öt˜HOÿQ‹J³îüÂn©lQñ¯+Z9er˜\ÉXvåVÃøz«!/ÍÒî aYàðÜWW¾^Ùþ·uøÂc»k«Û_ט–çd+³¾åo;˹é–Lûaƒ8¤©ºUU›ðbþÄŒõ‡TD@Cëp‰u Ý‡ÒÇÎ%Œ†ÀþJ³ßÙ3›\½T<ÕÇ >H",[©°&PãRùL›´N­ÆÚ®WÌw7yˆcB„?»S§å€+bj÷“çöÂêð÷þ±Mê7äÔi¹8÷Fâ_\Yö§5ÕÙKŽËtò”DþHûëÉ• C¶ÀØvã¢2‰ïªXP5}²aâ?UsÈvÓÛ½˜Osyòº 4L\²3]‰°Ûjö†Bé:‘Yàjê Øt8ÙNlí1¶y»-̽(û´g¤÷;nP­o˜ qŒp‘H(ÉR‡“º.Uÿ{]u]GÃÅ9~³fžèëëë;ü™ú`†>ÖëôlñÖh"d é_ßf™Öº·ávLþvûòêt*“ÖCî¥Y®G¶ÖuùMýÏ.;1ðéD]f}yo;ž ¥P†°£@ë(bÈT'ûª†|šJLµÀûÚøgþñˆÞ`ŽÇÔ©*a€£IRäÀèBSzš¡Ýl¶¸úL»¢y¢ìϤ^XâÚ勈|MËŠomìýâ}Js®Ûh›ÐãUâà-Nkš+¢÷$L“ϘÚðT}š£Ö›H4w÷9õ K<òíG6Ÿ™é¶Å•}Ý–l}dß¡.μôä=‡«§—ŠŒ¦8qâÙgŸÕ8Œ‰¯q&ÂîN™p Q£3¾ EsE[ž1cÆ€Œ¬… 3ƒ‚ñ”èñÉEøŒ«(á 7¢¨óTÜc« £fà ž"Q45á>\Qû¹ÅæŽ[*C WJ€kxNŠæy© €O¹%ÎUP p@P*Cv^ÀŽFI˜ !PI‰¤®©8º ¯®X±âšk®§;xð ¥HÐ9眳yóf6¹ÓùùncõO'”KÇ>º‚Þ¿\È,"ƒ¨ò.lÔR2D gÁ<’]®DØúŠH Zø½$× ™’ò‹l HÇ[#éÒ$Š[Ö±‹ž”%8»H=ð$èJù8$—’Šy Œ‹íBò[<½ g; ÞtP§÷£!DâÔX–ëH¦G×໾– ½/ìi)˵»a ö·xFØ fË:ª±µ¾ØŽ©Ô% ŠØ „Òþüî:§ÓlÄ`¢¹·÷7«h€úžë°Ó+’ mJ|{ W¿ Þys /_TLÊ—ïÛÆõ7W.`øüÚC;7ì¼réD¡L]S-ðAl:lc·ÔvŸ6=ïý°­;Š6 œNÞºj") W-ðåûU·tÞÞ»ˆTw¨P A>8oT©à 8 bÎîõ4«±Ó¬ïòaf´drÊ*¨¿¥Þãel»ñR™ð]uÛCãÊ`“{zBñÊk®,Ål¾åðö7­AüM«ͯE­sÊbnOá†Ç>Ñ·(sW´(èïlóÄZýú@"bèŽgX;=¡¯>´ƒŒ¸¥»ûÚ•,“2S<’…d€ƒ¸ßºìÎõbd6èt{±š˜žoûËòu몳ªÜŽ4 ,ŤWVåzƒñ´°/p÷ùu®G§ÌÍ>°½kâóîiq¥÷¢ï¯þ4Û§ô†½M^WœÁ–åoê0µlOrtË—N-““ΩÀøbðZ §"©Ç`?¬Âæ s–Ù–g-:ÝÒoO—6ý³ìµä-eû¦M›¨ªNÈuŠ7Ñ‹xÃæ)ŠÕZ”‚äÊËJ)¦¾í¾„3²Çæ§© Š~Õ§8¢ías©Q¿Ð†£¿Y}ð+gNN¼v[»úBÞ¨a¦ñЙŽ½9·¤é{w„ÊNLw[lލÂÔ9Ø%·ôhãìBºþú믾úêo¼qË–-¢¦Ž–éÈò p@ç>„`dšš-ˆ˜d‡‰Bƒ*N®–zƒKáô t{ˆÉˆò€!cS!(¡ÖD ªcu ¶ZA@ @`s<‚¹l “\èùìà“ØæP apeR)©8Š·ºøâ‹Åf“ˆ=¶œË—/ÇæŽ¾t°=üŽ[ú6Ý’N{¥ƒ,ÈÒ¤íøF R®lWç€^–—’tÐy9žbpÝÀìøD€ÁÉGÜM>2²~L¢àéЂªË§Ѧˆ°H1A ¦µ²KwpY©”±jq±] òì¹ùÁÓåŠòôÎfL],&}(âÁpÁbÔ¿+ZGö>ƒi —‚ã¹û7Õ×txÑ>8ÛqiY6n@¦¤½§ö‘ʰýÍŽFcì^bZ¦2x|{óêýhûÙN [êØTÛåU>U5Rq»übC[=A´bñ¤Ã*=– ð"¢æ`3ˆe¨¢v³‹\x,aO’Z€¢ ¿ËOhR×T ç-€ä°7ß ‹f­bÁ€¿®½ë© Ñx\v£SÃk–•úB1ìé^«l;}F>gÈn«ïýóÚœWòôË÷oå)þ+ÿ¸¦š¿jq¿iÿwô(òý{Q™6lذhÑ"& „þ>Þ–ÁOüœ¢ ̹O)Ïåºäío}rÁ¡Ž·@ëHÑÕåY‹©çñ—µÕz.Õ^hË+4šl£Ãb8sza–Ó°ã°àL xêaNf5 Îùâ¤ài®ªÍˆÄ‹2m 0YvóƒW¨Z\0a·è•:¿¾%hd3ó­ÉÅùE>·ÞgÐ[¢a‹íþ–ÓgÎu©†Üì\º·»ç®ÎN\+'”DY®“ýÞâxr€kÆ3z¼ü ¨Oê6ÕÙЛœñ¨_Ð:ÁxWp2hq± BÄc@)jÑÕ@IDAT—¯¢uŠÒØÔ4ÂuÇÊÐ:xÎËõ»L1¦°³‹Ò\6Cþ)ÍÖþŒL»¹P¯:Ëÿͦš?¶yⱸ/nØá±å[½VCÜdˆ§[<3[}lÂ/˜m2Û1­PJêv”-Àºûî»s‡¶nÝÊЃ¾šlp7Jæ#É.5a$ô€tXÄPOˆÑÃÑ¥qÚÅMž+¯r!Aà³wå‰nO^ôsò¢œK}ƒ¤Sn^^ª>”8£ÛK„ZiY`EMä\Z®ð×p‡w­LŠ Õïµè rÀXàtÐtQú*׳Ï>[;žå±ÇãLÐ:(‘Ž÷ZĸӋ¼ƒ•¨ŒÝðb%ÊWB«?q- ftX¼þöÎL®ªnãÛ{ÍfS6…dI$!$@ !H5A¢¨4é ¨| ¨€¨ÀPʇ¢ "M:R“é¤÷ìf7Ûëì÷›œx¸ÎÎÎÎì”òÞgŸ»çž{î)¿[æþßû?çÊhs¤ndbL$%#á±×dh³R äŒï‚9ˬ9S!/Âk†¨u&ïŒ4Ô:Ž ØÒÿ¾v†×ôÎÈEܳÌ!«Ñÿ”À¼µ»vÖµtì™’~p ÆJwâ5l$ÿÏS‹écDnTƒ1YKr3þøÝƒy›BÂ{éÓ-ë+ë+j›p;xüÂÃtÜþW~e$æÛÿZy×ë«Èv`a]”~õâR„B6s3ÓЫ[Óÿ­;ÈœQ¼^®?þ{ k&(³sk”eoⵈ€„Àu'îorÛZÝÀ‹£{X‡Ž¤Ò¼Œo2_"nvzr·~m‚Ûïõ¯mDµ7“Õ,XWÉÿÁšJÔ:vÝòõIF€™dM†ZûO 4‚]¯›LŒ6E›ù¨jØëªàA¿a#Yºç;mU{û”Q¥¯ÝtÌÑûõ›8´Ï© jjm_¹£æ£õ˶Õ8±}äɯÑ!û”ðˆw«¢œ r§d¸{¥'ó‘)9+5©­#¹¦#‹P&`-(Iw¥º\Íiu®äƺ¤75Ö'upÝ”m:{䧤¹o)‘F ÈcMþšÁín =‚V,$ÆbíÛƒ)5Ëý™—Ù¨¿ØøîþÌÂWh¦`kij̪ßF&ÓGúá±#S’’S“;ø.ͬgO\}b¿Üõûå\šéž7½¶)£Á•›Ÿì~4¡ƒ¸³Ð:/¿ü2ã@áB–˜¦ØHG}tè²÷•“±·1‰ñsÁ„6F8Ü8†f:¢VÄS%Æ72‚jF¾¡‘Qa#UÓÎZÚÛh|Ý1Ž9äl.×®jƒçùs¸±Ï £n8ÍEr Cº²’ƒY0w»L€L¨‹i'A˜>°f ¬®JW¼ô˜—º¹YŒZ‡{Ïdü7¹ø'OžJÔ® ôŠïÂÖÝîW :ÖcʊгŒãæŒ?Ía&’Ýyæø_m,£îd¤¹%?|¾}Ø—}Kﻺ۬L_Ô]õÍtÂ]_YÇØsø=ðisî>hˆÛã¡0‡E¼E%3*7}W‰9a쀳'»ïËc=uXþÓ—L!°ew“ —mÝã=|tè±7° ]àÖÓ$ÀØü¬Yþ÷›añ÷ËSÇš­E@BK`D¿øîÚÓfŽ”œ–Ÿ;úêæOïNnX•’]–3ä„â’²û7^÷JÕI®Å»«øóÞyæm¨À7;F?åÇwëÖTV^ÒêJfxsñy…{÷¬ñæ­nh¡·öÀ¢ìKž­þúÀ>™É;ÒSæ1æHR[vnŽÞ¨yÌ1›þ•œÔ1fwñêäöÕ­Ie…î«ü¾{ÐûÏ|Aà©ÖZŒ¦eaØ{SÜSoöZ®"E@ ?ê¼Ú §¼W “&MbjFûZ·nV"ÂVñàðšžÈìÓ¯:ôowÍKÎJOkmËd^³ùëêí›”êê(ËjINIݸ­‚Lúô©>#»½º¼6%«#;µ}éÒ¥ÛK/JÚùÀ€´u[ûW5—ŒN­lªiBAÑH«]ÑîY<ö¼1MYÏ™3W;cá÷,·€ŽÂT&=~pœY¤,pœ\ºèŽŠ‘l”D.6v‘ÒÈpô_ca«;ÜÊj˜ú8¡µY›ŸMÜBY(ù]] ¥JnEqXõø-Ƙ'sPP7"é ÇÚ {Ob.ZbXÃlÉÜdš…¢ÙËñÐTÒV̤ÑZ‚$ÀAˆËÜ,\]te0w 71èÚô„¥ëÆ'Ÿ|B Ïm¤.¬¹,MÊ +ªÃ©ï»ƒŸ0%’˜†£Kð]„~pûó‘ 韣XˆáÖ¦ùÜ• @äfá^&Cn|¼sÉH@zÖTÀwYÚ<ã»pÏ=÷8ݽgΜ|Î>rXèöŒcž:Ö­mõMmŒejçmðq ÙÅÐT ÖUUÔ530óTÞóæ*¬jžÿd3®1&MÞ§˜n³ÂtÇqæ_[ÞÒÞÑ7/ƒiñl~mE=_19– ôXãÑÍÚ£›-B!‘ﮫ]ö·E8.ç¤õÉÍdVÙa%yÌà‡:0 Ð`¢ƒ†‘!á~ùÝÜ;¤Ñ""Œi³½ô¯ ™Tbù¶ZFcÿ«µ|kͯ­4 Öãê,ÆUö•;x¼»ÚýöˤÏ6ü$Á®M¦K½ð‡%I-µkF—ä§eÀ)ú»‡C Fêb,†ï”<=¨jËú§ll>¸=½Kê!-í“úöŸT‘´ÿÆÙ|ºé€ö–ÇEfxñ{ƒèV”“þÔ%ǘ6éƒíjMkhÈÜõEmaNêè!]úȘŸ+¦s}à;ß÷ÖêO6îþçÖ äspÿÔýó–®ß^YßvÏß^Ø{nr³û·e¼ßÜ£³R>j(ž6ý;e%¥ìšyÙ8÷zÏx«fØ{DÉm»›M%)bïáú'"à –]Ú•TgRá»1}úô÷Þ{M<Ý0fðzèv(®‰‡}ë/‡1«Lã¼l®nX–áJÞ[>ª=%+)#'w}MGËöÚÛ¸ZÒ:RÚÒ÷Œ„ÂÒ¯½cWÚiXÝ’‘69oçàœ6üG¨€£¾ Kà?ø1€=ðÀÏ<óŒ3&Üac ;ÍuÔ:lf”c<Fó²ÕÀðÆ´æ d/—W—"{‰gíC†è¶‡¬õ 1c‘ÞÈpd‹üúfë€B‡G=)™v/šcÒcös,14“!í|(†ÎÃÉŠž°ÜFÉâ@®[n .p;Ðѽ‰õígŒ71ùe]ÈD‚¶GñdÈúÀÁ¾Æ)2‡k-"Z׿ޯYi©txǃ±ûùÂ%–R÷ɹâo‹Ð(˜'ÓC©@ÅÃçŽ{ÜÜæ$¾bÆHù.zj‚ì¢ÄdºûH÷³¾xüI¶ý¨uEi»÷_[™D7Õú£Ò5ÍE‡ ÷¯ZAÝÜö$WvÉè¤êåwOß’\6Óø“o®v_v¨~6ÀҞG¼Ý´?Š\ŽD:Gl=ºê¯¯¼kS7yìЂêÆM©Y¥GîZ›·ó…¤’óì^\ñæÇŒ Vö]î6™Æ .üðÙ5“¿VîL¦°ˆ€ŸÌ˜Ðø7aó j¡ÄŸcéÎýø“GŸïÛgíþõ…µ©yÉíIÉU®¢æÖÜäæ6ºy¤¸ ‹‹¦MŸ‚áôïW?®¨ 3lÝ7ÓKS›²Z“òû L¦8üž–-[†ŽãO‰JÓ-×_gÆn“…;f3œ³,dÔ·®ô5®:ìgá@ÂæX#@p× Òö¶3Og} '>ƒŸNWB°UëÝ苇êa„c½hîNãµE·œvàOžù̪uÖôxùÓLz,‘÷¿¨ôG°#=‡3îö¢Õ3Fõ[¶­öÕ%ÛþÓi7í³O0FYÛÊàwÃß[ —÷)0nH‘g”!̱-ÕM¤g¼àìŒÂf¯5µlbD@ÂMßXnpëÁPY×Âð—xÅÒCö¨ýûñœáV%G5†öÉþñqûY·;õ^÷àãçf°‚]”˜LU™ÅÙnÏ»0ÙëÎÝ-xirwEò\šÓ¥´¯h©^ÎŽ”Œ"†H0)ìuuù¶šûßZýÕË| ·jÕ*32|ÉÈ‘ny®ób¿±¯ø@zôAå `g“µÖ|A¸½i'ëô/¯¸î[šÚž»ÿ³œÁîáðX*7×½tï§Æ ÏÄh-"ß#u•Õî¥|cÀ†ºf×Ð}kÖV ¨©ËkNNÏHosÊÂC`pæ²àØ_]_·63%;«#¯&9¿2iKYJ[æŽun'p4ce™M­CB`êÔ©?úѾöµ¯…$7?31ÝNIŒæa„ã­†°…âFO4 f+œ‘ãÅ–Hìj$ 錌i¸ÆïâÉ8?P~n›÷«ÖI˜ ÉŠ°×…ΰ¦.ÚœSn£Î NG)Õ°ùàÀÈ^’ß=gЉuå³)àê2:µ•êL&lr}"jsG0©ÚÝfÑ©¹¡ðñärõ!g÷ ½{ˆ¹­h#­sÞ¡¶Vtzå.æ‡Ã¨u¬¹—Adî}«¡óp€ñËs‰C ŒŠÔN$²úõ±TCèuß…+¾2rö‚¤?;ÉÙ.£Öýlæ¿ziÙÆ]î¯þ^—ÒüLl lïü¬4†ýÙYë6¦ÌÈà&ýaå}>Z³ ®Ì?ÉwêÈRþ8vÌ Âo<˜ ×Û (DAwÌY¾|›ÛeryŸ ŽÜë‚0´(£Ü¡Ö™Øö¼ýÅüuU¸óT¹?r%‘ÕoÏÜ;RI£µˆ@$ †sž‡cï¬ÃöAš7j‰yàx­Ig·;¯Ééƒ@°‚ͺWL¦ºµÏ.ÜȨ½•k+ 6ŒØû1hOn:u,BïÛµ‡~¥àÝò¼úƒû7|Vs.Ôéýê_ñ;~–RÒ³:\-Ed—}yŽåfÔ†'j7Î]±ƒÉ^ó2Ý‚ßÄòüw—¸ÇM8|”[8ã½Çªulf°}…ø,¼r¥»G7/ Œ¢e {6Ó öÍ0µqÛ»®ÌÁÙeGÓyù×CKñþÛïÐ#'õc/jëUów˜ÍÎé#"(îJÁÂÁ1_{‡Ú|j–þ¡©bÁÀ´¶Çw}eqCû1ý×7»mjÈ+Hsa ì_jÙº¦jwö†ª¬äÌö¤üŽšüa.×Ö¼´–¼‚Æy¥YÒ66¶¥ H*¿Ùæ¬@ |þùç‹uûí·Ïš5Ëx´áq†cNóôó@¬k¤.c›C0¶¾“Ö„vfhçF­CÔ3R…3"ö6Ž0\ŠÄ³iò!g›¡é׆Ša#dãC°3j"µ©3é9ÖŠîÆÒmÇÒ˜ôÔA¼6ÆYy…E œ7‘ópî.3.¶Í›7óâÄ-ƒ')Ú1—=»º:Ê™CL„QçM=¹åi#aÍmÎô4œÆ{Žæé öÉÀ#ˆ…4¤$@~Γ'‘,&à‘¿)]ë D‰ï‚G+p\ ÛjG¼×MÛMýŽ«nŸ³|æe£Ë 6îrKeû ȯ۹׳Ûk]Eš íÞ¿¿ û¸hú¾MOÚÝØJÙ~{êæÏQJ#"V (™–š2ª_žߦ°V Ñ2ßÛ§ÇÍÆdⵃaÊ”)˜L¼F°˜iòzœ§Ÿ2uãÊ5[èŠfÓ{Øi|ÉùŬ¯O™ùÛÓÿÁ­°¨YÆ&?Wœ›ÁE†ZW°ÿù¹ÃN±‡›¾ð¾^WÑÀlG„ÉŸÏá…MF­#fÅŠ¬ÇŽëÎpìXCÀëbÔ:¼ðÌ«ÕgŸ}f“e ˜BÝZ‹µ1^Vž3¾uk»ò´ˆ€O×W“ ^ fô£ßٜۛ*š+²™]4ò¾Ÿ5Ž™³½<'muNJmJrJ~n6Ï“¸jk}sÎ6ŒËíÄ`"aj såt$vT¥%»?q'·l¯Z<Û¤×:xHQ·Þz+>hW\qÖ¬Óû,øÌ»Êë×ÃWÛ¸«Ä6ÞØÌfùÌÆ›€ÙKï6Zd<¬)î‘’Mãa俟Ù=±s¹3÷ãl…ï”Ú+=&À›$—·«Ž{aÀ€\ÆD²«ÇÙFÛæ~ôq#“ÀÛ\½zGýó‹·üòÅÏ¿ýà‡¦#j¿ü. ê6ÃààX'µ.xŒÊABE€YžÉêá÷Öýí£ ¯.ÙŠ›‡÷\’1(ÙÅßµO}ª¢3Ÿ/Õ®`Úß+&Š[Kû^ÿì”d÷Èt|Tì¶ínϳ¸Úö8Xÿg“ÿõ{¦1:zTé·†ÞàâlÔ:Çþÿ âÊÁ¶Yÿ׎.6xÓ2R¦6¨‹T~E·í™ʯ¤J$"à“€‘Z|ÜÈ<(\{ä•äÔñey7ž2æÀÁ}Ñô§ËZÖ«ÉfßÔÐÚ‘Ô‘’êâ‰ÔÚ±÷éÊtÏ©©M.×^&9Éíé %xÌrüñÇc‘²0ýî[o½õÐCa&Ÿ³Í‘‹ ƒ'>IÇãºBØôxõáãftNìàì'k ÒÀ>§Ï)åv52•!~ $cMØÄ˜L:¯Í^Üjp g/îuüaºsb¯1Vã#Ðp,¦t¯‡(R‚'€,…8…NÇÂeÌMALðÙFUŒÍG}p¯3Ï1ÂýÓyPЧ•x|oéÆÁÍHb>çx”¦E$3")í=kÂdnÒðáEÔ„µ!^ô]ðÝ Ló7ºl¯£´ïôfïƒÜŽ ƒŠ³ñZ0Yø0…üÉPiD@âŒÀ & u?(Þ\¾ƒYkLë.œ¶¯G3o}Å=ø ³hÚ1(MŒÖ¶K,&Ó/ùË×^{Í”zàb21Hp@•ð˜·4óùñ¿øÅ5×\c§fö1 ý“ î.«©¹ÃYw~ƒ±‰m à€ ¬{KzÞPo¹{&/çªB-Þ7ïØ1ný¸ó‚ÝŨ"|ð_}±[Hà´Ä:§ç ‰n³ÖmJõÕ-Õ;êíç)Ew>ÖÄ4Õ·fåº[jºÄ¶Ï1bvQNÚÿ™P©«c/"àƒÀ¨Q£Ð2ðÌÂ4¢Iyy¹3= ž6íÍ»Új×޹4»asnÎ'I99E£ŽÛ\ñ‘3eniJGZkVRÛ©Û“’ÝŠTåŽêÚ–¢Ò”¤ìŽäÔäWÆÀüQç9R¸g.¼ðÂåË—ŸwÞyp€Éqåî¸ãŽÿøÇ=ËÐã(žØO?ýôI'äo6QèŒUŒà‹~Çç"~ª:§¤£®QÊŒ»œ§9Že/Ýf1È‘'œv;W‘ù¡á§!ÏxÒ'×ו´g÷b®÷s>­‘¿Q¨í”‘Ôœ# xÔß rçæpÕA0Éh>µòÝ" µ)þàºå™Ìbõ&ÿ¡”ÜA܃LFŒ±zõjnRüˆÍwóSEÄCœ›—MîM³6-€Æ&7)›x‹æ‘Bæ>¼ùbZtVÕø.\~ùå·ÝvÛ}÷Ýwï0¡ ³Ñw&ïsã Kpm“Z&ÈÊVbšÀeGØTÕøîꊺ¦ÖCö)?´M£ª¾ÙǬž1"•V°ë]“)§dÿƒÚ,ø<©¾±977 ›ÄkoÜÖº u«ÿM:ŸÚuWpùeúÕKK7W5½¿3l“%·U¥/Ô¹¼?!رËQˆ±);è ËK§1{öÛo¿÷_S_ãv\ü¯&1ýæ^<$;ß-Éy,£&\ñáÖ7ùrŠí­­ïïÜkªU7´¡Üé×Ôš6E ôiåÝ[hÓêy…MÿNªø ­øì´ÜA6“¾‡Í®øðÚö–];?øaFûfgrçäT·û~4÷oûo$곪 ©É¸C¤t$¹ýéè0›Ü–ÑÒÑR•¾ë]SV·1zØ!}ZÒ²KmÎ Cõá‡öÈ!T‚Ý«¯¾Êsûë_ÿúé§Ÿþøã;KÁ æ¼Cû!Ì«teÔ:ŽõSÛ™ÈY #áA?”ÂB¡xÉ!zÚLÛÑZÓV¿¹hì•é…nÙn„ o½ùj]CÓg}úä6µ7WŒ9ḑóW¸:Z’;0™’P¯=3³_AFRMÕkŸgrdê£4Ý0“Nt›¬Ç ˜#õ‡?üáÍ7ߌ×Þ¸qã>ýÔ=áÏìÙžãbEóÅ…]ÆAÆ£8 lã5Ýë;`Š 3£¾ù>Ðc¯© ’»Ì&™;³õ8Än½€M>s,ú©ªøS„-«gÁïõ† ¼V£«x¯‰{"¨X4”Bï„àÏZð9øsš"PŠ×ÛéÖÖ9°ʹ‹¹+ÙdM Sʰ6{íÚ¤!žÌŽ{$Ð*½ë»ªVØ|~ÿ­ ³_Y¶jGýÁû]r”/w{HäÆü1å¢ôÉI3SÓF¾&*QD +(éü1 µ™ÖfGmÓOŸYBâú†ú7Ö/¾ó›ã»:Pñ‚ì¢ÇdjíÚdÊl¨³Í®Z³Wù²1~èìÔðŸ|œ™W;?31ÉÌ;YmSU[K3[åÊmXåœÞÇëà'~9NVÍœ ò©ßµmMcE{KcS›kéÊÕy~}óQD@Mð‘8EPzJ‰@‘iHdJ‰\]ÝæÉíµîÛ?#)¥/<7V¦tì\ÓÚÔž‘“ZVœ´®Öm4­©Èou5×®X‘’ÝŠƒ]GÙ¥t¤7g¸:ò]MiÉmímüD%%I°a ,>ú¨©%.{v4†k¯u»i³ÿ2<ïðvÁõÌŒaÇWº‘:‡5Ä„¦ï*Ÿµð‘Á+Í÷à &çÎk¦92®p8Ú9^;'ó'Æz…SlxjÎOÏðáÃMþÝæ€“Ž×Ò¸ùxuÍëœÏûɪóÞÄ„¼ˆ¡C=Í žˆ\^ãCÒ“IŠˆLCü,%È #äçÝë© S)ö~dNz¼šÛ‡‰ÛÎÖ„þF˜3ã«ØxÆÙµkÒ¹q¦#ž›:£ßÙdDr¿sÃ:…?»Wè-ß…T¾s&מ¸w ‰Î»¢*Æt0B¼{äýõì¢êÔ¨2"` ØI¨g¿²‚Hº0ÎY¼®¶©û¹Úl +Ø…› o“ÉôQýܤ̂atOÃÙíµkwÖ|P™óÁ]×1îã7šª*séÅV8 }Ú¬‘=¨ö†êòü·g\z~y^p~œËrÜbåªö¾ƒ ]mµ›ÝÕ™1klÉà/U9wTË9Órž\°éþ5ìOÍÈÎÍH·¿_- Ó§³š(‚â"PJŠˆLC"SJL㲃K~øáÛª‡å¥¸ñŒâ±¹û”°Ëî–w™™šMF ›ó0.½{—ÑS'MÚç“/*ò+Ò]®lc8eegÒMÉåâ/ M$'©hë’ ‡–º‡hÕ8qŒ×†O }c9ä:[¡ µ‹]ÖðÆë óØ8lål½Z¸$øÀÓÍcÛçQ@7ÉÅ §† bpÔÐOµŽ²8ŠÖÙáóBU«@[¡ô"ìCƒ'ÃÊ•+¹³¸¡ˆ¤·;#Í9x¡¬i¯=„§¿5¨uÈÇÆÅ•®ë|0àÖÞóÓãžÆåŽd<=ˆç6g‰hÑÙ„(ñ]àM,Ü|ÂZD@™ïùü¹÷ã½3ì€ ò'ÃÎi"P…F ”aèE¦ ”""F,ämÙRéÑk@j}êž~Lõi&0-ÒÚhì|›L{û¬Í™=ýèci¤uæKKK{ÇŒýû™–/^8¡®µ!½À½U»«ñí¿­šþ-¿.s¸Y·M/N§³¹Ñõué»[jZöPZ<0wÍâŠöìÝ~ªudrü˜¼ŠÙ9Y4€«€JÀªuùí+6üûEf¸)žò+¾´T-IË)Í,=ÔfX8æ26¿¦:ó5/}wó ]sHš»s>½&Ñ2°ŽðZÂpÊK­V¼©²mø¶Žò–†ºýò’‡ì7 ­Þä£u”à<¢»á“‚½nÝ:µ5ƒCáƒÍ0sXÑV°3.-FØÂØÆ–ö‘OdvÙñì(Ž ÛÙ'ü,íXüL¬d" ^ ˜É[æpšÃ·Ž4¼×1‹Ž™VUÝ)Øyä`ž'|¨««ãÇÜÑ|±æñÂØšäÌÒèt,¼B3ÔÝÚµkMò°ó +›¾}L+®Á8«Þøü’Ú¦öé£ú…|X7¬÷ ýa}4óÜœ]äö›»×¹9¹‡•÷ñ³nä£Î]íŠ@R"P„a™‚"PJŠˆ±p´åÇ>øïµÿ¼žÞøI­ëêÒ9>Ú;ÿM¦9Ÿ»Çõà‘}Üè7¿¸ôom°‚Ýîî†ÌqÛuU{'¹ëŒ#b1£§–Ù²ÊÇ÷]³Æí.çÿrÂØüùŸ^)E@:0ûèÀ8qâÄï¾Æ0AØ3Ûßù¾™V¢aó\§`çq¸yž|6wÓ†¥»VÎÛqÀî¹é ç$Œ¨ÊM ûåÖ÷-ʘ^9q䙯¿ó±ÉaK]Æyîyä«Í¨!€-m†ië¶FÆuÍÀØ-+%€òk&‹ €¬Æ3ÄÄøÇt}E›ÃUÖôOG¡cÎh~×íÈ™lÉÍ~$4ÿk¢”á&àÛwÁYºu\0½‡\³s–Õ»aœlc^üý#õbÕ»'D¥‹@÷&—— ,̾oîªQŹלâÙs¥ûã8E´ vþ›L¦/t^fú>%îù³´ˆ€ˆ€oFgÁÚépµaÞ ÖjÒ¤g¥7Õµ®š¿™a”»ÝŽp³bÎh>LíÞ–V›†EdkëÞ!Ã]íÌ£ùa|Ÿ–ØÛ‹‡ Nvf1×€m†=×lG6Œg†£ÂçÅÌêh“õ Àt8rb´Ûîr=ÈD‡ˆ€ô #±q 3d¡ÑÔèÙŠ·íoî£VÜûÜøŒI~¿ÌŒÏÆíŽÜ{Ñìø93Îzö+ù۰̵+ øé»p×ë+©ü‡ >hHÑÏþ¹Í.Ž;ZªFQx­ªJ"à›*Íìo„•ä;™özp‹å»ÇøxcÙvóÉåaŶ]é™î ð­3}Ù\}²¬ÞÝÜê² z`Ü ºÔ}øá‡=ÎAŠ€ô3°7‹þýîûNT}­pÔyþÔgÁKë–¼½ùƒgV×T4u¸\¿²Þv•åðAƒ¥æ Z·«ÀÕá~Ʀ¦2›…{IIMœÿå´¡&Rë8 `U3ìgç€ñŒ%o[‡ ͆LÌ,«6²gÜjÌ’ÜzV%" ~&Ìá(kÈm %Igsfñš3»ˆçÞ7º²Ÿ‡'ˆ7j‰ñø&LoAs¬×låŒï}ÎÀ€P4d`AÖ€‚¬(o‘ª'" "à?øìhógdZ¾oiîÅÓ÷µŽ»`Œ ·÷Ëé;±k{ÒÂ5µ6²gœ&0õ93lþüù=ËDG‰€ô"CÝ;JÝæ´“ŠÇ_×6肬þ‡38¦ùóZ1Óvûºš¹»ágä¤O=sdZºûYZ_½×“Õ&%-;½`Ä絇}Vsø¢E‹ŠsSgL=äðQüË+Ô˜ÄB6f6Þs1ñ¬¹*Øe|ëè³æ‘, M¼ùH­ÎšÑ:V‰E@¢ûp°úZ·ã¦ŠFÁážÈ|fŠj3(ž9œ4LWm³â¡$ß:K#Žìéú»7WÇ…aêoÇ'[MH$ÑÞ%6 sQ”“Þ•ƒ´±±Éí£•Õíí®ÔF÷h‡Ûªšïõ| ¨ “˜ùþL˜0kÜùªÔƒ¬tˆˆ@¯ÀÚ™6mš-ÚŸîH$æy²scí’¹›3sÒ&;´dP^Ù~Å>¯DÅcHJ“Ùnܸюö=~üx;-N„"À§+®9'¦è,ömÛ¶á;ñ>§ïAÎ:DD Ê Õ1V=aùfŒ­Qÿlgk¾gn}²<"п 놓ÇÜôÂç´vú¨ÒïLvûcjˆuq%Øùs2vÌßîê»g»M»^_o…<ŽõH3vìX¤:âm×4ÚˆK¥Cò˜Zöñ+뿾±¡¦µŽfõ¥S›t‡töˆŒKj”WÌ=Âì« SUZZJÂŒ*…“ Nv iÇ”»Õ«W³—ÞmDb~{ÍÇk$SC’›8Xs¶z¥¤Hˆc<[íx’˜L„qòíª½/ˆ3jŽ%€ MÇ7¦†À¢&Œ`Ç…ι‰ýLf¢0ÓD²«ÛÅŒ^‡ Y!óI°ë–˜ˆ@¼@îïÖÁ–³|-`ŠX ðÀáó@¼ÒP»D@D@D . ¸JËeã2·ÂÈIn)­¡Ö=-cëžé“R3R+jÝï+mí¬“ÚÜvTËžuc‹¿óÆ6m£2ŠöwgPç–µˆ€$&œ‚Œ‘“úùVëš*µµ6¹’³“’ÓÒ:ÚÝCi‰W%%%4 µÎL4a¦qD›3ãL™y'L÷4f,Åni`u£ëÑ–”FøÓèuÝBSˆ~ˆhæiŽªÒkÿ;ò7EàŽŽR”§ˆ€ˆ€ˆ@˜Ä­‡ÝA3Ó%vÕü황ƒáãš‘ž”Ôܰ»µ_afu}Cfúžñ>RÜ’eÖž rºÄÒwµÖu¸Zr[¿î¹–êåîcûM²{ˆQí [S²ÜÃ…cÉê;¡qÓk)dÞšR˜ÕçËy«ÃQœòì]8³Ð¿uݺu(ttƒE’Ã!k³™®²Þ0›q¬cè:48âIà»ÎÛ·oǟΤ¡',½n»õ¬ñ¡öŠ€Dõë×›j?ÜpT‰1[ðÌÅÕçßÁƒ‡£å)" " "&q+Ø¥¦§L›5êÝ%»F¸þ@¸±%5/¿ßöªÑµ ÃWÿeYRfª+'=½ª©Ï¸ÒÑû5µ´§§¥öÉë†FíO´Õ®5g¢`Ôy­5_dõŸ¦£lE@"F jñlSVJÖ‘IIåá(·xüµÙ;>ªÞµ½¹µhø_ÎK޲”g¯@•£³*Â5A¶C•cܨM›6™¾iHxEEEFqóÇ·ŽL¬ZG˜œYz½ª€ˆ@œTýï߃BñÆea»«CD@D@D@z‘@7U/Ö,ø¢SsÓŠúç¤V¸}èšZÛ:jÚ¹ÌýËòºÄ6·§4·ïwèú²±·0Ç/V­ãÔìRþ‚¯¤rè]í;mÒªç&%a7CÈêwØ€~I 2™B‹5*s3]=ª&ß Ú'à|P˜Žó DÍð à—PåqL¬lf¤¹¥:åèﺞÓ"" †@JFEÑ‘Vhà ˆ@ `{÷íÛwÇŽxÐ0Œ]WR{ékƃ÷]ÚL×¹AƒùN©½" ±B€ýâwíÚÅsÀ8äzÔ×Z?p=Ô¦ˆ€ˆ€ˆ@|ð"iÅGÃhEZjò~e9•Eߦ‘EÙÉ…c/÷Ú´ú¦vמù'¼îuFÒ©Íl޾į°ˆ@ìHNÍÌ|<õOÍ*míóÕÎ iÕÄ2¡(¦;Ì/1|øpÖèqt†Å&÷8nëÖ­6l`z ]^7ÑìXœ.9^“)RD †0)÷5£^v®3¯´ƒÜuN ¸'Ïvœ¼’üô’1å»—ösµTï^rOf餜A3œ'õƒ{-¥!}3—t?OŸÕ윙(,"Ó2ûŽç&Ttê¯j‡·Ó½Ó§¸·*o½fñW[ çØUÕÕÕšAÂ’Q@D;wºÇj`:¦„&\Zª1Xt]ˆ€ˆ€ˆ@"ˆg;{>QëL¸yç|I ¡Ù="¸Y6V4ÿ'¨ÿ" "à&P·öŸ¬3ŠÜýèMØ«EzD€)bqµ«««ãh9Êõ¡D!@'YšŠZÇÚ„¥åj§ˆ€ˆ€ˆ€ƒ@BvŽö&-~}#óNlXêîäu;gb…E@™@j–Û%ª¥z9kNdj{ÐÙÍU__Ã]ee%Þvf;v¡Ü9ÓØÄ ˆ€Ä=-[¶ÐãÕk§W&’føKàdG8îQ¨" " " ^ Äy—XÓfú²™~mï¿wbRR‘ŸÍÝäjïv` ƒÜ­ÜÒ•ž<¡üËç½’R¤ˆ@\X°`ñb˜6mšG³No«ÛÔV¿)½p$a½Ú Ç1Jý^1¼éúʸuŒgÇÀU oÇâOJ#"Pð[[[M»p¿íÜÀtŽTŒˆ€ˆ€ˆ@BHÁŽ3Šf·jþޤ¤mý‡Œ<¤ÿ»O®úüÍv rwø(Í ™P×¼+_`O£Öµ|ùòŒŒŒ/÷í åü¶GŒ6E P.—‹C˜}¢óhVJ/"::¾œïÌvMS+D@D@D@BE ºÄ¶·¹’:’ÒÒS ûe‡ ŸòˆiFI1MpÎÓRå£@MMMcc#N4ˆÂXæ’í¢í©>"yyyy¶ÐþýûÛ°" " " "` $Ї þbáŽëkù[ôÚÆ¢Ù¥CÕÖ^ ˆ@‚ ÏÑÊ•+MãÇ·¦Ó,± ÊEÍ3@UVVVSS¹"ØáYC$ÃØ 2$tå('#àuüJèûŒnɼÒô™5Ï ¯)c¬µª®ˆ€ˆ€ˆ@àE°{éÞOÓwH^Ŧ:üìkÚò'õÿ`ÅîÔ”¤CGªKlàŽŽx!Ðyè:Zƨ—-5«S3úä 9¾½qG[Ýz"éY/V;"M`ذa¹sçN¼íš››Qëðª*..ŽtUTžˆ@´@­ãù@íÚÚÚø’]éÙd²š’’’h­µê%" " ".‰"Ø~'_y_]¿egcuuKFvZ»+iÝŽÆaê$® LùŠ@ì@­k«w+tí-»š¶¾•”š™œì~T6lœƒ~{íQ£†æ7uÁÃ.==ßç VQSGUDD 7 ØœÏÙ›5SÙ" " " '@cØY¶é™©I©nï†ìLwó]_ûk“( "Ð\ím¦ý®öäÿ îj«Oh(j|ð­ÃG¦®®Žžn©©©fvHº¼‘¥ˆ7 fG÷yÓ*žƽŽÍÒÒÒxkªÚ#" " "àÄò°[ðÒºô¬ÔMË«ê²r3›Ý3÷•÷×~\)J" C oĬºUµÔ®¥Å)_v™7ü´„a ††ŒƒOÑÍÍLFŒN‡“‚óNää䄬 e$"PëL÷ù¸h!" " ",Dìf^6Ža춯«X]USFVÚ®××ç—dÜWØ{ éxˆ3éyC‹'ü4Î¥æô"4;F‘ß²e =aM5˜’˜…1ª4–|/ž-" " " " ÑL ºÄ¢ÙzÊð¦ºÖÜ¢,F¯ã¬ÔV6®œ·ÍÌGÍ'Iuˆ<ú£y晬ƒIõ¬;vì ƒÇ4‘nàÀ&Ÿ7ö,C%" " " " "ßb@° ¡É4ïùµYyé̸5ã» ßùÍýÌÙ}õKâû4«u" " ˜4i]YŸxâ Ö^§ö?7fƒ5‰‘êŒlG—Xã[Çhª¦·¬ÿ¹)¥ˆ@ܨ­­EÍÇ7î[ªŠ€ˆ€ˆ€ø í‚]M¦eïm3 ê²óÝý’ úf&ÐÞêÚ¶f·LÚ%"€š+>©^ò»ö†­ Øv5yÁ‚C‡…ëwß}7 õõîéJð­óȤ¤¤„³×c—6E@–À®= jþÖ­[¥Ù%ìeàlx}œÙ*," "ý¢]° ¡É´y…ÛÇᨳGyœ•ƒ¾2„˜Í+ª=âµ)"È6¿Þ°éÕŽ¶Æš•ikØžÈ(Ôö ´··“ƒñ­sfeæ`x;g¤Â"  N€é¤!`žMMM NCÍ¡ï‚`Š€ˆ€ÄD™t‚ÓÜØÊ:·0Óã$ Ø·ð“76îÜXë¯MD&Ð\ùÍÏ(Ú¿¥zy[݆´œþ‰L#ÁÛŽwƒ!0{ölkÖ¬ñˆé¼‰ £à­^½š)b{òÚÚÚ˜4¶ÛLlò±•qfLØfL&ÝRBXDyyù† ¼6ª«x¯‰{"¨X4”RVVüY >NSJ±E˜'ƒé,O¸¢¢ÂŸ*M¼¡ïB¼"R»D@D Ž Ä’`g­”ž™Li-5mŸÎ_‘W²wž>s^·¯jä­¨tßlûªÔÕùÞ›ÀÕ’ìªïH+î*YÏâ»-½gÙzR"PŠ@)("2 ‰L)qƒË6$5¹<­aACÃVÕ•$Õu¯ËRKܸð ïºë®«®ºêÖ=‹i×µ×^ël ?F(;ίáÝ»w3!,»<æ„et<³ëß¿VV–×M$×$¥ØL:çããØnw™Ì»Md‚”ò"L‡h†#ry÷HÌfŠ zÑSŠ?wž!?ï^ËŠ@)E òã{[XXèµ>=‹¤ˆž¨£D@D@D@z‹@´ v!4™Ú'æ1'ìÆyÍ3/û¯^±Ë^ù”~c'•÷’çã4˜w©¦mï5nÛ;˜Qñøÿ²Ü|Ûí.µnÓ÷,AJ‰@´=¥D ˆÈ4$2¥Ä ®ÿnHy{ã­5_dõŸÜ³;ÎëQá5^‘ÑFà2dÈÕW_=yòä>ø ˜êauÁ…';\fXZ[[ øVël¹&³‰_^ç¶6¥" ±N)Ÿ%Ö[¡ú‡œ@¾ AÖ'¬/0aÍÜÙð"hQJ‰@æÔD¦ ”""F,bmqÞž {%í‚]M¦‘“ú!ØAá¥{?MÏJknhmªk­ÛÕL¸|B©oµÎ²³j1®Öš”ô»K8#š]Ê_œ5JÍñŸ³4úŸØwJ|ëÈÍc~ L¯ææfßzÝ+µÎ+EŠ€ˆ@ü¡ïB0p°Þƒô‡õQzX3w–‚"P-Š@)(œšÈR"PDĈ…µ-dî¼+î–@´ v4 „&s¾ú‡%[Wýׄ°©iIU[ÝãûºH­ ”˜Ò‹€ˆ@ÂÈÎÎÆ«ß:\êpëfÙ±c4˜ràÀÝbAòÃAdt¡í6±ˆ€ˆ€Äú.ĵBD@Š@ v¡=Ê v·4Õ·–”åQ4pdá¼ç×RÄ»O®žzƈnË¢lÕb÷ ãù#fu›X D@D@DÀÀ½Žù%œ Fƒc°*?y ççQJ&" "ÓBè»ÓTyH@ 'Øm^Q•™“vÚ5íÉÆíŽN²»w4Øß]ç» íˆotne@:<ïÒÓÿk6¤ønµZ'" " " " "Ð-”nS$B‚Ü‚LšYW寛C"0QE@D@ÂMµŽ"¤Ö…›³ò˜#pv^ÏP}{Øï¼bMÈå"E@D@BCÀ9Á«QëÊ× cÇ(xÉ. nJ," " " " 1G A»†š–œ‚½òá@O›Æ.-xþ¾gz¬Ò‹€ˆ€$ ²²²-[¶lÞ¼¹…ùaáЯ_??i0=…IÙÔÔÄ䌈ççJ&" " " " "sN°;êÛû¿õ×ås]Þwp^Ÿ²¼ªmõ;7ÔrÚ=e¸Ÿ'¯fÅ#&e[íZWk]JzžŸ*™ˆ€ˆ@ ß뀶m󠆮b8“™é“ÁŸ¥££Ã&s†m¤" " " " " qC á»Ü¢ŒÃOÛ÷ƒg¾¨ØTÇŸ9‘Äô˜ëïIu¹‡Ú»8Ãÿ‰ÓèLmŽ™^Qëp¯#ŒŸ]ç4>bðÑ3]bIÃl>Rj—ˆ€ˆ€ˆ€ˆ€ˆ@¬HÄ7~´9f†­Ü\·kKCŸ²œ’A¹Èpé˹OÉ,Šõ+@õˆ$==b“êlõÐûlX8&ˆ‚9ètJuö:(­ + " " " " " " " " !$¼”•ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ@$Ø P‡‹€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ@( H° %Må%" " " " " " " " A`$@." " " " " " " " ¡$ Á.”4•—ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€I@‚]u¸ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€„’€»PÒT^" " " " " " " " "$ vAÔá" " " " " " " " "JìBISy‰€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ@$Ø P‡‹€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ@( H° %Må%" " " " " " " " A`$@." " " " " " " " ¡$ Á.”4•—ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€I@‚]u¸ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€„’€»PÒT^" " " " " " " " "$´ ×á" " "—Ž:ê(Ó® .¸àì³ÏŽË6ªQ" " " " " ÑI ;™LÑyé¨V" "ǶnÝzòÉ'ÿèG?Šã6ªi" " " " " QK Ú;™LQ{é¨b" "Ç^yå•SO=Õår¥¤hìˆ8>Ïjšˆ€D;ù.DûRýD@D l¢]°“ɶS¯ŒE@D@º$ðꫯ^~ùåçœsÎý÷ßÿÉ'ŸŒ7®Ë¤ÚÊÊÊ:7ËkdçdÁÄD ªO¥C[ÇŠ@ôïBôŸ#ÕPD@ÂG Ú;™Lá;÷ÊYD@DÀƒ@rr21O<ñ„Ùuß}÷I ›³gÏöHß9Æ#6E@D œqÆÑP Õ!Pò]”˜Ò‹€ˆ@<ˆRÁN&S<]dj‹ˆ€% “É¢ˆÎ€æ¨ÛUW]u×]whoo·U½öÚkm˜jGŒso¨Âa-%¬™[(%EDæŒÇMC„Ë^ÿþ"sÞý©‰ÒDù.DÛQ}D@D ’¢T°“ÉŽ‹ ¯ƒ(2(%ED¦!‘)%npE¦!ḻ•gÈ ìÞ½{Ê”)·Ür SO¼óÎ;!Ï_Š€ˆ€ˆ@W׬î@IDATä»ÐÅ‹€Ä4ù.zú¢T°³ÍÉdQ( " "1úÓŸ***æÏŸ_]]m §ˆ­‚D@D@œ@@¾ `Ö/šaÍÜ 'E ZR"P„95‘)(¥D ˆˆ‹X[œ·§Â]ˆöÉï0™ž{î¹úúzL¦#<²«f(^D@D@BK oß¾'žx¢ÔºÐRUn" " þ0¾ 8z÷éÓGîÞþsSJˆÑîaec2ÅnµBD@D nD`;X…µ”°fnOtJ‰@á>WÜ4D¸ìõïO 2çÝŸš(M´»w´ÕGD@"I »nqDæ-'¬¥„5s 0¥D šR"PDd™RâWdboID@D@D@DÀ7ù.øæ£½" "Ç’í qÜH5MD@D@D@D@D@D@D@D@b…@´a+UO„%ð׿þõª«® ¨ù›6m:üðÿùÍo:ºûî»wíÚåŒéA˜A÷222ìÏ>ûl@Ãð]sÍ5pÀÂ… m¦NêÜŒ@ø§?ý©GµÙüàƒ*úÜsÏø`«ÐÝ|óÍ'N (gª}ë­·~øá‡ýúõ3Þwß}¹¹¹LcP>½•¸sýo¿ýöI“&¡9öV•(·¸¸¸©©‰«”þÿøÇ?ÌEâg}8ŠëjåÊ•v\ÿµ}ûöË.»ìÉ'Ÿô3‡Ð&Û°aÃÚµk{g´5¤MÐ!" " " 1A ®»—ÉÔ-4™L&ŸL¦n¯™€Èd —G9<Ñ žž>aªÊãâ·¿ý-þnøï¼ôÒK¶òøI]yå•lÉ#55åâ ƒ²i‚ üùÏ~àLN}‡jPëO>ù„½Ð_N?ýt[Ù´iÓØ|ðÁ¯»î:'œpÂüùóm‚H~ö³Ÿ}ãß0%þâ¿°‚r䨱c©Ü¹±0ªÊ§¯€JììQ ^Ø4Áö®…î´ÂÃ[¿«Ê{Ä?ôÐC|iã Ê_RRâÜŒd˜oK´Å,]]áï½÷žÇ§Mî/¾€üñÎ/[!éHѳ¶ÿ§îÿüjš‰W¿ò®hš¹Òû&Ÿ‚L&ßg= ½2™™L]?Ý&–ÉÔ-"%ˆ!ÙÙÙÛ¶mÃfX¼x±©öG}„€-á|=ÂÎÁ—Í$0Oú¥/$=ãŒ3.½ôR²zâ‰'xñ5y¾ýöÛo¼ñRR_|±‰¤îÓO?ݹP<¹L¨KçˆAÚ²e‹)hß}÷5¯¼¼ó¿dÉbŸ°7??߸%¾õÖ[cÆŒ1é9t½4ag[p[3‘Q¾îªþxönÍkkkÌë>ØNe6oÞüòË/sQ9/r:b£ë­X±‚o~¶ÂÎËÞD"óõÖÕE¸é®¿þzs§˜úp3¢ÓquÁ1qöìÙ¦5 ±Ú1)£­!¦òZ‹@ ¬sçš…/ÿsàá|ÅW°Æõá[ßúrãðíaݺuþg”çœsÎ/ùËY³fùŸggòØòÂv¶4 =²™Ýò«_ýª³žþ„_yå•þóŸ|Ê2½8„W—#<2øCü)Ýk~§x!1· =!¼J‡|A´o,&~¯ù)œ3gN8:Rx­g·‘ÿ¹×;ø^P÷‹Î~å½{¿wÛÒ8KŸ‚L¦^¦2™ L§É'“)„˜L¦ÂTV½N!Ì ïekröÙg›pee¥äÕ‡×&³‰fmãCÈËË#7l[éÓ§óÔ‚’eKgÓk¡¼—––zÝÉÈ[n¹…7uœ°¬ªXTTd¾u˜šðf»"ÕsÌ1¶n´Ë¶1JÚbëæ0j£GdÔÖŸëŠ}ØrfΜùÌ3ÏàUWQQA…Yl+öÙgÂ\i‹-²‘ÎËÞFönàW¿úÕý÷ßÏ{Ž©^füGì+”;"ñ–eýõ¯ÝøšdQØS1­E x‡z(Yngë5räÈÁƒã|XѱWqŠãñû÷¿ÿÀСC÷»ß_žåÀƒÈùK±ÿþûóó‡OµÍ0¦ºõ(*/l8PgCƒï K—.ÅÛý?øyÇ`Ì"'¨(ôÈ>ÿüóË‚_ SÏ›nºÉö30ãTÐÿ€²œrÊ)N4dß_|‘£ø}1éÐ[#'P ^¢î¹çãNÎ&§ÀH‡kÖ¬¡ò\TüLð]ÿýñãǛƚõ¸qãÌQ¸ró­+´)œõ üÔSO=öØcØØØH+î¤ÈV›rDòöesîìWÞ»÷»­X‚âS°“ÉÚËW&<£Êä“ÉÄ‘ÉÚÛ\¹õ€Ns ‘Æ<xiCz£ç¦Ç•i_òœùózjß>]Vi‚ cAÝyçÎLxååaŽÔÂK?¯bÎ]üÌôõµs!‰¡[.ór ªØÎ¼+¦555¦ˆ¯|å+÷Þ{/.¼ÜÛB\ðŠ2›´Åcr›,’æ\Fzýõ×­¶hë…õ7u³­ÈÉÉÁ>ÁÃ=‹&°üë_ÿ²M €æ4`œ—½3Yï†QÒ‘'<ê@[L ÷£::oäèlˆG´)þÀ–›Ú,¤çi‰3ŽÌÜÖ=aŽ>ìøh;ogü†úöíë,‚(½å0ûÅ_ðC@ePèìÀ”Œç€Z„ó/b„©'¿ÔöÛ³æÎOòQî…Í*óŽÁ¯¹mBzdsý˜¯†ø&3F‡­ª3@d’±¼öÚk¬í®ºº:6ñ\i½uiQ~×xæÛ*ÙŽÿT¿SO=•á}|Ãv­ ÍܹsmG .°O?ý¥•V˜@6“^ðîÊ«Ï~Í™HŠÛœ~îÈ‹4Íù´³_y/Þï½-òpuë &£º0œ‡ÉD»ø2­s¾iÙörû1<ÙYgEL$M&¾0<úè£øúr‡ÛÊx DÉij’—Z>•˜zb2Ü`(7ºdÞxãüžv®R´ÕßÔì8  ¯^½úÇ?þ1çéÖŠ[ /6\ð¿ùÍoŒ3Ë AƒºŽ=öXë^Áá4Ÿ/ìaΠuµèÜðÇP®ùW_}•ré?ÎÅö·¿ý Sßx1Â#uþ¿ÿû?g­¢³!Î*,~@ÌröãûãÿˆzÂ5ï<ܳñN—(Ù[3fðhű—7^n^󵯨wø Ùªz´ËÖ–_™hð(·õé@¤3‘ξù6í²¦M”´…_”)sF°MØ´µ5ú_3«Fy¤ïÅM^0PÍDI^«µÞ9Þùí6JNJçJƒZg}ë8eȬ¼Ùš”ö¢bÓÙ¯ù(2¼8±¾0Bï[FªÀd2Ïe":ê(ØñÔfÌ`|kM3‰q¶½†÷N^@1™l<ïмqÚÍžlA0™ÈÄÄ`,ñþGm1íð&µñÎR0™†/¼ßÛx>80pŒÝŒLà'?ù n ”Åo±3èý÷ßç|eÍ÷êÆ.žh¶Õ¶z˜ßýîwÌo¡ätð8°›‘ €Á“‰ry/g¼Lìm¼ýù$Æ£ ^ëOK$kî,‹+–j32ÂwÜA…1™LÍ‹ºj6ùH§:øp_8硱{1™l¼GÃm|XÔÍæÏ;–©&Ó@G*lrL&“€]˜L6±Œ’†xTL›‰C­™ç¹m/Ã7ß|Ól"UØx¯¼ó“Øë®0E"&ò­ˆÌ7nÜØU|Yå´«½ÑÏÏúªU«¨ €0Ïðèãó[çºñÑØ¤ì¼«Wb°„ñ±EƒšY2ìfç@´ÕßÔï>:2À¶³ÂHÏ hbÌ“œqˆ°œiLiŒ÷´ÎñQƒç&Ö—?5Œò†D OU#š ðÁÃ9†Uåã_kL¹… Ø—C§3Ã)˜½|ÂiÈ„±óMÀ™ÞÆD `+i+0jÔ(kÊ™½Î4¦J6æê«¯f NÙ‹ ],™ ÀTÀ¶†8s1d¿à •÷Øä•q$¾­[sªaÂø|àÞÈ&¯ÆNÁ;ÛTE½Õ„YãóežÀ ¶@mïÌÓFF €%‚Î` â7Â\ð¶>t˜EÑ»à‚ œ?èüüYË×yƒ„Dd°• (`+ÌQü‚Ó1™€G$š ·ƒÉö¤“N²ù³¹Ùtâ ÛÄ „œÀ©W!Ï=bÊdŠj[L&‹¢W2™ v™L½rù©PˆÈ+|{‹žú¨&zƒ×5 1A ³`Gµ¹ÑßQîøbm6ùzŠxçñõ—®¦¤ÄUíõë×ÛöFþöLJàòË/·ÀýùçŸG°C%áûŸ®éëgbÓ˜€W÷ˆ^ìL%™»%Ë|‚B!e”'†«ÃUÍ·`Uîx[ãÃaiã’üÃþN£¸cãyÍ…d;Ì‚W.NÁ÷mçq)ÀYÄÌÙe²Šü¥e›pØa‡QIzE\tÑETÃøIоzLTb*Æ|Œ¾g!Àƒã ²)NÜ6¾w;jÈÂàzÔÙ8Í¡Äá$„Ló)—]t€Å/‡]¶Úˆ—“„H$Ã_úÐr´ˆ@ ðÙ„A‚pvèq:0´œò¡ÍY¹‰€ˆ@ôÀøðÃçÕ?zª”à5ÁËÀN×›à(Ô|ˆEh%˜ôÎ>€t©1ƒÄy4ÁŽ>‘Ѱ‰æµ|ùòwß}娫! ¨'Nñ dW4ÔÙÔت"y3©mãoÐaËî%ÀEÈ”²QÞáŸAÆé£ÿŸ­9þwv¦$ÀÈ•Œtû§3Rá`È Q„L¦h;Í2™¢íŒ¨>" " " " á&`»p—¢üƒ!€cþŒÌ°ÄÜ LV:V⛀»ø>¿jˆ€ˆ€ˆ€ˆ€ˆ€$ ºUF³¯Y¢œ†îÚÉœõÙÙÙf ôîÒj¿$. v‰{îÕr($ NÈQxRT%Ä% Á.qϽZ." " " " " " " "…Ò¢°Nª’ˆ€ˆ€ˆ€Ä%;ƒ*CnO›6íÞ{ï%—-U£D@D@D@D@‚! 1ì‚¡§cc˜€L¦>yªºˆ€Ä,#Ï•––ÖÕÕ566ÒŽ¶¶¶ÔÔÔÎ ûì³ÕÕÕ.¼ðBÖ7nd*½qãÆÍ›7M-" " " |ÌÂË¥—^ªCÁÀÔ±">ìÂÇV9Ç™L1p’TEˆkï½÷íËÍÍ]¼xñ©§žzõÕWŸtÒIÖç.##å!¯¢¢¢¾¾þ°Ã[´hQ\óPãD@D@ÂN@¾ aG¬D $Ø…‚¢òˆ 2™ââ4ª" "“ø€„07kÖ,jÿÎ;ïœ|òÉEEE„ï¹çžÓN;À† –-[Fà¸ãŽc­ED@D@‚$ ß… êp7 vá&¬ücŒ€L¦;aª®ˆ€Ä8ÌÌLZЯ_?†_¸òÊ+;·fÁ‚DfíYHSYYÙ9bD@D@D ò]†žŽ0Ð,±a«lc€5™¨úÁܹÖdb—L¦Î|#" "™3g’þÅ_œ3g—ËÅ ‹G&${üñÇÙK<#Üx$Цˆ€ˆ€„„¾ L‹ôþûïãß»7¿P¸{3 ›3fÌ ܽYc7áîÍhà!)T™ˆ€tE@‚]WdŸ@d2%ÐÉVSE@D XUîüóÏ?æ˜cjjj¨TZZšÑìÌì±GqÄ“O>IJÆG°³‡`MEA T¸" ß…¸:jL¼`/gRíèkÿÈdê?$" "¯3ñá4‡ÿÂܹsÍ$“ãO<±k×.£åqSRÔÕÕM:5°ò”ZD@D@|ï‚O<Ú)½IÀýÙ¶7ËWÙ"eÕš˜' Á.æO¡ " " " " " " " "ORâ©1j‹ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€Ä: v±~U¸" Á.®N§#" " " " " " " "ë$ØÅúTýE@D@D@D@D@D@D@D@⊀»¸:jŒˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ@¬`ëgPõˆ+ìâêtª1" " " " " " " " ±N@‚]¬ŸAÕ_D@D@D@D@D@D@D@D ®H°‹«Ó©Æˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€Ä: v±~U¸" Á.®N§#" " " " " " " "ë$ØÅúTýE@D@D@D@D@D@D@D@âŠ@Z\µFH˜={ögœ©ÒTŽˆ€E ¼¼<¨ãu°ˆ€ˆ€ˆ@d ă`'“)²×ŒJŠ€L¦ ðEÙÁŸìÈíYŽß7/+µgÇöà¨5kÖÄÊ…§ªöàüúsˆÀúC)Ð4±E5ÐÖ)½ˆ€ˆ€ˆ@ïˆÁ‚=6™¼Òÿúäþ^㊌Îw8ÕÊÿ“(Vbå?ÿSr]ùŸX)E@D@D@DÀ7ù.øæ£½" QE V>!G ´8좄¦ª!" " " " " ‘$ÐÐÐÂâÆŽ|núòí?C±+ÿ øŸ2j¯+ÿ› ”Фº D@D@D@D@D@D@D@D@D ŠH°‹¢“¡ªˆ€ˆ€„ÀÛo¿ÝÒÒâÌðñÇwnz û“ÆëŠPˆ„`'“)TgKùˆ€ˆ€øOà‘Gñè%ô /t{ø¬Y³ºM£" " " " " "V‘ìd2…õ*s·Þzë?þñ¹xñâ 6ÜyçlN:•õ펥ó¦9ŠõgŸ}öï|ç½÷Þ³1 ˆ€ˆ€ˆ@ ä»(1¥0Â+ØÉdÒu&" "aÉÉÉ_ýêW§M›Æç"ŠþàƒÆŒc†Ð¾ôÒK‰Á‡Žeܸq7ÝtSçMbX^}õÕË/¿üÑG½çž{n¼ñÆ=qZ‰€ˆ€ˆ@Àä»02 " "°‡@g‰Ådúä“OJKKÏ:ë,\0™þçþç™gž¡\L¦K.¹{‰ðçŸ~úé§_sÍ5›æa2ÝrË-|˜bï¿þõ/YM‹Ö" " ^ ´µµxàˆqì9s¦Iså•W{ì±6ýàÁƒI6dÈŽŽ"=6M²O<±ªªªººúþûï/..Ö¯¥§€ˆ€ˆ€Ÿð]>|¸IŒ»wŸ>}ž~ú髯¾Úº{Û|0…pþvnÚ0îÞ·ÝvÛÅ_ú(;;ÛìòØ4‘“&M*++ÃaÛ¶mÇsÌgœA¼~}:ÃTŒˆ€ˆ@gò]èÌD1" " —`V“)ÐF*½ˆ€ˆ@â˜;w.~ ,×_½×VïÚµë°Ã3»èë±iâçÍ›7hР-[¶Œ=𡼿£HðJ@¾ ^±(RD@D á쨄L¦€Î„‹€ˆ€„„ÀQGe§3¹1dªÍÖÄ;÷²««ÍÍ›7Ûÿ ÈwÁVJ)" "Ð0 v2™º‚®x8& ß…8>¹jšˆ€D†@»È4@¥ˆ€ˆ€ˆ€ˆ€ˆ€ˆ@TïBTUFD@b‘@J,VZux% Á.^ϬÚ%" " " " " " " "“$ØÅäiS¥E@D@D@D@D@D@D@D@╀ư‹×3«v‰€ˆ€„@V†¾{…² $'‚L¦¼vÕdèu'N,íõ:¨" " " " " ñG N;™LñwiªE" "à$°xñb—Ë5qâDg¤Â" " " ééé‚ " "âD°‹¿£‰€ˆ€8 <üðÿÿýï;::œ‘½~üñÇ{½~V`Þ¼y^SžuÖY^ã)" "+F+UU=E@D@ü''‚]tšL]YGþŸžp¤T­ü§êd%›ÖnJ)á 0kÖ¬‡zh̘1ßüæ7ÓÒÜ?^?ýéOÃQò^''‚]¯sTD@D@ÂJàW¿úUCCÃÒ¥Ko¸áS»°Wæ" " ±B`É’%QXUÕÊÿ“ý¬ÆŽës”RD T$Ø…Š¤ò#—^z)Œ¹+kˆ&)ÑTÕED@D@º$ÀvÉÉÉ)))wß}÷Ûo¿Ýeº@v0‘Å/~ñ‹@ŽPZ;hìd2…ýT«ˆeŒUzå•WfggÓd»£Ž:*$­8pàÌ™3C’•2PˆÁN&S¨Î¨ò¸$ð“Ÿüäˆ#ލ®®¦u(w¬çÌ™dK.\¸cÇŽE‹‘OKKËùçŸíµ×Ú<é„{ÓM7ñ=éµ×^³‘ ˆ€ˆ€ˆ@È Èw!äH•¡ˆ€Ä¨ÃΚL‡v&Ó%—\Ò·oßÙ³g¾˜L ,øùÏþúë¯wÜqq]M”@ÿþý²f,//·áž&NœÈ_|1k|÷ÚÛÛ·lÙ‚û^GGs\0ôßÿþ÷Áƒ>\¿>=#¬£D@D@ü!€ïÂóÏ?ïOJ¥HQ!ØÉdJœ N-ž˜;wnNNNff&‡£©±9rdϲê|Ôm·Ýf&Ÿ-++3½nùJ„lGÊ 6LŸ>½ó!ŠïBH0*ˆ?Q!ØY¬2™, D@D@œÐÑPÐn¾ùæùóç3êÜE]äÜdxõêÕgœq†É$55Õ™nwvÓº~ÛD@D@D Hò] x%]‚L¦x½ÎÔ.ž@]]Ý´iÓ¦L™BVo¾ùæŒ3‚ÏÓäÀP çž{îâŋ٤Ö¥¥¥•••%%%§Ÿ~º-Å9‘Òï,D@D@‚' ß…à*ˆ' vC† Ù´i“é(r2™BŽTŠ€ˆ@|xæ™gœÚ á/Ñ„ ÐMO[†±#s&£ Ë-ýa8ÏÌqÕ ¨% ß…¨=5ª˜ˆ€ô €»—_~yܸqtòè4’ÚËd Fe"" ñG€)\™wbëÖ­FV U­êwïžÅf‹8¸jÕ*6Ÿ~úé“O>ÙÆ+ " "Èä»Èg_mH ´¼ë®»ŽCÒÒÒ0™Ìh^Ó;M&Â,L–DJc2577/[¶L&“WtŠD ðµ¯}íˆ#Žàw'2½ï¾û²²²(îúë¯ÿÁ~™BUŠˆ€ˆ@”Àw:‡7 a…­ï¿>ÖÝ{òäÉL¸Ä ü*…°,e%" "ýö°;á„ ±†“ ÁnÔ¨QË—/X¹*HD@D J\vÙeŸþ9•yûí·qî>òÈ#MÅÞzë­ðÕo¾¦¦¦ð寜E@D@b‘€õ]°•ÇÏÀ†{°™ÈÝ»Ç u ˆ€Ä€»+®¸ K–,A>;î¸ã  E&SXñ*sˆ~ .üðÃM=].²]ô×Y5ˆKò]ˆËÓªF‰€ˆ@tX°£¦Gë;ï¼ó§?ýi}}}t¶Mµˆ|ðA´BMˆò]ˆƒ“¨&ˆ€ˆ@¬x »3Ï<“¶1}ë /¼°¡¡ÁŒ°+ V=E@D@b‘ÀE]tùå—ÛšóÑÈL acàèÀÄ&úÓŸþ”››U„ˆ€ˆ@bØÃŽ‘VÏ>ûìÂÂBxegg³ž7oc'&>µZD@D Œg71¸-®¼¼Ü†{+`~{«tÿËmkkc¶(ÿÓ+¥ˆ€ˆ€WÖwq{ð]¸êª«ð](++óšX‘" " " €_ß™,ï7¿ùyï?þøã)û”SN ¦!96 M¦è´ŽT+ÿ¯·èdåý•Râ‰Ît?ûÙÏÒÓÓo¸áÓ.Ôº””€ÄCÎäÔSO yžáÈpÍš5Ñ o†£iÊSD@"I@¾ ‘¤­²D@D Á ,Øýú׿ž?þ#<¸×^{íÿø‡L&¯×PtZGª•דå52:Yy­ª"E î Œ1âñÇûfª" " QN :}ø mÜ¢óË·jåÿu¬ü¯¿RŠ@|X°£ÙètñÑxµBD@D VTVVöíÛ×YÛŽŽçf¯„‡]÷R¯”ë£Ðu·Îô±W»D@D@‚!¾ £F ¦Qá86:¿|«VþŸëèdåý•Râƒ@O;°«©©±í—ÉdQt ,ë ¡¯•ìÃh8¯ªƒÄ7ë®»®  ÍîCaä 'Ÿ|2¾Û«Ö‰€ˆ€D'ù.DçyQ­D@D þ,Ø}ôѨuhv%%%ñ‡C-ˆNO=õÔyç‡l7räH~†xàæææÌÌÌ謭j%" "¯ä»à÷™ ½—€ßEûHúZÉwÁní†@À#v¯_¿þûßÿ~uuõÿY‚)^ÇŠ€ˆ€ˆ€?N;í´»îº+++«¶¶öâ‹/niiyë­·ü9PiD@D@D T¬ï3ù˜%T9+ð °`·hÑ¢‡~˜±„<2Ò¦ˆ€ˆ€„ÀC=tÒI'¡ÓqƸ×%''›™ÊC^¢f·9Re(" qC@¾ qs*Õˆ~v‡~8RQQQ{{;£6K˜)“)L`•­ˆ€Ä(^x¡´´ô¾ûîcìT—˦VPJ˜rV¶" " ±N@¾ ±~Uˆ!vwÜqfLç%L­¥ 0å¬lE@D@b‘@ZZŠúõëw÷Ýw><˜&Ü{ï½~Á°f´³Ï>Û">uêT"Ÿ~úiÖ7Þxãí·ßN@‹ˆ€ˆ@‚ïB‚_j¾ˆ€Dž@‚Ý”)SèŽôÙgŸ-[¶Œ€Y~ùË_nÛ¶­Çõ–ÉÔct:PD@ŠÀùçŸ÷K/½D«gΜ¹nݺݻw÷˜?^ÅÅÅ—^zéŠ+í{ì±O?ýô{ßûÉšÁVÁ9鍊nêqA:PD@D >Èw!>ΣZ!" 1D ÁŽV 2ä׿þ5v–ÁƒÏŸ?‡7X&SÑé@H(ýë_™òè˜cŽ¡Õ#FŒ`=gΜ`0ÑĉGõæ›o®]»vãÆˆwÎ /^Ì&=pŸ{î9g¼Â" "  H@¾ xÒÕdè]i¿iÓ&“¾®®Î пÿ€2ñHŒÉTPP@d·&ÓÁ|à 7x®MD 0kÖ¬?þñGqýÍo~ÃúÔSO ¦áæ§çÁ¼ùæ›ÿûßßyçcÎ é{Ë&¥ìxy³gÏv&PXD@D ¡à¯PUUÅïÂï~÷;΀ª›7oÒwág?ûÙo¼Á£K.¹k?>>PýùÏÆÝ›ܽÏ;ï<<Ê ãî-S(Ðë1oó2ÓšZÚÛ:Ü?è®ô" "лìøYÂ\IOOÿáªzËd Iå#" qLàOúýaÏ=÷\Úxýõ×cºdffß^:½2ÔÝcùuã7Îw†×^{­3ô;' …E@D î Èw!æNqjJÊØyY©Éאַt%¥$'Ö½,æÚ« ‹€ÄÀ;¹Â—ŸSN9Å€¨®®~çw‚‡"“)x†ÊAD@☲ڎ;BÞÀ… öéÓ‡l—/_Ž›CÈóW†" " qC@¾ 1w*S’“†¦]ô•1÷¿þùS‹¶¸R3¤ÙÅÜIT…E ‘ ,ØáãpÍ5ׄ™L¦#U†" "O®»îº—_~™áâ}Ë-·dddÓºíÛ·›Ãé÷jëZ[[‰4›fv “Æ fLq:VD@D >Èw!ÆÎ£Ûu¾cŸ¾yetjJÒ?lv¥eJ³‹±“¨êŠ@X°ûíoË}ú_|1ée%ß•ù²‡%_a*ID@DÀð]hРÁü1iÒ$ù.ø Ã*ž>dË– Ë]bKP~ð|Ú´iÈõF=2ÓÈw ½£°±ÙM™2å‹/¾¸páB† °Ù]¼x‘tï()MD@R3Dì>þøc~ÂØ6lØ€ñ®K—.©ŸÚ." " " " "JÈw!Ä:šQ-Aî† öùçŸGGGc³;uê”lv!ÖËjŽø—@¢ vcƌᇠ÷ºòåËŸ>}zðàÁþm¼4 Èw!@P>ʆÍnß¾}Øì>ûì3LuéÓ§Ç}ãé>j…TI‰6Ø-Z´ˆ²Zµj}ùå—¬>ÁO[HrQ£D@D@B›À÷ßß´iS&˜fN:Õ#GŽD˜6íÏeŸþyshNi/" "Ê Èw!$olsû÷ïÇåÃ?Ä`‡{ kPÈf’}­F‰€¿$Ú`7gÎfø?üðÃÇÇÏŽ-ðó„kÙ²åæÍ›¹dõêÕ Åé£7bĈxààÁƒd°c¤ØBà5*§ˆ€ˆ€Ä&Àƒ†UáfΜ¹sçNc›{óÍ7M¶§Ÿ~(Ô7Þx#;2 :4v JH…ä»Jž3gÎfÍš=wucLÚµk׌3ž?ž@íéÒ¥“Ÿ](õµÚ">%hƒîu„YňöÃ?$ªÍ=zôÈŸ??žÆ 4àÂeË–(P€ñRÉ’%ëÔ©CJžL:c§={ö ð[Ê^›ˆ€ˆ€\'´iÓ®[·nÁ‚/¼ðB‹-â,mýúõ¤7»^œ”(" "ÚÈwÁï=Ž»IXXKÁÒsçÎ-Z´cÇŽ}¯nýû÷oÛ¶-a É“={vòDEE]ºtÉïM–þ" þ%hƒˆ°²mß¾½S§NÇ|n??yƒ â½{÷^“'O&±]»vK—.%ó\åÊ•æÏŸOcǎʼnûÝš5kðqX¹r¥uµ3—k/" " I#;wnž_<_ø„Þ¯_¿8 á3é¼µ_¾|Ùdxå·8¯R¢ˆ€ˆ@;—gzÙ²ey˜7oÞœ†°&,ãP–RDÞ¶mÛÏ?ÿ¼e˼RØÖ®]»{÷n"¶Ë`çë—ò"àwéÛf­òe)±W‘ôèÑÌ*zê©§°ÓýöÛo¤˜±-Š`F&p¿ž‘‘‘o½õÖâÅ‹úé'¦&ýþû︠› üÈÚ«$ˆ€ˆ€ˆ@b áæ›oæ3ŸÓ ,8jÔ¨7Ra§(jÀ€γz9iHÔ@ß…¹sçÒR|xv,s`q²#D ák·nÝ:kÖ,æÌâ^Ç$Yš0Þv.ꬪE@Dr÷Õm " " " " " "‚°Öa€cC‚2±F<ÛM7ÝÄâ,rˆ“]­ZµL†O>ùdóæÍY³fÅÉîÂ… !BMð!ì|ØiRYD@D@D@D@D@D A&B:Aë\Kü¥ˆˆ¦»âaÇþüùó ÀÉ®hÑ¢”ÁlÙùóçc­cæ,;l| ¬“" " š ʪCD@D@D@D@D@D ˜ŒÝåà *”3gN ƒÝ‰'HÇÃŽíÔ©S,ߺukœïÈÀJ™2eÒ¬Ø`ö”ꈓ€ vqbQ¢ˆ€ˆ€\›@ºL¹®É9¢/]È.£¹¶ RõÚŒ’”C`“„íùˆê5Z¢Ó"¢Xï•Y®Xßðªc3Ö:ÚŠé̊Ŋ׫W/–7ÄÏŽµ)XŒ‚™³áááD² Q$j–ˆ€Ÿ„ˆÁ.kŽ0?Q—®" " !A wÍw|ÑŽ;w¸:åÇûÚJÕê#M °>¢šÍW™"à}ìXn=­©ÎèÌ!;<ìØJ•*…3w,Iqüøñ|ùò±B6>ï·NŠ€„<1ØuqwÈw•(" "à5¯¼òŠ×T’>" "'Ä™®Ä !C†hE 5!†©ÎÖ4\ꢢ¢ðªcì‘#G°Ó±tlŽ9˜0‹ žÍ)AD@Ü" _"·È«^ßðËâTMö»ÍGTi»´•ªÉ~¯š %ŠU™!pë­·zD©ókÅž;wŽ%)0êaªËž=;¦:Y?}Ô‰RUBž@ˆì,láµ®Êrà vxM©¤Uà]"V!É*_ýi·K9E@D@D@D@B•¶9V™ÀÏŽáN¦ºPíhµKüK D vþíi." " " " " I&°iÓ¦$_›rúB«+W®@ ¾i³)'Fɾ`Cg·Å*pò±Y•-[6ðË•Ó d°óB/H pÝTÔÖª2¿Hc¾*øMíÿÑ—X'ܱú’t " qp}J,K õÉbqñSšˆ€ˆ€ˆ€·0jÔ¨‘·t’6" ž$ງ†B‰½/Ò&öåH92Ø¥[•," " " " " " " " ‰&à¾Áîûï¿oÚ´é”)SŒîS§N5ÂÈ‘#¦MûsIÇçŸÞšSÚ‹€ˆ€ˆ€DEE5nܸaÆ—.]ò‚>Njÿµõë×Ϥ?úè£UªT9|ø°3›ërDD„ÕaòäÉyòäq>ñ=¥³SÕ¿èÖ¶xû÷ï_ªT©µk×Úæ¸%ÜÿýaaaÏ>û¬Q•J–,Ù¥K«è ,h^±l¢+BûöíY–±mÛ¶¦vKÁ¤Ä¾%\Ñ“JQ5kÖ¬={ö´ ĸ9¿úê« pØ )-4kÖ,GŽ£F2y™^J£Pù" " "’\6ؽçqÊ–-ÛÒ«›ÏßtÓM#FŒX¹r%1Ðþì³ÏŠ/~ôèQ£Ï¼yó¾üòËC‡ñ%ïÝwß%Ñ;:/Z´¨FVUt‹÷î»ïÆt»eË–ªU«ºk½ >|ø¹sçfÏžýÎ;ï\¾|•¶mÛ†Áî–[nAó¿xß¾}˜–6oÞl໲ÇÒ}ï½÷FGGc³kݺ5:,[¶ÌÜ´ì9Œ}K¸¢'•>ðÀ-Z´8sæÌŽ;^xáRbÜœû÷ïÇ–ÇÈmÚ´ ‚ž¹sç~õÕW?Žû›o¾¡FÏÒ  _W!ß_wŸ”%àò¢|çgäÀP§Aƒ¦•*UZ³f 2KöðBIÕ±cÇ)RùÎ;ïŒóõHÑ[D…‹@(Т¡Ô›^hKùòå7lØ€&ÅŠ[¿~=F/h…|åZ¼xqÇŽíúwéÒ¥3†$›7oî…å@Õüùó›õ¯ wêÔ)0mÚ´Xš¼£ó‘#G.^¼hU×¼´ <~úé'§“ iQÐö3f¼páÕaœ2dHýúõ¹9ñ¹#Å(iUݳgw‚yé šzΊ¬ªÜؾñW½ùæ›÷îÝkóľ%ì© ;wž8q"•òʺqãFÜ*cÜœãÇÿý÷såÊEK8E•´* 8cbÅŠ=K/E9ø½p|î¹ç8ƒ®P¡]{(´bÅŠ'N<ñÄùòåãŸ:v“µèDl&Jˆ“€ˆ‹—]ö°ãó oi¼,òÞc'•Äàe|xûáÝÝœâ±äÜbäסˆ€ˆ€À’%Kð«21ÄÂL„J¬bþüùŒç»uëÆã•I…811)ÒªŠm1ÀrR4ƒO6[ÅéÓ§­Ì»§tÆãT5^«6w‚»xµzÕªUûôÓO™§iïR§žÈø‚ssŒô U©.gΜÿþ÷¿ðû×ϴ¥K—æ0Æ-4ÅbWd¬uü7a[á}5ö͉!ÏXëb_›B)F%^'Mš„µŽZ}zôèÁ‹E¦L™víÚe"­pˆ×w¨W»D@D@BƒAͦOŸnÚBÈ-ëÂæ…Ö1ðsªAˆ½_~ùŤ|ñÅ­ZµržõˆŒ›•q¥ÿã?îºë./ë¯ xÛµkçL ²Y³fM¦—b­£ê&Mš|þùçqê0kÖ,wUÅ ”Û3“±Öá h¾ÚZmcÜ6=ø‚5ñ_¿}ûöØ7'Ö«Y®(Ì\Ζ-[ó1F.–È`Å &ýaþ6.E±o Wô¤RìžõêÕ{òÉ';tè@˜BRbüCa»ÇBŠòü—ýúë¯AÐ+g×®]™ïÌ]·iÓ&þß=K/4ü[¾ Üíæ¿¯Xb|XÄÆ¿’æ" " ÉEÀåE'’¥¼ƒ>rÇêd)J…ˆ€„6-:ÚýëJë¶nÝá©ù°†>V .ÄñâIa‘‚‚±0«+ ¬“GÓ¦MmfÏê/l™)‰o UÞ;ÖX}û²Q‰4X“Ä®÷å=ÑdîܹDd3;£XŒ[Â-m±t³ŽÖd㺈±oÎ «Êâ¿l¬´k{Ö³ôÜêµTR/C!/¬#”Jh«™"àkZtÂwÝ'ƒïºL ‹€$€ vIg§+E@D@D@¼G@;ïõ‰4ÁΣ¿Z.ǰ‹_1ÔH@»ÔØëj³ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€g È`çÙ®‘b" " " " " " " " ©‘€û«Ä& õt™r%K9ÉXHô¥ Òý7Îw2{EI«ÀŠ•XN@9E@D@D@D@D@D@D  „ˆÁ.wÍw’J²µsçÎE‹&KQÉXˆ´ ¦X‰Uà”SD@D@D@Ü"!C·ªŽ¯^V£NŸÞs#Mi_ÅN«ØLâK«øÈ(ýú xîg4iMbu¤¤]¨«D@D È äUˆ€ˆ€ˆ@¸õÖ[½Ö:¾|õ¤ï‚´ ðVQŠlb8+åL,1ØypŒ QZx;ŠU€ È&V!À*ð&(§ˆ@Š(S¦ åÿüóϦ{õêÕ¥K—ÄVZ«V­'N¬_¿>±&¿F+W®ŒŽŽ¶yŒ¶æð¶Ûn>|8{{6… *´jÕjàÀÉX¦ŠHQò]HQ¼*\D  xÐH’Œ­Kö¢BÄ`—ì\T ˆ€ˆ€ˆ@hؼy3 ;vl=8}ÇŽ)á*²aÆjÕª9«–,"àqësà÷ŒX‰UàÏéÙû*ð&('´J¬nTJ€K={öŒÑøFmÙ²…Ä3f #,^¼¸Y³fÈ"##ûöíKJ‹-/_¾l¯8qb¾|ù† bSš6mš5kÖÞ½{›ŠÂ28~üø|ÐæAؿٲeúwïÎ!Ƹ>úáìÙ³ÎlÈh{èСÇc³ãÐ:ÁÕ«W/W®\/¿ü²ÍŸ`öìÙï½÷^““ôAƒ…‡‡—,YÒ4ô9}útëÖ­ÍU=öXÞ¼yW­Ze ¡½… Ζ-›³d{V‚ˆ€ˆ€ˆ€ˆ@ŠÁ.Eñªpïhذ!ÊÝwß}NçÎ{ìØ1R~ÿýwd„={öÌœ9³bÅŠÑÆŒ“&M"LoܸÑRcÜùóç‡ öÌ3Ïp yfÍšõÐC½þúëùóç'…¢¦L™Ò­[7²qh6Lf û쳘IÇî†õ°nݺóæÍK›öÏ7=ls/½ôÒý÷ßOæÒ¥KïÚµ‹Dô!ç§Ÿ~Š\®\9t+R¤H•*U84[ñâÅëÔ©Ó¾}{J5jÔ_Éú+" " " " ¡0%ÖƒNàt´ üþ+± œ@à9u_ÎJ9E 5Àã¬T©R—.]º&cÅÃæ…qíã?®Y³æ7ß|c¯2f8Î2 …9­¤oß¾½P¡BLb%Åd‹ŠŠ².o&¥~ýú»wïf]Œàñ”¼nÝ:,ƒ&Ýc%¤|sˆµŽlË—/ç3"éØì˜'Ë!^xøÄa7üᇰّ‚m§NŠLÎJ•*‘¹]»v&L@`VoõêÕMsL&Ãwß}÷Æo¼ýöÛäÑ&" " " "Lò° &mÕ%" " "à-,Ý€¥ŒÙ¬±ÕŠÏŠg<æ2eÊû’%JH8öÌ?e¢ë«¯¾j­lxÏŸdíÚµ6ÅéÝf}ØûØúôéƒ ›&92à‘GEÌfµU®\™t\lJ0)FfR­°â½kRòäÉÓ¿ÿmÛ¶ÝsÏ=;iÒ$›S‚ˆ€§è eàÝ!Vb8Àsê¾ò;«Àõ~Nì‚Ï\5Š€ˆ€ˆ€xˆÀÁƒÍrØÂŒZ8¸!|øá‡‰Õ¯º 2ÜqÇ\ȹ#þõ•3Te:ãÙ :Ôy*ÙDÊc]Zb×?*B[[ÑäÉ“¹œ°tÖ„çŒCWµjUS¸sJ¯ñ d¹[sŠ©²?þøãUóà éܹsú蔈€ˆ€ˆ€ˆ@²ƒsL7nL$šø>†';¸ò‰›P56‘Ò̯±ŸÍøÌNh›!oí9räp~Q÷‚V½zõÊ’%‹ ²c xA+£‰sÜâ­jÿµõë×Ï;¬œDX%¢,Ùa­ë¬þâôß¿ÁgÅ/¾-ć2UïÝ»—Áp«V­Ì!{FÎøªŒ9Ò¦HðßX ªÄ tX©œÁæìÙøò³qÖ,çZ¾|yÖZ%…0p‹-Šï*³¾ÄÕKÓ%kãËéLgA Y­‚ù³¬kÁ㞘ßM:52c—2^Q8`jቮìÛsÏ=Gid (“Ž%S#)ùÁ1b„3¿d'`¾ú}&» BðÛÂløüóÏMՌ՗-[†œ1cF´Â†ˆÀáš5kn¾ùæà¨Ç»ø]wÝE]ÌÊa‚à­Þzë-×C>ã Û#Z¡qÁéD#{šÝèã­»2JDŸ£Gš»Ú ¬ Ú¡&êS+Ló , RŒò_}õ‚¹‘Ž?nþõ… é„{çw´‰€xŸÀo¿ývêÔ©ÄêÉâÎKøÀOíܹsÎÄ8eâè±"-/ qž½fâ¾}û¦M›æ¼œ_i"Öñ£í¼–Ÿ#Þ œ)N™å/0,:Sq¤ä3gÎÄHסˆ€§ðOÊ{cƒ Ì€(øº1Ìae[/›‰6`½t€£®ÍaôèÑ7Þx£™ê¼ Õ“O>IƒæÍ›[^ÐÊ(c^\½ÃŠÀ¬f3ãGó+‹|'+S¦ Žíaõ§ÿþ ¾Vüò$„¾©šuºñѲeKsÈž¨ÄóÅfbS$N࿊À/ðfÎÈÈH£XÑ¢EYj-ÈJþç?ÿyâ‰'¬ÁÎÚ}˜œÂÏ n56*ÙS)­!Þ=|*wVj«vQ«þóŸF%FÆ„á­P‰=]»vµÊXÁEV;vì`?û$@I/h5hР_~ùÅt¢Ù{A+«Q¨‚¯î-æ ÷œ•+WòÂÊ«˜S ­†øªXY‚ˆ€ˆ€ˆ€$ ÞÇä»à$)ß'@dù.BI¾ ±)Éw!6“äMùsôÈO¼¯7>höÙgãǧüÖôìÙ“I(An³™|úÀP¯“#lݺe.\¸€zyóæuž šzÌæ“;Õ½£Á}ì+…G´ÂYŒn²ÊXÁÅ7n+>þøãï^Ý:vìè­î¼óNóñ _ÜóÏ?ÿ¼´2ÿM8§|ÿý÷f)Æ kEuèÀW\¼i,Uºti3–öfšý™µŠí@‰€ˆ€ˆ€„<æà›µnŠ+FtËlÙ²³É¼í 6ŒÙ31†BøùþôÓO ‚Њy¨´!†„¯½öšY ÈTj«vQ+Vzá…à€G$gÏö‚VèCôUÍDg7¹Èjç΄e0# TòˆVxŸ´oßžY5F%he•É•+— ÈÌû _Z¬1ü¯5kÖlàÀ ¹mX_ÞÂáçˆ) FI‚làb–Pˆag¼Lk™‚¿e -B£ŒS½ Tj«à——ÿÕ‡~kMDpW+`ö">‰èæ­ (À|%§2Vv‘U·nݰò`†ÆÞÚ©S'«‚‹ZíÚµ‹0F8‘ñž#8º‹Z8¼k]Yq'¯^€ ƒóÿ0ðNe$‹€ˆ€ˆ€ˆ@²`´|÷Ýw›b+Uª„,Ù«H¸@¾P2I0v”ÁzHÀ«^ì<)”B8c­Ãw¥®µ¸¨•±Öá»Àô ¬uÑ 5˜à9a§>Fv‘Õüùó™CÍ€YFɪç¢VóæÍkÛ¶- pÃã¸`UBpQ+£¾ Ø7*G+\M3 Zºt)LÎÿwã±ÄÔ:«•ub°)®I  v5jÔ`Ωi*7ŠÓæ}Íö§h†/¾ø‚ÐMš4a¶lŠV»pæxš Æ½Ù™ÁE­¬‘ŽçºS%dµ"Šs‰/ŽfousQ«Þ½{[5b.jU³fÍ?þø#†>æÐE­P€O»xÿÅV,8Z™ïØ,¨Âü çÿ;n›¨”3gNó) tüWÆVR)" " " "dÎ…æj’‹JÞ Ýýš+ß…{S¾ ‚"›|b³’ïBl&Éœ‚™36Ì·x`0ã+Í¡jÃŽ¸˜Õ«WÿàƒÌÂjèCŸ± \¹råæÌ™õ˜L G»Q©´báÚ"EŠði¢páÂ|ðˆV¶Gè&#{Kûñø\¾|9nÆ&„§´‚”ðÙÄ,…qÓ;=øÈ#`¬·]LVØàˆÝÀ×$ s|FF¾2 ƒv àð×_å+ÿû<Ï0áY%%ˆ€ˆ€ˆ€ˆÀõ ®ŒL9xo]I(Á9²oÔ|~楑Hóo¾ù¦)ÓžJB‰º„˜Ëf!2{•­ÚE­¬hed›â¢Vá1Ê XÝ\Ôê©§žŠÑw^`ÅdB]żӃèÃô5|bã0=hû…êòäɳzõj»¦Š9ÅÉ,ŠÅt]ç"0V[ ‘E'h$+¬:t(áÖí,ó´W­Z嬷^çòmÎSA“½ ŸÚðQgo[í­¬2Vð‚VL;ŷٹƟ´¶'‚µyŠ•UÆ ÁdÅ×¶ýë_gÏžµµ³úDTT”=D +‡’E@D@D@D ¹Èw!Iù.ÄÈ¡µ¼óËw|ŠÉw!>2q¦Ëw!N,¡‘ ‹Nðã¢MD@D@D@D@D@R'"IEDDx$–7Q«X Ì ä믿®_¿>Ó\ì/hE€ã 4hз#ƒÂ ZÅî/hÅÇï… ‚å=Å ¿Q,Â,7ç)­btb0{p÷îÝß~û-î‡aaaF \—"##3gÎlµš9s&Óì¡„À È`8+å'àæWŽoœ*¿ÁÎo=&}E@D@D@D@D@D@D@D@Bš€ v!ݽjœˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ßÈ`ç·“¾" " " " " " " " !M@»î^5ND@D@D@D@D@D@D@DÀod°ó[…¢¾e®n¶e½ûî»ö0p¡V­Z*T< 9kÔ¨‘!CgN£­Ù·hÑbë֭γÉ(Ó–—_~9 TQ" " " " " " " ¾ ƒ/º)Ä•Ü|u;v¬i'GGŽIB›7mÚ´aÆ$\ß%Ç_±bE·nÝœŒ¶‡>tèÐôéÓK•*µsçNg†ä’iËÞ½{“«4•#" " " " "à5ÆÀj%߃B¾ ö–š È`—š{ßCmOŸ>}Ïž=c(Ô¨Q£-[¶8cÆ d„Å‹7kÖlÇŽ‘‘‘}ûö%7äË—/Ûk'Nœ˜/_¾!C†Ø”¦M›fÍšµwïÞ&…¢zôè1~üø|ÐæAؿٲeúwïÎ!†¹>úáìÙ³ÎlÈh‹µ›Ý•+W88p ÉP¯^½\¹r9Ýâºté’={ö{ï½×ä$Û AƒÂÃÃK–,išF úœ>}ºuëÖ¦Ç{,oÞ¼«V­2‡ìioáÂ…³eËæ,Ùž• " " " " "àSÆ@¾ 1ºO¾ 1€è0uÁ.uö»çZݰaCtºï¾ûœšÍ;÷رc¤üþûïÈ{öì™9sfÅŠ1¢3&Mš4/^ܸq#4{!ƸóçÏ6ì™gž!‘<³fÍz衇^ýõüùó“BQS¦LÁoŽlö*Lf û쳘IÇî†õ°nݺóæÍK›öÏÿ5ls/½ôÒý÷ßOæÒ¥KïÚµ‹Dô!ç§Ÿ~Š\®\9t+R¤H•*U84[ñâÅëÔ©Ó¾}{J5jÔ_Éú+" " " " "à{ò]ï‚ïob5 …àø£MÜ%À½%Ëxœa€ã¹¨¨„€E áµ×^CFxÿý÷`Îb\C ÌœI¼é¦›œg‘MiÛ·oÇ67`Às–½¸ÖnÆ@fûõëg2ôêÕ+vÎ?/vlXë¸jÙ²e¤;wŽŠlQ¤àÇÙ5kÖ`@D Åèläܹs¡k×®F®^½º•±*ú(Ór‰Ó}ûí·#3Ké¯\þ%ó¢I!v‡ŒƒL:j4oÞœö¤äÉ“™©NìÙHaÖBÛ¶mÚ à“a.4ú ã3Azåʕٳ12:tè`‚9’m"zü¿_’ù¯Ð^Ü"pÛm·EDDð ‰­À¥K—b'’b<æ2eÊûl‰%°Ó™væ‘@ž"&'Þs1.Y»v­Mqz·ÙD§ÀS*::š¦åâè÷ÜsÏýðÃ<[‘yÞàÈfJ°O ™Tk±â½ý¾dRxÔõïߟ‡Ó=÷ÜC .{:u²™%ˆ€ˆ€ˆ€ˆ€ˆ€ø¾ „ÆŽoÔãl™ÄpƒÄÇ\³fÍo¾ùÆf0Sˆ8ûÊ+¯`ø#1Q¡B…p Åd‹ŠŠÂÞa/A¨_¿>ûÝ»w³gDÆœž[o½•’×­[Ç`Ç™—;ØÁwlË—/')P¤3߈0ßAßæ<1PÂ󀿡óÔ©S‘ÉY©R%ò ·k×n„ D$ÇwÁ4ÇTa2|÷Ýwo¼ñÆÛo¿Mm"ªü9MO›x„Þdf¹ ûá!n~øab5äÉÄê®wÜqòñ‡ wÄ¿³Ï3AÕY¦3žÝСC§M¤¼õë×›Ïb<>©mmE“'Oær>jÙ§š3]ÕªUMáÎ)½æÉzâÄ sŠ©²?þø£ù†@!;wN@ðd÷]€€õ] H÷«¯¾jÇ#×ï»`Æ&}úôÁˆ†MÍú.P‘¸mEÖwaðàÁ¦G’æ»°mÛ6|(vÒ¤I¾ëY),×C@»ë¡§k“™ë5Mј±HÇO³3ØÜ5«$?Ùàʾ|ùòÕªU#…0p‹-Šïr³¾ÄÕKÓφ}/¿IgA V«àëZàN |#2þwÔÈW/RLÈë0©+" " " " " " " "Úd° íþUëD@D@D@D@D@D@D@D@|F@;Ÿu˜Ôm2Ø…vÿªu" " " " " " " " ># ƒÏ:Lꊀˆ€ˆ€ˆ€ˆ€ˆ€ˆ€ˆ€„6ìB»Õ:ŸÁÎg&uE@D@D@D@D@D@D@D@B›ÀÿñÒúNý+5ãIEND®B`‚altair-5.0.1/pyproject.toml000066400000000000000000000126401443422213100157040ustar00rootroot00000000000000# this file contains: # 1 build system configuration # 2 project configuration # 3 tool configuration, for: # - hatch # - black # - ruff # - pytest # - mypy [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "altair" authors = [ {name = "Vega-Altair Contributors"} ] dependencies = [ "importlib_metadata; python_version<\"3.8\"", "typing_extensions>=4.0.1; python_version<\"3.11\"", "jinja2", "jsonschema>=3.0", "numpy", "pandas>=0.18", "toolz" ] description = "Vega-Altair: A declarative statistical visualization library for Python." readme = "README.md" keywords = [ "declarative", "statistics", "visualization", "interactive", "json", "vega-lite", ] requires-python = ">=3.7" dynamic = ["version"] license-files = { paths = ["LICENSE"] } classifiers= [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", ] [project.urls] Documentation = "https://altair-viz.github.io" Source = "https://github.com/altair-viz/altair" [project.optional-dependencies] dev = [ "hatch", "ruff", "black<24", "ipython", "pytest", "pytest-cov", "m2r", "vega_datasets", # following is rejected by PyPI "altair_viewer @ git+https://github.com/altair-viz/altair_viewer.git", "vl-convert-python", "mypy", "pandas-stubs", "types-jsonschema", "types-setuptools" ] doc = [ "sphinx", "docutils", "sphinxext_altair", "jinja2", "numpydoc", "pillow", "pydata-sphinx-theme", "geopandas", "myst-parser", "sphinx_copybutton", "sphinx-design" ] [tool.hatch.version] path = "altair/__init__.py" [tool.hatch.metadata] allow-direct-references = true [tool.hatch.build] include = ["/altair"] [tool.hatch.envs.default] features = ["dev"] [tool.hatch.envs.default.scripts] test = [ "black --diff --color --check .", "ruff check .", "mypy altair tests", "python -m pytest --pyargs --doctest-modules tests" ] test-coverage = "python -m pytest --pyargs --doctest-modules --cov=altair --cov-report term altair" test-coverage-html = "python -m pytest --pyargs --doctest-modules --cov=altair --cov-report html altair" [tool.hatch.envs.doc] features = ["dev", "doc"] [tool.hatch.envs.doc.scripts] clean = "rm -rf doc/_build" clean-generated = [ "rm -rf doc/user_guide/generated", "rm -rf doc/gallery" ] clean-all = [ "clean", "clean-generated", "rm -rf doc/_images" ] clean-win = "if exist doc\\_build rd /s /q doc\\_build" clean-generated-win = [ "if exist doc\\user_guide\\generated rd /s /q doc\\user_guide\\generated", "if exist doc\\gallery rd /s /q doc\\gallery", ] clean-all-win = [ "clean-win", "clean-generated-win", "if exist doc\\_images rd /s /q doc\\_images", ] build-html = [ "mkdir -p doc/_images", "sphinx-build -b html -d doc/_build/doctrees doc doc/_build/html" ] build-html-win = [ "if not exist doc\\_images md doc\\_images", "sphinx-build -b html -d doc\\_build\\doctrees doc doc\\_build\\html" ] doctest = "sphinx-build -b doctest -d doc/_build/doctrees doc doc/_build/doctest" coverage = "sphinx-build -b coverage -d doc/_build/doctrees doc doc/_build/coverage" serve = "(cd doc/_build/html && python -m http.server)" publish-clean-build = [ "clean-all", "build-html", "(cd doc && bash sync_website.sh)" ] [tool.black] line-length = 88 target-version = ["py37", "py38", "py39", "py310", "py311"] include = '\.pyi?$' exclude = ''' /( \.eggs | \.git | \.mypy_cache | build | dist | doc | tests/examples_arguments_syntax | tests/examples_methods_syntax | altair/vegalite/v\d*/schema )/ ''' [tool.ruff] target-version = "py310" line-length = 88 select = [ # flake8-bugbear "B", # flake8-comprehensions "C4", # pycodestyle-error "E", # pycodestyle-warning "W", # pyflakes "F", # flake8-tidy-imports "TID" ] ignore = [ # E203, E266, W503 not yet supported by ruff, # see https://github.com/charliermarsh/ruff/issues/2402 # Whitespace before ':' # "E203", # Too many leading '#' for block comment # "E266", # Line break occurred before a binary operator # "W503", # Line too long "E501", # Relative imports are banned "TID252", # zip() without an explicit strict= parameter set. # python>=3.10 only "B905" ] exclude = [ ".git", "build", "__pycache__", "tests/examples_arguments_syntax", "tests/examples_methods_syntax", "altair/vegalite/v?/schema", ] [tool.ruff.mccabe] max-complexity = 18 [tool.pytest.ini_options] markers = [ "save_engine: marks some of the tests which are using an external package to save a chart to e.g. a png file. This mark is used to run those tests selectively in the build GitHub Action.", ] [[tool.mypy.overrides]] module = [ "vega_datasets.*", "toolz.*", "altair_viewer.*", "altair_saver.*", "pyarrow.*", "yaml.*", "vl_convert.*", "pandas.lib.*" ] ignore_missing_imports = true [[tool.mypy.overrides]] module = [ "altair.vegalite.v5.schema.*" ] ignore_errors = true altair-5.0.1/sphinxext/000077500000000000000000000000001443422213100150175ustar00rootroot00000000000000altair-5.0.1/sphinxext/__init__.py000066400000000000000000000000001443422213100171160ustar00rootroot00000000000000altair-5.0.1/sphinxext/altairgallery.py000066400000000000000000000261031443422213100202270ustar00rootroot00000000000000import hashlib import os import json import random import collections from operator import itemgetter import warnings import shutil import jinja2 from docutils import nodes from docutils.statemachine import ViewList from docutils.parsers.rst import Directive from docutils.parsers.rst.directives import flag from sphinx.util.nodes import nested_parse_with_titles from .utils import ( get_docstring_and_rest, prev_this_next, create_thumbnail, create_generic_image, ) from altair.utils.execeval import eval_block from tests.examples_arguments_syntax import iter_examples_arguments_syntax from tests.examples_methods_syntax import iter_examples_methods_syntax EXAMPLE_MODULE = "altair.examples" GALLERY_TEMPLATE = jinja2.Template( """ .. This document is auto-generated by the altair-gallery extension. Do not modify directly. .. _{{ gallery_ref }}: {{ title }} {% for char in title %}-{% endfor %} This gallery contains a selection of examples of the plots Altair can create. Some may seem fairly complicated at first glance, but they are built by combining a simple set of declarative building blocks. Many draw upon sample datasets compiled by the `Vega `_ project. To access them yourself, install `vega_datasets `_. .. code-block:: none python -m pip install vega_datasets If you can't find the plots you are looking for here, make sure to check out the :ref:`altair-ecosystem` section, which has links to packages for making e.g. network diagrams and animations. {% for grouper, group in examples %} .. _gallery-category-{{ grouper }}: {{ grouper }} {% for char in grouper %}~{% endfor %} .. raw:: html {% for example in group %} {{ example.title }} {% endfor %}
{% endfor %} .. toctree:: :maxdepth: 2 :caption: Examples :hidden: Gallery Tutorials <../case_studies/exploring-weather> """ ) MINIGALLERY_TEMPLATE = jinja2.Template( """ .. raw:: html
{% for example in examples %} {% endfor %}
""" ) EXAMPLE_TEMPLATE = jinja2.Template( """ :orphan: :html_theme.sidebar_secondary.remove: .. This document is auto-generated by the altair-gallery extension. Do not modify directly. .. _gallery_{{ name }}: {{ docstring }} .. altair-plot:: {% if code_below %}:remove-code:{% endif %} {% if strict %}:strict:{% endif %} {{ code | indent(4) }} .. tab-set:: .. tab-item:: Method syntax :sync: method .. code:: python {{ method_code | indent(12) }} .. tab-item:: Attribute syntax :sync: attribute .. code:: python {{ code | indent(12) }} """ ) def save_example_pngs(examples, image_dir, make_thumbnails=True): """Save example pngs and (optionally) thumbnails""" if not os.path.exists(image_dir): os.makedirs(image_dir) # store hashes so that we know whether images need to be generated hash_file = os.path.join(image_dir, "_image_hashes.json") if os.path.exists(hash_file): with open(hash_file) as f: hashes = json.load(f) else: hashes = {} for example in examples: filename = example["name"] + (".svg" if example["use_svg"] else ".png") image_file = os.path.join(image_dir, filename) example_hash = hashlib.md5(example["code"].encode()).hexdigest() hashes_match = hashes.get(filename, "") == example_hash if hashes_match and os.path.exists(image_file): print("-> using cached {}".format(image_file)) else: # the file changed or the image file does not exist. Generate it. print("-> saving {}".format(image_file)) chart = eval_block(example["code"]) try: chart.save(image_file) hashes[filename] = example_hash except ImportError: warnings.warn("Unable to save image: using generic image", stacklevel=1) create_generic_image(image_file) with open(hash_file, "w") as f: json.dump(hashes, f) if make_thumbnails: params = example.get("galleryParameters", {}) if example["use_svg"]: # Thumbnail for SVG is identical to original image thumb_file = os.path.join(image_dir, example["name"] + "-thumb.svg") shutil.copyfile(image_file, thumb_file) else: thumb_file = os.path.join(image_dir, example["name"] + "-thumb.png") create_thumbnail(image_file, thumb_file, **params) # Save hashes so we know whether we need to re-generate plots with open(hash_file, "w") as f: json.dump(hashes, f) def populate_examples(**kwds): """Iterate through Altair examples and extract code""" examples = sorted(iter_examples_arguments_syntax(), key=itemgetter("name")) method_examples = {x["name"]: x for x in iter_examples_methods_syntax()} for example in examples: docstring, category, code, lineno = get_docstring_and_rest(example["filename"]) if example["name"] in method_examples.keys(): _, _, method_code, _ = get_docstring_and_rest( method_examples[example["name"]]["filename"] ) else: method_code = code code += ( "# No channel encoding options are specified in this chart\n" "# so the code is the same as for the method-based syntax.\n" ) example.update(kwds) if category is None: raise Exception( f"The example {example['name']} is not assigned to a category" ) example.update( { "docstring": docstring, "title": docstring.strip().split("\n")[0], "code": code, "method_code": method_code, "category": category.title(), "lineno": lineno, } ) return examples class AltairMiniGalleryDirective(Directive): has_content = False option_spec = { "size": int, "names": str, "indices": lambda x: list(map(int, x.split())), "shuffle": flag, "seed": int, "titles": bool, "width": str, } def run(self): size = self.options.get("size", 15) names = [name.strip() for name in self.options.get("names", "").split(",")] indices = self.options.get("indices", []) shuffle = "shuffle" in self.options seed = self.options.get("seed", 42) titles = self.options.get("titles", False) width = self.options.get("width", None) env = self.state.document.settings.env app = env.app gallery_dir = app.builder.config.altair_gallery_dir examples = populate_examples() if names: if len(names) < size: raise ValueError( "altair-minigallery: if names are specified, " "the list must be at least as long as size." ) mapping = {example["name"]: example for example in examples} examples = [mapping[name] for name in names] else: if indices: examples = [examples[i] for i in indices] if shuffle: random.seed(seed) random.shuffle(examples) if size: examples = examples[:size] include = MINIGALLERY_TEMPLATE.render( image_dir="/_static", gallery_dir=gallery_dir, examples=examples, titles=titles, width=width, ) # parse and return documentation result = ViewList() for line in include.split("\n"): result.append(line, "") node = nodes.paragraph() node.document = self.state.document nested_parse_with_titles(self.state, result, node) return node.children def main(app): gallery_dir = app.builder.config.altair_gallery_dir target_dir = os.path.join(app.builder.srcdir, gallery_dir) image_dir = os.path.join(app.builder.srcdir, "_images") gallery_ref = app.builder.config.altair_gallery_ref gallery_title = app.builder.config.altair_gallery_title examples = populate_examples(gallery_ref=gallery_ref, code_below=True, strict=False) if not os.path.exists(target_dir): os.makedirs(target_dir) examples = sorted(examples, key=lambda x: x["title"]) examples_toc = collections.OrderedDict( { "Simple Charts": [], "Bar Charts": [], "Line Charts": [], "Area Charts": [], "Circular Plots": [], "Scatter Plots": [], "Uncertainties And Trends": [], "Distributions": [], "Tables": [], "Maps": [], "Interactive Charts": [], "Advanced Calculations": [], "Case Studies": [], } ) for d in examples: examples_toc[d["category"]].append(d) # Write the gallery index file with open(os.path.join(target_dir, "index.rst"), "w") as f: f.write( GALLERY_TEMPLATE.render( title=gallery_title, examples=examples_toc.items(), image_dir="/_static", gallery_ref=gallery_ref, ) ) # save the images to file save_example_pngs(examples, image_dir) # Write the individual example files for prev_ex, example, next_ex in prev_this_next(examples): if prev_ex: example["prev_ref"] = "gallery_{name}".format(**prev_ex) if next_ex: example["next_ref"] = "gallery_{name}".format(**next_ex) target_filename = os.path.join(target_dir, example["name"] + ".rst") with open(os.path.join(target_filename), "w", encoding="utf-8") as f: f.write(EXAMPLE_TEMPLATE.render(example)) def setup(app): app.connect("builder-inited", main) app.add_css_file("altair-gallery.css") app.add_config_value("altair_gallery_dir", "gallery", "env") app.add_config_value("altair_gallery_ref", "example-gallery", "env") app.add_config_value("altair_gallery_title", "Example Gallery", "env") app.add_directive_to_domain("py", "altair-minigallery", AltairMiniGalleryDirective) altair-5.0.1/sphinxext/schematable.py000066400000000000000000000151521443422213100176450ustar00rootroot00000000000000import importlib import re import sys import warnings from os.path import abspath, dirname from docutils import nodes, utils, frontend from docutils.parsers.rst import Directive from docutils.parsers.rst.directives import flag from myst_parser.docutils_ import Parser from sphinx import addnodes sys.path.insert(0, abspath(dirname(dirname(dirname(__file__))))) from tools.schemapi.utils import fix_docstring_issues, SchemaInfo # noqa: E402 def type_description(schema): """Return a concise type description for the given schema""" if not schema or not isinstance(schema, dict) or schema.keys() == {"description"}: return "any" elif "$ref" in schema: return ":class:`{}`".format(schema["$ref"].split("/")[-1]) elif "enum" in schema: return "[{}]".format(", ".join(repr(s) for s in schema["enum"])) elif "type" in schema: if isinstance(schema["type"], list): return "[{}]".format(", ".join(schema["type"])) elif schema["type"] == "array": return "array({})".format(type_description(schema.get("items", {}))) elif schema["type"] == "object": return "dict" else: return "`{}`".format(schema["type"]) elif "anyOf" in schema: return "anyOf({})".format( ", ".join(type_description(s) for s in schema["anyOf"]) ) else: warnings.warn( "cannot infer type for schema with keys {}" "".format(schema.keys()), stacklevel=1, ) return "--" def prepare_table_header(titles, widths): """Build docutil empty table""" ncols = len(titles) assert len(widths) == ncols tgroup = nodes.tgroup(cols=ncols) for width in widths: tgroup += nodes.colspec(colwidth=width) header = nodes.row() for title in titles: header += nodes.entry("", nodes.paragraph(text=title)) tgroup += nodes.thead("", header) tbody = nodes.tbody() tgroup += tbody return nodes.table("", tgroup), tbody reClassDef = re.compile(r":class:`([^`]+)`") reCode = re.compile(r"`([^`]+)`") def add_class_def(node, classDef): """Add reference on classDef to node""" ref = addnodes.pending_xref( reftarget=classDef, reftype="class", refdomain="py", # py:class="None" py:module="altair" refdoc="user_guide/marks" refexplicit=False, # refdoc="", refwarn=False, ) ref["py:class"] = "None" ref["py:module"] = "altair" ref += nodes.literal(text=classDef, classes=["xref", "py", "py-class"]) node += ref return node def add_text(node, text): """Add text with inline code to node""" is_text = True for part in reCode.split(text): if part: if is_text: node += nodes.Text(part, part) else: node += nodes.literal(part, part) is_text = not is_text return node def build_row(item, rootschema): """Return nodes.row with property description""" prop, propschema, required = item row = nodes.row() # Property row += nodes.entry("", nodes.paragraph(text=prop), classes=["vl-prop"]) # Type str_type = type_description(propschema) par_type = nodes.paragraph() is_text = True for part in reClassDef.split(str_type): if part: if is_text: add_text(par_type, part) else: add_class_def(par_type, part) is_text = not is_text # row += nodes.entry('') row += nodes.entry("", par_type, classes=["vl-type-def"]) # Description md_parser = Parser() # str_descr = "***Required.*** " if required else "" description = SchemaInfo(propschema, rootschema).deep_description description = description if description else " " str_descr = "" str_descr += description str_descr = fix_docstring_issues(str_descr) document_settings = frontend.get_default_settings() document_settings.setdefault("raw_enabled", True) doc_descr = utils.new_document("schema_description", document_settings) md_parser.parse(str_descr, doc_descr) # row += nodes.entry('', *doc_descr.children, classes="vl-decsr") row += nodes.entry("", *doc_descr.children, classes=["vl-decsr"]) return row def build_schema_table(items, rootschema): """Return schema table of items (iterator of prop, schema.item, required)""" table, tbody = prepare_table_header( ["Property", "Type", "Description"], [10, 20, 50] ) for item in items: tbody += build_row(item, rootschema) return table def select_items_from_schema(schema, props=None): """Return iterator (prop, schema.item, required) on prop, return all in None""" properties = schema.get("properties", {}) required = schema.get("required", []) if not props: for prop, item in properties.items(): yield prop, item, prop in required else: for prop in props: try: yield prop, properties[prop], prop in required except KeyError as err: raise Exception(f"Can't find property: {prop}") from err def prepare_schema_table(schema, rootschema, props=None): items = select_items_from_schema(schema, props) return build_schema_table(items, rootschema) def validate_properties(properties): return properties.strip().split() class AltairObjectTableDirective(Directive): """ Directive for building a table of attribute descriptions. Usage: .. altair-object-table:: altair.MarkConfig """ has_content = False required_arguments = 1 option_spec = {"properties": validate_properties, "dont-collapse-table": flag} def run(self): objectname = self.arguments[0] modname, classname = objectname.rsplit(".", 1) module = importlib.import_module(modname) cls = getattr(module, classname) schema = cls.resolve_references(cls._schema) properties = self.options.get("properties", None) dont_collapse_table = "dont-collapse-table" in self.options result = [] if not dont_collapse_table: html = "
Click to show table" raw_html = nodes.raw("", html, format="html") result += [raw_html] # create the table from the object result.append(prepare_schema_table(schema, cls._rootschema, props=properties)) if not dont_collapse_table: html = "
" raw_html = nodes.raw("", html, format="html") result += [raw_html] return result def setup(app): app.add_directive("altair-object-table", AltairObjectTableDirective) altair-5.0.1/sphinxext/utils.py000066400000000000000000000133431443422213100165350ustar00rootroot00000000000000import ast import hashlib import itertools import json import re def create_thumbnail(image_filename, thumb_filename, window_size=(280, 160)): """Create a thumbnail whose shortest dimension matches the window""" from PIL import Image im = Image.open(image_filename) im_width, im_height = im.size width, height = window_size width_factor, height_factor = width / im_width, height / im_height if width_factor > height_factor: final_width = width final_height = int(im_height * width_factor) else: final_height = height final_width = int(im_width * height_factor) thumb = im.resize((final_width, final_height), Image.ANTIALIAS) thumb.save(thumb_filename) def create_generic_image(filename, shape=(200, 300), gradient=True): """Create a generic image""" from PIL import Image import numpy as np assert len(shape) == 2 arr = np.zeros((shape[0], shape[1], 3)) if gradient: # gradient from gray to white arr += np.linspace(128, 255, shape[1])[:, None] im = Image.fromarray(arr.astype("uint8")) im.save(filename) SYNTAX_ERROR_DOCSTRING = """ SyntaxError =========== Example script with invalid Python syntax """ def _parse_source_file(filename): """Parse source file into AST node Parameters ---------- filename : str File path Returns ------- node : AST node content : utf-8 encoded string Notes ----- This function adapted from the sphinx-gallery project; license: BSD-3 https://github.com/sphinx-gallery/sphinx-gallery/ """ with open(filename, "r", encoding="utf-8") as fid: content = fid.read() # change from Windows format to UNIX for uniformity content = content.replace("\r\n", "\n") try: node = ast.parse(content) except SyntaxError: node = None return node, content def get_docstring_and_rest(filename): """Separate ``filename`` content between docstring and the rest Strongly inspired from ast.get_docstring. Parameters ---------- filename: str The path to the file containing the code to be read Returns ------- docstring: str docstring of ``filename`` category: list list of categories specified by the "# category:" comment rest: str ``filename`` content without the docstring lineno: int the line number on which the code starts Notes ----- This function adapted from the sphinx-gallery project; license: BSD-3 https://github.com/sphinx-gallery/sphinx-gallery/ """ node, content = _parse_source_file(filename) # Find the category comment find_category = re.compile(r"^#\s*category:\s*(.*)$", re.MULTILINE) match = find_category.search(content) if match is not None: category = match.groups()[0] # remove this comment from the content content = find_category.sub("", content) else: category = None if node is None: return SYNTAX_ERROR_DOCSTRING, category, content, 1 if not isinstance(node, ast.Module): raise TypeError( "This function only supports modules. " "You provided {}".format(node.__class__.__name__) ) try: # In python 3.7 module knows its docstring. # Everything else will raise an attribute error docstring = node.docstring import tokenize from io import BytesIO ts = tokenize.tokenize(BytesIO(content).readline) ds_lines = 0 # find the first string according to the tokenizer and get # it's end row for tk in ts: if tk.exact_type == 3: ds_lines, _ = tk.end break # grab the rest of the file rest = "\n".join(content.split("\n")[ds_lines:]) lineno = ds_lines + 1 except AttributeError: # this block can be removed when python 3.6 support is dropped if ( node.body and isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, (ast.Str, ast.Constant)) ): docstring_node = node.body[0] docstring = docstring_node.value.s # python2.7: Code was read in bytes needs decoding to utf-8 # unless future unicode_literals is imported in source which # make ast output unicode strings if hasattr(docstring, "decode") and not isinstance(docstring, str): docstring = docstring.decode("utf-8") # python3.8: has end_lineno lineno = ( getattr(docstring_node, "end_lineno", None) or docstring_node.lineno ) # The last line of the string. # This get the content of the file after the docstring last line # Note: 'maxsplit' argument is not a keyword argument in python2 rest = content.split("\n", lineno)[-1] lineno += 1 else: docstring, rest = "", "" if not docstring: raise ValueError( ( 'Could not find docstring in file "{0}". ' "A docstring is required for the example gallery." ).format(filename) ) return docstring, category, rest, lineno def prev_this_next(it, sentinel=None): """Utility to return (prev, this, next) tuples from an iterator""" i1, i2, i3 = itertools.tee(it, 3) next(i3, None) return zip(itertools.chain([sentinel], i1), i2, itertools.chain(i3, [sentinel])) def dict_hash(dct): """Return a hash of the contents of a dictionary""" serialized = json.dumps(dct, sort_keys=True) try: m = hashlib.md5(serialized) except TypeError: m = hashlib.md5(serialized.encode()) return m.hexdigest() altair-5.0.1/tests/000077500000000000000000000000001443422213100141275ustar00rootroot00000000000000altair-5.0.1/tests/__init__.py000066400000000000000000000000001443422213100162260ustar00rootroot00000000000000altair-5.0.1/tests/examples_arguments_syntax/000077500000000000000000000000001443422213100214405ustar00rootroot00000000000000altair-5.0.1/tests/examples_arguments_syntax/__init__.py000066400000000000000000000017641443422213100235610ustar00rootroot00000000000000import os from typing import Set # Set of the names of examples that should have SVG static images. # This is for examples that VlConvert's PNG export does not support. SVG_EXAMPLES: Set[str] = {"isotype_emoji"} def iter_examples_arguments_syntax(): """Iterate over the examples in this directory. Each item is a dict with the following keys: - "name" : the unique name of the example - "filename" : the full file path to the example - "use_svg": Flag indicating whether the static image for the example should be an SVG instead of a PNG """ examples_arguments_syntax_dir = os.path.abspath(os.path.dirname(__file__)) for filename in os.listdir(examples_arguments_syntax_dir): name, ext = os.path.splitext(filename) if name.startswith("_") or ext != ".py": continue yield { "name": name, "filename": os.path.join(examples_arguments_syntax_dir, filename), "use_svg": name in SVG_EXAMPLES } altair-5.0.1/tests/examples_arguments_syntax/airport_connections.py000066400000000000000000000037611443422213100261030ustar00rootroot00000000000000""" Connections Among U.S. Airports Interactive ------------------------------------------- This example shows all the connections between major U.S. airports. Lookup transformations are used to find the coordinates of each airport and connecting airports. Connections are displayed on mouseover via a single selection. """ # category: case studies import altair as alt from vega_datasets import data # Since these data are each more than 5,000 rows we'll import from the URLs airports = data.airports.url flights_airport = data.flights_airport.url states = alt.topo_feature(data.us_10m.url, feature="states") # Create mouseover selection select_city = alt.selection_point( on="mouseover", nearest=True, fields=["origin"], empty=False ) # Define which attributes to lookup from airports.csv lookup_data = alt.LookupData( airports, key="iata", fields=["state", "latitude", "longitude"] ) background = alt.Chart(states).mark_geoshape( fill="lightgray", stroke="white" ).properties( width=750, height=500 ).project("albersUsa") connections = alt.Chart(flights_airport).mark_rule(opacity=0.35).encode( latitude="latitude:Q", longitude="longitude:Q", latitude2="lat2:Q", longitude2="lon2:Q" ).transform_lookup( lookup="origin", from_=lookup_data ).transform_lookup( lookup="destination", from_=lookup_data, as_=["state", "lat2", "lon2"] ).transform_filter( select_city ) points = alt.Chart(flights_airport).mark_circle().encode( latitude="latitude:Q", longitude="longitude:Q", size=alt.Size("routes:Q", scale=alt.Scale(range=[0, 1000]), legend=None), order=alt.Order("routes:Q", sort="descending"), tooltip=["origin:N", "routes:Q"] ).transform_aggregate( routes="count()", groupby=["origin"] ).transform_lookup( lookup="origin", from_=lookup_data ).transform_filter( (alt.datum.state != "PR") & (alt.datum.state != "VI") ).add_params( select_city ) (background + connections + points).configure_view(stroke=None) altair-5.0.1/tests/examples_arguments_syntax/annual_weather_heatmap.py000066400000000000000000000012071443422213100265060ustar00rootroot00000000000000""" Annual Weather Heatmap ---------------------- """ # category: tables import altair as alt from vega_datasets import data source = data.seattle_weather() alt.Chart(source, title="Daily Max Temperatures (C) in Seattle, WA").mark_rect().encode( x=alt.X("date(date):O", title="Day", axis=alt.Axis(format="%e", labelAngle=0)), y=alt.Y("month(date):O", title="Month"), color=alt.Color("max(temp_max)", legend=alt.Legend(title=None)), tooltip=[ alt.Tooltip("monthdate(date)", title="Date"), alt.Tooltip("max(temp_max)", title="Max Temp"), ], ).configure_view(step=13, strokeWidth=0).configure_axis(domain=False) altair-5.0.1/tests/examples_arguments_syntax/anscombe_plot.py000066400000000000000000000011771443422213100246450ustar00rootroot00000000000000""" Anscombe's Quartet ------------------ This example shows how to use the column channel to make a trellis plot. Anscombe's Quartet is a famous dataset constructed by Francis Anscombe. Common summary statistics are identical for each subset of the data, despite the subsets having vastly different characteristics. """ # category: case studies import altair as alt from vega_datasets import data source = data.anscombe() alt.Chart(source).mark_circle().encode( alt.X('X', scale=alt.Scale(zero=False)), alt.Y('Y', scale=alt.Scale(zero=False)), alt.Facet('Series', columns=2), ).properties( width=180, height=180, ) altair-5.0.1/tests/examples_arguments_syntax/area_chart_gradient.py000066400000000000000000000014131443422213100257570ustar00rootroot00000000000000""" Area Chart with Gradient ------------------------ This example shows how to make an area chart with a gradient fill. For more information about gradient options see the Vega-Lite `Gradient documentation `_. """ # category: area charts import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).transform_filter( 'datum.symbol==="GOOG"' ).mark_area( line={'color':'darkgreen'}, color=alt.Gradient( gradient='linear', stops=[alt.GradientStop(color='white', offset=0), alt.GradientStop(color='darkgreen', offset=1)], x1=1, x2=1, y1=1, y2=0 ) ).encode( alt.X('date:T'), alt.Y('price:Q') )altair-5.0.1/tests/examples_arguments_syntax/bar_and_line_with_dual_axis.py000066400000000000000000000007641443422213100275020ustar00rootroot00000000000000""" Bar Chart with Line on Dual Axis -------------------------------- This example shows how to combine two plots and keep their axes. For a more polished version of this chart, see :ref:`gallery_wheat_wages`. """ # category: bar charts import altair as alt from vega_datasets import data source = data.wheat() base = alt.Chart(source).encode(x='year:O') bar = base.mark_bar().encode(y='wheat:Q') line = base.mark_line(color='red').encode( y='wages:Q' ) (bar + line).properties(width=600) altair-5.0.1/tests/examples_arguments_syntax/bar_chart_horizontal.py000066400000000000000000000005231443422213100262100ustar00rootroot00000000000000""" Horizontal Bar Chart -------------------- This example is a bar chart drawn horizontally by putting the quantitative value on the x axis. """ # category: bar charts import altair as alt from vega_datasets import data source = data.wheat() alt.Chart(source).mark_bar().encode( x='wheat:Q', y="year:O" ).properties(height=700) altair-5.0.1/tests/examples_arguments_syntax/bar_chart_sorted.py000066400000000000000000000004511443422213100253170ustar00rootroot00000000000000""" Sorted Bar Chart ================ This example shows a bar chart sorted by a calculated value. """ # category: bar charts import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( x='sum(yield):Q', y=alt.Y('site:N', sort='-x') ) altair-5.0.1/tests/examples_arguments_syntax/bar_chart_trellis_compact.py000066400000000000000000000041351443422213100272060ustar00rootroot00000000000000""" Compact Trellis Grid of Bar Charts ================================== This example shows a simple grid of bar charts to compare performance data.. """ # category: bar charts import altair as alt import pandas as pd source = pd.DataFrame( [ {"a": "a1", "b": "b1", "c": "x", "p": "0.14"}, {"a": "a1", "b": "b1", "c": "y", "p": "0.60"}, {"a": "a1", "b": "b1", "c": "z", "p": "0.03"}, {"a": "a1", "b": "b2", "c": "x", "p": "0.80"}, {"a": "a1", "b": "b2", "c": "y", "p": "0.38"}, {"a": "a1", "b": "b2", "c": "z", "p": "0.55"}, {"a": "a1", "b": "b3", "c": "x", "p": "0.11"}, {"a": "a1", "b": "b3", "c": "y", "p": "0.58"}, {"a": "a1", "b": "b3", "c": "z", "p": "0.79"}, {"a": "a2", "b": "b1", "c": "x", "p": "0.83"}, {"a": "a2", "b": "b1", "c": "y", "p": "0.87"}, {"a": "a2", "b": "b1", "c": "z", "p": "0.67"}, {"a": "a2", "b": "b2", "c": "x", "p": "0.97"}, {"a": "a2", "b": "b2", "c": "y", "p": "0.84"}, {"a": "a2", "b": "b2", "c": "z", "p": "0.90"}, {"a": "a2", "b": "b3", "c": "x", "p": "0.74"}, {"a": "a2", "b": "b3", "c": "y", "p": "0.64"}, {"a": "a2", "b": "b3", "c": "z", "p": "0.19"}, {"a": "a3", "b": "b1", "c": "x", "p": "0.57"}, {"a": "a3", "b": "b1", "c": "y", "p": "0.35"}, {"a": "a3", "b": "b1", "c": "z", "p": "0.49"}, {"a": "a3", "b": "b2", "c": "x", "p": "0.91"}, {"a": "a3", "b": "b2", "c": "y", "p": "0.38"}, {"a": "a3", "b": "b2", "c": "z", "p": "0.91"}, {"a": "a3", "b": "b3", "c": "x", "p": "0.99"}, {"a": "a3", "b": "b3", "c": "y", "p": "0.80"}, {"a": "a3", "b": "b3", "c": "z", "p": "0.37"}, ] ) alt.Chart(source, width=60, height=alt.Step(8)).mark_bar().encode( y=alt.Y("c:N", axis=None), x=alt.X("p:Q", title=None, axis=alt.Axis(format="%")), color=alt.Color( "c:N", title="settings", legend=alt.Legend(orient="bottom", titleOrient="left") ), row=alt.Row("a:N", title="Factor A", header=alt.Header(labelAngle=0)), column=alt.Column("b:N", title="Factor B"), ) altair-5.0.1/tests/examples_arguments_syntax/bar_chart_with_highlighted_bar.py000066400000000000000000000012231443422213100301540ustar00rootroot00000000000000""" Bar Chart with Highlighted Bar ------------------------------ This example shows a basic bar chart with a single bar highlighted. """ # category: bar charts import altair as alt from vega_datasets import data source = data.wheat() alt.Chart(source).mark_bar().encode( x='year:O', y="wheat:Q", # The highlight will be set on the result of a conditional statement color=alt.condition( alt.datum.year == 1810, # If the year is 1810 this test returns True, alt.value('orange'), # which sets the bar orange. alt.value('steelblue') # And if it's not true it sets the bar steelblue. ) ).properties(width=600) altair-5.0.1/tests/examples_arguments_syntax/bar_chart_with_highlighted_segment.py000066400000000000000000000013171443422213100310560ustar00rootroot00000000000000""" Bar Chart with Highlighted Segment ---------------------------------- This example shows a bar chart that highlights values beyond a threshold. """ # category: bar charts import altair as alt import pandas as pd from vega_datasets import data source = data.wheat() threshold = pd.DataFrame([{"threshold": 90}]) bars = alt.Chart(source).mark_bar().encode( x="year:O", y="wheat:Q", ) highlight = alt.Chart(source).mark_bar(color="#e45755").encode( x='year:O', y='baseline:Q', y2='wheat:Q' ).transform_filter( alt.datum.wheat > 90 ).transform_calculate("baseline", "90") rule = alt.Chart(threshold).mark_rule().encode( y='threshold:Q' ) (bars + highlight + rule).properties(width=600) altair-5.0.1/tests/examples_arguments_syntax/bar_chart_with_labels.py000066400000000000000000000005611443422213100263160ustar00rootroot00000000000000""" Bar Chart with Labels ===================== This example shows a basic horizontal bar chart with labels created with Altair. """ # category: bar charts import altair as alt from vega_datasets import data source = data.wheat() base = alt.Chart(source).encode( x='wheat', y="year:O", text='wheat' ) base.mark_bar() + base.mark_text(align='left', dx=2) altair-5.0.1/tests/examples_arguments_syntax/bar_chart_with_mean_line.py000066400000000000000000000006411443422213100270020ustar00rootroot00000000000000""" Bar Chart with Line at Mean --------------------------- This example shows the mean value overlayed on a bar chart. """ # category: bar charts import altair as alt from vega_datasets import data source = data.wheat() bar = alt.Chart(source).mark_bar().encode( x='year:O', y='wheat:Q' ) rule = alt.Chart(source).mark_rule(color='red').encode( y='mean(wheat):Q' ) (bar + rule).properties(width=600) altair-5.0.1/tests/examples_arguments_syntax/bar_chart_with_negatives.py000066400000000000000000000010151443422213100270340ustar00rootroot00000000000000""" Bar Chart with Negative Values ============================== This example shows a bar chart with both positive and negative values. """ # category: bar charts import altair as alt from vega_datasets import data source = data.us_employment() alt.Chart(source).mark_bar().encode( x="month:T", y="nonfarm_change:Q", color=alt.condition( alt.datum.nonfarm_change > 0, alt.value("steelblue"), # The positive color alt.value("orange") # The negative color ) ).properties(width=600) altair-5.0.1/tests/examples_arguments_syntax/bar_rounded.py000066400000000000000000000006161443422213100243010ustar00rootroot00000000000000""" Bar Chart with Rounded Edges ---------------------------- This example shows how to create a bar chart with rounded edges. """ # category: bar charts import altair as alt from vega_datasets import data source = data.seattle_weather() alt.Chart(source).mark_bar( cornerRadiusTopLeft=3, cornerRadiusTopRight=3 ).encode( x='month(date):O', y='count():Q', color='weather:N' ) altair-5.0.1/tests/examples_arguments_syntax/bar_with_rolling_mean.py000066400000000000000000000012431443422213100263370ustar00rootroot00000000000000""" Bar Chart with Rolling Mean --------------------------- A bar chart overlayed with a rolling mean. In this example the average of values over the previous decade is displayed as a line. """ # category: bar charts import altair as alt from vega_datasets import data source = data.wheat() bar = alt.Chart(source).mark_bar().encode( x='year:O', y='wheat:Q' ) line = alt.Chart(source).mark_line(color='red').transform_window( # The field to average rolling_mean='mean(wheat)', # The number of values before and after the current value to include. frame=[-9, 0] ).encode( x='year:O', y='rolling_mean:Q' ) (bar + line).properties(width=600) altair-5.0.1/tests/examples_arguments_syntax/beckers_barley_trellis_plot.py000066400000000000000000000022371443422213100275660ustar00rootroot00000000000000""" Becker's Barley Trellis Plot ---------------------------- The example demonstrates the trellis charts created by Richard Becker, William Cleveland and others in the 1990s. Using the visualization technique below they identified an anomoly in a widely used agriculatural dataset, which they termed `"The Morris Mistake." `_. It became their favored way of showcasing the power of this pioneering plot. """ # category: case studies import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source, title="The Morris Mistake").mark_point().encode( alt.X( 'yield:Q', title="Barley Yield (bushels/acre)", scale=alt.Scale(zero=False), axis=alt.Axis(grid=False) ), alt.Y( 'variety:N', title="", sort='-x', axis=alt.Axis(grid=True) ), color=alt.Color('year:N', legend=alt.Legend(title="Year")), row=alt.Row( 'site:N', title="", sort=alt.EncodingSortField(field='yield', op='sum', order='descending'), ) ).properties( height=alt.Step(20) ).configure_view(stroke="transparent") altair-5.0.1/tests/examples_arguments_syntax/beckers_barley_wrapped_facet.py000066400000000000000000000013461443422213100276560ustar00rootroot00000000000000""" Becker's Barley Trellis Plot (Wrapped Facet) -------------------------------------------- The example demonstrates the trellis charts created by Richard Becker, William Cleveland and others in the 1990s. This is the Altair replicate of `the VegaLite version `_ demonstrating the usage of `columns` argument to create wrapped facet. """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.barley.url alt.Chart(source).mark_point().encode( alt.X('median(yield):Q', scale=alt.Scale(zero=False)), y='variety:O', color='year:N', facet=alt.Facet('site:O', columns=2), ).properties( width=200, height=100, ) altair-5.0.1/tests/examples_arguments_syntax/boxplot.py000066400000000000000000000010361443422213100235010ustar00rootroot00000000000000""" Boxplot with Min/Max Whiskers ------------------------------ This example shows how to make a boxplot using US Population data from 2000. Note that the default value of the `extent` property is 1.5, which represents the convention of extending the whiskers to the furthest points within 1.5 * IQR from the first and third quartile. """ # category: distributions import altair as alt from vega_datasets import data source = data.population.url alt.Chart(source).mark_boxplot(extent='min-max').encode( x='age:O', y='people:Q' ) altair-5.0.1/tests/examples_arguments_syntax/bubble_plot.py000066400000000000000000000004501443422213100243020ustar00rootroot00000000000000""" Bubble Plot ----------------- This example shows how to make a bubble plot. """ # category: scatter plots import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_point().encode( x='Horsepower', y='Miles_per_Gallon', size='Acceleration' ) altair-5.0.1/tests/examples_arguments_syntax/bump_chart.py000066400000000000000000000013731443422213100241420ustar00rootroot00000000000000""" Bump Chart ---------- This example shows a bump chart. The data is first grouped into six-month intervals using pandas. The ranks are computed by Altair using a window transform. """ # category: line charts import altair as alt from vega_datasets import data import pandas as pd stocks = data.stocks() source = stocks.groupby([pd.Grouper(key="date", freq="6M"),"symbol"]).mean().reset_index() alt.Chart(source).mark_line(point = True).encode( x = alt.X("date:O", timeUnit="yearmonth", title="date"), y="rank:O", color=alt.Color("symbol:N") ).transform_window( rank="rank()", sort=[alt.SortField("price", order="descending")], groupby=["date"] ).properties( title="Bump Chart for Stock Prices", width=600, height=150, )altair-5.0.1/tests/examples_arguments_syntax/candlestick_chart.py000066400000000000000000000023431443422213100254610ustar00rootroot00000000000000""" Candlestick Chart ================= A candlestick chart inspired from `Protovis `_. This example shows the performance of the Chicago Board Options Exchange `Volatility Index `_ (VIX) in the summer of 2009. The thick bar represents the opening and closing prices, while the thin bar shows intraday high and low prices; if the index closed higher on a given day, the bars are colored green rather than red. """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.ohlc() open_close_color = alt.condition("datum.open <= datum.close", alt.value("#06982d"), alt.value("#ae1325")) base = alt.Chart(source).encode( alt.X('date:T', axis=alt.Axis( format='%m/%d', labelAngle=-45, title='Date in 2009' ) ), color=open_close_color ) rule = base.mark_rule().encode( alt.Y( 'low:Q', title='Price', scale=alt.Scale(zero=False), ), alt.Y2('high:Q') ) bar = base.mark_bar().encode( alt.Y('open:Q'), alt.Y2('close:Q') ) rule + baraltair-5.0.1/tests/examples_arguments_syntax/choropleth.py000066400000000000000000000007431443422213100241650ustar00rootroot00000000000000""" Choropleth Map ============== A choropleth map of unemployment rate per county in the US """ # category: maps import altair as alt from vega_datasets import data counties = alt.topo_feature(data.us_10m.url, 'counties') source = data.unemployment.url alt.Chart(counties).mark_geoshape().encode( color='rate:Q' ).transform_lookup( lookup='id', from_=alt.LookupData(source, 'id', ['rate']) ).project( type='albersUsa' ).properties( width=500, height=300 ) altair-5.0.1/tests/examples_arguments_syntax/choropleth_repeat.py000066400000000000000000000012641443422213100255240ustar00rootroot00000000000000""" Repeated Choropleth Map ======================= Three choropleths representing disjoint data from the same table. """ # category: maps import altair as alt from vega_datasets import data states = alt.topo_feature(data.us_10m.url, 'states') source = data.population_engineers_hurricanes.url variable_list = ['population', 'engineers', 'hurricanes'] alt.Chart(states).mark_geoshape().encode( alt.Color(alt.repeat('row'), type='quantitative') ).transform_lookup( lookup='id', from_=alt.LookupData(source, 'id', variable_list) ).properties( width=500, height=300 ).project( type='albersUsa' ).repeat( row=variable_list ).resolve_scale( color='independent' ) altair-5.0.1/tests/examples_arguments_syntax/co2_concentration.py000066400000000000000000000032321443422213100254230ustar00rootroot00000000000000""" Atmospheric CO2 Concentration ----------------------------- This example is a fully developed line chart that uses a window transformation. It was inspired by `Gregor Aisch's work at datawrapper `_. """ # category: case studies import altair as alt from vega_datasets import data source = data.co2_concentration.url base = alt.Chart( source, title="Carbon Dioxide in the Atmosphere" ).transform_calculate( year="year(datum.Date)" ).transform_calculate( decade="floor(datum.year / 10)" ).transform_calculate( scaled_date="(datum.year % 10) + (month(datum.Date)/12)" ).transform_window( first_date='first_value(scaled_date)', last_date='last_value(scaled_date)', sort=[{"field": "scaled_date", "order": "ascending"}], groupby=['decade'], frame=[None, None] ).transform_calculate( end="datum.first_date === datum.scaled_date ? 'first' : datum.last_date === datum.scaled_date ? 'last' : null" ).encode( x=alt.X( "scaled_date:Q", axis=alt.Axis(title="Year into Decade", tickCount=11) ), y=alt.Y( "CO2:Q", title="CO2 concentration in ppm", scale=alt.Scale(zero=False) ) ) line = base.mark_line().encode( color=alt.Color( "decade:O", scale=alt.Scale(scheme="magma"), legend=None ) ) text = base.encode(text="year:N") start_year = text.transform_filter( alt.datum.end == 'first' ).mark_text(baseline="top") end_year = text.transform_filter( alt.datum.end == 'last' ).mark_text(baseline="bottom") (line + start_year + end_year).configure_text( align="left", dx=1, dy=3 ).properties(width=600, height=375) altair-5.0.1/tests/examples_arguments_syntax/comet_chart.py000066400000000000000000000026321443422213100243050ustar00rootroot00000000000000""" Comet Chart ----------- Inspired by `Zan Armstrong's comet chart `_ this plot uses ``mark_trail`` to visualize change of grouped data over time. A more elaborate example and explanation of creating comet charts in Altair is shown in `this blogpost `_. """ # category: advanced calculations import altair as alt import vega_datasets ( alt.Chart(vega_datasets.data.barley.url) .transform_pivot("year", value="yield", groupby=["variety", "site"]) .transform_fold(["1931", "1932"], as_=["year", "yield"]) .transform_calculate(calculate="datum['1932'] - datum['1931']", as_="delta") .mark_trail() .encode( x=alt.X('year:O', title=None), y=alt.Y('variety:N', title='Variety'), size=alt.Size('yield:Q', scale=alt.Scale(range=[0, 12]), legend=alt.Legend(values=[20, 60], title='Barley Yield (bushels/acre)')), color=alt.Color('delta:Q', scale=alt.Scale(domainMid=0), legend=alt.Legend(title='Yield Delta (%)')), tooltip=alt.Tooltip(['year:O', 'yield:Q']), column=alt.Column('site:N', title='Site') ) .configure_view(stroke=None) .configure_legend(orient='bottom', direction='horizontal') .properties(title='Barley Yield comparison between 1932 and 1931') ) altair-5.0.1/tests/examples_arguments_syntax/connected_scatterplot.py000066400000000000000000000013301443422213100263750ustar00rootroot00000000000000""" Connected Scatter Plot (Lines with Custom Paths) ------------------------------------------------ This example show how the order encoding can be used to draw a custom path. The dataset tracks miles driven per capita along with gas prices annually from 1956 to 2010. It is based on Hannah Fairfield's article 'Driving Shifts Into Reverse'. See https://archive.nytimes.com/www.nytimes.com/imagepages/2010/05/02/business/02metrics.html for the original. """ # category: scatter plots import altair as alt from vega_datasets import data source = data.driving() alt.Chart(source).mark_line(point=True).encode( alt.X('miles', scale=alt.Scale(zero=False)), alt.Y('gas', scale=alt.Scale(zero=False)), order='year' ) altair-5.0.1/tests/examples_arguments_syntax/cumulative_count_chart.py000066400000000000000000000007351443422213100265660ustar00rootroot00000000000000""" Cumulative Count Chart ---------------------- This example shows an area chart with cumulative count. Adapted from https://vega.github.io/vega-lite/examples/area_cumulative_freq.html """ # category: distributions import altair as alt from vega_datasets import data source = data.movies.url alt.Chart(source).transform_window( cumulative_count="count()", sort=[{"field": "IMDB_Rating"}], ).mark_area().encode( x="IMDB_Rating:Q", y="cumulative_count:Q" ) altair-5.0.1/tests/examples_arguments_syntax/dendrogram.py000066400000000000000000000100141443422213100241300ustar00rootroot00000000000000""" Dendrogram of Hierarchical Clustering ------------------------------------- This is a dendrogram from the result of a hierarchical clustering. It's based on the example from https://scikit-learn.org/stable/auto_examples/cluster/plot_agglomerative_dendrogram.html """ # category: case studies import pandas as pd import altair as alt import numpy as np # the variable `den` shown below is an exemplary output of `scipy.cluster.hierarchy.dendrogram` # (https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.dendrogram.html#scipy.cluster.hierarchy.dendrogram) # where the dendrogram itself is truncated such that no more than 3 levels of the dendrogram tree are shown. den = { 'dcoord': [[0.0, 0.8187388676087964, 0.8187388676087964, 0.0], [0.0, 1.105139508538779, 1.105139508538779, 0.0], [0.8187388676087964, 1.3712698320830048, 1.3712698320830048, 1.105139508538779], [0.0, 0.9099819926189507, 0.9099819926189507, 0.0], [0.0, 1.2539936203984452, 1.2539936203984452, 0.0], [0.9099819926189507, 1.9187528699821954, 1.9187528699821954, 1.2539936203984452], [1.3712698320830048, 3.828052620290243, 3.828052620290243, 1.9187528699821954], [0.0, 1.7604450194955439, 1.7604450194955439, 0.0], [0.0, 1.845844754344974, 1.845844754344974, 0.0], [1.7604450194955439, 4.847708507921838, 4.847708507921838, 1.845844754344974], [0.0, 2.8139388316471536, 2.8139388316471536, 0.0], [0.0, 2.8694176394568705, 2.8694176394568705, 0.0], [2.8139388316471536, 6.399406819518539, 6.399406819518539, 2.8694176394568705], [4.847708507921838, 12.300396052792589, 12.300396052792589, 6.399406819518539], [3.828052620290243, 32.44760699959244, 32.44760699959244, 12.300396052792589]], 'icoord': [[5.0, 5.0, 15.0, 15.0], [25.0, 25.0, 35.0, 35.0], [10.0, 10.0, 30.0, 30.0], [45.0, 45.0, 55.0, 55.0], [65.0, 65.0, 75.0, 75.0], [50.0, 50.0, 70.0, 70.0], [20.0, 20.0, 60.0, 60.0], [85.0, 85.0, 95.0, 95.0], [105.0, 105.0, 115.0, 115.0], [90.0, 90.0, 110.0, 110.0], [125.0, 125.0, 135.0, 135.0], [145.0, 145.0, 155.0, 155.0], [130.0, 130.0, 150.0, 150.0], [100.0, 100.0, 140.0, 140.0], [40.0, 40.0, 120.0, 120.0]], 'ivl': [ '(7)', '(8)', '41', '(5)', '(10)', '(7)', '(4)', '(8)', '(9)', '(15)', '(5)', '(7)', '(4)', '(22)', '(15)', '(23)' ], } def get_leaf_loc(den): """ Get the location of the leaves """ _from = int(np.array(den["icoord"]).min()) _to = int(np.array(den["icoord"]).max() + 1) return range(_from, _to, 10) def get_df_coord(den): """ Get coordinate dataframe. """ # if you view the dendrogram as a collection of upside-down "U" shapes, then # we can regard the 4 corners of the upside-down "U" as points 1, 2, 3 and 4. cols_xk = ["xk1", "xk2", "xk3", "xk4"] cols_yk = ["yk1", "yk2", "yk3", "yk4"] df_coord = pd.merge( pd.DataFrame(den["icoord"], columns=cols_xk), pd.DataFrame(den["dcoord"], columns=cols_yk), left_index=True, right_index=True ) return df_coord source = get_df_coord(den) base = alt.Chart(source) # the U shape is composed of a shoulder plus two arms shoulder = base.mark_rule().encode( alt.X("xk2:Q", title=""), alt.X2("xk3:Q"), alt.Y("yk2:Q", title="") ) arm1 = base.mark_rule().encode( alt.X("xk1:Q"), alt.Y("yk1:Q"), alt.Y2("yk2:Q") ) arm2 = base.mark_rule().encode( alt.X("xk3:Q"), alt.Y("yk3:Q"), alt.Y2("yk4:Q") ) chart_den = shoulder + arm1 + arm2 df_text = pd.DataFrame(dict(labels=den["ivl"], x=get_leaf_loc(den))) chart_text = alt.Chart( df_text ).mark_text( dy=0, angle=0, align="center" ).encode( x = alt.X("x:Q", axis={"grid":False, "title":"Number of points in nodes"}), text = alt.Text("labels:N") ) (chart_den & chart_text).resolve_scale( x="shared" ).configure( padding={"top":10,"left":10} ).configure_concat( spacing=0 ).configure_axis( labels=False, ticks=False, grid=False ).properties( title="Hierarchical Clustering Dendrogram" )altair-5.0.1/tests/examples_arguments_syntax/density_facet.py000066400000000000000000000011751443422213100246370ustar00rootroot00000000000000""" Faceted Density Estimates ------------------------- Density estimates of measurements for each iris flower feature """ # category: distributions import altair as alt from vega_datasets import data source = data.iris() alt.Chart(source).transform_fold( ['petalWidth', 'petalLength', 'sepalWidth', 'sepalLength'], as_ = ['Measurement_type', 'value'] ).transform_density( density='value', bandwidth=0.3, groupby=['Measurement_type'], extent= [0, 8] ).mark_area().encode( alt.X('value:Q'), alt.Y('density:Q'), alt.Row('Measurement_type:N') ).properties(width=300, height=50) altair-5.0.1/tests/examples_arguments_syntax/density_stack.py000066400000000000000000000020021443422213100246500ustar00rootroot00000000000000""" Stacked Density Estimates ------------------------- To plot a stacked graph of estimates, use a shared ``extent`` and a fixed number of subdivision ``steps`` to ensure that the points for each area align well. Density estimates of measurements for each iris flower feature are plot in a stacked method. In addition, setting ``counts`` to true multiplies the densities by the number of data points in each group, preserving proportional differences. """ # category: distributions import altair as alt from vega_datasets import data source = data.iris() alt.Chart(source).transform_fold( ['petalWidth', 'petalLength', 'sepalWidth', 'sepalLength'], as_ = ['Measurement_type', 'value'] ).transform_density( density='value', bandwidth=0.3, groupby=['Measurement_type'], extent= [0, 8], counts = True, steps=200 ).mark_area().encode( alt.X('value:Q'), alt.Y('density:Q', stack='zero'), alt.Color('Measurement_type:N') ).properties(width=400, height=100) altair-5.0.1/tests/examples_arguments_syntax/diverging_stacked_bar_chart.py000066400000000000000000000210401443422213100274700ustar00rootroot00000000000000""" Diverging Stacked Bar Chart --------------------------- This example shows a diverging stacked bar chart for sentiments towards a set of eight questions, displayed as percentages with neutral responses straddling the 0% mark. """ # category: bar charts import altair as alt source = alt.pd.DataFrame([ { "question": "Question 1", "type": "Strongly disagree", "value": 24, "percentage": 0.7, "percentage_start": -19.1, "percentage_end": -18.4 }, { "question": "Question 1", "type": "Disagree", "value": 294, "percentage": 9.1, "percentage_start": -18.4, "percentage_end": -9.2 }, { "question": "Question 1", "type": "Neither agree nor disagree", "value": 594, "percentage": 18.5, "percentage_start": -9.2, "percentage_end": 9.2 }, { "question": "Question 1", "type": "Agree", "value": 1927, "percentage": 59.9, "percentage_start": 9.2, "percentage_end": 69.2 }, { "question": "Question 1", "type": "Strongly agree", "value": 376, "percentage": 11.7, "percentage_start": 69.2, "percentage_end": 80.9 }, { "question": "Question 2", "type": "Strongly disagree", "value": 2, "percentage": 18.2, "percentage_start": -36.4, "percentage_end": -18.2 }, { "question": "Question 2", "type": "Disagree", "value": 2, "percentage": 18.2, "percentage_start": -18.2, "percentage_end": 0 }, { "question": "Question 2", "type": "Neither agree nor disagree", "value": 0, "percentage": 0, "percentage_start": 0, "percentage_end": 0 }, { "question": "Question 2", "type": "Agree", "value": 7, "percentage": 63.6, "percentage_start": 0, "percentage_end": 63.6 }, { "question": "Question 2", "type": "Strongly agree", "value": 11, "percentage": 0, "percentage_start": 63.6, "percentage_end": 63.6 }, { "question": "Question 3", "type": "Strongly disagree", "value": 2, "percentage": 20, "percentage_start": -30, "percentage_end": -10 }, { "question": "Question 3", "type": "Disagree", "value": 0, "percentage": 0, "percentage_start": -10, "percentage_end": -10 }, { "question": "Question 3", "type": "Neither agree nor disagree", "value": 2, "percentage": 20, "percentage_start": -10, "percentage_end": 10 }, { "question": "Question 3", "type": "Agree", "value": 4, "percentage": 40, "percentage_start": 10, "percentage_end": 50 }, { "question": "Question 3", "type": "Strongly agree", "value": 2, "percentage": 20, "percentage_start": 50, "percentage_end": 70 }, { "question": "Question 4", "type": "Strongly disagree", "value": 0, "percentage": 0, "percentage_start": -15.6, "percentage_end": -15.6 }, { "question": "Question 4", "type": "Disagree", "value": 2, "percentage": 12.5, "percentage_start": -15.6, "percentage_end": -3.1 }, { "question": "Question 4", "type": "Neither agree nor disagree", "value": 1, "percentage": 6.3, "percentage_start": -3.1, "percentage_end": 3.1 }, { "question": "Question 4", "type": "Agree", "value": 7, "percentage": 43.8, "percentage_start": 3.1, "percentage_end": 46.9 }, { "question": "Question 4", "type": "Strongly agree", "value": 6, "percentage": 37.5, "percentage_start": 46.9, "percentage_end": 84.4 }, { "question": "Question 5", "type": "Strongly disagree", "value": 0, "percentage": 0, "percentage_start": -10.4, "percentage_end": -10.4 }, { "question": "Question 5", "type": "Disagree", "value": 1, "percentage": 4.2, "percentage_start": -10.4, "percentage_end": -6.3 }, { "question": "Question 5", "type": "Neither agree nor disagree", "value": 3, "percentage": 12.5, "percentage_start": -6.3, "percentage_end": 6.3 }, { "question": "Question 5", "type": "Agree", "value": 16, "percentage": 66.7, "percentage_start": 6.3, "percentage_end": 72.9 }, { "question": "Question 5", "type": "Strongly agree", "value": 4, "percentage": 16.7, "percentage_start": 72.9, "percentage_end": 89.6 }, { "question": "Question 6", "type": "Strongly disagree", "value": 1, "percentage": 6.3, "percentage_start": -18.8, "percentage_end": -12.5 }, { "question": "Question 6", "type": "Disagree", "value": 1, "percentage": 6.3, "percentage_start": -12.5, "percentage_end": -6.3 }, { "question": "Question 6", "type": "Neither agree nor disagree", "value": 2, "percentage": 12.5, "percentage_start": -6.3, "percentage_end": 6.3 }, { "question": "Question 6", "type": "Agree", "value": 9, "percentage": 56.3, "percentage_start": 6.3, "percentage_end": 62.5 }, { "question": "Question 6", "type": "Strongly agree", "value": 3, "percentage": 18.8, "percentage_start": 62.5, "percentage_end": 81.3 }, { "question": "Question 7", "type": "Strongly disagree", "value": 0, "percentage": 0, "percentage_start": -10, "percentage_end": -10 }, { "question": "Question 7", "type": "Disagree", "value": 0, "percentage": 0, "percentage_start": -10, "percentage_end": -10 }, { "question": "Question 7", "type": "Neither agree nor disagree", "value": 1, "percentage": 20, "percentage_start": -10, "percentage_end": 10 }, { "question": "Question 7", "type": "Agree", "value": 4, "percentage": 80, "percentage_start": 10, "percentage_end": 90 }, { "question": "Question 7", "type": "Strongly agree", "value": 0, "percentage": 0, "percentage_start": 90, "percentage_end": 90 }, { "question": "Question 8", "type": "Strongly disagree", "value": 0, "percentage": 0, "percentage_start": 0, "percentage_end": 0 }, { "question": "Question 8", "type": "Disagree", "value": 0, "percentage": 0, "percentage_start": 0, "percentage_end": 0 }, { "question": "Question 8", "type": "Neither agree nor disagree", "value": 0, "percentage": 0, "percentage_start": 0, "percentage_end": 0 }, { "question": "Question 8", "type": "Agree", "value": 0, "percentage": 0, "percentage_start": 0, "percentage_end": 0 }, { "question": "Question 8", "type": "Strongly agree", "value": 2, "percentage": 100, "percentage_start": 0, "percentage_end": 100 } ]) color_scale = alt.Scale( domain=[ "Strongly disagree", "Disagree", "Neither agree nor disagree", "Agree", "Strongly agree" ], range=["#c30d24", "#f3a583", "#cccccc", "#94c6da", "#1770ab"] ) y_axis = alt.Axis( title='Question', offset=5, ticks=False, minExtent=60, domain=False ) alt.Chart(source).mark_bar().encode( x='percentage_start:Q', x2='percentage_end:Q', y=alt.Y('question:N', axis=y_axis), color=alt.Color( 'type:N', legend=alt.Legend( title='Response'), scale=color_scale, ) ) altair-5.0.1/tests/examples_arguments_syntax/donut_chart.py000066400000000000000000000010461443422213100243250ustar00rootroot00000000000000""" Donut Chart ----------- This example shows how to make a Donut Chart using ``mark_arc``. This is adapted from a corresponding Vega-Lite Example: `Donut Chart `_. """ # category: circular plots import pandas as pd import altair as alt source = pd.DataFrame({"category": [1, 2, 3, 4, 5, 6], "value": [4, 6, 10, 3, 7, 8]}) alt.Chart(source).mark_arc(innerRadius=50).encode( theta=alt.Theta(field="value", type="quantitative"), color=alt.Color(field="category", type="nominal"), ) altair-5.0.1/tests/examples_arguments_syntax/dot_dash_plot.py000066400000000000000000000023311443422213100246340ustar00rootroot00000000000000""" Dot Dash Plot ============= How to make the dot-dash plot presented in Edward Tufte's `Visual Display of Quantitative Information `_. Based on a JavaScript implementation by `g3o2 `_. """ # category: distributions import altair as alt from vega_datasets import data source = data.cars() # Configure the options common to all layers brush = alt.selection_interval() base = alt.Chart(source).add_params(brush) # Configure the points points = base.mark_point().encode( x=alt.X('Miles_per_Gallon', title=''), y=alt.Y('Horsepower', title=''), color=alt.condition(brush, 'Origin', alt.value('grey')) ) # Configure the ticks tick_axis = alt.Axis(labels=False, domain=False, ticks=False) x_ticks = base.mark_tick().encode( alt.X('Miles_per_Gallon', axis=tick_axis), alt.Y('Origin', title='', axis=tick_axis), color=alt.condition(brush, 'Origin', alt.value('lightgrey')) ) y_ticks = base.mark_tick().encode( alt.X('Origin', title='', axis=tick_axis), alt.Y('Horsepower', axis=tick_axis), color=alt.condition(brush, 'Origin', alt.value('lightgrey')) ) # Build the chart y_ticks | (points & x_ticks) altair-5.0.1/tests/examples_arguments_syntax/empirical_cumulative_distribution_function.py000066400000000000000000000007061443422213100327240ustar00rootroot00000000000000""" Empirical Cumulative Distribution Function ------------------------------------------ This example shows an empirical cumulative distribution function. """ # category: distributions import altair as alt from vega_datasets import data source = data.movies.url alt.Chart(source).transform_window( ecdf="cume_dist()", sort=[{"field": "IMDB_Rating"}], ).mark_line( interpolate="step-after" ).encode( x="IMDB_Rating:Q", y="ecdf:Q" ) altair-5.0.1/tests/examples_arguments_syntax/errorbars_with_ci.py000066400000000000000000000013531443422213100255230ustar00rootroot00000000000000""" Error Bars with Confidence Interval ====================================== This example shows how to show error bars using confidence intervals. The confidence intervals are computed internally in vega by a non-parametric `bootstrap of the mean `_. """ # category: uncertainties and trends import altair as alt from vega_datasets import data source = data.barley() error_bars = alt.Chart(source).mark_errorbar(extent='ci').encode( x=alt.X('yield:Q', scale=alt.Scale(zero=False)), y=alt.Y('variety:N') ) points = alt.Chart(source).mark_point(filled=True, color='black').encode( x=alt.X('yield:Q', aggregate='mean'), y=alt.Y('variety:N'), ) error_bars + points altair-5.0.1/tests/examples_arguments_syntax/errorbars_with_std.py000066400000000000000000000011601443422213100257160ustar00rootroot00000000000000""" Error Bars with Standard Deviation ---------------------------------- This example shows how to show error bars with standard deviation using crop yields data of different in the years of 1930s. """ # category: uncertainties and trends import altair as alt from vega_datasets import data source = data.barley() error_bars = alt.Chart(source).mark_errorbar(extent='stdev').encode( x=alt.X('yield:Q', scale=alt.Scale(zero=False)), y=alt.Y('variety:N') ) points = alt.Chart(source).mark_point(filled=True, color='black').encode( x=alt.X('yield:Q', aggregate='mean'), y=alt.Y('variety:N'), ) error_bars + points altair-5.0.1/tests/examples_arguments_syntax/falkensee.py000066400000000000000000000050121443422213100237450ustar00rootroot00000000000000""" Population of Falkensee from 1875 to 2014 ----------------------------------------- This example is a reproduction of the Falkensee plot found in the Vega-Lite examples. """ # category: case studies import altair as alt import pandas as pd source = [ {"year": "1875", "population": 1309}, {"year": "1890", "population": 1558}, {"year": "1910", "population": 4512}, {"year": "1925", "population": 8180}, {"year": "1933", "population": 15915}, {"year": "1939", "population": 24824}, {"year": "1946", "population": 28275}, {"year": "1950", "population": 29189}, {"year": "1964", "population": 29881}, {"year": "1971", "population": 26007}, {"year": "1981", "population": 24029}, {"year": "1985", "population": 23340}, {"year": "1989", "population": 22307}, {"year": "1990", "population": 22087}, {"year": "1991", "population": 22139}, {"year": "1992", "population": 22105}, {"year": "1993", "population": 22242}, {"year": "1994", "population": 22801}, {"year": "1995", "population": 24273}, {"year": "1996", "population": 25640}, {"year": "1997", "population": 27393}, {"year": "1998", "population": 29505}, {"year": "1999", "population": 32124}, {"year": "2000", "population": 33791}, {"year": "2001", "population": 35297}, {"year": "2002", "population": 36179}, {"year": "2003", "population": 36829}, {"year": "2004", "population": 37493}, {"year": "2005", "population": 38376}, {"year": "2006", "population": 39008}, {"year": "2007", "population": 39366}, {"year": "2008", "population": 39821}, {"year": "2009", "population": 40179}, {"year": "2010", "population": 40511}, {"year": "2011", "population": 40465}, {"year": "2012", "population": 40905}, {"year": "2013", "population": 41258}, {"year": "2014", "population": 41777}, ] source2 = [ {"start": "1933", "end": "1945", "event": "Nazi Rule"}, {"start": "1948", "end": "1989", "event": "GDR (East Germany)"}, ] source_df = pd.DataFrame(source) source2_df = pd.DataFrame(source2) line = alt.Chart(source_df).mark_line(color="#333").encode( x=alt.X("year:T", axis=alt.Axis(format="%Y"), title="Year"), y=alt.Y("population", title="Population"), ) point = line.mark_point(color="#333") rect = alt.Chart(source2_df).mark_rect().encode( x="start:T", x2="end:T", color=alt.Color("event:N", title="Event") ) (rect + line + point).properties( title="Population of Falkensee from 1875 to 2014", width=500, height=300 ) altair-5.0.1/tests/examples_arguments_syntax/filled_step_chart.py000066400000000000000000000006711443422213100254710ustar00rootroot00000000000000""" Filled Step Chart ----------------- This example shows Google's stock price over time as a step chart with its area filled in and its line emphasized. """ # category: line charts import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_area( color="lightblue", interpolate='step-after', line=True ).encode( x='date', y='price' ).transform_filter(alt.datum.symbol == 'GOOG') altair-5.0.1/tests/examples_arguments_syntax/gantt_chart.py000066400000000000000000000006171443422213100243140ustar00rootroot00000000000000""" Gantt Chart ----------------- This example shows how to make a simple Gantt chart. """ # category: advanced calculations import altair as alt import pandas as pd source = pd.DataFrame([ {"task": "A", "start": 1, "end": 3}, {"task": "B", "start": 3, "end": 8}, {"task": "C", "start": 8, "end": 10} ]) alt.Chart(source).mark_bar().encode( x='start', x2='end', y='task' ) altair-5.0.1/tests/examples_arguments_syntax/gapminder_bubble_plot.py000066400000000000000000000011751443422213100263350ustar00rootroot00000000000000""" Gapminder Bubble Plot ===================== This example shows how to make a bubble plot showing the correlation between health and income for 187 countries in the world (modified from an example in Lisa Charlotte Rost's blog post `'One Chart, Twelve Charting Libraries' `_. """ # category: case studies import altair as alt from vega_datasets import data source = data.gapminder_health_income.url alt.Chart(source).mark_circle().encode( alt.X('income:Q', scale=alt.Scale(type='log')), alt.Y('health:Q', scale=alt.Scale(zero=False)), size='population:Q' ) altair-5.0.1/tests/examples_arguments_syntax/groupby-map.py000066400000000000000000000017161443422213100242610ustar00rootroot00000000000000""" Grouped Points with Proportional Symbols Map ============================================ This is a layered geographic visualization that groups points by state. """ # category: maps import altair as alt from vega_datasets import data airports = data.airports.url states = alt.topo_feature(data.us_10m.url, feature='states') # US states background background = alt.Chart(states).mark_geoshape( fill='lightgray', stroke='white' ).properties( width=500, height=300 ).project('albersUsa') # Airports grouped by state points = alt.Chart(airports).transform_aggregate( latitude='mean(latitude)', longitude='mean(longitude)', count='count()', groupby=['state'] ).mark_circle().encode( longitude='longitude:Q', latitude='latitude:Q', size=alt.Size('count:Q', title='Number of Airports'), color=alt.value('steelblue'), tooltip=['state:N','count:Q'] ).properties( title='Number of airports in US' ) background + points altair-5.0.1/tests/examples_arguments_syntax/grouped_bar_chart.py000066400000000000000000000004551443422213100254700ustar00rootroot00000000000000""" Grouped Bar Chart ----------------- This example shows a grouped bar chart. """ # category: bar charts import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( x='year:O', y='sum(yield):Q', color='year:N', column='site:N' ) altair-5.0.1/tests/examples_arguments_syntax/grouped_bar_chart2.py000066400000000000000000000014201443422213100255430ustar00rootroot00000000000000""" Grouped Bar Chart with xOffset ------------------------------ Like :ref:`gallery_grouped_bar_chart`, this example shows a grouped bar chart. Whereas :ref:`gallery_grouped_bar_chart` used the ``column`` encoding channel, this example uses the ``xOffset`` encoding channel. This is adapted from a corresponding Vega-Lite Example: `Grouped Bar Chart `_. """ # category: bar charts import altair as alt import pandas as pd source = pd.DataFrame({"Category":list("AAABBBCCC"), "Group":list("xyzxyzxyz"), "Value":[0.1, 0.6, 0.9, 0.7, 0.2, 1.1, 0.6, 0.1, 0.2]}) alt.Chart(source).mark_bar().encode( x="Category:N", y="Value:Q", xOffset="Group:N", color="Group:N" ) altair-5.0.1/tests/examples_arguments_syntax/grouped_bar_chart_horizontal.py000066400000000000000000000005131443422213100277340ustar00rootroot00000000000000""" Horizontal Grouped Bar Chart ---------------------------- This example shows a horizontal grouped bar chart. """ # category: bar charts import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( x='sum(yield):Q', y='year:O', color='year:N', row='site:N' ) altair-5.0.1/tests/examples_arguments_syntax/grouped_bar_chart_with_error_bars.py000066400000000000000000000010141443422213100307330ustar00rootroot00000000000000""" Grouped Bar Chart with Error Bars --------------------------------- This example shows a grouped bar chart with error bars. """ # category: bar charts import altair as alt from vega_datasets import data source = data.barley() bars = alt.Chart().mark_bar().encode( x='year:O', y=alt.Y('mean(yield):Q', title='Mean Yield'), color='year:N', ) error_bars = alt.Chart().mark_errorbar(extent='ci').encode( x='year:O', y='yield:Q' ) alt.layer(bars, error_bars, data=source).facet( column='site:N' ) altair-5.0.1/tests/examples_arguments_syntax/heat_lane.py000066400000000000000000000026141443422213100237350ustar00rootroot00000000000000""" Heat Lane Chart --------------- This example shows how to make an alternative form of a histogram `designed at Google `_ with the goal of increasing accessibility. """ # category: distributions import altair as alt from vega_datasets import data source = data.cars.url chart = alt.Chart(source, title="Car horsepower", height=100, width=300).encode( x=alt.X( "bin_Horsepower_start:Q", title="Horsepower", axis=alt.Axis(grid=False) ), x2="bin_Horsepower_end:Q", y=alt.Y("y:O", axis=None), y2="y2", ).transform_bin( ["bin_Horsepower_start", "bin_Horsepower_end"], field='Horsepower' ).transform_aggregate( count='count()', groupby=["bin_Horsepower_start", "bin_Horsepower_end"] ).transform_bin( ["bin_count_start", "bin_count_end"], field='count' ).transform_calculate( y="datum.bin_count_end/2", y2="-datum.bin_count_end/2", ).transform_joinaggregate( max_bin_count_end="max(bin_count_end)", ) layer1 = chart.mark_bar(xOffset=1, x2Offset=-1, cornerRadius=3).encode( color=alt.Color("max_bin_count_end:O", scale=alt.Scale(scheme="lighttealblue"), title="Number of models") ) layer2 = chart.mark_bar(xOffset=1, x2Offset=-1, yOffset=-3, y2Offset=3).encode( color=alt.Color("bin_count_end:O", title="Number of models") ) layer1 + layer2 altair-5.0.1/tests/examples_arguments_syntax/hexbins.py000066400000000000000000000027561443422213100234640ustar00rootroot00000000000000""" Hexbin Chart ------------ This example shows a hexbin chart. """ # category: tables import altair as alt from vega_datasets import data source = data.seattle_weather() # Size of the hexbins size = 15 # Count of distinct x features xFeaturesCount = 12 # Count of distinct y features yFeaturesCount = 7 # Name of the x field xField = 'date' # Name of the y field yField = 'date' # the shape of a hexagon hexagon = "M0,-2.3094010768L2,-1.1547005384 2,1.1547005384 0,2.3094010768 -2,1.1547005384 -2,-1.1547005384Z" alt.Chart(source).mark_point(size=size**2, shape=hexagon).encode( x=alt.X('xFeaturePos:Q', axis=alt.Axis(title='Month', grid=False, tickOpacity=0, domainOpacity=0)), y=alt.Y('day(' + yField + '):O', axis=alt.Axis(title='Weekday', labelPadding=20, tickOpacity=0, domainOpacity=0)), stroke=alt.value('black'), strokeWidth=alt.value(0.2), fill=alt.Color('mean(temp_max):Q', scale=alt.Scale(scheme='darkblue')), tooltip=['month(' + xField + '):O', 'day(' + yField + '):O', 'mean(temp_max):Q'] ).transform_calculate( # This field is required for the hexagonal X-Offset xFeaturePos='(day(datum.' + yField + ') % 2) / 2 + month(datum.' + xField + ')' ).properties( # Exact scaling factors to make the hexbins fit width=size * xFeaturesCount * 2, height=size * yFeaturesCount * 1.7320508076, # 1.7320508076 is approx. sin(60°)*2 ).configure_view( strokeWidth=0 ) altair-5.0.1/tests/examples_arguments_syntax/histogram_heatmap.py000066400000000000000000000007001443422213100255030ustar00rootroot00000000000000""" 2D Histogram Heatmap -------------------- This example shows how to make a heatmap from binned quantitative data. """ # category: distributions import altair as alt from vega_datasets import data source = data.movies.url alt.Chart(source).mark_rect().encode( alt.X('IMDB_Rating:Q', bin=alt.Bin(maxbins=60)), alt.Y('Rotten_Tomatoes_Rating:Q', bin=alt.Bin(maxbins=40)), alt.Color('count():Q', scale=alt.Scale(scheme='greenblue')) ) altair-5.0.1/tests/examples_arguments_syntax/histogram_responsive.py000066400000000000000000000014471443422213100262720ustar00rootroot00000000000000""" Histogram with Responsive Bins ------------------------------ This shows an example of a histogram with bins that are responsive to a selection domain. Click and drag on the bottom panel to see the bins change on the top panel. """ # category: distributions import altair as alt from vega_datasets import data source = data.flights_5k.url brush = alt.selection_interval(encodings=['x']) base = alt.Chart(source).transform_calculate( time="hours(datum.date) + minutes(datum.date) / 60" ).mark_bar().encode( y='count():Q' ).properties( width=600, height=100 ) alt.vconcat( base.encode( alt.X('time:Q', bin=alt.Bin(maxbins=30, extent=brush), scale=alt.Scale(domain=brush) ) ), base.encode( alt.X('time:Q', bin=alt.Bin(maxbins=30)), ).add_params(brush) ) altair-5.0.1/tests/examples_arguments_syntax/histogram_scatterplot.py000066400000000000000000000005651443422213100264410ustar00rootroot00000000000000""" 2D Histogram Scatter Plot ------------------------- This example shows how to make a 2d histogram scatter plot. """ # category: distributions import altair as alt from vega_datasets import data source = data.movies.url alt.Chart(source).mark_circle().encode( alt.X('IMDB_Rating:Q', bin=True), alt.Y('Rotten_Tomatoes_Rating:Q', bin=True), size='count()' ) altair-5.0.1/tests/examples_arguments_syntax/histogram_with_a_global_mean_overlay.py000066400000000000000000000007401443422213100314240ustar00rootroot00000000000000""" Histogram with a Global Mean Overlay ------------------------------------ This example shows a histogram with a global mean overlay. """ # category: distributions import altair as alt from vega_datasets import data source = data.movies.url base = alt.Chart(source) bar = base.mark_bar().encode( x=alt.X('IMDB_Rating:Q', bin=True, axis=None), y='count()' ) rule = base.mark_rule(color='red').encode( x='mean(IMDB_Rating):Q', size=alt.value(5) ) bar + rule altair-5.0.1/tests/examples_arguments_syntax/horizon_graph.py000066400000000000000000000021621443422213100246640ustar00rootroot00000000000000""" Horizon Graph ------------- This example shows how to make a Horizon Graph with 2 layers. (See https://idl.cs.washington.edu/papers/horizon/ for more details on Horizon Graphs.) """ # category: area charts import altair as alt import pandas as pd source = pd.DataFrame([ {"x": 1, "y": 28}, {"x": 2, "y": 55}, {"x": 3, "y": 43}, {"x": 4, "y": 91}, {"x": 5, "y": 81}, {"x": 6, "y": 53}, {"x": 7, "y": 19}, {"x": 8, "y": 87}, {"x": 9, "y": 52}, {"x": 10, "y": 48}, {"x": 11, "y": 24}, {"x": 12, "y": 49}, {"x": 13, "y": 87}, {"x": 14, "y": 66}, {"x": 15, "y": 17}, {"x": 16, "y": 27}, {"x": 17, "y": 68}, {"x": 18, "y": 16}, {"x": 19, "y": 49}, {"x": 20, "y": 15} ]) area1 = alt.Chart(source).mark_area( clip=True, interpolate='monotone' ).encode( alt.X('x', scale=alt.Scale(zero=False, nice=False)), alt.Y('y', scale=alt.Scale(domain=[0, 50]), title='y'), opacity=alt.value(0.6) ).properties( width=500, height=75 ) area2 = area1.encode( alt.Y('ny:Q', scale=alt.Scale(domain=[0, 50])) ).transform_calculate( "ny", alt.datum.y - 50 ) area1 + area2 altair-5.0.1/tests/examples_arguments_syntax/horizontal_stacked_bar_chart.py000066400000000000000000000006271443422213100277130ustar00rootroot00000000000000""" Horizontal Stacked Bar Chart ============================ This is an example of a horizontal stacked bar chart using data which contains crop yields over different regions and different years in the 1930s. """ # category: bar charts import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( x='sum(yield)', y='variety', color='site' ) altair-5.0.1/tests/examples_arguments_syntax/image_tooltip.py000066400000000000000000000013321443422213100246450ustar00rootroot00000000000000""" Image Tooltip ------------- This example shows how to render images in tooltips. Either URLs or local file paths can be used to reference the images. To render the image, you must use the special column name "image" in your data and pass it as a list to the tooltip encoding. """ # category: interactive charts import altair as alt import pandas as pd source = pd.DataFrame.from_records( [{'a': 1, 'b': 1, 'image': 'https://altair-viz.github.io/_static/altair-logo-light.png'}, {'a': 2, 'b': 2, 'image': 'https://avatars.githubusercontent.com/u/11796929?s=200&v=4'}] ) alt.Chart(source).mark_circle(size=200).encode( x='a', y='b', tooltip=['image'] # Must be a list containing a field called "image" ) altair-5.0.1/tests/examples_arguments_syntax/interactive_brush.py000066400000000000000000000010331443422213100255270ustar00rootroot00000000000000""" Interactive Rectangular Brush ============================= This example shows how to add a simple rectangular brush to a scatter plot. By clicking and dragging on the plot, you can highlight points within the range. """ # category: interactive charts import altair as alt from vega_datasets import data source = data.cars() brush = alt.selection_interval() alt.Chart(source).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color=alt.condition(brush, 'Cylinders:O', alt.value('grey')), ).add_params(brush) altair-5.0.1/tests/examples_arguments_syntax/interactive_cross_highlight.py000066400000000000000000000023071443422213100275710ustar00rootroot00000000000000""" Interactive Chart with Cross-Highlight ====================================== This example shows an interactive chart where selections in one portion of the chart affect what is shown in other panels. Click on the bar chart to see a detail of the distribution in the upper panel. """ # category: interactive charts import altair as alt from vega_datasets import data source = data.movies.url pts = alt.selection_point(encodings=['x']) rect = alt.Chart(data.movies.url).mark_rect().encode( alt.X('IMDB_Rating:Q', bin=True), alt.Y('Rotten_Tomatoes_Rating:Q', bin=True), alt.Color('count()', scale=alt.Scale(scheme='greenblue'), legend=alt.Legend(title='Total Records') ) ) circ = rect.mark_point().encode( alt.ColorValue('grey'), alt.Size('count()', legend=alt.Legend(title='Records in Selection') ) ).transform_filter( pts ) bar = alt.Chart(source).mark_bar().encode( x='Major_Genre:N', y='count()', color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey")) ).properties( width=550, height=200 ).add_params(pts) alt.vconcat( rect + circ, bar ).resolve_legend( color="independent", size="independent" ) altair-5.0.1/tests/examples_arguments_syntax/interactive_layered_crossfilter.py000066400000000000000000000021361443422213100304550ustar00rootroot00000000000000""" Interactive Crossfilter ======================= This example shows a multi-panel view of the same data, where you can interactively select a portion of the data in any of the panels to highlight that portion in any of the other panels. """ # category: interactive charts import altair as alt from vega_datasets import data source = alt.UrlData( data.flights_2k.url, format={'parse': {'date': 'date'}} ) brush = alt.selection_interval(encodings=['x']) # Define the base chart, with the common parts of the # background and highlights base = alt.Chart().mark_bar().encode( x=alt.X(alt.repeat('column'), type='quantitative', bin=alt.Bin(maxbins=20)), y='count()' ).properties( width=160, height=130 ) # gray background with selection background = base.encode( color=alt.value('#ddd') ).add_params(brush) # blue highlights on the transformed data highlight = base.transform_filter(brush) # layer the two charts & repeat alt.layer( background, highlight, data=source ).transform_calculate( "time", "hours(datum.date)" ).repeat(column=["distance", "delay", "time"]) altair-5.0.1/tests/examples_arguments_syntax/interactive_legend.py000066400000000000000000000014371443422213100256520ustar00rootroot00000000000000""" Interactive Legend ------------------ The following shows how to create a chart with an interactive legend, by binding the selection to ``"legend"``. Such a binding only works with ``selection_point`` when projected over a single field or encoding. """ # category: interactive charts import altair as alt from vega_datasets import data source = data.unemployment_across_industries.url selection = alt.selection_point(fields=['series'], bind='legend') alt.Chart(source).mark_area().encode( alt.X('yearmonth(date):T', axis=alt.Axis(domain=False, format='%Y', tickSize=0)), alt.Y('sum(count):Q', stack='center', axis=None), alt.Color('series:N', scale=alt.Scale(scheme='category20b')), opacity=alt.condition(selection, alt.value(1), alt.value(0.2)) ).add_params( selection ) altair-5.0.1/tests/examples_arguments_syntax/interactive_scatter_plot.py000066400000000000000000000005701443422213100271140ustar00rootroot00000000000000""" Simple Interactive Colored Scatter Plot --------------------------------------- This example shows how to make an interactive scatter plot. """ # category: interactive charts import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_circle().encode( x='Horsepower', y='Miles_per_Gallon', color='Origin', ).interactive() altair-5.0.1/tests/examples_arguments_syntax/interval_selection.py000066400000000000000000000011461443422213100257050ustar00rootroot00000000000000""" Interval Selection ================== This is an example of creating a stacked chart for which the domain of the top chart can be selected by interacting with the bottom chart. """ # category: area charts import altair as alt from vega_datasets import data source = data.sp500.url brush = alt.selection_interval(encodings=['x']) base = alt.Chart(source).mark_area().encode( x = 'date:T', y = 'price:Q' ).properties( width=600, height=200 ) upper = base.encode( alt.X('date:T', scale=alt.Scale(domain=brush)) ) lower = base.properties( height=60 ).add_params(brush) upper & lower altair-5.0.1/tests/examples_arguments_syntax/iowa_electricity.py000066400000000000000000000012421443422213100253500ustar00rootroot00000000000000""" Iowa's Renewable Energy Boom ---------------------------- This example is a fully developed stacked chart using the sample dataset of Iowa's electricity sources. """ # category: case studies import altair as alt from vega_datasets import data source = data.iowa_electricity() alt.Chart(source, title="Iowa's renewable energy boom").mark_area().encode( x=alt.X( "year:T", title="Year" ), y=alt.Y( "net_generation:Q", stack="normalize", title="Share of net generation", axis=alt.Axis(format=".0%"), ), color=alt.Color( "source:N", legend=alt.Legend(title="Electricity source"), ) ) altair-5.0.1/tests/examples_arguments_syntax/isotype.py000066400000000000000000000142151443422213100235110ustar00rootroot00000000000000''' Isotype Visualization ===================== Isotype Visualization shows the distribution of animals across UK and US. Inspired by `Only An Ocean Between, 1943 `_. Population Live Stock, p.13. This is adapted from Vega-Lite example https://vega.github.io/editor/#/examples/vega-lite/isotype_bar_chart ''' # category: advanced calculations import altair as alt import pandas as pd source = pd.DataFrame([ {'country': 'Great Britain', 'animal': 'cattle'}, {'country': 'Great Britain', 'animal': 'cattle'}, {'country': 'Great Britain', 'animal': 'cattle'}, {'country': 'Great Britain', 'animal': 'pigs'}, {'country': 'Great Britain', 'animal': 'pigs'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'} ]) domains = ['person', 'cattle', 'pigs', 'sheep'] shape_scale = alt.Scale( domain=domains, range=[ 'M1.7 -1.7h-0.8c0.3 -0.2 0.6 -0.5 0.6 -0.9c0 -0.6 -0.4 -1 -1 -1c-0.6 0 -1 0.4 -1 1c0 0.4 0.2 0.7 0.6 0.9h-0.8c-0.4 0 -0.7 0.3 -0.7 0.6v1.9c0 0.3 0.3 0.6 0.6 0.6h0.2c0 0 0 0.1 0 0.1v1.9c0 0.3 0.2 0.6 0.3 0.6h1.3c0.2 0 0.3 -0.3 0.3 -0.6v-1.8c0 0 0 -0.1 0 -0.1h0.2c0.3 0 0.6 -0.3 0.6 -0.6v-2c0.2 -0.3 -0.1 -0.6 -0.4 -0.6z', 'M4 -2c0 0 0.9 -0.7 1.1 -0.8c0.1 -0.1 -0.1 0.5 -0.3 0.7c-0.2 0.2 1.1 1.1 1.1 1.2c0 0.2 -0.2 0.8 -0.4 0.7c-0.1 0 -0.8 -0.3 -1.3 -0.2c-0.5 0.1 -1.3 1.6 -1.5 2c-0.3 0.4 -0.6 0.4 -0.6 0.4c0 0.1 0.3 1.7 0.4 1.8c0.1 0.1 -0.4 0.1 -0.5 0c0 0 -0.6 -1.9 -0.6 -1.9c-0.1 0 -0.3 -0.1 -0.3 -0.1c0 0.1 -0.5 1.4 -0.4 1.6c0.1 0.2 0.1 0.3 0.1 0.3c0 0 -0.4 0 -0.4 0c0 0 -0.2 -0.1 -0.1 -0.3c0 -0.2 0.3 -1.7 0.3 -1.7c0 0 -2.8 -0.9 -2.9 -0.8c-0.2 0.1 -0.4 0.6 -0.4 1c0 0.4 0.5 1.9 0.5 1.9l-0.5 0l-0.6 -2l0 -0.6c0 0 -1 0.8 -1 1c0 0.2 -0.2 1.3 -0.2 1.3c0 0 0.3 0.3 0.2 0.3c0 0 -0.5 0 -0.5 0c0 0 -0.2 -0.2 -0.1 -0.4c0 -0.1 0.2 -1.6 0.2 -1.6c0 0 0.5 -0.4 0.5 -0.5c0 -0.1 0 -2.7 -0.2 -2.7c-0.1 0 -0.4 2 -0.4 2c0 0 0 0.2 -0.2 0.5c-0.1 0.4 -0.2 1.1 -0.2 1.1c0 0 -0.2 -0.1 -0.2 -0.2c0 -0.1 -0.1 -0.7 0 -0.7c0.1 -0.1 0.3 -0.8 0.4 -1.4c0 -0.6 0.2 -1.3 0.4 -1.5c0.1 -0.2 0.6 -0.4 0.6 -0.4z', 'M1.2 -2c0 0 0.7 0 1.2 0.5c0.5 0.5 0.4 0.6 0.5 0.6c0.1 0 0.7 0 0.8 0.1c0.1 0 0.2 0.2 0.2 0.2c0 0 -0.6 0.2 -0.6 0.3c0 0.1 0.4 0.9 0.6 0.9c0.1 0 0.6 0 0.6 0.1c0 0.1 0 0.7 -0.1 0.7c-0.1 0 -1.2 0.4 -1.5 0.5c-0.3 0.1 -1.1 0.5 -1.1 0.7c-0.1 0.2 0.4 1.2 0.4 1.2l-0.4 0c0 0 -0.4 -0.8 -0.4 -0.9c0 -0.1 -0.1 -0.3 -0.1 -0.3l-0.2 0l-0.5 1.3l-0.4 0c0 0 -0.1 -0.4 0 -0.6c0.1 -0.1 0.3 -0.6 0.3 -0.7c0 0 -0.8 0 -1.5 -0.1c-0.7 -0.1 -1.2 -0.3 -1.2 -0.2c0 0.1 -0.4 0.6 -0.5 0.6c0 0 0.3 0.9 0.3 0.9l-0.4 0c0 0 -0.4 -0.5 -0.4 -0.6c0 -0.1 -0.2 -0.6 -0.2 -0.5c0 0 -0.4 0.4 -0.6 0.4c-0.2 0.1 -0.4 0.1 -0.4 0.1c0 0 -0.1 0.6 -0.1 0.6l-0.5 0l0 -1c0 0 0.5 -0.4 0.5 -0.5c0 -0.1 -0.7 -1.2 -0.6 -1.4c0.1 -0.1 0.1 -1.1 0.1 -1.1c0 0 -0.2 0.1 -0.2 0.1c0 0 0 0.9 0 1c0 0.1 -0.2 0.3 -0.3 0.3c-0.1 0 0 -0.5 0 -0.9c0 -0.4 0 -0.4 0.2 -0.6c0.2 -0.2 0.6 -0.3 0.8 -0.8c0.3 -0.5 1 -0.6 1 -0.6z', 'M-4.1 -0.5c0.2 0 0.2 0.2 0.5 0.2c0.3 0 0.3 -0.2 0.5 -0.2c0.2 0 0.2 0.2 0.4 0.2c0.2 0 0.2 -0.2 0.5 -0.2c0.2 0 0.2 0.2 0.4 0.2c0.2 0 0.2 -0.2 0.4 -0.2c0.1 0 0.2 0.2 0.4 0.1c0.2 0 0.2 -0.2 0.4 -0.3c0.1 0 0.1 -0.1 0.4 0c0.3 0 0.3 -0.4 0.6 -0.4c0.3 0 0.6 -0.3 0.7 -0.2c0.1 0.1 1.4 1 1.3 1.4c-0.1 0.4 -0.3 0.3 -0.4 0.3c-0.1 0 -0.5 -0.4 -0.7 -0.2c-0.3 0.2 -0.1 0.4 -0.2 0.6c-0.1 0.1 -0.2 0.2 -0.3 0.4c0 0.2 0.1 0.3 0 0.5c-0.1 0.2 -0.3 0.2 -0.3 0.5c0 0.3 -0.2 0.3 -0.3 0.6c-0.1 0.2 0 0.3 -0.1 0.5c-0.1 0.2 -0.1 0.2 -0.2 0.3c-0.1 0.1 0.3 1.1 0.3 1.1l-0.3 0c0 0 -0.3 -0.9 -0.3 -1c0 -0.1 -0.1 -0.2 -0.3 -0.2c-0.2 0 -0.3 0.1 -0.4 0.4c0 0.3 -0.2 0.8 -0.2 0.8l-0.3 0l0.3 -1c0 0 0.1 -0.6 -0.2 -0.5c-0.3 0.1 -0.2 -0.1 -0.4 -0.1c-0.2 -0.1 -0.3 0.1 -0.4 0c-0.2 -0.1 -0.3 0.1 -0.5 0c-0.2 -0.1 -0.1 0 -0.3 0.3c-0.2 0.3 -0.4 0.3 -0.4 0.3l0.2 1.1l-0.3 0l-0.2 -1.1c0 0 -0.4 -0.6 -0.5 -0.4c-0.1 0.3 -0.1 0.4 -0.3 0.4c-0.1 -0.1 -0.2 1.1 -0.2 1.1l-0.3 0l0.2 -1.1c0 0 -0.3 -0.1 -0.3 -0.5c0 -0.3 0.1 -0.5 0.1 -0.7c0.1 -0.2 -0.1 -1 -0.2 -1.1c-0.1 -0.2 -0.2 -0.8 -0.2 -0.8c0 0 -0.1 -0.5 0.4 -0.8z' ] ) color_scale = alt.Scale( domain=domains, range=['rgb(162,160,152)', 'rgb(194,81,64)', 'rgb(93,93,93)', 'rgb(91,131,149)'] ) alt.Chart(source).mark_point(filled=True, opacity=1, size=100).encode( alt.X('x:O', axis=None), alt.Y('animal:O', axis=None), alt.Row('country:N', header=alt.Header(title='')), alt.Shape('animal:N', legend=None, scale=shape_scale), alt.Color('animal:N', legend=None, scale=color_scale), ).transform_window( x='rank()', groupby=['country', 'animal'] ).properties(width=550, height=140) altair-5.0.1/tests/examples_arguments_syntax/isotype_emoji.py000066400000000000000000000055621443422213100247010ustar00rootroot00000000000000''' Isotype Visualization with Emoji ================================ Isotype Visualization shows the distribution of animals across UK and US, using unicode emoji marks rather than custom SVG paths (see https://altair-viz.github.io/gallery/isotype.html). This is adapted from Vega-Lite example https://vega.github.io/vega-lite/examples/isotype_bar_chart_emoji.html. ''' # category:advanced calculations import altair as alt import pandas as pd source = pd.DataFrame([ {'country': 'Great Britain', 'animal': 'cattle'}, {'country': 'Great Britain', 'animal': 'cattle'}, {'country': 'Great Britain', 'animal': 'cattle'}, {'country': 'Great Britain', 'animal': 'pigs'}, {'country': 'Great Britain', 'animal': 'pigs'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'} ]) alt.Chart(source).mark_text(size=45, baseline='middle').encode( alt.X('x:O', axis=None), alt.Y('animal:O', axis=None), alt.Row('country:N', header=alt.Header(title='')), alt.Text('emoji:N') ).transform_calculate( emoji="{'cattle': 'ðŸ„', 'pigs': 'ðŸ–', 'sheep': 'ðŸ'}[datum.animal]" ).transform_window( x='rank()', groupby=['country', 'animal'] ).properties(width=550, height=140) altair-5.0.1/tests/examples_arguments_syntax/isotype_grid.py000066400000000000000000000016631443422213100245210ustar00rootroot00000000000000""" Isotype Grid ------------ This example is a grid of isotype figures. """ # category: advanced calculations import altair as alt import pandas as pd data = pd.DataFrame([dict(id=i) for i in range(1, 101)]) person = ( "M1.7 -1.7h-0.8c0.3 -0.2 0.6 -0.5 0.6 -0.9c0 -0.6 " "-0.4 -1 -1 -1c-0.6 0 -1 0.4 -1 1c0 0.4 0.2 0.7 0.6 " "0.9h-0.8c-0.4 0 -0.7 0.3 -0.7 0.6v1.9c0 0.3 0.3 0.6 " "0.6 0.6h0.2c0 0 0 0.1 0 0.1v1.9c0 0.3 0.2 0.6 0.3 " "0.6h1.3c0.2 0 0.3 -0.3 0.3 -0.6v-1.8c0 0 0 -0.1 0 " "-0.1h0.2c0.3 0 0.6 -0.3 0.6 -0.6v-2c0.2 -0.3 -0.1 " "-0.6 -0.4 -0.6z" ) alt.Chart(data).transform_calculate( row="ceil(datum.id/10)" ).transform_calculate( col="datum.id - datum.row*10" ).mark_point( filled=True, size=50 ).encode( x=alt.X("col:O", axis=None), y=alt.Y("row:O", axis=None), shape=alt.ShapeValue(person) ).properties( width=400, height=400 ).configure_view( strokeWidth=0 ) altair-5.0.1/tests/examples_arguments_syntax/lasagna_plot.py000066400000000000000000000014341443422213100244600ustar00rootroot00000000000000""" Lasagna Plot (Dense Time-Series Heatmap) ---------------------------------------- """ # category: tables import altair as alt from vega_datasets import data source = data.stocks() color_condition = alt.condition( "month(datum.value) == 1 && date(datum.value) == 1", alt.value("black"), alt.value(None), ) alt.Chart(source, width=300, height=100).transform_filter( alt.datum.symbol != "GOOG" ).mark_rect().encode( x=alt.X( "yearmonthdate(date):O", axis=alt.Axis( format="%Y", labelAngle=0, labelOverlap=False, labelColor=color_condition, tickColor=color_condition, ), title="Time", ), y=alt.Y("symbol:N", title=None), color=alt.Color("sum(price)", title="Price"), ) altair-5.0.1/tests/examples_arguments_syntax/layer_line_color_rule.py000066400000000000000000000011631443422213100263630ustar00rootroot00000000000000""" Line Chart with Layered Aggregates ---------------------------------- This example shows how to make a multi-series line chart of the daily closing stock prices for AAPL, AMZN, GOOG, IBM, and MSFT between 2000 and 2010, along with a layered rule showing the average values. """ # category: line charts import altair as alt from vega_datasets import data source = data.stocks() base = alt.Chart(source).properties(width=550) line = base.mark_line().encode( x='date', y='price', color='symbol' ) rule = base.mark_rule().encode( y='average(price)', color='symbol', size=alt.value(2) ) line + rule altair-5.0.1/tests/examples_arguments_syntax/layered_area_chart.py000066400000000000000000000005131443422213100256070ustar00rootroot00000000000000""" Layered Area Chart ------------------ This example shows a layered area chart. """ # category: area charts import altair as alt from vega_datasets import data source = data.iowa_electricity() alt.Chart(source).mark_area(opacity=0.3).encode( x="year:T", y=alt.Y("net_generation:Q", stack=None), color="source:N" ) altair-5.0.1/tests/examples_arguments_syntax/layered_bar_chart.py000066400000000000000000000005551443422213100254510ustar00rootroot00000000000000""" Layered Bar Chart ----------------- This example shows a segmented bar chart that is layered rather than stacked. """ # category: bar charts import altair as alt from vega_datasets import data source = data.iowa_electricity() alt.Chart(source).mark_bar(opacity=0.7).encode( x='year:O', y=alt.Y('net_generation:Q', stack=None), color="source", ) altair-5.0.1/tests/examples_arguments_syntax/layered_chart_bar_mark.py000066400000000000000000000011671443422213100264630ustar00rootroot00000000000000""" Bar and Tick Chart ------------------ How to layer a tick chart on top of a bar chart. """ # category: bar charts import altair as alt import pandas as pd source = pd.DataFrame({ 'project': ['a', 'b', 'c', 'd', 'e', 'f', 'g'], 'score': [25, 57, 23, 19, 8, 47, 8], 'goal': [25, 47, 30, 27, 38, 19, 4] }) bar = alt.Chart(source).mark_bar().encode( x='project', y='score' ).properties( width=alt.Step(40) # controls width of bar. ) tick = alt.Chart(source).mark_tick( color='red', thickness=2, size=40 * 0.9, # controls width of tick. ).encode( x='project', y='goal' ) bar + tick altair-5.0.1/tests/examples_arguments_syntax/layered_chart_with_dual_axis.py000066400000000000000000000014341443422213100277060ustar00rootroot00000000000000""" Layered chart with Dual-Axis ---------------------------- This example shows how to create a second independent y axis. """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.seattle_weather() base = alt.Chart(source).encode( alt.X('month(date):T', axis=alt.Axis(title=None)) ) area = base.mark_area(opacity=0.3, color='#57A44C').encode( alt.Y('average(temp_max)', axis=alt.Axis(title='Avg. Temperature (°C)', titleColor='#57A44C')), alt.Y2('average(temp_min)') ) line = base.mark_line(stroke='#5276A7', interpolate='monotone').encode( alt.Y('average(precipitation)', axis=alt.Axis(title='Precipitation (inches)', titleColor='#5276A7')) ) alt.layer(area, line).resolve_scale( y = 'independent' ) altair-5.0.1/tests/examples_arguments_syntax/layered_heatmap_text.py000066400000000000000000000017511443422213100262060ustar00rootroot00000000000000""" Text over a Heatmap ------------------- An example of a layered chart of text over a heatmap using the cars dataset. """ # category: tables import altair as alt from vega_datasets import data source = data.cars() # Configure common options. We specify the aggregation # as a transform here so we can reuse it in both layers. base = alt.Chart(source).transform_aggregate( mean_horsepower='mean(Horsepower)', groupby=['Origin', 'Cylinders'] ).encode( alt.X('Cylinders:O'), alt.Y('Origin:O'), ) # Configure heatmap heatmap = base.mark_rect().encode( color=alt.Color('mean_horsepower:Q', scale=alt.Scale(scheme='viridis'), legend=alt.Legend(title="Mean of Horsepower"), ) ) # Configure text text = base.mark_text(baseline='middle').encode( text=alt.Text('mean_horsepower:Q', format=".0f"), color=alt.condition( alt.datum.mean_horsepower > 150, alt.value('black'), alt.value('white') ) ) # Draw the chart heatmap + text altair-5.0.1/tests/examples_arguments_syntax/layered_histogram.py000066400000000000000000000012711443422213100255150ustar00rootroot00000000000000""" Layered Histogram ================= This example shows how to use opacity to make a layered histogram in Altair. """ # category: distributions import pandas as pd import altair as alt import numpy as np np.random.seed(42) # Generating Data source = pd.DataFrame({ 'Trial A': np.random.normal(0, 0.8, 1000), 'Trial B': np.random.normal(-2, 1, 1000), 'Trial C': np.random.normal(3, 2, 1000) }) alt.Chart(source).transform_fold( ['Trial A', 'Trial B', 'Trial C'], as_=['Experiment', 'Measurement'] ).mark_bar( opacity=0.3, binSpacing=0 ).encode( alt.X('Measurement:Q', bin=alt.Bin(maxbins=100)), alt.Y('count()', stack=None), alt.Color('Experiment:N') ) altair-5.0.1/tests/examples_arguments_syntax/line_chart_with_color_datum.py000066400000000000000000000012761443422213100275530ustar00rootroot00000000000000""" Line Chart with Datum for Color ------------------------------- An example of using ``datum`` and ``repeat`` to color a multi-series line chart. This is adapted from this corresponding Vega-Lite Example: `Repeat and Layer to Show Different Movie Measures `_. """ # category: line charts import altair as alt from vega_datasets import data source = data.movies() alt.Chart(source).mark_line().encode( x=alt.X("IMDB_Rating", bin=True), y=alt.Y( alt.repeat("layer"), aggregate="mean", title="Mean of US and Worldwide Gross" ), color=alt.datum(alt.repeat("layer")), ).repeat(layer=["US_Gross", "Worldwide_Gross"]) altair-5.0.1/tests/examples_arguments_syntax/line_chart_with_cumsum.py000066400000000000000000000013441443422213100265500ustar00rootroot00000000000000""" Line Chart with Cumulative Sum ------------------------------ This chart creates a simple line chart from the cumulative sum of a fields. """ # category: line charts import altair as alt from vega_datasets import data source = data.wheat() alt.Chart(source).mark_line().transform_window( # Sort the data chronologically sort=[{'field': 'year'}], # Include all previous records before the current record and none after # (This is the default value so you could skip it and it would still work.) frame=[None, 0], # What to add up as you go cumulative_wheat='sum(wheat)' ).encode( x='year:O', # Plot the calculated field created by the transformation y='cumulative_wheat:Q' ).properties(width=600)altair-5.0.1/tests/examples_arguments_syntax/line_chart_with_custom_legend.py000066400000000000000000000015431443422213100300700ustar00rootroot00000000000000""" Line Chart with Custom Legend ----------------------------- This example uses the argmax aggregation function in order to create a custom legend for a line chart. """ # category: line charts import altair as alt from vega_datasets import data source = data.stocks() base = alt.Chart(source).encode( color=alt.Color("symbol", legend=None) ).transform_filter( "datum.symbol !== 'IBM'" ).properties( width=500 ) line = base.mark_line().encode(x="date", y="price") last_price = base.mark_circle().encode( x=alt.X("last_date['date']:T"), y=alt.Y("last_date['price']:Q") ).transform_aggregate( last_date="argmax(date)", groupby=["symbol"] ) company_name = last_price.mark_text(align="left", dx=4).encode(text="symbol") chart = (line + last_price + company_name).encode( x=alt.X(title="date"), y=alt.Y(title="price") ) chart altair-5.0.1/tests/examples_arguments_syntax/line_chart_with_datum.py000066400000000000000000000014231443422213100263470ustar00rootroot00000000000000""" Line Chart with Datum --------------------------------- An example of using ``datum`` to highlight certain values, including a ``DateTime`` value. This is adapted from two corresponding Vega-Lite Examples: `Highlight a Specific Value `_. """ # category: line charts import altair as alt from vega_datasets import data source = data.stocks() lines = ( alt.Chart(source) .mark_line() .encode(x="date", y="price", color="symbol") ) xrule = ( alt.Chart() .mark_rule(color="cyan", strokeWidth=2) .encode(x=alt.datum(alt.DateTime(year=2006, month="November"))) ) yrule = ( alt.Chart().mark_rule(strokeDash=[12, 6], size=2).encode(y=alt.datum(350)) ) lines + yrule + xrule altair-5.0.1/tests/examples_arguments_syntax/line_chart_with_generator.py000066400000000000000000000007171443422213100272300ustar00rootroot00000000000000""" Line Chart with Sequence Generator ---------------------------------- This examples shows how to create multiple lines using the sequence generator. """ # category: line charts import altair as alt source = alt.sequence(start=0, stop=12.7, step=0.1, as_='x') alt.Chart(source).mark_line().transform_calculate( sin='sin(datum.x)', cos='cos(datum.x)' ).transform_fold( ['sin', 'cos'] ).encode( x='x:Q', y='value:Q', color='key:N' ) altair-5.0.1/tests/examples_arguments_syntax/line_chart_with_interpolation.py000066400000000000000000000010321443422213100301200ustar00rootroot00000000000000""" Line Chart with Interpolation ----------------------------- This chart shows a line chart with the path interpolated. A full list of interpolation methods is available `in the documentation `_. """ # category: line charts import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_line(interpolate="monotone").encode( x="date:T", y="price:Q", color="symbol:N" ) altair-5.0.1/tests/examples_arguments_syntax/line_chart_with_points.py000066400000000000000000000005251443422213100265530ustar00rootroot00000000000000""" Line Chart with Point Markers ----------------------------- This chart shows a simple line chart with points marking each value. """ # category: line charts import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_line(point=True).encode( x='date:T', y='price:Q', color='symbol:N' )altair-5.0.1/tests/examples_arguments_syntax/line_chart_with_points_stroked.py000066400000000000000000000006321443422213100303050ustar00rootroot00000000000000""" Line Chart with Stroked Point Markers ------------------------------------- This example shows a simple line chart with points in a different color. """ # category: line charts import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_line( point=alt.OverlayMarkDef(filled=False, fill="white") ).encode( x='date:T', y='price:Q', color='symbol:N' ) altair-5.0.1/tests/examples_arguments_syntax/line_percent.py000066400000000000000000000006621443422213100244650ustar00rootroot00000000000000""" Line Chart with Percent axis ---------------------------- This example shows how to format the tick labels of the y-axis of a chart as percentages. """ # category: line charts import altair as alt from vega_datasets import data source = data.jobs.url alt.Chart(source).mark_line().encode( alt.X('year:O'), alt.Y('perc:Q', axis=alt.Axis(format='%')), color='sex:N' ).transform_filter( alt.datum.job == 'Welder' ) altair-5.0.1/tests/examples_arguments_syntax/line_with_ci.py000066400000000000000000000010031443422213100244410ustar00rootroot00000000000000""" Line Chart with Confidence Interval Band ---------------------------------------- How to make a line chart with a bootstrapped 95% confidence interval band. """ # category: uncertainties and trends import altair as alt from vega_datasets import data source = data.cars() line = alt.Chart(source).mark_line().encode( x='Year', y='mean(Miles_per_Gallon)' ) band = alt.Chart(source).mark_errorband(extent='ci').encode( x='Year', y=alt.Y('Miles_per_Gallon', title='Miles/Gallon'), ) band + line altair-5.0.1/tests/examples_arguments_syntax/line_with_last_value_labeled.py000066400000000000000000000017431443422213100276700ustar00rootroot00000000000000""" Line Chart with Last Value Labeled ---------------------------------- This chart shows a line chart with a label annotating the final value """ # category: line charts import altair as alt from vega_datasets import data # Import example data source = data.stocks() # Create a common chart object # Use `transform_filter` to reduce the dataset to clarify our example. Not required. chart = ( alt.Chart(source) .transform_filter(alt.datum.symbol != "IBM") .encode(color=alt.Color("symbol", legend=None)) ) # Draw the line line = chart.mark_line().encode(x="date:T", y="price:Q") # Use the `argmax` aggregate to limit the dataset to the final value label = chart.encode( x=alt.X("max(date):T"), y=alt.Y("price:Q", aggregate=alt.ArgmaxDef(argmax="date")), text="symbol", ) # Create a text label text = label.mark_text(align="left", dx=4) # Create a circle annotation circle = label.mark_circle() # Draw the chart with all the layers combined line + circle + text altair-5.0.1/tests/examples_arguments_syntax/line_with_log_scale.py000066400000000000000000000006751443422213100260140ustar00rootroot00000000000000""" Line Chart with Logarithmic Scale --------------------------------- How to make a line chart on a `Logarithmic scale `_. """ # category: line charts import altair as alt from vega_datasets import data source = data.population() alt.Chart(source).mark_line().encode( x='year:O', y=alt.Y( 'sum(people)', scale=alt.Scale(type="log") # Here the scale is applied ) )altair-5.0.1/tests/examples_arguments_syntax/london_tube.py000066400000000000000000000034741443422213100243320ustar00rootroot00000000000000""" London Tube Lines ================= This example shows the London tube lines against the background of the borough boundaries. It is based on the vega-lite example at https://vega.github.io/vega-lite/examples/geo_layer_line_london.html. """ # category: case studies import altair as alt from vega_datasets import data boroughs = alt.topo_feature(data.londonBoroughs.url, 'boroughs') tubelines = alt.topo_feature(data.londonTubeLines.url, 'line') centroids = data.londonCentroids.url background = alt.Chart(boroughs).mark_geoshape( stroke='white', strokeWidth=2 ).encode( color=alt.value('#eee'), ).properties( width=700, height=500 ) labels = alt.Chart(centroids).mark_text().encode( longitude='cx:Q', latitude='cy:Q', text='bLabel:N', size=alt.value(8), opacity=alt.value(0.6) ).transform_calculate( "bLabel", "indexof (datum.name,' ') > 0 ? substring(datum.name,0,indexof(datum.name, ' ')) : datum.name" ) line_scale = alt.Scale(domain=["Bakerloo", "Central", "Circle", "District", "DLR", "Hammersmith & City", "Jubilee", "Metropolitan", "Northern", "Piccadilly", "Victoria", "Waterloo & City"], range=["rgb(137,78,36)", "rgb(220,36,30)", "rgb(255,206,0)", "rgb(1,114,41)", "rgb(0,175,173)", "rgb(215,153,175)", "rgb(106,114,120)", "rgb(114,17,84)", "rgb(0,0,0)", "rgb(0,24,168)", "rgb(0,160,226)", "rgb(106,187,170)"]) lines = alt.Chart(tubelines).mark_geoshape( filled=False, strokeWidth=2 ).encode( alt.Color( 'id:N', legend=alt.Legend( title=None, orient='bottom-right', offset=0 ), scale=line_scale ) ) background + labels + lines altair-5.0.1/tests/examples_arguments_syntax/mosaic_with_labels.py000066400000000000000000000044751443422213100256540ustar00rootroot00000000000000""" Mosaic Chart with Labels ------------------------ """ # category: tables import altair as alt from vega_datasets import data source = data.cars() base = ( alt.Chart(source) .transform_aggregate(count_="count()", groupby=["Origin", "Cylinders"]) .transform_stack( stack="count_", as_=["stack_count_Origin1", "stack_count_Origin2"], offset="normalize", sort=[alt.SortField("Origin", "ascending")], groupby=[], ) .transform_window( x="min(stack_count_Origin1)", x2="max(stack_count_Origin2)", rank_Cylinders="dense_rank()", distinct_Cylinders="distinct(Cylinders)", groupby=["Origin"], frame=[None, None], sort=[alt.SortField("Cylinders", "ascending")], ) .transform_window( rank_Origin="dense_rank()", frame=[None, None], sort=[alt.SortField("Origin", "ascending")], ) .transform_stack( stack="count_", groupby=["Origin"], as_=["y", "y2"], offset="normalize", sort=[alt.SortField("Cylinders", "ascending")], ) .transform_calculate( ny="datum.y + (datum.rank_Cylinders - 1) * datum.distinct_Cylinders * 0.01 / 3", ny2="datum.y2 + (datum.rank_Cylinders - 1) * datum.distinct_Cylinders * 0.01 / 3", nx="datum.x + (datum.rank_Origin - 1) * 0.01", nx2="datum.x2 + (datum.rank_Origin - 1) * 0.01", xc="(datum.nx+datum.nx2)/2", yc="(datum.ny+datum.ny2)/2", ) ) rect = base.mark_rect().encode( x=alt.X("nx:Q", axis=None), x2="nx2", y="ny:Q", y2="ny2", color=alt.Color("Origin:N", legend=None), opacity=alt.Opacity("Cylinders:Q", legend=None), tooltip=["Origin:N", "Cylinders:Q"], ) text = base.mark_text(baseline="middle").encode( x=alt.X("xc:Q", axis=None), y=alt.Y("yc:Q", title="Cylinders"), text="Cylinders:N" ) mosaic = rect + text origin_labels = base.mark_text(baseline="middle", align="center").encode( x=alt.X( "min(xc):Q", axis=alt.Axis(title="Origin", orient="top"), ), color=alt.Color("Origin", legend=None), text="Origin", ) ( (origin_labels & mosaic) .resolve_scale(x="shared") .configure_view(stroke="") .configure_concat(spacing=10) .configure_axis(domain=False, ticks=False, labels=False, grid=False) ) altair-5.0.1/tests/examples_arguments_syntax/multi_series_line.py000066400000000000000000000005151443422213100255260ustar00rootroot00000000000000""" Multiple Series Line Chart -------------------------- This example shows how to make a line chart with multiple series of data. """ # category: line charts import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_line().encode( x='date:T', y='price:Q', color='symbol:N', ) altair-5.0.1/tests/examples_arguments_syntax/multifeature_scatter_plot.py000066400000000000000000000006751443422213100273130ustar00rootroot00000000000000""" Multifeature Scatter Plot ========================= This example shows how to make a scatter plot with multiple feature encodings. """ # category: scatter plots import altair as alt from vega_datasets import data source = data.iris() alt.Chart(source).mark_circle().encode( alt.X('sepalLength', scale=alt.Scale(zero=False)), alt.Y('sepalWidth', scale=alt.Scale(zero=False, padding=1)), color='species', size='petalWidth' ) altair-5.0.1/tests/examples_arguments_syntax/multiline_highlight.py000066400000000000000000000015431443422213100260460ustar00rootroot00000000000000""" Multi-Line Highlight ==================== This multi-line chart uses an invisible Voronoi tessellation to handle mouseover to identify the nearest point and then highlight the line on which the point falls. It is adapted from the Vega-Lite example found at https://bl.ocks.org/amitkaps/fe4238e716db53930b2f1a70d3401701 """ # category: interactive charts import altair as alt from vega_datasets import data source = data.stocks() highlight = alt.selection_point(on='mouseover', fields=['symbol'], nearest=True) base = alt.Chart(source).encode( x='date:T', y='price:Q', color='symbol:N' ) points = base.mark_circle().encode( opacity=alt.value(0) ).add_params( highlight ).properties( width=600 ) lines = base.mark_line().encode( size=alt.condition(~highlight, alt.value(1), alt.value(3)) ) points + lines altair-5.0.1/tests/examples_arguments_syntax/multiline_tooltip.py000066400000000000000000000041251443422213100255700ustar00rootroot00000000000000""" Multi-Line Tooltip ================== This example shows how you can use selections and layers to create a tooltip-like behavior tied to the x position of the cursor. If you are looking for more standard tooltips, it is recommended to use the tooltip encoding channel as shown in the `Scatter Plot With Tooltips `_ example. The following example employs a little trick to isolate the x-position of the cursor: we add some transparent points with only an x encoding (no y encoding) and tie a *nearest* selection to these, tied to the "x" field. """ # category: interactive charts import altair as alt import pandas as pd import numpy as np np.random.seed(42) source = pd.DataFrame(np.cumsum(np.random.randn(100, 3), 0).round(2), columns=['A', 'B', 'C'], index=pd.RangeIndex(100, name='x')) source = source.reset_index().melt('x', var_name='category', value_name='y') # Create a selection that chooses the nearest point & selects based on x-value nearest = alt.selection_point(nearest=True, on='mouseover', fields=['x'], empty=False) # The basic line line = alt.Chart(source).mark_line(interpolate='basis').encode( x='x:Q', y='y:Q', color='category:N' ) # Transparent selectors across the chart. This is what tells us # the x-value of the cursor selectors = alt.Chart(source).mark_point().encode( x='x:Q', opacity=alt.value(0), ).add_params( nearest ) # Draw points on the line, and highlight based on selection points = line.mark_point().encode( opacity=alt.condition(nearest, alt.value(1), alt.value(0)) ) # Draw text labels near the points, and highlight based on selection text = line.mark_text(align='left', dx=5, dy=-5).encode( text=alt.condition(nearest, 'y:Q', alt.value(' ')) ) # Draw a rule at the location of the selection rules = alt.Chart(source).mark_rule(color='gray').encode( x='x:Q', ).transform_filter( nearest ) # Put the five layers into a chart and bind the data alt.layer( line, selectors, points, rules, text ).properties( width=600, height=300 ) altair-5.0.1/tests/examples_arguments_syntax/multiple_interactions.py000066400000000000000000000055131443422213100264330ustar00rootroot00000000000000""" Multiple Interactions ===================== This example shows how multiple user inputs can be layered onto a chart. The four inputs have functionality as follows: * Dropdown: Filters the movies by genre * Radio Buttons: Highlights certain films by Worldwide Gross * Mouse Drag and Scroll: Zooms the x and y scales to allow for panning. """ # category: interactive charts import altair as alt from vega_datasets import data movies = alt.UrlData( data.movies.url, format=alt.DataFormat(parse={"Release_Date":"date"}) ) ratings = ['G', 'NC-17', 'PG', 'PG-13', 'R'] genres = ['Action', 'Adventure', 'Black Comedy', 'Comedy', 'Concert/Performance', 'Documentary', 'Drama', 'Horror', 'Musical', 'Romantic Comedy', 'Thriller/Suspense', 'Western'] base = alt.Chart(movies, width=200, height=200).mark_point(filled=True).transform_calculate( Rounded_IMDB_Rating = "floor(datum.IMDB_Rating)", Hundred_Million_Production = "datum.Production_Budget > 100000000.0 ? 100 : 10", Release_Year = "year(datum.Release_Date)" ).transform_filter( alt.datum.IMDB_Rating > 0 ).transform_filter( alt.FieldOneOfPredicate(field='MPAA_Rating', oneOf=ratings) ).encode( x=alt.X('Worldwide_Gross:Q', scale=alt.Scale(domain=(100000,10**9), clamp=True)), y='IMDB_Rating:Q', tooltip="Title:N" ) # A slider filter year_slider = alt.binding_range(min=1969, max=2018, step=1, name="Release Year") slider_selection = alt.selection_point(bind=year_slider, fields=['Release_Year']) filter_year = base.add_params( slider_selection ).transform_filter( slider_selection ).properties(title="Slider Filtering") # A dropdown filter genre_dropdown = alt.binding_select(options=genres, name="Genre") genre_select = alt.selection_point(fields=['Major_Genre'], bind=genre_dropdown) filter_genres = base.add_params( genre_select ).transform_filter( genre_select ).properties(title="Dropdown Filtering") #color changing marks rating_radio = alt.binding_radio(options=ratings, name="Rating") rating_select = alt.selection_point(fields=['MPAA_Rating'], bind=rating_radio) rating_color_condition = alt.condition( rating_select, alt.Color('MPAA_Rating:N', legend=None), alt.value('lightgray') ) highlight_ratings = base.add_params( rating_select ).encode( color=rating_color_condition ).properties(title="Radio Button Highlighting") # Boolean selection for format changes input_checkbox = alt.binding_checkbox(name="Big Budget Films ") checkbox_selection = alt.param(bind=input_checkbox) size_checkbox_condition = alt.condition( checkbox_selection, alt.Size('Hundred_Million_Production:Q'), alt.SizeValue(25) ) budget_sizing = base.add_params( checkbox_selection ).encode( size=size_checkbox_condition ).properties(title="Checkbox Formatting") (filter_year | filter_genres) & (highlight_ratings | budget_sizing) altair-5.0.1/tests/examples_arguments_syntax/natural_disasters.py000066400000000000000000000026271443422213100255500ustar00rootroot00000000000000""" Global Deaths from Natural Disasters ------------------------------------ This example shows a proportional symbols visualization of deaths from natural disasters by year and type. """ # category: case studies import altair as alt from vega_datasets import data source = data.disasters.url alt.Chart(source).transform_filter( alt.datum.Entity != 'All natural disasters' ).mark_circle( opacity=0.8, stroke='black', strokeWidth=1, strokeOpacity=0.4 ).encode( x=alt.X('Year:T', title=None, scale=alt.Scale(domain=['1899','2018'])), y=alt.Y( 'Entity:N', sort=alt.EncodingSortField(field="Deaths", op="sum", order='descending'), title=None ), size=alt.Size('Deaths:Q', scale=alt.Scale(range=[0, 2500]), legend=alt.Legend(title='Deaths', clipHeight=30, format='s') ), color=alt.Color('Entity:N', legend=None), tooltip=[ "Entity:N", alt.Tooltip("Year:T", format='%Y'), alt.Tooltip("Deaths:Q", format='~s') ], ).properties( width=450, height=320, title=alt.Title( text="Global Deaths from Natural Disasters (1900-2017)", subtitle="The size of the bubble represents the total death count per year, by type of disaster", anchor='start' ) ).configure_axisY( domain=False, ticks=False, offset=10 ).configure_axisX( grid=False, ).configure_view( stroke=None ) altair-5.0.1/tests/examples_arguments_syntax/normalized_stacked_area_chart.py000066400000000000000000000005641443422213100300320ustar00rootroot00000000000000""" Normalized Stacked Area Chart ----------------------------- This example shows how to make a normalized stacked area chart. """ # category: area charts import altair as alt from vega_datasets import data source = data.iowa_electricity() alt.Chart(source).mark_area().encode( x="year:T", y=alt.Y("net_generation:Q", stack="normalize"), color="source:N" ) altair-5.0.1/tests/examples_arguments_syntax/normalized_stacked_bar_chart.py000066400000000000000000000006611443422213100276640ustar00rootroot00000000000000""" Normalized Stacked Bar Chart ---------------------------- This is an example of a normalized stacked bar chart using data which contains crop yields over different regions and different years in the 1930s. """ # category: bar charts import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( x=alt.X('sum(yield)', stack="normalize"), y='variety', color='site' ) altair-5.0.1/tests/examples_arguments_syntax/normed_parallel_coordinates.py000066400000000000000000000024151443422213100275460ustar00rootroot00000000000000""" Normalized Parallel Coordinates ------------------------------- A `Parallel Coordinates `_ chart is a chart that lets you visualize the individual data points by drawing a single line for each of them. Such a chart can be created in Altair by first transforming the data into a suitable representation. This example shows a modified parallel coordinates chart with the Iris dataset, where the y-axis shows the value after min-max rather than the raw value. It's a simplified Altair version of `the VegaLite version `_ """ # category: advanced calculations import altair as alt from vega_datasets import data from altair import datum source = data.iris() alt.Chart(source).transform_window( index='count()' ).transform_fold( ['petalLength', 'petalWidth', 'sepalLength', 'sepalWidth'] ).transform_joinaggregate( min='min(value)', max='max(value)', groupby=['key'] ).transform_calculate( minmax_value=(datum.value-datum.min)/(datum.max-datum.min), mid=(datum.min+datum.max)/2 ).mark_line().encode( x='key:N', y='minmax_value:Q', color='species:N', detail='index:N', opacity=alt.value(0.5) ).properties(width=500) altair-5.0.1/tests/examples_arguments_syntax/one_dot_per_zipcode.py000066400000000000000000000011461443422213100260260ustar00rootroot00000000000000""" One Dot Per Zipcode ----------------------- This example shows a geographical plot with one dot per zipcode. """ # category: case studies import altair as alt from vega_datasets import data # Since the data is more than 5,000 rows we'll import it from a URL source = data.zipcodes.url alt.Chart(source).transform_calculate( "leading digit", alt.expr.substring(alt.datum.zip_code, 0, 1) ).mark_circle(size=3).encode( longitude='longitude:Q', latitude='latitude:Q', color='leading digit:N', tooltip='zip_code:N' ).project( type='albersUsa' ).properties( width=650, height=400 ) altair-5.0.1/tests/examples_arguments_syntax/pacman_chart.py000066400000000000000000000007161443422213100244360ustar00rootroot00000000000000""" Pacman Chart ------------ Chart made using ``mark_arc`` and constant values. This could also be made using ``alt.Chart(source).mark_arc(color = "gold", theta = (5/8)*np.pi, theta2 = (19/8)*np.pi,radius=100)``. """ # category: circular plots import numpy as np import altair as alt alt.Chart().mark_arc(color="gold").encode( theta=alt.datum((5 / 8) * np.pi, scale=None), theta2=alt.datum((19 / 8) * np.pi), radius=alt.datum(100, scale=None), ) altair-5.0.1/tests/examples_arguments_syntax/parallel_coordinates.py000066400000000000000000000014531443422213100262030ustar00rootroot00000000000000""" Parallel Coordinates -------------------- A `Parallel Coordinates `_ chart is a chart that lets you visualize the individual data points by drawing a single line for each of them. Such a chart can be created in Altair by first transforming the data into a suitable representation. This example shows a parallel coordinates chart with the Iris dataset. """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.iris() alt.Chart(source).transform_window( index='count()' ).transform_fold( ['petalLength', 'petalWidth', 'sepalLength', 'sepalWidth'] ).mark_line().encode( x='key:N', y='value:Q', color='species:N', detail='index:N', opacity=alt.value(0.5) ).properties(width=500) altair-5.0.1/tests/examples_arguments_syntax/percentage_of_total.py000066400000000000000000000011671443422213100260230ustar00rootroot00000000000000""" Calculating Percentage of Total ------------------------------- This chart demonstrates how to use a joinaggregate transform to display data values as a percentage of total. """ # category: bar charts import altair as alt import pandas as pd source = pd.DataFrame({'Activity': ['Sleeping', 'Eating', 'TV', 'Work', 'Exercise'], 'Time': [8, 2, 4, 8, 2]}) alt.Chart(source).transform_joinaggregate( TotalTime='sum(Time)', ).transform_calculate( PercentOfTotal="datum.Time / datum.TotalTime" ).mark_bar().encode( alt.X('PercentOfTotal:Q', axis=alt.Axis(format='.0%')), y='Activity:N' ) altair-5.0.1/tests/examples_arguments_syntax/pie_chart.py000066400000000000000000000010161443422213100237460ustar00rootroot00000000000000""" Pie Chart --------- This example shows how to make a Pie Chart using ``mark_arc``. This is adapted from a corresponding Vega-Lite Example: `Pie Chart `_. """ # category: circular plots import pandas as pd import altair as alt source = pd.DataFrame({"category": [1, 2, 3, 4, 5, 6], "value": [4, 6, 10, 3, 7, 8]}) alt.Chart(source).mark_arc().encode( theta=alt.Theta(field="value", type="quantitative"), color=alt.Color(field="category", type="nominal"), ) altair-5.0.1/tests/examples_arguments_syntax/pie_chart_with_labels.py000066400000000000000000000012771443422213100263340ustar00rootroot00000000000000""" Pie Chart with Labels --------------------- This example shows how to layer text over arc marks (``mark_arc``) to label pie charts. This is adapted from a corresponding Vega-Lite Example: `Pie Chart with Labels `_. """ # category: circular plots import pandas as pd import altair as alt source = pd.DataFrame( {"category": ["a", "b", "c", "d", "e", "f"], "value": [4, 6, 10, 3, 7, 8]} ) base = alt.Chart(source).encode( theta=alt.Theta("value:Q", stack=True), color=alt.Color("category:N", legend=None) ) pie = base.mark_arc(outerRadius=120) text = base.mark_text(radius=140, size=20).encode(text="category:N") pie + text altair-5.0.1/tests/examples_arguments_syntax/point_map.py000066400000000000000000000013511443422213100240000ustar00rootroot00000000000000""" Point map ========= This is a layered map that shows the positions of airports on a background of U.S. states. """ # category: maps import altair as alt from vega_datasets import data # Read in points airports = data.airports() # Read in polygons from topojson states = alt.topo_feature(data.us_10m.url, feature='states') # US states background background = alt.Chart(states).mark_geoshape( fill='lightgray', stroke='white' ).properties( width=500, height=300 ).project('albersUsa') # airport positions on background points = alt.Chart(airports).mark_circle( size=10, color='steelblue' ).encode( longitude='longitude:Q', latitude='latitude:Q', tooltip=['name', 'city', 'state'] ) background + points altair-5.0.1/tests/examples_arguments_syntax/poly_fit_regression.py000066400000000000000000000016341443422213100261030ustar00rootroot00000000000000""" Polynomial Fit Plot with Regression Transform ============================================= This example shows how to overlay data with multiple fitted polynomials using the regression transform. """ # category: uncertainties and trends import numpy as np import pandas as pd import altair as alt # Generate some random data rng = np.random.RandomState(1) x = rng.rand(40) ** 2 y = 10 - 1.0 / (x + 0.1) + rng.randn(40) source = pd.DataFrame({"x": x, "y": y}) # Define the degree of the polynomial fits degree_list = [1, 3, 5] base = alt.Chart(source).mark_circle(color="black").encode( alt.X("x"), alt.Y("y") ) polynomial_fit = [ base.transform_regression( "x", "y", method="poly", order=order, as_=["x", str(order)] ) .mark_line() .transform_fold([str(order)], as_=["degree", "y"]) .encode(alt.Color("degree:N")) for order in degree_list ] alt.layer(base, *polynomial_fit) altair-5.0.1/tests/examples_arguments_syntax/pyramid.py000066400000000000000000000013271443422213100234620ustar00rootroot00000000000000""" Pyramid Pie Chart ----------------- Altair reproduction of http://robslink.com/SAS/democd91/pyramid_pie.htm """ # category: case studies import altair as alt import pandas as pd category = ['Sky', 'Shady side of a pyramid', 'Sunny side of a pyramid'] color = ["#416D9D", "#674028", "#DEAC58"] df = pd.DataFrame({'category': category, 'value': [75, 10, 15]}) alt.Chart(df).mark_arc(outerRadius=80).encode( alt.Theta('value:Q', scale=alt.Scale(range=[2.356, 8.639])), alt.Color('category:N', scale=alt.Scale(domain=category, range=color), legend=alt.Legend(title=None, orient='none', legendX=160, legendY=50)), order='value:Q' ).properties(width=150, height=150).configure_view(strokeOpacity=0) altair-5.0.1/tests/examples_arguments_syntax/radial_chart.py000066400000000000000000000012731443422213100244320ustar00rootroot00000000000000""" Radial Chart ------------ This radial plot uses both angular and radial extent to convey multiple dimensions of data. This is adapted from a corresponding Vega-Lite Example: `Radial Plot `_. """ # category: circular plots import pandas as pd import altair as alt source = pd.DataFrame({"values": [12, 23, 47, 6, 52, 19]}) base = alt.Chart(source).encode( theta=alt.Theta("values:Q", stack=True), radius=alt.Radius("values", scale=alt.Scale(type="sqrt", zero=True, rangeMin=20)), color="values:N", ) c1 = base.mark_arc(innerRadius=20, stroke="#fff") c2 = base.mark_text(radiusOffset=10).encode(text="values:Q") c1 + c2 altair-5.0.1/tests/examples_arguments_syntax/ranged_dot_plot.py000066400000000000000000000017621443422213100251640ustar00rootroot00000000000000""" Ranged Dot Plot --------------- This example shows a ranged dot plot to convey changing life expectancy for the five most populous countries (between 1955 and 2000). """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.countries.url chart = alt.Chart( data=source ).transform_filter( filter={"field": 'country', "oneOf": ["China", "India", "United States", "Indonesia", "Brazil"]} ).transform_filter( filter={'field': 'year', "oneOf": [1955, 2000]} ) line = chart.mark_line(color='#db646f').encode( x='life_expect:Q', y='country:N', detail='country:N' ) # Add points for life expectancy in 1955 & 2000 points = chart.mark_point( size=100, opacity=1, filled=True ).encode( x='life_expect:Q', y='country:N', color=alt.Color('year:O', scale=alt.Scale( domain=[1955, 2000], range=['#e6959c', '#911a24'] ) ) ).interactive() (line + points) altair-5.0.1/tests/examples_arguments_syntax/ridgeline_plot.py000066400000000000000000000030711443422213100250130ustar00rootroot00000000000000""" Ridgeline plot -------------- A `Ridgeline plot `_ chart is a chart that lets you visualize distribution of a numeric value for several groups. Such a chart can be created in Altair by first transforming the data into a suitable representation. """ # category: distributions import altair as alt from vega_datasets import data source = data.seattle_weather.url step = 20 overlap = 1 alt.Chart(source, height=step).transform_timeunit( Month='month(date)' ).transform_joinaggregate( mean_temp='mean(temp_max)', groupby=['Month'] ).transform_bin( ['bin_max', 'bin_min'], 'temp_max' ).transform_aggregate( value='count()', groupby=['Month', 'mean_temp', 'bin_min', 'bin_max'] ).transform_impute( impute='value', groupby=['Month', 'mean_temp'], key='bin_min', value=0 ).mark_area( interpolate='monotone', fillOpacity=0.8, stroke='lightgray', strokeWidth=0.5 ).encode( alt.X('bin_min:Q', bin='binned', title='Maximum Daily Temperature (C)'), alt.Y( 'value:Q', scale=alt.Scale(range=[step, -step * overlap]), axis=None ), alt.Fill( 'mean_temp:Q', legend=None, scale=alt.Scale(domain=[30, 5], scheme='redyellowblue') ) ).facet( row=alt.Row( 'Month:T', title=None, header=alt.Header(labelAngle=0, labelAlign='right', format='%B') ) ).properties( title='Seattle Weather', bounds='flush' ).configure_facet( spacing=0 ).configure_view( stroke=None ).configure_title( anchor='end' ) altair-5.0.1/tests/examples_arguments_syntax/scatter_href.py000066400000000000000000000011161443422213100244620ustar00rootroot00000000000000""" Scatter Plot with Href ---------------------- This example shows a scatter plot with an ``href`` encoding constructed from the car name. With this encoding, you can click on any of the points to open a google search for the car name. """ # category: scatter plots import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).transform_calculate( url='https://www.google.com/search?q=' + alt.datum.Name ).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color='Origin:N', href='url:N', tooltip=['Name:N', 'url:N'] ) altair-5.0.1/tests/examples_arguments_syntax/scatter_linked_brush.py000066400000000000000000000011661443422213100262140ustar00rootroot00000000000000""" Multi-panel Scatter Plot with Linked Brushing --------------------------------------------- This is an example of using an interval selection to control the color of points across multiple panels. """ # category: interactive charts import altair as alt from vega_datasets import data source = data.cars() brush = alt.selection_interval(resolve='global') base = alt.Chart(source).mark_point().encode( y='Miles_per_Gallon', color=alt.condition(brush, 'Origin', alt.ColorValue('gray')), ).add_params( brush ).properties( width=250, height=250 ) base.encode(x='Horsepower') | base.encode(x='Acceleration') altair-5.0.1/tests/examples_arguments_syntax/scatter_linked_table.py000066400000000000000000000026301443422213100261550ustar00rootroot00000000000000""" Brushing Scatter Plot to Show Data on a Table --------------------------------------------- A scatter plot of the cars dataset, with data tables for horsepower, MPG, and origin. The tables update to reflect the selection on the scatter plot. """ # category: scatter plots import altair as alt from vega_datasets import data source = data.cars() # Brush for selection brush = alt.selection_interval() # Scatter Plot points = alt.Chart(source).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color=alt.condition(brush, alt.value('steelblue'), alt.value('grey')) ).add_params(brush) # Base chart for data tables ranked_text = alt.Chart(source).mark_text(align='right').encode( y=alt.Y('row_number:O', axis=None) ).transform_filter( brush ).transform_window( row_number='row_number()' ).transform_filter( alt.datum.row_number < 15 ) # Data Tables horsepower = ranked_text.encode(text='Horsepower:N').properties( title=alt.Title(text='Horsepower', align='right') ) mpg = ranked_text.encode(text='Miles_per_Gallon:N').properties( title=alt.Title(text='MPG', align='right') ) origin = ranked_text.encode(text='Origin:N').properties( title=alt.Title(text='Origin', align='right') ) text = alt.hconcat(horsepower, mpg, origin) # Combine data tables # Build chart alt.hconcat( points, text ).resolve_legend( color="independent" ).configure_view( stroke=None ) altair-5.0.1/tests/examples_arguments_syntax/scatter_marginal_hist.py000066400000000000000000000025751443422213100263710ustar00rootroot00000000000000""" Facetted Scatter Plot with Marginal Histograms ---------------------------------------------- This example demonstrates how to generate a facetted scatter plot, with marginal facetted histograms, and how to share their respective - x,some y-limits. """ # category: distributions import altair as alt from vega_datasets import data source = data.iris() base = alt.Chart(source) xscale = alt.Scale(domain=(4.0, 8.0)) yscale = alt.Scale(domain=(1.9, 4.55)) bar_args = {'opacity': .3, 'binSpacing': 0} points = base.mark_circle().encode( alt.X('sepalLength', scale=xscale), alt.Y('sepalWidth', scale=yscale), color='species', ) top_hist = base.mark_bar(**bar_args).encode( alt.X('sepalLength:Q', # when using bins, the axis scale is set through # the bin extent, so we do not specify the scale here # (which would be ignored anyway) bin=alt.Bin(maxbins=20, extent=xscale.domain), stack=None, title='' ), alt.Y('count()', stack=None, title=''), alt.Color('species:N'), ).properties(height=60) right_hist = base.mark_bar(**bar_args).encode( alt.Y('sepalWidth:Q', bin=alt.Bin(maxbins=20, extent=yscale.domain), stack=None, title='', ), alt.X('count()', stack=None, title=''), alt.Color('species:N'), ).properties(width=60) top_hist & (points | right_hist) altair-5.0.1/tests/examples_arguments_syntax/scatter_matrix.py000066400000000000000000000011341443422213100250420ustar00rootroot00000000000000""" Scatter Matrix -------------- An example of using a RepeatChart to construct a multi-panel scatter plot with linked panning and zooming. """ # category: scatter plots import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_circle().encode( alt.X(alt.repeat("column"), type='quantitative'), alt.Y(alt.repeat("row"), type='quantitative'), color='Origin:N' ).properties( width=150, height=150 ).repeat( row=['Horsepower', 'Acceleration', 'Miles_per_Gallon'], column=['Miles_per_Gallon', 'Acceleration', 'Horsepower'] ).interactive() altair-5.0.1/tests/examples_arguments_syntax/scatter_qq.py000066400000000000000000000010231443422213100241540ustar00rootroot00000000000000""" Quantile-Quantile Plot ---------------------- A quantile-quantile plot comparing input data to theoretical distributions. """ # category: distributions import altair as alt from vega_datasets import data source = data.normal_2d.url base = alt.Chart(source).transform_quantile( 'u', step=0.01, as_ = ['p', 'v'] ).transform_calculate( uniform = 'quantileUniform(datum.p)', normal = 'quantileNormal(datum.p)' ).mark_point().encode( alt.Y('v:Q') ) base.encode(x='uniform:Q') | base.encode(x='normal:Q') altair-5.0.1/tests/examples_arguments_syntax/scatter_tooltips.py000066400000000000000000000010441443422213100254130ustar00rootroot00000000000000""" Simple Scatter Plot with Tooltips --------------------------------- A scatter plot of the cars dataset, with tooltips showing selected column values when you hover over points. We make the points larger so that it is easier to hover over them. """ # category: simple charts import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_circle(size=60).encode( x='Horsepower', y='Miles_per_Gallon', color='Origin', tooltip=['Name', 'Origin', 'Horsepower', 'Miles_per_Gallon'] ).interactive() altair-5.0.1/tests/examples_arguments_syntax/scatter_with_histogram.py000066400000000000000000000026561443422213100266000ustar00rootroot00000000000000""" Scatter Plot and Histogram with Interval Selection ================================================== This example shows how to link a scatter plot and a histogram together such that an interval selection in the histogram will plot the selected values in the scatter plot. Note that both subplots need to know about the `mbin` field created by the `transform_bin` method. In order to achieve this, the data is not passed to the `Chart()` instances creating the subplots, but directly in the `hconcat()` function, which joins the two plots together. """ # category: interactive charts import altair as alt import pandas as pd import numpy as np x = np.random.normal(size=100) y = np.random.normal(size=100) m = np.random.normal(15, 1, size=100) source = pd.DataFrame({"x": x, "y":y, "m":m}) # interval selection in the scatter plot pts = alt.selection_interval(encodings=["x"]) # left panel: scatter plot points = alt.Chart().mark_point(filled=True, color="black").encode( x='x', y='y' ).transform_filter( pts ).properties( width=300, height=300 ) # right panel: histogram mag = alt.Chart().mark_bar().encode( x='mbin:N', y="count()", color=alt.condition(pts, alt.value("black"), alt.value("lightgray")) ).properties( width=300, height=300 ).add_params(pts) # build the chart: alt.hconcat( points, mag, data=source ).transform_bin( "mbin", field="m", bin=alt.Bin(maxbins=20) ) altair-5.0.1/tests/examples_arguments_syntax/scatter_with_labels.py000066400000000000000000000010171443422213100260330ustar00rootroot00000000000000""" Simple Scatter Plot with Labels =============================== This example shows a basic scatter plot with labels created with Altair. """ # category: scatter plots import altair as alt import pandas as pd source = pd.DataFrame({ 'x': [1, 3, 5, 7, 9], 'y': [1, 3, 5, 7, 9], 'label': ['A', 'B', 'C', 'D', 'E'] }) points = alt.Chart(source).mark_point().encode( x='x:Q', y='y:Q' ) text = points.mark_text( align='left', baseline='middle', dx=7 ).encode( text='label' ) points + text altair-5.0.1/tests/examples_arguments_syntax/scatter_with_layered_histogram.py000066400000000000000000000033651443422213100303030ustar00rootroot00000000000000""" Interactive Scatter Plot and Linked Layered Histogram ===================================================== This example shows how to link a scatter plot and a histogram together such that clicking on a point in the scatter plot will isolate the distribution corresponding to that point, and vice versa. """ # category: interactive charts import altair as alt import pandas as pd import numpy as np # generate fake data source = pd.DataFrame({ 'gender': ['M']*1000 + ['F']*1000, 'height':np.concatenate(( np.random.normal(69, 7, 1000), np.random.normal(64, 6, 1000) )), 'weight': np.concatenate(( np.random.normal(195.8, 144, 1000), np.random.normal(167, 100, 1000) )), 'age': np.concatenate(( np.random.normal(45, 8, 1000), np.random.normal(51, 6, 1000) )) }) selector = alt.selection_point(fields=['gender']) color_scale = alt.Scale(domain=['M', 'F'], range=['#1FC3AA', '#8624F5']) base = alt.Chart(source).properties( width=250, height=250 ).add_params(selector) points = base.mark_point(filled=True, size=200).encode( x=alt.X('mean(height):Q', scale=alt.Scale(domain=[0,84])), y=alt.Y('mean(weight):Q', scale=alt.Scale(domain=[0,250])), color=alt.condition( selector, 'gender:N', alt.value('lightgray'), scale=color_scale), ) hists = base.mark_bar(opacity=0.5, thickness=100).encode( x=alt.X('age', bin=alt.Bin(step=5), # step keeps bin size the same scale=alt.Scale(domain=[0,100])), y=alt.Y('count()', stack=None, scale=alt.Scale(domain=[0,350])), color=alt.Color('gender:N', scale=color_scale) ).transform_filter( selector ) points | hists altair-5.0.1/tests/examples_arguments_syntax/scatter_with_loess.py000066400000000000000000000013771443422213100257270ustar00rootroot00000000000000""" Scatter Plot with LOESS Lines ----------------------------- This example shows how to add a trend line to a scatter plot using the LOESS transform (LOcally Estimated Scatter Plot Smoothing). """ # category: uncertainties and trends import altair as alt import pandas as pd import numpy as np np.random.seed(1) source = pd.DataFrame({ 'x': np.arange(100), 'A': np.random.randn(100).cumsum(), 'B': np.random.randn(100).cumsum(), 'C': np.random.randn(100).cumsum(), }) base = alt.Chart(source).mark_circle(opacity=0.5).transform_fold( fold=['A', 'B', 'C'], as_=['category', 'y'] ).encode( alt.X('x:Q'), alt.Y('y:Q'), alt.Color('category:N') ) base + base.transform_loess('x', 'y', groupby=['category']).mark_line(size=4) altair-5.0.1/tests/examples_arguments_syntax/scatter_with_minimap.py000066400000000000000000000022321443422213100262230ustar00rootroot00000000000000""" Scatter Plot with Minimap ------------------------- This example shows how to create a miniature version of a plot such that creating a selection in the miniature version adjusts the axis limits in another, more detailed view. """ # category: scatter plots import altair as alt from vega_datasets import data source = data.seattle_weather() zoom = alt.selection_interval(encodings=["x", "y"]) minimap = ( alt.Chart(source) .mark_point() .add_params(zoom) .encode( x="date:T", y="temp_max:Q", color=alt.condition(zoom, "weather", alt.value("lightgray")), ) .properties( width=200, height=200, title="Minimap -- click and drag to zoom in the detail view", ) ) detail = ( alt.Chart(source) .mark_point() .encode( x=alt.X( "date:T", scale=alt.Scale(domain={"param": zoom.name, "encoding": "x"}) ), y=alt.Y( "temp_max:Q", scale=alt.Scale(domain={"param": zoom.name, "encoding": "y"}), ), color="weather", ) .properties(width=600, height=400, title="Seattle weather -- detail view") ) detail | minimap altair-5.0.1/tests/examples_arguments_syntax/scatter_with_rolling_mean.py000066400000000000000000000012501443422213100272360ustar00rootroot00000000000000""" Scatter Plot with Rolling Mean ------------------------------ A scatter plot with a rolling mean overlay. In this example a 30 day window is used to calculate the mean of the maximum temperature around each date. """ # category: scatter plots import altair as alt from vega_datasets import data source = data.seattle_weather() line = alt.Chart(source).mark_line( color='red', size=3 ).transform_window( rolling_mean='mean(temp_max)', frame=[-15, 15] ).encode( x='date:T', y='rolling_mean:Q' ) points = alt.Chart(source).mark_point().encode( x='date:T', y=alt.Y('temp_max:Q', axis=alt.Axis(title='Max Temp')) ) points + line altair-5.0.1/tests/examples_arguments_syntax/seattle_weather_interactive.py000066400000000000000000000031701443422213100275700ustar00rootroot00000000000000""" Seattle Weather Interactive =========================== This chart provides an interactive exploration of Seattle weather over the course of the year. It includes a one-axis brush selection to easily see the distribution of weather types in a particular date range. """ # category: case studies import altair as alt from vega_datasets import data source = data.seattle_weather() scale = alt.Scale(domain=['sun', 'fog', 'drizzle', 'rain', 'snow'], range=['#e7ba52', '#a7a7a7', '#aec7e8', '#1f77b4', '#9467bd']) color = alt.Color('weather:N', scale=scale) # We create two selections: # - a brush that is active on the top panel # - a multi-click that is active on the bottom panel brush = alt.selection_interval(encodings=['x']) click = alt.selection_point(encodings=['color']) # Top panel is scatter plot of temperature vs time points = alt.Chart().mark_point().encode( alt.X('monthdate(date):T', title='Date'), alt.Y('temp_max:Q', title='Maximum Daily Temperature (C)', scale=alt.Scale(domain=[-5, 40]) ), color=alt.condition(brush, color, alt.value('lightgray')), size=alt.Size('precipitation:Q', scale=alt.Scale(range=[5, 200])) ).properties( width=550, height=300 ).add_params( brush ).transform_filter( click ) # Bottom panel is a bar chart of weather type bars = alt.Chart().mark_bar().encode( x='count()', y='weather:N', color=alt.condition(click, color, alt.value('lightgray')), ).transform_filter( brush ).properties( width=550, ).add_params( click ) alt.vconcat( points, bars, data=source, title="Seattle Weather: 2012-2015" ) altair-5.0.1/tests/examples_arguments_syntax/select_detail.py000066400000000000000000000035671443422213100246260ustar00rootroot00000000000000""" Selection Detail ================ This example shows a selection that links two views of data: the left panel contains one point per object, and the right panel contains one line per object. Clicking on either the points or lines will select the corresponding objects in both views of the data. The challenge lies in expressing such hierarchical data in a way that Altair can handle. We do this by merging the data into a "long form" dataframe, and aggregating identical metadata for the final plot. """ # category: interactive charts import altair as alt import pandas as pd import numpy as np np.random.seed(0) n_objects = 20 n_times = 50 # Create one (x, y) pair of metadata per object locations = pd.DataFrame({ 'id': range(n_objects), 'x': np.random.randn(n_objects), 'y': np.random.randn(n_objects) }) # Create a 50-element time-series for each object timeseries = pd.DataFrame(np.random.randn(n_times, n_objects).cumsum(0), columns=locations['id'], index=pd.RangeIndex(0, n_times, name='time')) # Melt the wide-form timeseries into a long-form view timeseries = timeseries.reset_index().melt('time') # Merge the (x, y) metadata into the long-form view timeseries['id'] = timeseries['id'].astype(int) # make merge not complain data = pd.merge(timeseries, locations, on='id') # Data is prepared, now make a chart selector = alt.selection_point(fields=['id']) base = alt.Chart(data).properties( width=250, height=250 ).add_params(selector) points = base.mark_point(filled=True, size=200).encode( x='mean(x)', y='mean(y)', color=alt.condition(selector, 'id:O', alt.value('lightgray'), legend=None), ) timeseries = base.mark_line().encode( x='time', y=alt.Y('value', scale=alt.Scale(domain=(-15, 15))), color=alt.Color('id:O', legend=None) ).transform_filter( selector ) points | timeseries altair-5.0.1/tests/examples_arguments_syntax/select_mark_area.py000066400000000000000000000014221443422213100252720ustar00rootroot00000000000000""" Using Selection Interval with mark_area ========================================= Because area is considered one object, just using the plain selector will select the entire area instead of just one part of it. This example shows how to use two areas, one on top of the other, and a `transform_filter` to fake out this effect. """ # category: interactive charts import altair as alt from vega_datasets import data source = data.unemployment_across_industries.url base = alt.Chart(source).mark_area( color='goldenrod', opacity=0.3 ).encode( x='yearmonth(date):T', y='sum(count):Q', ) brush = alt.selection_interval(encodings=['x']) background = base.add_params(brush) selected = base.transform_filter(brush).mark_area(color='goldenrod') background + selected altair-5.0.1/tests/examples_arguments_syntax/selection_histogram.py000066400000000000000000000013331443422213100260540ustar00rootroot00000000000000""" Selection Histogram =================== This chart shows an example of using an interval selection to filter the contents of an attached histogram, allowing the user to see the proportion of items in each category within the selection. """ # category: interactive charts import altair as alt from vega_datasets import data source = data.cars() brush = alt.selection_interval() points = alt.Chart(source).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color=alt.condition(brush, 'Origin:N', alt.value('lightgray')) ).add_params( brush ) bars = alt.Chart(source).mark_bar().encode( y='Origin:N', color='Origin:N', x='count(Origin):Q' ).transform_filter( brush ) points & bars altair-5.0.1/tests/examples_arguments_syntax/selection_layer_bar_month.py000066400000000000000000000014701443422213100272260ustar00rootroot00000000000000""" Interactive Average =================== The plot below uses an interval selection, which causes the chart to include an interactive brush (shown in grey). The brush selection parameterizes the red guideline, which visualizes the average value within the selected interval. """ # category: interactive charts import altair as alt from vega_datasets import data source = data.seattle_weather() brush = alt.selection_interval(encodings=['x']) bars = alt.Chart().mark_bar().encode( x='month(date):O', y='mean(precipitation):Q', opacity=alt.condition(brush, alt.OpacityValue(1), alt.OpacityValue(0.7)), ).add_params( brush ) line = alt.Chart().mark_rule(color='firebrick').encode( y='mean(precipitation):Q', size=alt.SizeValue(3) ).transform_filter( brush ) alt.layer(bars, line, data=source) altair-5.0.1/tests/examples_arguments_syntax/simple_bar_chart.py000066400000000000000000000005461443422213100253150ustar00rootroot00000000000000""" Simple Bar Chart ================ This example shows a basic bar chart created with Altair. """ # category: simple charts import altair as alt import pandas as pd source = pd.DataFrame({ 'a': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'], 'b': [28, 55, 43, 91, 81, 53, 19, 87, 52] }) alt.Chart(source).mark_bar().encode( x='a', y='b' ) altair-5.0.1/tests/examples_arguments_syntax/simple_heatmap.py000066400000000000000000000010411443422213100247760ustar00rootroot00000000000000""" Simple Heatmap -------------- This example shows a simple heatmap for showing gridded data. """ # category: simple charts import altair as alt import numpy as np import pandas as pd # Compute x^2 + y^2 across a 2D grid x, y = np.meshgrid(range(-5, 5), range(-5, 5)) z = x ** 2 + y ** 2 # Convert this grid to columnar data expected by Altair source = pd.DataFrame({'x': x.ravel(), 'y': y.ravel(), 'z': z.ravel()}) alt.Chart(source).mark_rect().encode( x='x:O', y='y:O', color='z:Q' ) altair-5.0.1/tests/examples_arguments_syntax/simple_histogram.py000066400000000000000000000005701443422213100253620ustar00rootroot00000000000000""" Simple Histogram ---------------- This example shows how to make a basic histogram, based on the vega-lite docs https://vega.github.io/vega-lite/examples/histogram.html """ # category: simple charts import altair as alt from vega_datasets import data source = data.movies.url alt.Chart(source).mark_bar().encode( alt.X("IMDB_Rating:Q", bin=True), y='count()', ) altair-5.0.1/tests/examples_arguments_syntax/simple_line_chart.py000066400000000000000000000005511443422213100254740ustar00rootroot00000000000000""" Simple Line Chart ----------------- This chart shows the most basic line chart, made from a dataframe with two columns. """ # category: simple charts import altair as alt import numpy as np import pandas as pd x = np.arange(100) source = pd.DataFrame({ 'x': x, 'f(x)': np.sin(x / 5) }) alt.Chart(source).mark_line().encode( x='x', y='f(x)' ) altair-5.0.1/tests/examples_arguments_syntax/simple_scatter_with_errorbars.py000066400000000000000000000016131443422213100301450ustar00rootroot00000000000000""" Simple Scatter Plot with Errorbars ---------------------------------- A simple scatter plot of a data set with errorbars. """ # category: uncertainties and trends import altair as alt import pandas as pd import numpy as np # generate some data points with uncertainties np.random.seed(0) x = [1, 2, 3, 4, 5] y = np.random.normal(10, 0.5, size=len(x)) yerr = 0.2 # set up data frame source = pd.DataFrame({"x": x, "y": y, "yerr": yerr}) # the base chart base = alt.Chart(source).transform_calculate( ymin="datum.y-datum.yerr", ymax="datum.y+datum.yerr" ) # generate the points points = base.mark_point( filled=True, size=50, color='black' ).encode( x=alt.X('x', scale=alt.Scale(domain=(0, 6))), y=alt.Y('y', scale=alt.Scale(zero=False)) ) # generate the error bars errorbars = base.mark_errorbar().encode( x="x", y="ymin:Q", y2="ymax:Q" ) points + errorbars altair-5.0.1/tests/examples_arguments_syntax/simple_stacked_area_chart.py000066400000000000000000000005201443422213100271470ustar00rootroot00000000000000""" Simple Stacked Area Chart ------------------------- This example shows how to make a simple stacked area chart. """ # category: simple charts import altair as alt from vega_datasets import data source = data.iowa_electricity() alt.Chart(source).mark_area().encode( x="year:T", y="net_generation:Q", color="source:N" ) altair-5.0.1/tests/examples_arguments_syntax/slider_cutoff.py000066400000000000000000000017001443422213100246400ustar00rootroot00000000000000""" Slider Cutoff ============= This example shows how to bind a variable parameter to a slider, and how to use the corresponding bound value to color data points. This example is based on an example from the Altair 4 documentation for Interactions, in which the interactivity was accomplished using a selection. The version below has been simplified significantly through the use of a variable parameter. Variable parameters were added in Altair 5. """ # category: interactive charts import altair as alt import pandas as pd import numpy as np rand = np.random.RandomState(42) df = pd.DataFrame({ 'xval': range(100), 'yval': rand.randn(100).cumsum() }) slider = alt.binding_range(min=0, max=100, step=1) cutoff = alt.param(bind=slider, value=50) alt.Chart(df).mark_point().encode( x='xval', y='yval', color=alt.condition( alt.datum.xval < cutoff, alt.value('red'), alt.value('blue') ) ).add_params( cutoff )altair-5.0.1/tests/examples_arguments_syntax/slope_graph.py000066400000000000000000000004351443422213100243170ustar00rootroot00000000000000""" Slope Graph ----------------------- This example shows how to make Slope Graph. """ # category: line charts import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_line().encode( x='year:O', y='median(yield)', color='site' ) altair-5.0.1/tests/examples_arguments_syntax/sorted_error_bars_with_ci.py000066400000000000000000000014301443422213100272360ustar00rootroot00000000000000""" Sorted Error Bars showing Confidence Interval ============================================= This example shows how to show error bars using confidence intervals, while also sorting the y-axis based on x-axis values. """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.barley() points = alt.Chart(source).mark_point( filled=True, color='black' ).encode( x=alt.X('mean(yield)', title='Barley Yield'), y=alt.Y( 'variety', sort=alt.EncodingSortField( field='yield', op='mean', order='descending' ) ) ).properties( width=400, height=250 ) error_bars = points.mark_rule().encode( x='ci0(yield)', x2='ci1(yield)', ) points + error_bars altair-5.0.1/tests/examples_arguments_syntax/stacked_bar_chart.py000066400000000000000000000005671443422213100254450ustar00rootroot00000000000000""" Stacked Bar Chart ----------------- This is an example of a stacked bar chart using data which contains crop yields over different regions and different years in the 1930s. """ # category: bar charts import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( x='variety', y='sum(yield)', color='site' ) altair-5.0.1/tests/examples_arguments_syntax/stacked_bar_chart_sorted_segments.py000066400000000000000000000007361443422213100307300ustar00rootroot00000000000000""" Stacked Bar Chart with Sorted Segments -------------------------------------- This is an example of a stacked-bar chart with the segments of each bar resorted. """ # category: bar charts import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( x='sum(yield)', y='variety', color='site', order=alt.Order( # Sort the segments of the bars by this field 'site', sort='ascending' ) ) altair-5.0.1/tests/examples_arguments_syntax/stacked_bar_chart_with_text.py000066400000000000000000000013471443422213100275410ustar00rootroot00000000000000""" Stacked Bar Chart with Text Overlay =================================== This example shows how to overlay text on a stacked bar chart. For both the bar and text marks, we use the ``stack`` argument in the ``x`` encoding to cause the values to be stacked horizontally. """ # category: bar charts import altair as alt from vega_datasets import data source=data.barley() bars = alt.Chart(source).mark_bar().encode( x=alt.X('sum(yield):Q', stack='zero'), y=alt.Y('variety:N'), color=alt.Color('site') ) text = alt.Chart(source).mark_text(dx=-15, dy=3, color='white').encode( x=alt.X('sum(yield):Q', stack='zero'), y=alt.Y('variety:N'), detail='site:N', text=alt.Text('sum(yield):Q', format='.1f') ) bars + text altair-5.0.1/tests/examples_arguments_syntax/stem_and_leaf.py000066400000000000000000000016761443422213100246050ustar00rootroot00000000000000""" Stem and Leaf Plot ------------------ This example shows how to make a stem and leaf plot. """ # category: advanced calculations import altair as alt import pandas as pd import numpy as np np.random.seed(42) # Generating random data source = pd.DataFrame({'samples': np.random.normal(50, 15, 100).astype(int).astype(str)}) # Splitting stem and leaf source['stem'] = source['samples'].str[:-1] source['leaf'] = source['samples'].str[-1] source = source.sort_values(by=['stem', 'leaf']) # Determining leaf position source['position'] = source.groupby('stem').cumcount().add(1) # Creating stem and leaf plot alt.Chart(source).mark_text( align='left', baseline='middle', dx=-5 ).encode( alt.X('position:Q', title='', axis=alt.Axis(ticks=False, labels=False, grid=False) ), alt.Y('stem:N', title='', axis=alt.Axis(tickSize=0)), text='leaf:N', ).configure_axis( labelFontSize=20 ).configure_text( fontSize=20 ) altair-5.0.1/tests/examples_arguments_syntax/step_chart.py000066400000000000000000000011271443422213100241470ustar00rootroot00000000000000""" Step Chart ---------- This example shows Google's stock price over time. This uses the "step-after" interpolation scheme. The full list of interpolation options includes 'linear', 'linear-closed', 'step', 'step-before', 'step-after', 'basis', 'basis-open', 'basis-closed', 'cardinal', 'cardinal-open', 'cardinal-closed', 'bundle', and 'monotone'. """ # category: line charts import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).mark_line(interpolate='step-after').encode( x='date', y='price' ).transform_filter( alt.datum.symbol == 'GOOG' ) altair-5.0.1/tests/examples_arguments_syntax/streamgraph.py000066400000000000000000000007761443422213100243410ustar00rootroot00000000000000""" Streamgraph ----------------- This example shows the streamgraph from vega-lite examples. """ # category: area charts import altair as alt from vega_datasets import data source = data.unemployment_across_industries.url alt.Chart(source).mark_area().encode( alt.X('yearmonth(date):T', axis=alt.Axis(format='%Y', domain=False, tickSize=0) ), alt.Y('sum(count):Q', stack='center', axis=None), alt.Color('series:N', scale=alt.Scale(scheme='category20b') ) ).interactive() altair-5.0.1/tests/examples_arguments_syntax/strip_plot.py000066400000000000000000000004221443422213100242070ustar00rootroot00000000000000""" Simple Strip Plot ----------------- A simple example of how to make a strip plot. """ # category: simple charts import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_tick().encode( x='Horsepower:Q', y='Cylinders:O' ) altair-5.0.1/tests/examples_arguments_syntax/strip_plot_jitter.py000066400000000000000000000024301443422213100255710ustar00rootroot00000000000000""" Strip Plot with Jitter ---------------------- In this chart, we encode the ``Major_Genre`` column from the ``movies`` dataset in the ``y``-channel. In the default presentation of this data, it would be difficult to gauge the relative frequencies with which different values occur because there would be so much overlap. To address this, we use the ``yOffset`` channel to incorporate a random offset (jittering). The example is shown twice, on the left side using normally distributed and on the right side using uniformally distributed jitter. """ # category: distributions import altair as alt from vega_datasets import data source = data.movies.url gaussian_jitter = alt.Chart(source, title='Normally distributed jitter').mark_circle(size=8).encode( y="Major_Genre:N", x="IMDB_Rating:Q", yOffset="jitter:Q", color=alt.Color('Major_Genre:N', legend=None) ).transform_calculate( # Generate Gaussian jitter with a Box-Muller transform jitter="sqrt(-2*log(random()))*cos(2*PI*random())" ) uniform_jitter = gaussian_jitter.transform_calculate( # Generate uniform jitter jitter='random()' ).encode( y=alt.Y('Major_Genre:N', axis=None) ).properties( title='Uniformly distributed jitter' ) (gaussian_jitter | uniform_jitter).resolve_scale(yOffset='independent') altair-5.0.1/tests/examples_arguments_syntax/table_bubble_plot_github.py000066400000000000000000000005731443422213100270210ustar00rootroot00000000000000""" Table Bubble Plot (Github Punch Card) ------------------------------------- This example shows github contributions by the day of week and hour of the day. """ # category: distributions import altair as alt from vega_datasets import data source = data.github.url alt.Chart(source).mark_circle().encode( x='hours(time):O', y='day(time):O', size='sum(count):Q' ) altair-5.0.1/tests/examples_arguments_syntax/top_k_items.py000066400000000000000000000012461443422213100243320ustar00rootroot00000000000000""" Top K Items ----------- This example shows how to use the window and transformation filter to display the Top items of a long list of items in decreasing order. Here we sort the top 10 highest ranking movies of IMDB. """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.movies.url # Top 10 movies by IMBD rating alt.Chart( source, ).mark_bar().encode( x=alt.X('Title:N', sort='-y'), y=alt.Y('IMDB_Rating:Q'), color=alt.Color('IMDB_Rating:Q') ).transform_window( rank='rank(IMDB_Rating)', sort=[alt.SortField('IMDB_Rating', order='descending')] ).transform_filter( (alt.datum.rank < 10) ) altair-5.0.1/tests/examples_arguments_syntax/top_k_letters.py000066400000000000000000000026431443422213100246750ustar00rootroot00000000000000""" Top K Letters ------------- This example shows how to use a window transform in order to display only the top K categories by number of entries. In this case, we rank the characters in the first paragraph of Dickens' *A Tale of Two Cities* by number of occurances. """ # category: advanced calculations import altair as alt import pandas as pd import numpy as np # Excerpt from A Tale of Two Cities; public domain text text = """ It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way - in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only. """ source = pd.DataFrame( {'letters': np.array([c for c in text if c.isalpha()])} ) alt.Chart(source).transform_aggregate( count='count()', groupby=['letters'] ).transform_window( rank='rank(count)', sort=[alt.SortField('count', order='descending')] ).transform_filter( alt.datum.rank < 10 ).mark_bar().encode( y=alt.Y('letters:N', sort='-x'), x='count:Q', ) altair-5.0.1/tests/examples_arguments_syntax/top_k_with_others.py000066400000000000000000000017211443422213100255460ustar00rootroot00000000000000""" Top-K Plot with Others ---------------------- This example shows how to use aggregate, window, and calculate transfromations to display the top-k directors by average worldwide gross while grouping the remaining directors as 'All Others'. """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.movies.url alt.Chart(source).mark_bar().encode( x=alt.X("aggregate_gross:Q", aggregate="mean", title=None), y=alt.Y( "ranked_director:N", sort=alt.Sort(op="mean", field="aggregate_gross", order="descending"), title=None, ), ).transform_aggregate( aggregate_gross='mean(Worldwide_Gross)', groupby=["Director"], ).transform_window( rank='row_number()', sort=[alt.SortField("aggregate_gross", order="descending")], ).transform_calculate( ranked_director="datum.rank < 10 ? datum.Director : 'All Others'" ).properties( title="Top Directors by Average Worldwide Gross", ) altair-5.0.1/tests/examples_arguments_syntax/trail_marker.py000066400000000000000000000005151443422213100244670ustar00rootroot00000000000000""" Line Chart with Varying Size ---------------------------- This is example of using the ``trail`` marker to vary the size of a line. """ # category: line charts import altair as alt from vega_datasets import data source = data.wheat() alt.Chart(source).mark_trail().encode( x='year:T', y='wheat:Q', size='wheat:Q' ) altair-5.0.1/tests/examples_arguments_syntax/trellis_area.py000066400000000000000000000005521443422213100244620ustar00rootroot00000000000000""" Trellis Area Chart ------------------ This example shows small multiples of an area chart. """ # category: area charts import altair as alt from vega_datasets import data source = data.iowa_electricity() alt.Chart(source).mark_area().encode( x="year:T", y="net_generation:Q", color="source:N", row="source:N" ).properties( height=100 ) altair-5.0.1/tests/examples_arguments_syntax/trellis_area_sort_array.py000066400000000000000000000010301443422213100267170ustar00rootroot00000000000000''' Trellis Area Sort Chart ----------------------- This example shows small multiples of an area chart. Stock prices of four large companies sorted by `['MSFT', 'AAPL', 'IBM', 'AMZN']` ''' # category: area charts import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).transform_filter( alt.datum.symbol != 'GOOG' ).mark_area().encode( x='date:T', y='price:Q', color='symbol:N', row=alt.Row('symbol:N', sort=['MSFT', 'AAPL', 'IBM', 'AMZN']) ).properties(height=50, width=400) altair-5.0.1/tests/examples_arguments_syntax/trellis_histogram.py000066400000000000000000000005761443422213100255550ustar00rootroot00000000000000""" Trellis Histogram ----------------- This example shows how to make a basic trellis histogram. https://vega.github.io/vega-lite/examples/trellis_bar_histogram.html """ # category: distributions import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_bar().encode( alt.X("Horsepower:Q", bin=True), y='count()', row='Origin' ) altair-5.0.1/tests/examples_arguments_syntax/trellis_scatter_plot.py000066400000000000000000000004771443422213100262630ustar00rootroot00000000000000""" Trellis Scatter Plot ----------------------- This example shows how to make a trellis scatter plot. """ # category: scatter plots import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', row='Origin:N' ) altair-5.0.1/tests/examples_arguments_syntax/trellis_stacked_bar_chart.py000066400000000000000000000006671443422213100272040ustar00rootroot00000000000000""" Trellis Stacked Bar Chart ========================= This is an example of a horizontal stacked bar chart using data which contains crop yields over different regions and different years in the 1930s. """ # category: bar charts import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( column='year:O', x='yield', y='variety', color='site' ).properties(width=220) altair-5.0.1/tests/examples_arguments_syntax/us_employment.py000066400000000000000000000025501443422213100247140ustar00rootroot00000000000000""" The U.S. Employment Crash During the Great Recession ---------------------------------------------------- This example is a fully developed bar chart with negative values using the sample dataset of U.S. employment changes during the Great Recession. """ # category: case studies import altair as alt import pandas as pd from vega_datasets import data source = data.us_employment() presidents = pd.DataFrame([ { "start": "2006-01-01", "end": "2009-01-19", "president": "Bush" }, { "start": "2009-01-20", "end": "2015-12-31", "president": "Obama" } ]) bars = alt.Chart( source, title="The U.S. employment crash during the Great Recession" ).mark_bar().encode( x=alt.X("month:T", title=""), y=alt.Y("nonfarm_change:Q", title="Change in non-farm employment (in thousands)"), color=alt.condition( alt.datum.nonfarm_change > 0, alt.value("steelblue"), alt.value("orange") ) ) rule = alt.Chart(presidents).mark_rule( color="black", strokeWidth=2 ).encode( x='end:T' ).transform_filter(alt.datum.president == "Bush") text = alt.Chart(presidents).mark_text( align='left', baseline='middle', dx=7, dy=-135, size=11 ).encode( x='start:T', text='president', color=alt.value('#000000') ) (bars + rule + text).properties(width=600) altair-5.0.1/tests/examples_arguments_syntax/us_incomebrackets_by_state_facet.py000066400000000000000000000012231443422213100305440ustar00rootroot00000000000000""" US Income by State: Wrapped Facet --------------------------------- This example shows how to create a map of income in the US by state, faceted over income brackets """ # category: maps import altair as alt from vega_datasets import data states = alt.topo_feature(data.us_10m.url, 'states') source = data.income.url alt.Chart(source).mark_geoshape().encode( shape='geo:G', color='pct:Q', tooltip=['name:N', 'pct:Q'], facet=alt.Facet('group:N', columns=2), ).transform_lookup( lookup='id', from_=alt.LookupData(data=states, key='id'), as_='geo' ).properties( width=300, height=175, ).project( type='albersUsa' )altair-5.0.1/tests/examples_arguments_syntax/us_population_over_time.py000066400000000000000000000022141443422213100267630ustar00rootroot00000000000000""" US Population by Age and Sex ============================ This chart visualizes the age distribution of the US population over time. It uses a slider widget that is bound to the year to visualize the age distribution over time. """ # category: case studies import altair as alt from vega_datasets import data source = data.population.url select_year = alt.selection_point( name="Year", fields=["year"], bind=alt.binding_range(min=1900, max=2000, step=10, name="Year"), value={"year": 2000}, ) alt.Chart(source).mark_bar().encode( x=alt.X("sex:N", axis=alt.Axis(labels=False, title=None, ticks=False)), y=alt.Y("people:Q", scale=alt.Scale(domain=(0, 12000000)), title="Population"), color=alt.Color( "sex:N", scale=alt.Scale(domain=("Male", "Female"), range=["steelblue", "salmon"]), title="Sex", ), column=alt.Column("age:O", title="Age"), ).properties( width=20, title="U.S. Population by Age and Sex" ).add_params( select_year ).transform_calculate( "sex", alt.expr.if_(alt.datum.sex == 1, "Male", "Female") ).transform_filter( select_year ).configure_facet( spacing=8 ) altair-5.0.1/tests/examples_arguments_syntax/us_population_over_time_facet.py000066400000000000000000000011101443422213100301170ustar00rootroot00000000000000""" US Population: Wrapped Facet ============================ This chart visualizes the age distribution of the US population over time, using a wrapped faceting of the data by decade. """ # category: case studies import altair as alt from vega_datasets import data source = data.population.url alt.Chart(source).mark_area().encode( x='age:O', y=alt.Y( 'sum(people):Q', title='Population', axis=alt.Axis(format='~s') ), facet=alt.Facet('year:O', columns=5), ).properties( title='US Age Distribution By Year', width=90, height=80 )altair-5.0.1/tests/examples_arguments_syntax/us_population_pyramid_over_time.py000066400000000000000000000030741443422213100305150ustar00rootroot00000000000000''' US Population Pyramid Over Time =============================== A population pyramid shows the distribution of age groups within a population. It uses a slider widget that is bound to the year to visualize the age distribution over time. ''' # category: case studies import altair as alt from vega_datasets import data source = data.population.url slider = alt.binding_range(min=1850, max=2000, step=10) select_year = alt.selection_point(name='year', fields=['year'], bind=slider, value={'year': 2000}) base = alt.Chart(source).add_params( select_year ).transform_filter( select_year ).transform_calculate( gender=alt.expr.if_(alt.datum.sex == 1, 'Male', 'Female') ).properties( width=250 ) color_scale = alt.Scale(domain=['Male', 'Female'], range=['#1f77b4', '#e377c2']) left = base.transform_filter( alt.datum.gender == 'Female' ).encode( y=alt.Y('age:O', axis=None), x=alt.X('sum(people):Q', title='population', sort=alt.SortOrder('descending')), color=alt.Color('gender:N', scale=color_scale, legend=None) ).mark_bar().properties(title='Female') middle = base.encode( y=alt.Y('age:O', axis=None), text=alt.Text('age:Q'), ).mark_text().properties(width=20) right = base.transform_filter( alt.datum.gender == 'Male' ).encode( y=alt.Y('age:O', axis=None), x=alt.X('sum(people):Q', title='population'), color=alt.Color('gender:N', scale=color_scale, legend=None) ).mark_bar().properties(title='Male') alt.concat(left, middle, right, spacing=5) altair-5.0.1/tests/examples_arguments_syntax/us_state_capitals.py000066400000000000000000000021251443422213100255210ustar00rootroot00000000000000""" U.S. State Capitals Overlayed on a Map of the U.S ------------------------------------------------- This is a layered geographic visualization that shows US capitals overlayed on a map. """ # category: case studies import altair as alt from vega_datasets import data states = alt.topo_feature(data.us_10m.url, 'states') capitals = data.us_state_capitals.url # US states background background = alt.Chart(states).mark_geoshape( fill='lightgray', stroke='white' ).properties( title='US State Capitols', width=650, height=400 ).project('albersUsa') # Points and text hover = alt.selection_point(on='mouseover', nearest=True, fields=['lat', 'lon']) base = alt.Chart(capitals).encode( longitude='lon:Q', latitude='lat:Q', ) text = base.mark_text(dy=-5, align='right').encode( alt.Text('city', type='nominal'), opacity=alt.condition(~hover, alt.value(0), alt.value(1)) ) points = base.mark_point().encode( color=alt.value('black'), size=alt.condition(~hover, alt.value(30), alt.value(100)) ).add_params(hover) background + points + text altair-5.0.1/tests/examples_arguments_syntax/violin_plot.py000066400000000000000000000015711443422213100243540ustar00rootroot00000000000000""" Violin Plot ----------- This example shows how to make a Violin Plot using Altair's density transform. """ # category: distributions import altair as alt from vega_datasets import data alt.Chart(data.cars()).transform_density( 'Miles_per_Gallon', as_=['Miles_per_Gallon', 'density'], extent=[5, 50], groupby=['Origin'] ).mark_area(orient='horizontal').encode( y='Miles_per_Gallon:Q', color='Origin:N', x=alt.X( 'density:Q', stack='center', impute=None, title=None, axis=alt.Axis(labels=False, values=[0],grid=False, ticks=True), ), column=alt.Column( 'Origin:N', header=alt.Header( titleOrient='bottom', labelOrient='bottom', labelPadding=0, ), ) ).properties( width=100 ).configure_facet( spacing=0 ).configure_view( stroke=None ) altair-5.0.1/tests/examples_arguments_syntax/waterfall_chart.py000066400000000000000000000063361443422213100251640ustar00rootroot00000000000000""" Waterfall Chart --------------- This example shows how to recreate a Vega-Lite implementation of a waterfall chart. Original inspiration is from https://vega.github.io/vega-lite/examples/waterfall_chart.html """ # category: advanced calculations import altair as alt import pandas as pd data = [ {"label": "Begin", "amount": 4000}, {"label": "Jan", "amount": 1707}, {"label": "Feb", "amount": -1425}, {"label": "Mar", "amount": -1030}, {"label": "Apr", "amount": 1812}, {"label": "May", "amount": -1067}, {"label": "Jun", "amount": -1481}, {"label": "Jul", "amount": 1228}, {"label": "Aug", "amount": 1176}, {"label": "Sep", "amount": 1146}, {"label": "Oct", "amount": 1205}, {"label": "Nov", "amount": -1388}, {"label": "Dec", "amount": 1492}, {"label": "End", "amount": 0}, ] source = pd.DataFrame(data) # The "base_chart" defines the transform_window, transform_calculate, and X axis base_chart = alt.Chart(source).transform_window( window_sum_amount="sum(amount)", window_lead_label="lead(label)", ).transform_calculate( calc_lead="datum.window_lead_label === null ? datum.label : datum.window_lead_label", calc_prev_sum="datum.label === 'End' ? 0 : datum.window_sum_amount - datum.amount", calc_amount="datum.label === 'End' ? datum.window_sum_amount : datum.amount", calc_text_amount="(datum.label !== 'Begin' && datum.label !== 'End' && datum.calc_amount > 0 ? '+' : '') + datum.calc_amount", calc_center="(datum.window_sum_amount + datum.calc_prev_sum) / 2", calc_sum_dec="datum.window_sum_amount < datum.calc_prev_sum ? datum.window_sum_amount : ''", calc_sum_inc="datum.window_sum_amount > datum.calc_prev_sum ? datum.window_sum_amount : ''", ).encode( x=alt.X( "label:O", axis=alt.Axis(title="Months", labelAngle=0), sort=None, ) ) # alt.condition does not support multiple if else conditions which is why # we use a dictionary instead. See https://stackoverflow.com/a/66109641 # for more information color_coding = { "condition": [ {"test": "datum.label === 'Begin' || datum.label === 'End'", "value": "#878d96"}, {"test": "datum.calc_amount < 0", "value": "#24a148"}, ], "value": "#fa4d56", } bar = base_chart.mark_bar(size=45).encode( y=alt.Y("calc_prev_sum:Q", title="Amount"), y2=alt.Y2("window_sum_amount:Q"), color=color_coding, ) # The "rule" chart is for the horizontal lines that connect the bars rule = base_chart.mark_rule( xOffset=-22.5, x2Offset=22.5, ).encode( y="window_sum_amount:Q", x2="calc_lead", ) # Add values as text text_pos_values_top_of_bar = base_chart.mark_text( baseline="bottom", dy=-4 ).encode( text=alt.Text("calc_sum_inc:N"), y="calc_sum_inc:Q" ) text_neg_values_bot_of_bar = base_chart.mark_text( baseline="top", dy=4 ).encode( text=alt.Text("calc_sum_dec:N"), y="calc_sum_dec:Q" ) text_bar_values_mid_of_bar = base_chart.mark_text(baseline="middle").encode( text=alt.Text("calc_text_amount:N"), y="calc_center:Q", color=alt.value("white"), ) alt.layer( bar, rule, text_pos_values_top_of_bar, text_neg_values_bot_of_bar, text_bar_values_mid_of_bar ).properties( width=800, height=450 )altair-5.0.1/tests/examples_arguments_syntax/wheat_wages.py000066400000000000000000000034721443422213100243160ustar00rootroot00000000000000""" Wheat and Wages --------------- A recreation of William Playfair's classic chart visualizing the price of wheat, the wages of a mechanic, and the reigning British monarch. This is a more polished version of the simpler chart in :ref:`gallery_bar_and_line_with_dual_axis`. """ # category: case studies import altair as alt from vega_datasets import data base_wheat = alt.Chart(data.wheat.url).transform_calculate( year_end="+datum.year + 5") base_monarchs = alt.Chart(data.monarchs.url).transform_calculate( offset="((!datum.commonwealth && datum.index % 2) ? -1: 1) * 2 + 95", off2="((!datum.commonwealth && datum.index % 2) ? -1: 1) + 95", y="95", x="+datum.start + (+datum.end - +datum.start)/2" ) bars = base_wheat.mark_bar(**{"fill": "#aaa", "stroke": "#999"}).encode( x=alt.X("year:Q", axis=alt.Axis(format='d', tickCount=5)), y=alt.Y("wheat:Q", axis=alt.Axis(zindex=1)), x2=alt.X2("year_end") ) area = base_wheat.mark_area(**{"color": "#a4cedb", "opacity": 0.7}).encode( x=alt.X("year:Q"), y=alt.Y("wages:Q") ) area_line_1 = area.mark_line(**{"color": "#000", "opacity": 0.7}) area_line_2 = area.mark_line(**{"yOffset": -2, "color": "#EE8182"}) top_bars = base_monarchs.mark_bar(stroke="#000").encode( x=alt.X("start:Q"), x2=alt.X2("end"), y=alt.Y("y:Q"), y2=alt.Y2("offset"), fill=alt.Fill("commonwealth:N", legend=None, scale=alt.Scale(range=["black", "white"])) ) top_text = base_monarchs.mark_text(**{"yOffset": 14, "fontSize": 9, "fontStyle": "italic"}).encode( x=alt.X("x:Q"), y=alt.Y("off2:Q"), text=alt.Text("name:N") ) (bars + area + area_line_1 + area_line_2 + top_bars + top_text).properties( width=900, height=400 ).configure_axis( title=None, gridColor="white", gridOpacity=0.25, domain=False ).configure_view( stroke="transparent" )altair-5.0.1/tests/examples_arguments_syntax/wilkinson-dot-plot.py000066400000000000000000000010721443422213100255670ustar00rootroot00000000000000""" Wilkinson Dot Plot ------------------ An example of a `Wilkinson Dot Plot `_ """ # category: advanced calculations import altair as alt import pandas as pd source = pd.DataFrame( {"data":[1,1,1,1,1,1,1,1,1,1, 2,2,2, 3,3, 4,4,4,4,4,4] } ) alt.Chart(source).mark_circle(opacity=1).transform_window( id='rank()', groupby=['data'] ).encode( alt.X('data:O'), alt.Y('id:O', axis=None, sort='descending') ).properties(height=100) altair-5.0.1/tests/examples_arguments_syntax/wind_vector_map.py000066400000000000000000000032601443422213100251730ustar00rootroot00000000000000""" Wind Vector Map --------------- An example showing a vector array map showing wind speed and direction using ``wedge`` as shape for ``mark_point`` and ``angle`` encoding for the wind direction. This is adapted from this corresponding Vega-Lite Example: `Wind Vector Map `_ with an added base map. """ # category: maps import altair as alt from vega_datasets import data df_wind = data.windvectors() data_world = alt.topo_feature(data.world_110m.url, "countries") wedge = ( alt.Chart(df_wind) .mark_point(shape="wedge", filled=True) .encode( latitude="latitude", longitude="longitude", color=alt.Color( "dir", scale=alt.Scale(domain=[0, 360], scheme="rainbow"), legend=None ), angle=alt.Angle("dir", scale=alt.Scale(domain=[0, 360], range=[180, 540])), size=alt.Size("speed", scale=alt.Scale(rangeMax=500)), ) .project("equalEarth") ) xmin, xmax, ymin, ymax = ( df_wind.longitude.min(), df_wind.longitude.max(), df_wind.latitude.min(), df_wind.latitude.max(), ) # extent as feature or featurecollection extent = { "type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[ [xmax, ymax], [xmax, ymin], [xmin, ymin], [xmin, ymax], [xmax, ymax]]] }, "properties": {} } # use fit combined with clip=True base = ( alt.Chart(data_world) .mark_geoshape(clip=True, fill="lightgray", stroke="black", strokeWidth=0.5) .project(type="equalEarth", fit=extent) ) base + wedgealtair-5.0.1/tests/examples_arguments_syntax/window_rank.py000066400000000000000000000031711443422213100243360ustar00rootroot00000000000000""" Window Rank Line Chart ---------------------- This example shows the Group F rankings in the 2018 World Cup after each matchday. A window transformation is used to rank each after each match day, sorting by points and difference. """ # category: line charts import altair as alt import pandas as pd source = pd.DataFrame( [ {"team": "Germany", "matchday": 1, "point": 0, "diff": -1}, {"team": "Germany", "matchday": 2, "point": 3, "diff": 0}, {"team": "Germany", "matchday": 3, "point": 3, "diff": -2}, {"team": "Mexico", "matchday": 1, "point": 3, "diff": 1}, {"team": "Mexico", "matchday": 2, "point": 6, "diff": 2}, {"team": "Mexico", "matchday": 3, "point": 6, "diff": -1}, {"team": "South Korea", "matchday": 1, "point": 0, "diff": -1}, {"team": "South Korea", "matchday": 2, "point": 0, "diff": -2}, {"team": "South Korea", "matchday": 3, "point": 3, "diff": 0}, {"team": "Sweden", "matchday": 1, "point": 3, "diff": 1}, {"team": "Sweden", "matchday": 2, "point": 3, "diff": 0}, {"team": "Sweden", "matchday": 3, "point": 6, "diff": 3}, ] ) color_scale = alt.Scale( domain=["Germany", "Mexico", "South Korea", "Sweden"], range=["#000000", "#127153", "#C91A3C", "#0C71AB"], ) alt.Chart(source).mark_line().encode( x="matchday:O", y="rank:O", color=alt.Color("team:N", scale=color_scale) ).transform_window( rank="rank()", sort=[ alt.SortField("point", order="descending"), alt.SortField("diff", order="descending"), ], groupby=["matchday"], ).properties(title="World Cup 2018: Group F Rankings") altair-5.0.1/tests/examples_arguments_syntax/world_map.py000066400000000000000000000013221443422213100237740ustar00rootroot00000000000000""" World Map --------- This example shows how to create a world map using data generators for different background layers. """ # category: maps import altair as alt from vega_datasets import data # Data generators for the background sphere = alt.sphere() graticule = alt.graticule() # Source of land data source = alt.topo_feature(data.world_110m.url, 'countries') # Layering and configuring the components alt.layer( alt.Chart(sphere).mark_geoshape(fill='lightblue'), alt.Chart(graticule).mark_geoshape(stroke='white', strokeWidth=0.5), alt.Chart(source).mark_geoshape(fill='ForestGreen', stroke='black') ).project( 'naturalEarth1' ).properties(width=600, height=400).configure_view(stroke=None) altair-5.0.1/tests/examples_arguments_syntax/world_projections.py000066400000000000000000000013051443422213100255570ustar00rootroot00000000000000""" World Projections ----------------- This example shows a map of the countries of the world using four available geographic projections. For more details on the projections available in Altair, see https://vega.github.io/vega-lite/docs/projection.html """ # category: maps import altair as alt from vega_datasets import data source = alt.topo_feature(data.world_110m.url, 'countries') base = alt.Chart(source).mark_geoshape( fill='#666666', stroke='white' ).properties( width=300, height=180 ) projections = ['equirectangular', 'mercator', 'orthographic', 'gnomonic'] charts = [base.project(proj).properties(title=proj) for proj in projections] alt.concat(*charts, columns=2) altair-5.0.1/tests/examples_methods_syntax/000077500000000000000000000000001443422213100210765ustar00rootroot00000000000000altair-5.0.1/tests/examples_methods_syntax/__init__.py000066400000000000000000000015701443422213100232120ustar00rootroot00000000000000import os from typing import Set # Set of the names of examples that should have SVG static images. # This is for examples that VlConvert's PNG export does not support. SVG_EXAMPLES: Set[str] = {"isotype_emoji"} def iter_examples_methods_syntax(): """Iterate over the examples in this directory. Each item is a dict with the following keys: - "name" : the unique name of the example - "filename" : the full file path to the example """ examples_methods_syntax_dir = os.path.abspath(os.path.dirname(__file__)) for filename in os.listdir(examples_methods_syntax_dir): name, ext = os.path.splitext(filename) if name.startswith("_") or ext != ".py": continue yield { "name": name, "filename": os.path.join(examples_methods_syntax_dir, filename), "use_svg": name in SVG_EXAMPLES } altair-5.0.1/tests/examples_methods_syntax/airport_connections.py000066400000000000000000000037351443422213100255420ustar00rootroot00000000000000""" Connections Among U.S. Airports Interactive ------------------------------------------- This example shows all the connections between major U.S. airports. Lookup transformations are used to find the coordinates of each airport and connecting airports. Connections are displayed on mouseover via a single selection. """ # category: case studies import altair as alt from vega_datasets import data # Since these data are each more than 5,000 rows we'll import from the URLs airports = data.airports.url flights_airport = data.flights_airport.url states = alt.topo_feature(data.us_10m.url, feature="states") # Create mouseover selection select_city = alt.selection_point( on="mouseover", nearest=True, fields=["origin"], empty=False ) # Define which attributes to lookup from airports.csv lookup_data = alt.LookupData( airports, key="iata", fields=["state", "latitude", "longitude"] ) background = alt.Chart(states).mark_geoshape( fill="lightgray", stroke="white" ).properties( width=750, height=500 ).project("albersUsa") connections = alt.Chart(flights_airport).mark_rule(opacity=0.35).encode( latitude="latitude:Q", longitude="longitude:Q", latitude2="lat2:Q", longitude2="lon2:Q" ).transform_lookup( lookup="origin", from_=lookup_data ).transform_lookup( lookup="destination", from_=lookup_data, as_=["state", "lat2", "lon2"] ).transform_filter( select_city ) points = alt.Chart(flights_airport).mark_circle().encode( latitude="latitude:Q", longitude="longitude:Q", size=alt.Size("routes:Q").legend(None).scale(range=[0, 1000]), order=alt.Order("routes:Q").sort("descending"), tooltip=["origin:N", "routes:Q"] ).transform_aggregate( routes="count()", groupby=["origin"] ).transform_lookup( lookup="origin", from_=lookup_data ).transform_filter( (alt.datum.state != "PR") & (alt.datum.state != "VI") ).add_params( select_city ) (background + connections + points).configure_view(stroke=None) altair-5.0.1/tests/examples_methods_syntax/annual_weather_heatmap.py000066400000000000000000000011601443422213100261420ustar00rootroot00000000000000""" Annual Weather Heatmap ---------------------- """ # category: tables import altair as alt from vega_datasets import data source = data.seattle_weather() alt.Chart(source, title="Daily Max Temperatures (C) in Seattle, WA").mark_rect().encode( alt.X("date(date):O").title("Day").axis(format="%e", labelAngle=0), alt.Y("month(date):O").title("Month"), alt.Color("max(temp_max)").title(None), tooltip=[ alt.Tooltip("monthdate(date)", title="Date"), alt.Tooltip("max(temp_max)", title="Max Temp"), ], ).configure_view( step=13, strokeWidth=0 ).configure_axis( domain=False ) altair-5.0.1/tests/examples_methods_syntax/anscombe_plot.py000066400000000000000000000011511443422213100242730ustar00rootroot00000000000000""" Anscombe's Quartet ------------------ This example shows how to use the column channel to make a trellis plot. Anscombe's Quartet is a famous dataset constructed by Francis Anscombe. Common summary statistics are identical for each subset of the data, despite the subsets having vastly different characteristics. """ # category: case studies import altair as alt from vega_datasets import data source = data.anscombe() alt.Chart(source).mark_circle().encode( alt.X('X').scale(zero=False), alt.Y('Y').scale(zero=False), alt.Facet('Series', columns=2), ).properties( width=180, height=180, ) altair-5.0.1/tests/examples_methods_syntax/bar_chart_sorted.py000066400000000000000000000004511443422213100247550ustar00rootroot00000000000000""" Sorted Bar Chart ================ This example shows a bar chart sorted by a calculated value. """ # category: bar charts import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( x='sum(yield):Q', y=alt.Y('site:N').sort('-x') ) altair-5.0.1/tests/examples_methods_syntax/bar_chart_trellis_compact.py000066400000000000000000000040301443422213100266360ustar00rootroot00000000000000""" Compact Trellis Grid of Bar Charts ================================== This example shows a simple grid of bar charts to compare performance data.. """ # category: bar charts import altair as alt import pandas as pd source = pd.DataFrame( [ {"a": "a1", "b": "b1", "c": "x", "p": "0.14"}, {"a": "a1", "b": "b1", "c": "y", "p": "0.60"}, {"a": "a1", "b": "b1", "c": "z", "p": "0.03"}, {"a": "a1", "b": "b2", "c": "x", "p": "0.80"}, {"a": "a1", "b": "b2", "c": "y", "p": "0.38"}, {"a": "a1", "b": "b2", "c": "z", "p": "0.55"}, {"a": "a1", "b": "b3", "c": "x", "p": "0.11"}, {"a": "a1", "b": "b3", "c": "y", "p": "0.58"}, {"a": "a1", "b": "b3", "c": "z", "p": "0.79"}, {"a": "a2", "b": "b1", "c": "x", "p": "0.83"}, {"a": "a2", "b": "b1", "c": "y", "p": "0.87"}, {"a": "a2", "b": "b1", "c": "z", "p": "0.67"}, {"a": "a2", "b": "b2", "c": "x", "p": "0.97"}, {"a": "a2", "b": "b2", "c": "y", "p": "0.84"}, {"a": "a2", "b": "b2", "c": "z", "p": "0.90"}, {"a": "a2", "b": "b3", "c": "x", "p": "0.74"}, {"a": "a2", "b": "b3", "c": "y", "p": "0.64"}, {"a": "a2", "b": "b3", "c": "z", "p": "0.19"}, {"a": "a3", "b": "b1", "c": "x", "p": "0.57"}, {"a": "a3", "b": "b1", "c": "y", "p": "0.35"}, {"a": "a3", "b": "b1", "c": "z", "p": "0.49"}, {"a": "a3", "b": "b2", "c": "x", "p": "0.91"}, {"a": "a3", "b": "b2", "c": "y", "p": "0.38"}, {"a": "a3", "b": "b2", "c": "z", "p": "0.91"}, {"a": "a3", "b": "b3", "c": "x", "p": "0.99"}, {"a": "a3", "b": "b3", "c": "y", "p": "0.80"}, {"a": "a3", "b": "b3", "c": "z", "p": "0.37"}, ] ) alt.Chart(source, width=60, height=alt.Step(8)).mark_bar().encode( alt.Y("c:N").axis(None), alt.X("p:Q").title(None).axis(format="%"), alt.Color("c:N").title("settings").legend(orient="bottom", titleOrient="left"), alt.Row("a:N").title("Factor A").header(labelAngle=0), alt.Column("b:N").title("Factor B"), ) altair-5.0.1/tests/examples_methods_syntax/beckers_barley_trellis_plot.py000066400000000000000000000021201443422213100272130ustar00rootroot00000000000000""" Becker's Barley Trellis Plot ---------------------------- The example demonstrates the trellis charts created by Richard Becker, William Cleveland and others in the 1990s. Using the visualization technique below they identified an anomoly in a widely used agriculatural dataset, which they termed `"The Morris Mistake." `_. It became their favored way of showcasing the power of this pioneering plot. """ # category: case studies import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source, title="The Morris Mistake").mark_point().encode( alt.X('yield:Q') .title("Barley Yield (bushels/acre)") .scale(zero=False) .axis(grid=False), alt.Y('variety:N') .title("") .sort('-x') .axis(grid=True), alt.Color('year:N') .legend(title="Year"), alt.Row('site:N') .title("") .sort(alt.EncodingSortField(field='yield', op='sum', order='descending')) ).properties( height=alt.Step(20) ).configure_view(stroke="transparent") altair-5.0.1/tests/examples_methods_syntax/beckers_barley_wrapped_facet.py000066400000000000000000000013331443422213100273100ustar00rootroot00000000000000""" Becker's Barley Trellis Plot (Wrapped Facet) -------------------------------------------- The example demonstrates the trellis charts created by Richard Becker, William Cleveland and others in the 1990s. This is the Altair replicate of `the VegaLite version `_ demonstrating the usage of `columns` argument to create wrapped facet. """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.barley.url alt.Chart(source).mark_point().encode( alt.X('median(yield):Q').scale(zero=False), y='variety:O', color='year:N', facet=alt.Facet('site:O', columns=2), ).properties( width=200, height=100, ) altair-5.0.1/tests/examples_methods_syntax/bump_chart.py000066400000000000000000000013701443422213100235750ustar00rootroot00000000000000""" Bump Chart ---------- This example shows a bump chart. The data is first grouped into six-month intervals using pandas. The ranks are computed by Altair using a window transform. """ # category: line charts import altair as alt from vega_datasets import data import pandas as pd stocks = data.stocks() source = stocks.groupby([pd.Grouper(key="date", freq="6M"),"symbol"]).mean().reset_index() alt.Chart(source).mark_line(point=True).encode( x=alt.X("date:O").timeUnit("yearmonth").title("date"), y="rank:O", color=alt.Color("symbol:N") ).transform_window( rank="rank()", sort=[alt.SortField("price", order="descending")], groupby=["date"] ).properties( title="Bump Chart for Stock Prices", width=600, height=150, ) altair-5.0.1/tests/examples_methods_syntax/candlestick_chart.py000066400000000000000000000021311443422213100251120ustar00rootroot00000000000000""" Candlestick Chart ================= A candlestick chart inspired from `Protovis `_. This example shows the performance of the Chicago Board Options Exchange `Volatility Index `_ (VIX) in the summer of 2009. The thick bar represents the opening and closing prices, while the thin bar shows intraday high and low prices; if the index closed higher on a given day, the bars are colored green rather than red. """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.ohlc() open_close_color = alt.condition( "datum.open <= datum.close", alt.value("#06982d"), alt.value("#ae1325") ) base = alt.Chart(source).encode( alt.X('date:T') .axis(format='%m/%d', labelAngle=-45) .title('Date in 2009'), color=open_close_color ) rule = base.mark_rule().encode( alt.Y('low:Q') .title('Price') .scale(zero=False), alt.Y2('high:Q') ) bar = base.mark_bar().encode( alt.Y('open:Q'), alt.Y2('close:Q') ) rule + bar altair-5.0.1/tests/examples_methods_syntax/co2_concentration.py000066400000000000000000000031601443422213100250610ustar00rootroot00000000000000""" Atmospheric CO2 Concentration ----------------------------- This example is a fully developed line chart that uses a window transformation. It was inspired by `Gregor Aisch's work at datawrapper `_. """ # category: case studies import altair as alt from vega_datasets import data source = data.co2_concentration.url base = alt.Chart( source, title="Carbon Dioxide in the Atmosphere" ).transform_calculate( year="year(datum.Date)" ).transform_calculate( decade="floor(datum.year / 10)" ).transform_calculate( scaled_date="(datum.year % 10) + (month(datum.Date)/12)" ).transform_window( first_date='first_value(scaled_date)', last_date='last_value(scaled_date)', sort=[{"field": "scaled_date", "order": "ascending"}], groupby=['decade'], frame=[None, None] ).transform_calculate( end=( "datum.first_date === datum.scaled_date ? 'first'" ": datum.last_date === datum.scaled_date ? 'last'" ": null" ) ).encode( alt.X("scaled_date:Q") .title("Year into Decade") .axis(tickCount=11), alt.Y("CO2:Q") .title("CO2 concentration in ppm") .scale(zero=False) ) line = base.mark_line().encode( alt.Color("decade:O") .scale(scheme="magma") .legend(None) ) text = base.encode(text="year:N") start_year = text.transform_filter( alt.datum.end == 'first' ).mark_text(baseline="top") end_year = text.transform_filter( alt.datum.end == 'last' ).mark_text(baseline="bottom") (line + start_year + end_year).configure_text( align="left", dx=1, dy=3 ).properties(width=600, height=375) altair-5.0.1/tests/examples_methods_syntax/comet_chart.py000066400000000000000000000025271443422213100237460ustar00rootroot00000000000000""" Comet Chart ----------- Inspired by `Zan Armstrong's comet chart `_ this plot uses ``mark_trail`` to visualize change of grouped data over time. A more elaborate example and explanation of creating comet charts in Altair is shown in `this blogpost `_. """ # category: advanced calculations import altair as alt import vega_datasets alt.Chart( vega_datasets.data.barley.url, title='Barley Yield comparison between 1932 and 1931' ).mark_trail().encode( alt.X('year:O').title(None), alt.Y('variety:N').title('Variety'), alt.Size('yield:Q') .scale(range=[0, 12]) .legend(values=[20, 60]) .title('Barley Yield (bushels/acre)'), alt.Color('delta:Q') .scale(domainMid=0) .title('Yield Delta (%)'), alt.Tooltip(['year:O', 'yield:Q']), alt.Column('site:N').title('Site') ).transform_pivot( "year", value="yield", groupby=["variety", "site"] ).transform_fold( ["1931", "1932"], as_=["year", "yield"] ).transform_calculate( calculate="datum['1932'] - datum['1931']", as_="delta" ).configure_legend( orient='bottom', direction='horizontal' ).configure_view( stroke=None ) altair-5.0.1/tests/examples_methods_syntax/connected_scatterplot.py000066400000000000000000000013021443422213100260320ustar00rootroot00000000000000""" Connected Scatter Plot (Lines with Custom Paths) ------------------------------------------------ This example show how the order encoding can be used to draw a custom path. The dataset tracks miles driven per capita along with gas prices annually from 1956 to 2010. It is based on Hannah Fairfield's article 'Driving Shifts Into Reverse'. See https://archive.nytimes.com/www.nytimes.com/imagepages/2010/05/02/business/02metrics.html for the original. """ # category: scatter plots import altair as alt from vega_datasets import data source = data.driving() alt.Chart(source).mark_line(point=True).encode( alt.X('miles').scale(zero=False), alt.Y('gas').scale(zero=False), order='year' ) altair-5.0.1/tests/examples_methods_syntax/density_stack.py000066400000000000000000000020021443422213100243060ustar00rootroot00000000000000""" Stacked Density Estimates ------------------------- To plot a stacked graph of estimates, use a shared ``extent`` and a fixed number of subdivision ``steps`` to ensure that the points for each area align well. Density estimates of measurements for each iris flower feature are plot in a stacked method. In addition, setting ``counts`` to true multiplies the densities by the number of data points in each group, preserving proportional differences. """ # category: distributions import altair as alt from vega_datasets import data source = data.iris() alt.Chart(source).transform_fold( ['petalWidth', 'petalLength', 'sepalWidth', 'sepalLength'], as_ = ['Measurement_type', 'value'] ).transform_density( density='value', bandwidth=0.3, groupby=['Measurement_type'], extent= [0, 8], counts = True, steps=200 ).mark_area().encode( alt.X('value:Q'), alt.Y('density:Q').stack('zero'), alt.Color('Measurement_type:N') ).properties(width=400, height=100) altair-5.0.1/tests/examples_methods_syntax/diverging_stacked_bar_chart.py000066400000000000000000000207561443422213100271430ustar00rootroot00000000000000""" Diverging Stacked Bar Chart --------------------------- This example shows a diverging stacked bar chart for sentiments towards a set of eight questions, displayed as percentages with neutral responses straddling the 0% mark. """ # category: bar charts import altair as alt source = alt.pd.DataFrame([ { "question": "Question 1", "type": "Strongly disagree", "value": 24, "percentage": 0.7, "percentage_start": -19.1, "percentage_end": -18.4 }, { "question": "Question 1", "type": "Disagree", "value": 294, "percentage": 9.1, "percentage_start": -18.4, "percentage_end": -9.2 }, { "question": "Question 1", "type": "Neither agree nor disagree", "value": 594, "percentage": 18.5, "percentage_start": -9.2, "percentage_end": 9.2 }, { "question": "Question 1", "type": "Agree", "value": 1927, "percentage": 59.9, "percentage_start": 9.2, "percentage_end": 69.2 }, { "question": "Question 1", "type": "Strongly agree", "value": 376, "percentage": 11.7, "percentage_start": 69.2, "percentage_end": 80.9 }, { "question": "Question 2", "type": "Strongly disagree", "value": 2, "percentage": 18.2, "percentage_start": -36.4, "percentage_end": -18.2 }, { "question": "Question 2", "type": "Disagree", "value": 2, "percentage": 18.2, "percentage_start": -18.2, "percentage_end": 0 }, { "question": "Question 2", "type": "Neither agree nor disagree", "value": 0, "percentage": 0, "percentage_start": 0, "percentage_end": 0 }, { "question": "Question 2", "type": "Agree", "value": 7, "percentage": 63.6, "percentage_start": 0, "percentage_end": 63.6 }, { "question": "Question 2", "type": "Strongly agree", "value": 11, "percentage": 0, "percentage_start": 63.6, "percentage_end": 63.6 }, { "question": "Question 3", "type": "Strongly disagree", "value": 2, "percentage": 20, "percentage_start": -30, "percentage_end": -10 }, { "question": "Question 3", "type": "Disagree", "value": 0, "percentage": 0, "percentage_start": -10, "percentage_end": -10 }, { "question": "Question 3", "type": "Neither agree nor disagree", "value": 2, "percentage": 20, "percentage_start": -10, "percentage_end": 10 }, { "question": "Question 3", "type": "Agree", "value": 4, "percentage": 40, "percentage_start": 10, "percentage_end": 50 }, { "question": "Question 3", "type": "Strongly agree", "value": 2, "percentage": 20, "percentage_start": 50, "percentage_end": 70 }, { "question": "Question 4", "type": "Strongly disagree", "value": 0, "percentage": 0, "percentage_start": -15.6, "percentage_end": -15.6 }, { "question": "Question 4", "type": "Disagree", "value": 2, "percentage": 12.5, "percentage_start": -15.6, "percentage_end": -3.1 }, { "question": "Question 4", "type": "Neither agree nor disagree", "value": 1, "percentage": 6.3, "percentage_start": -3.1, "percentage_end": 3.1 }, { "question": "Question 4", "type": "Agree", "value": 7, "percentage": 43.8, "percentage_start": 3.1, "percentage_end": 46.9 }, { "question": "Question 4", "type": "Strongly agree", "value": 6, "percentage": 37.5, "percentage_start": 46.9, "percentage_end": 84.4 }, { "question": "Question 5", "type": "Strongly disagree", "value": 0, "percentage": 0, "percentage_start": -10.4, "percentage_end": -10.4 }, { "question": "Question 5", "type": "Disagree", "value": 1, "percentage": 4.2, "percentage_start": -10.4, "percentage_end": -6.3 }, { "question": "Question 5", "type": "Neither agree nor disagree", "value": 3, "percentage": 12.5, "percentage_start": -6.3, "percentage_end": 6.3 }, { "question": "Question 5", "type": "Agree", "value": 16, "percentage": 66.7, "percentage_start": 6.3, "percentage_end": 72.9 }, { "question": "Question 5", "type": "Strongly agree", "value": 4, "percentage": 16.7, "percentage_start": 72.9, "percentage_end": 89.6 }, { "question": "Question 6", "type": "Strongly disagree", "value": 1, "percentage": 6.3, "percentage_start": -18.8, "percentage_end": -12.5 }, { "question": "Question 6", "type": "Disagree", "value": 1, "percentage": 6.3, "percentage_start": -12.5, "percentage_end": -6.3 }, { "question": "Question 6", "type": "Neither agree nor disagree", "value": 2, "percentage": 12.5, "percentage_start": -6.3, "percentage_end": 6.3 }, { "question": "Question 6", "type": "Agree", "value": 9, "percentage": 56.3, "percentage_start": 6.3, "percentage_end": 62.5 }, { "question": "Question 6", "type": "Strongly agree", "value": 3, "percentage": 18.8, "percentage_start": 62.5, "percentage_end": 81.3 }, { "question": "Question 7", "type": "Strongly disagree", "value": 0, "percentage": 0, "percentage_start": -10, "percentage_end": -10 }, { "question": "Question 7", "type": "Disagree", "value": 0, "percentage": 0, "percentage_start": -10, "percentage_end": -10 }, { "question": "Question 7", "type": "Neither agree nor disagree", "value": 1, "percentage": 20, "percentage_start": -10, "percentage_end": 10 }, { "question": "Question 7", "type": "Agree", "value": 4, "percentage": 80, "percentage_start": 10, "percentage_end": 90 }, { "question": "Question 7", "type": "Strongly agree", "value": 0, "percentage": 0, "percentage_start": 90, "percentage_end": 90 }, { "question": "Question 8", "type": "Strongly disagree", "value": 0, "percentage": 0, "percentage_start": 0, "percentage_end": 0 }, { "question": "Question 8", "type": "Disagree", "value": 0, "percentage": 0, "percentage_start": 0, "percentage_end": 0 }, { "question": "Question 8", "type": "Neither agree nor disagree", "value": 0, "percentage": 0, "percentage_start": 0, "percentage_end": 0 }, { "question": "Question 8", "type": "Agree", "value": 0, "percentage": 0, "percentage_start": 0, "percentage_end": 0 }, { "question": "Question 8", "type": "Strongly agree", "value": 2, "percentage": 100, "percentage_start": 0, "percentage_end": 100 } ]) color_scale = alt.Scale( domain=[ "Strongly disagree", "Disagree", "Neither agree nor disagree", "Agree", "Strongly agree" ], range=["#c30d24", "#f3a583", "#cccccc", "#94c6da", "#1770ab"] ) y_axis = alt.Axis( title='Question', offset=5, ticks=False, minExtent=60, domain=False ) alt.Chart(source).mark_bar().encode( x='percentage_start:Q', x2='percentage_end:Q', y=alt.Y('question:N').axis(y_axis), color=alt.Color('type:N').title('Response').scale(color_scale), ) altair-5.0.1/tests/examples_methods_syntax/donut_chart.py000066400000000000000000000007531443422213100237670ustar00rootroot00000000000000""" Donut Chart ----------- This example shows how to make a Donut Chart using ``mark_arc``. This is adapted from a corresponding Vega-Lite Example: `Donut Chart `_. """ # category: circular plots import pandas as pd import altair as alt source = pd.DataFrame({ "category": [1, 2, 3, 4, 5, 6], "value": [4, 6, 10, 3, 7, 8] }) alt.Chart(source).mark_arc(innerRadius=50).encode( theta="value", color="category:N", ) altair-5.0.1/tests/examples_methods_syntax/errorbars_with_ci.py000066400000000000000000000013101443422213100251520ustar00rootroot00000000000000""" Error Bars with Confidence Interval ====================================== This example shows how to show error bars using confidence intervals. The confidence intervals are computed internally in vega by a non-parametric `bootstrap of the mean `_. """ # category: uncertainties and trends import altair as alt from vega_datasets import data source = data.barley() error_bars = alt.Chart(source).mark_errorbar(extent='ci').encode( alt.X('yield').scale(zero=False), alt.Y('variety') ) points = alt.Chart(source).mark_point(filled=True, color='black').encode( x=alt.X('mean(yield)'), y=alt.Y('variety'), ) error_bars + points altair-5.0.1/tests/examples_methods_syntax/errorbars_with_std.py000066400000000000000000000011211443422213100253510ustar00rootroot00000000000000""" Error Bars with Standard Deviation ---------------------------------- This example shows how to show error bars with standard deviation using crop yields data of different in the years of 1930s. """ # category: uncertainties and trends import altair as alt from vega_datasets import data source = data.barley() error_bars = alt.Chart(source).mark_errorbar(extent='stdev').encode( x=alt.X('yield').scale(zero=False), y=alt.Y('variety') ) points = alt.Chart(source).mark_point(filled=True, color='black').encode( x=alt.X('mean(yield)'), y=alt.Y('variety'), ) error_bars + points altair-5.0.1/tests/examples_methods_syntax/falkensee.py000066400000000000000000000047741443422213100234210ustar00rootroot00000000000000""" Population of Falkensee from 1875 to 2014 ----------------------------------------- This example is a reproduction of the Falkensee plot found in the Vega-Lite examples. """ # category: case studies import altair as alt import pandas as pd source = [ {"year": "1875", "population": 1309}, {"year": "1890", "population": 1558}, {"year": "1910", "population": 4512}, {"year": "1925", "population": 8180}, {"year": "1933", "population": 15915}, {"year": "1939", "population": 24824}, {"year": "1946", "population": 28275}, {"year": "1950", "population": 29189}, {"year": "1964", "population": 29881}, {"year": "1971", "population": 26007}, {"year": "1981", "population": 24029}, {"year": "1985", "population": 23340}, {"year": "1989", "population": 22307}, {"year": "1990", "population": 22087}, {"year": "1991", "population": 22139}, {"year": "1992", "population": 22105}, {"year": "1993", "population": 22242}, {"year": "1994", "population": 22801}, {"year": "1995", "population": 24273}, {"year": "1996", "population": 25640}, {"year": "1997", "population": 27393}, {"year": "1998", "population": 29505}, {"year": "1999", "population": 32124}, {"year": "2000", "population": 33791}, {"year": "2001", "population": 35297}, {"year": "2002", "population": 36179}, {"year": "2003", "population": 36829}, {"year": "2004", "population": 37493}, {"year": "2005", "population": 38376}, {"year": "2006", "population": 39008}, {"year": "2007", "population": 39366}, {"year": "2008", "population": 39821}, {"year": "2009", "population": 40179}, {"year": "2010", "population": 40511}, {"year": "2011", "population": 40465}, {"year": "2012", "population": 40905}, {"year": "2013", "population": 41258}, {"year": "2014", "population": 41777}, ] source2 = [ {"start": "1933", "end": "1945", "event": "Nazi Rule"}, {"start": "1948", "end": "1989", "event": "GDR (East Germany)"}, ] source_df = pd.DataFrame(source) source2_df = pd.DataFrame(source2) line = alt.Chart(source_df).mark_line(color="#333").encode( alt.X("year:T").axis(format="%Y").title("Year"), alt.Y("population").title("Population"), ) point = line.mark_point(color="#333") rect = alt.Chart(source2_df).mark_rect().encode( x="start:T", x2="end:T", color=alt.Color("event:N").title("Event") ) (rect + line + point).properties( title="Population of Falkensee from 1875 to 2014", width=500, height=300 ) altair-5.0.1/tests/examples_methods_syntax/gapminder_bubble_plot.py000066400000000000000000000011471443422213100257720ustar00rootroot00000000000000""" Gapminder Bubble Plot ===================== This example shows how to make a bubble plot showing the correlation between health and income for 187 countries in the world (modified from an example in Lisa Charlotte Rost's blog post `'One Chart, Twelve Charting Libraries' `_. """ # category: case studies import altair as alt from vega_datasets import data source = data.gapminder_health_income.url alt.Chart(source).mark_circle().encode( alt.X('income:Q').scale(type='log'), alt.Y('health:Q').scale(zero=False), size='population:Q' ) altair-5.0.1/tests/examples_methods_syntax/groupby-map.py000066400000000000000000000016761443422213100237240ustar00rootroot00000000000000""" Grouped Points with Proportional Symbols Map ============================================ This is a layered geographic visualization that groups points by state. """ # category: maps import altair as alt from vega_datasets import data airports = data.airports.url states = alt.topo_feature(data.us_10m.url, feature='states') # US states background background = alt.Chart(states).mark_geoshape( fill='lightgray', stroke='white' ).properties( width=500, height=300 ).project('albersUsa') # Airports grouped by state points = alt.Chart(airports, title='Number of airports in US').transform_aggregate( latitude='mean(latitude)', longitude='mean(longitude)', count='count()', groupby=['state'] ).mark_circle().encode( longitude='longitude:Q', latitude='latitude:Q', size=alt.Size('count:Q').title('Number of Airports'), color=alt.value('steelblue'), tooltip=['state:N','count:Q'] ) background + points altair-5.0.1/tests/examples_methods_syntax/grouped_bar_chart2.py000066400000000000000000000013641443422213100252100ustar00rootroot00000000000000""" Grouped Bar Chart with xOffset ------------------------------ Like :ref:`gallery_grouped_bar_chart`, this example shows a grouped bar chart. Whereas :ref:`gallery_grouped_bar_chart` used the ``column`` encoding channel, this example uses the ``xOffset`` encoding channel. This is adapted from a corresponding Vega-Lite Example: `Grouped Bar Chart `_. """ # category: bar charts import altair as alt import pandas as pd source = pd.DataFrame({ "Category":list("AAABBBCCC"), "Group":list("xyzxyzxyz"), "Value":[0.1, 0.6, 0.9, 0.7, 0.2, 1.1, 0.6, 0.1, 0.2] }) alt.Chart(source).mark_bar().encode( x="Category:N", y="Value:Q", xOffset="Group:N", color="Group:N" ) altair-5.0.1/tests/examples_methods_syntax/grouped_bar_chart_with_error_bars.py000066400000000000000000000010141443422213100303710ustar00rootroot00000000000000""" Grouped Bar Chart with Error Bars --------------------------------- This example shows a grouped bar chart with error bars. """ # category: bar charts import altair as alt from vega_datasets import data source = data.barley() bars = alt.Chart().mark_bar().encode( x='year:O', y=alt.Y('mean(yield):Q').title('Mean Yield'), color='year:N', ) error_bars = alt.Chart().mark_errorbar(extent='ci').encode( x='year:O', y='yield:Q' ) alt.layer(bars, error_bars, data=source).facet( column='site:N' ) altair-5.0.1/tests/examples_methods_syntax/heat_lane.py000066400000000000000000000025671443422213100234020ustar00rootroot00000000000000""" Heat Lane Chart --------------- This example shows how to make an alternative form of a histogram `designed at Google `_ with the goal of increasing accessibility. """ # category: distributions import altair as alt from vega_datasets import data source = data.cars.url chart = alt.Chart(source, title="Car horsepower", height=100, width=300).encode( alt.X("bin_Horsepower_start:Q") .title("Horsepower") .axis(grid=False), alt.X2("bin_Horsepower_end:Q"), alt.Y("y:O").axis(None), alt.Y2("y2"), ).transform_bin( ["bin_Horsepower_start", "bin_Horsepower_end"], field='Horsepower' ).transform_aggregate( count='count()', groupby=["bin_Horsepower_start", "bin_Horsepower_end"] ).transform_bin( ["bin_count_start", "bin_count_end"], field='count' ).transform_calculate( y="datum.bin_count_end/2", y2="-datum.bin_count_end/2", ).transform_joinaggregate( max_bin_count_end="max(bin_count_end)", ) layer1 = chart.mark_bar(xOffset=1, x2Offset=-1, cornerRadius=3).encode( alt.Color("max_bin_count_end:O") .title("Number of models") .scale(scheme="lighttealblue") ) layer2 = chart.mark_bar(xOffset=1, x2Offset=-1, yOffset=-3, y2Offset=3).encode( alt.Color("bin_count_end:O").title("Number of models") ) layer1 + layer2 altair-5.0.1/tests/examples_methods_syntax/hexbins.py000066400000000000000000000026211443422213100231110ustar00rootroot00000000000000""" Hexbin Chart ------------ This example shows a hexbin chart. """ # category: tables import altair as alt from vega_datasets import data source = data.seattle_weather() # Size of the hexbins size = 15 # Count of distinct x features xFeaturesCount = 12 # Count of distinct y features yFeaturesCount = 7 # Name of the x field xField = 'date' # Name of the y field yField = 'date' # the shape of a hexagon hexagon = "M0,-2.3094010768L2,-1.1547005384 2,1.1547005384 0,2.3094010768 -2,1.1547005384 -2,-1.1547005384Z" alt.Chart(source).mark_point(size=size**2, shape=hexagon).encode( alt.X('xFeaturePos:Q') .title('Month') .axis(grid=False, tickOpacity=0, domainOpacity=0), alt.Y('day(' + yField + '):O') .title('Weekday') .axis(labelPadding=20, tickOpacity=0, domainOpacity=0), stroke=alt.value('black'), strokeWidth=alt.value(0.2), fill=alt.Color('mean(temp_max):Q').scale(scheme='darkblue'), tooltip=['month(' + xField + '):O', 'day(' + yField + '):O', 'mean(temp_max):Q'] ).transform_calculate( # This field is required for the hexagonal X-Offset xFeaturePos='(day(datum.' + yField + ') % 2) / 2 + month(datum.' + xField + ')' ).properties( # Exact scaling factors to make the hexbins fit width=size * xFeaturesCount * 2, height=size * yFeaturesCount * 1.7320508076, # 1.7320508076 is approx. sin(60°)*2 ).configure_view( strokeWidth=0 ) altair-5.0.1/tests/examples_methods_syntax/histogram_heatmap.py000066400000000000000000000006431443422213100251470ustar00rootroot00000000000000""" 2D Histogram Heatmap -------------------- This example shows how to make a heatmap from binned quantitative data. """ # category: distributions import altair as alt from vega_datasets import data source = data.movies.url alt.Chart(source).mark_rect().encode( alt.X('IMDB_Rating:Q').bin(maxbins=60), alt.Y('Rotten_Tomatoes_Rating:Q').bin(maxbins=40), alt.Color('count():Q').scale(scheme='greenblue') ) altair-5.0.1/tests/examples_methods_syntax/histogram_responsive.py000066400000000000000000000014071443422213100257240ustar00rootroot00000000000000""" Histogram with Responsive Bins ------------------------------ This shows an example of a histogram with bins that are responsive to a selection domain. Click and drag on the bottom panel to see the bins change on the top panel. """ # category: distributions import altair as alt from vega_datasets import data source = data.flights_5k.url brush = alt.selection_interval(encodings=['x']) base = alt.Chart(source).transform_calculate( time="hours(datum.date) + minutes(datum.date) / 60" ).mark_bar().encode( y='count():Q' ).properties( width=600, height=100 ) alt.vconcat( base.encode( alt.X('time:Q') .bin(maxbins=30, extent=brush) .scale(domain=brush) ), base.encode( alt.X('time:Q').bin(maxbins=30), ).add_params(brush) ) altair-5.0.1/tests/examples_methods_syntax/histogram_scatterplot.py000066400000000000000000000005551443422213100260760ustar00rootroot00000000000000""" 2D Histogram Scatter Plot ------------------------- This example shows how to make a 2d histogram scatter plot. """ # category: distributions import altair as alt from vega_datasets import data source = data.movies.url alt.Chart(source).mark_circle().encode( alt.X('IMDB_Rating:Q').bin(), alt.Y('Rotten_Tomatoes_Rating:Q').bin(), size='count()' ) altair-5.0.1/tests/examples_methods_syntax/histogram_with_a_global_mean_overlay.py000066400000000000000000000007321443422213100310630ustar00rootroot00000000000000""" Histogram with a Global Mean Overlay ------------------------------------ This example shows a histogram with a global mean overlay. """ # category: distributions import altair as alt from vega_datasets import data source = data.movies.url base = alt.Chart(source) bar = base.mark_bar().encode( alt.X('IMDB_Rating:Q').bin().axis(None), y='count()' ) rule = base.mark_rule(color='red').encode( x='mean(IMDB_Rating):Q', size=alt.value(5) ) bar + rule altair-5.0.1/tests/examples_methods_syntax/horizon_graph.py000066400000000000000000000021071443422213100243210ustar00rootroot00000000000000""" Horizon Graph ------------- This example shows how to make a Horizon Graph with 2 layers. (See https://idl.cs.washington.edu/papers/horizon/ for more details on Horizon Graphs.) """ # category: area charts import altair as alt import pandas as pd source = pd.DataFrame([ {"x": 1, "y": 28}, {"x": 2, "y": 55}, {"x": 3, "y": 43}, {"x": 4, "y": 91}, {"x": 5, "y": 81}, {"x": 6, "y": 53}, {"x": 7, "y": 19}, {"x": 8, "y": 87}, {"x": 9, "y": 52}, {"x": 10, "y": 48}, {"x": 11, "y": 24}, {"x": 12, "y": 49}, {"x": 13, "y": 87}, {"x": 14, "y": 66}, {"x": 15, "y": 17}, {"x": 16, "y": 27}, {"x": 17, "y": 68}, {"x": 18, "y": 16}, {"x": 19, "y": 49}, {"x": 20, "y": 15} ]) area1 = alt.Chart(source).mark_area( clip=True, interpolate='monotone', opacity=0.6 ).encode( alt.X('x').scale(zero=False, nice=False), alt.Y('y').scale(domain=[0, 50]).title('y'), ).properties( width=500, height=75 ) area2 = area1.encode( alt.Y('ny:Q').scale(domain=[0, 50]) ).transform_calculate( "ny", alt.datum.y - 50 ) area1 + area2 altair-5.0.1/tests/examples_methods_syntax/interactive_cross_highlight.py000066400000000000000000000021271443422213100272270ustar00rootroot00000000000000""" Interactive Chart with Cross-Highlight ====================================== This example shows an interactive chart where selections in one portion of the chart affect what is shown in other panels. Click on the bar chart to see a detail of the distribution in the upper panel. """ # category: interactive charts import altair as alt from vega_datasets import data source = data.movies.url pts = alt.selection_point(encodings=['x']) rect = alt.Chart(data.movies.url).mark_rect().encode( alt.X('IMDB_Rating:Q').bin(), alt.Y('Rotten_Tomatoes_Rating:Q').bin(), alt.Color('count()').scale(scheme='greenblue').title('Total Records') ) circ = rect.mark_point().encode( alt.ColorValue('grey'), alt.Size('count()').title('Records in Selection') ).transform_filter( pts ) bar = alt.Chart(source, width=550, height=200).mark_bar().encode( x='Major_Genre:N', y='count()', color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey")) ).add_params(pts) alt.vconcat( rect + circ, bar ).resolve_legend( color="independent", size="independent" ) altair-5.0.1/tests/examples_methods_syntax/interactive_layered_crossfilter.py000066400000000000000000000020511443422213100301070ustar00rootroot00000000000000""" Interactive Crossfilter ======================= This example shows a multi-panel view of the same data, where you can interactively select a portion of the data in any of the panels to highlight that portion in any of the other panels. """ # category: interactive charts import altair as alt from vega_datasets import data source = alt.UrlData( data.flights_2k.url, format={'parse': {'date': 'date'}} ) brush = alt.selection_interval(encodings=['x']) # Define the base chart, with the common parts of the # background and highlights base = alt.Chart(width=160, height=130).mark_bar().encode( x=alt.X(alt.repeat('column')).bin(maxbins=20), y='count()' ) # gray background with selection background = base.encode( color=alt.value('#ddd') ).add_params(brush) # blue highlights on the transformed data highlight = base.transform_filter(brush) # layer the two charts & repeat alt.layer( background, highlight, data=source ).transform_calculate( "time", "hours(datum.date)" ).repeat(column=["distance", "delay", "time"]) altair-5.0.1/tests/examples_methods_syntax/interactive_legend.py000066400000000000000000000014121443422213100253010ustar00rootroot00000000000000""" Interactive Legend ------------------ The following shows how to create a chart with an interactive legend, by binding the selection to ``"legend"``. Such a binding only works with ``selection_point`` when projected over a single field or encoding. """ # category: interactive charts import altair as alt from vega_datasets import data source = data.unemployment_across_industries.url selection = alt.selection_point(fields=['series'], bind='legend') alt.Chart(source).mark_area().encode( alt.X('yearmonth(date):T').axis(domain=False, format='%Y', tickSize=0), alt.Y('sum(count):Q').stack('center').axis(None), alt.Color('series:N').scale(scheme='category20b'), opacity=alt.condition(selection, alt.value(1), alt.value(0.2)) ).add_params( selection ) altair-5.0.1/tests/examples_methods_syntax/interval_selection.py000066400000000000000000000011061443422213100253370ustar00rootroot00000000000000""" Interval Selection ================== This is an example of creating a stacked chart for which the domain of the top chart can be selected by interacting with the bottom chart. """ # category: area charts import altair as alt from vega_datasets import data source = data.sp500.url brush = alt.selection_interval(encodings=['x']) base = alt.Chart(source, width=600, height=200).mark_area().encode( x = 'date:T', y = 'price:Q' ) upper = base.encode( alt.X('date:T').scale(domain=brush) ) lower = base.properties( height=60 ).add_params(brush) upper & lower altair-5.0.1/tests/examples_methods_syntax/iowa_electricity.py000066400000000000000000000012731443422213100250120ustar00rootroot00000000000000""" Iowa's Renewable Energy Boom ---------------------------- This example is a fully developed stacked chart using the sample dataset of Iowa's electricity sources. """ # category: case studies import altair as alt from vega_datasets import data source = data.iowa_electricity() alt.Chart( source, title=alt.Title( "Iowa's green energy boom", subtitle="A growing share of the state's energy has come from renewable sources" ) ).mark_area().encode( alt.X("year:T").title("Year"), alt.Y("net_generation:Q") .title("Share of net generation") .stack("normalize") .axis(format=".0%"), alt.Color("source:N").title("Electricity source") ) altair-5.0.1/tests/examples_methods_syntax/isotype.py000066400000000000000000000142131443422213100231450ustar00rootroot00000000000000''' Isotype Visualization ===================== Isotype Visualization shows the distribution of animals across UK and US. Inspired by `Only An Ocean Between, 1943 `_. Population Live Stock, p.13. This is adapted from Vega-Lite example https://vega.github.io/editor/#/examples/vega-lite/isotype_bar_chart ''' # category: advanced calculations import altair as alt import pandas as pd source = pd.DataFrame([ {'country': 'Great Britain', 'animal': 'cattle'}, {'country': 'Great Britain', 'animal': 'cattle'}, {'country': 'Great Britain', 'animal': 'cattle'}, {'country': 'Great Britain', 'animal': 'pigs'}, {'country': 'Great Britain', 'animal': 'pigs'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'} ]) domains = ['person', 'cattle', 'pigs', 'sheep'] shape_scale = alt.Scale( domain=domains, range=[ 'M1.7 -1.7h-0.8c0.3 -0.2 0.6 -0.5 0.6 -0.9c0 -0.6 -0.4 -1 -1 -1c-0.6 0 -1 0.4 -1 1c0 0.4 0.2 0.7 0.6 0.9h-0.8c-0.4 0 -0.7 0.3 -0.7 0.6v1.9c0 0.3 0.3 0.6 0.6 0.6h0.2c0 0 0 0.1 0 0.1v1.9c0 0.3 0.2 0.6 0.3 0.6h1.3c0.2 0 0.3 -0.3 0.3 -0.6v-1.8c0 0 0 -0.1 0 -0.1h0.2c0.3 0 0.6 -0.3 0.6 -0.6v-2c0.2 -0.3 -0.1 -0.6 -0.4 -0.6z', 'M4 -2c0 0 0.9 -0.7 1.1 -0.8c0.1 -0.1 -0.1 0.5 -0.3 0.7c-0.2 0.2 1.1 1.1 1.1 1.2c0 0.2 -0.2 0.8 -0.4 0.7c-0.1 0 -0.8 -0.3 -1.3 -0.2c-0.5 0.1 -1.3 1.6 -1.5 2c-0.3 0.4 -0.6 0.4 -0.6 0.4c0 0.1 0.3 1.7 0.4 1.8c0.1 0.1 -0.4 0.1 -0.5 0c0 0 -0.6 -1.9 -0.6 -1.9c-0.1 0 -0.3 -0.1 -0.3 -0.1c0 0.1 -0.5 1.4 -0.4 1.6c0.1 0.2 0.1 0.3 0.1 0.3c0 0 -0.4 0 -0.4 0c0 0 -0.2 -0.1 -0.1 -0.3c0 -0.2 0.3 -1.7 0.3 -1.7c0 0 -2.8 -0.9 -2.9 -0.8c-0.2 0.1 -0.4 0.6 -0.4 1c0 0.4 0.5 1.9 0.5 1.9l-0.5 0l-0.6 -2l0 -0.6c0 0 -1 0.8 -1 1c0 0.2 -0.2 1.3 -0.2 1.3c0 0 0.3 0.3 0.2 0.3c0 0 -0.5 0 -0.5 0c0 0 -0.2 -0.2 -0.1 -0.4c0 -0.1 0.2 -1.6 0.2 -1.6c0 0 0.5 -0.4 0.5 -0.5c0 -0.1 0 -2.7 -0.2 -2.7c-0.1 0 -0.4 2 -0.4 2c0 0 0 0.2 -0.2 0.5c-0.1 0.4 -0.2 1.1 -0.2 1.1c0 0 -0.2 -0.1 -0.2 -0.2c0 -0.1 -0.1 -0.7 0 -0.7c0.1 -0.1 0.3 -0.8 0.4 -1.4c0 -0.6 0.2 -1.3 0.4 -1.5c0.1 -0.2 0.6 -0.4 0.6 -0.4z', 'M1.2 -2c0 0 0.7 0 1.2 0.5c0.5 0.5 0.4 0.6 0.5 0.6c0.1 0 0.7 0 0.8 0.1c0.1 0 0.2 0.2 0.2 0.2c0 0 -0.6 0.2 -0.6 0.3c0 0.1 0.4 0.9 0.6 0.9c0.1 0 0.6 0 0.6 0.1c0 0.1 0 0.7 -0.1 0.7c-0.1 0 -1.2 0.4 -1.5 0.5c-0.3 0.1 -1.1 0.5 -1.1 0.7c-0.1 0.2 0.4 1.2 0.4 1.2l-0.4 0c0 0 -0.4 -0.8 -0.4 -0.9c0 -0.1 -0.1 -0.3 -0.1 -0.3l-0.2 0l-0.5 1.3l-0.4 0c0 0 -0.1 -0.4 0 -0.6c0.1 -0.1 0.3 -0.6 0.3 -0.7c0 0 -0.8 0 -1.5 -0.1c-0.7 -0.1 -1.2 -0.3 -1.2 -0.2c0 0.1 -0.4 0.6 -0.5 0.6c0 0 0.3 0.9 0.3 0.9l-0.4 0c0 0 -0.4 -0.5 -0.4 -0.6c0 -0.1 -0.2 -0.6 -0.2 -0.5c0 0 -0.4 0.4 -0.6 0.4c-0.2 0.1 -0.4 0.1 -0.4 0.1c0 0 -0.1 0.6 -0.1 0.6l-0.5 0l0 -1c0 0 0.5 -0.4 0.5 -0.5c0 -0.1 -0.7 -1.2 -0.6 -1.4c0.1 -0.1 0.1 -1.1 0.1 -1.1c0 0 -0.2 0.1 -0.2 0.1c0 0 0 0.9 0 1c0 0.1 -0.2 0.3 -0.3 0.3c-0.1 0 0 -0.5 0 -0.9c0 -0.4 0 -0.4 0.2 -0.6c0.2 -0.2 0.6 -0.3 0.8 -0.8c0.3 -0.5 1 -0.6 1 -0.6z', 'M-4.1 -0.5c0.2 0 0.2 0.2 0.5 0.2c0.3 0 0.3 -0.2 0.5 -0.2c0.2 0 0.2 0.2 0.4 0.2c0.2 0 0.2 -0.2 0.5 -0.2c0.2 0 0.2 0.2 0.4 0.2c0.2 0 0.2 -0.2 0.4 -0.2c0.1 0 0.2 0.2 0.4 0.1c0.2 0 0.2 -0.2 0.4 -0.3c0.1 0 0.1 -0.1 0.4 0c0.3 0 0.3 -0.4 0.6 -0.4c0.3 0 0.6 -0.3 0.7 -0.2c0.1 0.1 1.4 1 1.3 1.4c-0.1 0.4 -0.3 0.3 -0.4 0.3c-0.1 0 -0.5 -0.4 -0.7 -0.2c-0.3 0.2 -0.1 0.4 -0.2 0.6c-0.1 0.1 -0.2 0.2 -0.3 0.4c0 0.2 0.1 0.3 0 0.5c-0.1 0.2 -0.3 0.2 -0.3 0.5c0 0.3 -0.2 0.3 -0.3 0.6c-0.1 0.2 0 0.3 -0.1 0.5c-0.1 0.2 -0.1 0.2 -0.2 0.3c-0.1 0.1 0.3 1.1 0.3 1.1l-0.3 0c0 0 -0.3 -0.9 -0.3 -1c0 -0.1 -0.1 -0.2 -0.3 -0.2c-0.2 0 -0.3 0.1 -0.4 0.4c0 0.3 -0.2 0.8 -0.2 0.8l-0.3 0l0.3 -1c0 0 0.1 -0.6 -0.2 -0.5c-0.3 0.1 -0.2 -0.1 -0.4 -0.1c-0.2 -0.1 -0.3 0.1 -0.4 0c-0.2 -0.1 -0.3 0.1 -0.5 0c-0.2 -0.1 -0.1 0 -0.3 0.3c-0.2 0.3 -0.4 0.3 -0.4 0.3l0.2 1.1l-0.3 0l-0.2 -1.1c0 0 -0.4 -0.6 -0.5 -0.4c-0.1 0.3 -0.1 0.4 -0.3 0.4c-0.1 -0.1 -0.2 1.1 -0.2 1.1l-0.3 0l0.2 -1.1c0 0 -0.3 -0.1 -0.3 -0.5c0 -0.3 0.1 -0.5 0.1 -0.7c0.1 -0.2 -0.1 -1 -0.2 -1.1c-0.1 -0.2 -0.2 -0.8 -0.2 -0.8c0 0 -0.1 -0.5 0.4 -0.8z' ] ) color_scale = alt.Scale( domain=domains, range=['rgb(162,160,152)', 'rgb(194,81,64)', 'rgb(93,93,93)', 'rgb(91,131,149)'] ) alt.Chart(source).mark_point(filled=True, opacity=1, size=100).encode( alt.X('x:O').axis(None), alt.Y('animal:O').axis(None), alt.Row('country:N').header(title=''), alt.Shape('animal:N').legend(None).scale(shape_scale), alt.Color('animal:N').legend(None).scale(color_scale), ).transform_window( x='rank()', groupby=['country', 'animal'] ).properties( width=550, height=140 ) altair-5.0.1/tests/examples_methods_syntax/isotype_emoji.py000066400000000000000000000055511443422213100243350ustar00rootroot00000000000000''' Isotype Visualization with Emoji ================================ Isotype Visualization shows the distribution of animals across UK and US, using unicode emoji marks rather than custom SVG paths (see https://altair-viz.github.io/gallery/isotype.html). This is adapted from Vega-Lite example https://vega.github.io/vega-lite/examples/isotype_bar_chart_emoji.html. ''' # category:advanced calculations import altair as alt import pandas as pd source = pd.DataFrame([ {'country': 'Great Britain', 'animal': 'cattle'}, {'country': 'Great Britain', 'animal': 'cattle'}, {'country': 'Great Britain', 'animal': 'cattle'}, {'country': 'Great Britain', 'animal': 'pigs'}, {'country': 'Great Britain', 'animal': 'pigs'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'Great Britain', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'cattle'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'pigs'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'}, {'country': 'United States', 'animal': 'sheep'} ]) alt.Chart(source).mark_text(size=45, baseline='middle').encode( alt.X('x:O').axis(None), alt.Y('animal:O').axis(None), alt.Row('country:N').title(''), alt.Text('emoji:N') ).transform_calculate( emoji="{'cattle': 'ðŸ„', 'pigs': 'ðŸ–', 'sheep': 'ðŸ'}[datum.animal]" ).transform_window( x='rank()', groupby=['country', 'animal'] ).properties( width=550, height=140 ) altair-5.0.1/tests/examples_methods_syntax/isotype_grid.py000066400000000000000000000016511443422213100241540ustar00rootroot00000000000000""" Isotype Grid ------------ This example is a grid of isotype figures. """ # category: advanced calculations import altair as alt import pandas as pd data = pd.DataFrame([dict(id=i) for i in range(1, 101)]) person = ( "M1.7 -1.7h-0.8c0.3 -0.2 0.6 -0.5 0.6 -0.9c0 -0.6 " "-0.4 -1 -1 -1c-0.6 0 -1 0.4 -1 1c0 0.4 0.2 0.7 0.6 " "0.9h-0.8c-0.4 0 -0.7 0.3 -0.7 0.6v1.9c0 0.3 0.3 0.6 " "0.6 0.6h0.2c0 0 0 0.1 0 0.1v1.9c0 0.3 0.2 0.6 0.3 " "0.6h1.3c0.2 0 0.3 -0.3 0.3 -0.6v-1.8c0 0 0 -0.1 0 " "-0.1h0.2c0.3 0 0.6 -0.3 0.6 -0.6v-2c0.2 -0.3 -0.1 " "-0.6 -0.4 -0.6z" ) alt.Chart(data).transform_calculate( row="ceil(datum.id/10)" ).transform_calculate( col="datum.id - datum.row*10" ).mark_point( filled=True, size=50 ).encode( alt.X("col:O").axis(None), alt.Y("row:O").axis(None), alt.ShapeValue(person) ).properties( width=400, height=400 ).configure_view( strokeWidth=0 ) altair-5.0.1/tests/examples_methods_syntax/lasagna_plot.py000066400000000000000000000013721443422213100241170ustar00rootroot00000000000000""" Lasagna Plot (Dense Time-Series Heatmap) ---------------------------------------- """ # category: tables import altair as alt from vega_datasets import data source = data.stocks() color_condition = alt.condition( "month(datum.value) == 1 && date(datum.value) == 1", alt.value("black"), alt.value(None), ) alt.Chart(source, width=300, height=100).transform_filter( alt.datum.symbol != "GOOG" ).mark_rect().encode( alt.X("yearmonthdate(date):O") .title("Time") .axis( format="%Y", labelAngle=0, labelOverlap=False, labelColor=color_condition, tickColor=color_condition, ), alt.Y("symbol:N").title(None), alt.Color("sum(price)").title("Price") ) altair-5.0.1/tests/examples_methods_syntax/layered_area_chart.py000066400000000000000000000005131443422213100252450ustar00rootroot00000000000000""" Layered Area Chart ------------------ This example shows a layered area chart. """ # category: area charts import altair as alt from vega_datasets import data source = data.iowa_electricity() alt.Chart(source).mark_area(opacity=0.3).encode( x="year:T", y=alt.Y("net_generation:Q").stack(None), color="source:N" ) altair-5.0.1/tests/examples_methods_syntax/layered_bar_chart.py000066400000000000000000000005551443422213100251070ustar00rootroot00000000000000""" Layered Bar Chart ----------------- This example shows a segmented bar chart that is layered rather than stacked. """ # category: bar charts import altair as alt from vega_datasets import data source = data.iowa_electricity() alt.Chart(source).mark_bar(opacity=0.7).encode( x='year:O', y=alt.Y('net_generation:Q').stack(None), color="source", ) altair-5.0.1/tests/examples_methods_syntax/layered_chart_with_dual_axis.py000066400000000000000000000013361443422213100273450ustar00rootroot00000000000000""" Layered chart with Dual-Axis ---------------------------- This example shows how to create a second independent y axis. """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.seattle_weather() base = alt.Chart(source).encode( alt.X('month(date):T').axis(title=None) ) area = base.mark_area(opacity=0.3, color='#57A44C').encode( alt.Y('average(temp_max)').title('Avg. Temperature (°C)', titleColor='#57A44C'), alt.Y2('average(temp_min)') ) line = base.mark_line(stroke='#5276A7', interpolate='monotone').encode( alt.Y('average(precipitation)').title('Precipitation (inches)', titleColor='#5276A7') ) alt.layer(area, line).resolve_scale( y='independent' ) altair-5.0.1/tests/examples_methods_syntax/layered_heatmap_text.py000066400000000000000000000016741443422213100256500ustar00rootroot00000000000000""" Text over a Heatmap ------------------- An example of a layered chart of text over a heatmap using the cars dataset. """ # category: tables import altair as alt from vega_datasets import data source = data.cars() # Configure common options. We specify the aggregation # as a transform here so we can reuse it in both layers. base = alt.Chart(source).transform_aggregate( mean_horsepower='mean(Horsepower)', groupby=['Origin', 'Cylinders'] ).encode( alt.X('Cylinders:O'), alt.Y('Origin:O'), ) # Configure heatmap heatmap = base.mark_rect().encode( alt.Color('mean_horsepower:Q') .scale(scheme='viridis') .title("Mean of Horsepower") ) # Configure text text = base.mark_text(baseline='middle').encode( alt.Text('mean_horsepower:Q', format=".0f"), color=alt.condition( alt.datum.mean_horsepower > 150, alt.value('black'), alt.value('white') ) ) # Draw the chart heatmap + text altair-5.0.1/tests/examples_methods_syntax/layered_histogram.py000066400000000000000000000012601443422213100251510ustar00rootroot00000000000000""" Layered Histogram ================= This example shows how to use opacity to make a layered histogram in Altair. """ # category: distributions import pandas as pd import altair as alt import numpy as np np.random.seed(42) # Generating Data source = pd.DataFrame({ 'Trial A': np.random.normal(0, 0.8, 1000), 'Trial B': np.random.normal(-2, 1, 1000), 'Trial C': np.random.normal(3, 2, 1000) }) alt.Chart(source).transform_fold( ['Trial A', 'Trial B', 'Trial C'], as_=['Experiment', 'Measurement'] ).mark_bar( opacity=0.3, binSpacing=0 ).encode( alt.X('Measurement:Q').bin(maxbins=100), alt.Y('count()').stack(None), alt.Color('Experiment:N') ) altair-5.0.1/tests/examples_methods_syntax/line_chart_with_color_datum.py000066400000000000000000000013071443422213100272040ustar00rootroot00000000000000""" Line Chart with Datum for Color ------------------------------- An example of using ``repeat`` inside ``datum`` to color a multi-series line chart. This is adapted from this corresponding Vega-Lite Example: `Repeat and Layer to Show Different Movie Measures `_. """ # category: line charts import altair as alt from vega_datasets import data source = data.movies() alt.Chart(source).mark_line().encode( alt.X("IMDB_Rating").bin(True), alt.Y(alt.repeat("layer")) .aggregate("mean") .title("Mean of US and Worldwide Gross"), color=alt.datum(alt.repeat("layer")), ).repeat( layer=["US_Gross", "Worldwide_Gross"] ) altair-5.0.1/tests/examples_methods_syntax/line_chart_with_cumsum.py000066400000000000000000000013321443422213100262030ustar00rootroot00000000000000""" Line Chart with Cumulative Sum ------------------------------ This chart creates a simple line chart from the cumulative sum of a fields. """ # category: line charts import altair as alt from vega_datasets import data source = data.wheat() alt.Chart(source, width=600).mark_line().transform_window( # Sort the data chronologically sort=[{'field': 'year'}], # Include all previous records before the current record and none after # (This is the default value so you could skip it and it would still work.) frame=[None, 0], # What to add up as you go cumulative_wheat='sum(wheat)' ).encode( x='year:O', # Plot the calculated field created by the transformation y='cumulative_wheat:Q' ) altair-5.0.1/tests/examples_methods_syntax/line_chart_with_custom_legend.py000066400000000000000000000015351443422213100275270ustar00rootroot00000000000000""" Line Chart with Custom Legend ----------------------------- This example uses the argmax aggregation function in order to create a custom legend for a line chart. """ # category: line charts import altair as alt from vega_datasets import data source = data.stocks() base = alt.Chart(source).encode( alt.Color("symbol").legend(None) ).transform_filter( "datum.symbol !== 'IBM'" ).properties( width=500 ) line = base.mark_line().encode(x="date", y="price") last_price = base.mark_circle().encode( alt.X("last_date['date']:T"), alt.Y("last_date['price']:Q") ).transform_aggregate( last_date="argmax(date)", groupby=["symbol"] ) company_name = last_price.mark_text(align="left", dx=4).encode(text="symbol") chart = (line + last_price + company_name).encode( x=alt.X().title("date"), y=alt.Y().title("price") ) chart altair-5.0.1/tests/examples_methods_syntax/line_percent.py000066400000000000000000000006551443422213100241250ustar00rootroot00000000000000""" Line Chart with Percent axis ---------------------------- This example shows how to format the tick labels of the y-axis of a chart as percentages. """ # category: line charts import altair as alt from vega_datasets import data source = data.jobs.url alt.Chart(source).mark_line().encode( alt.X('year:O'), alt.Y('perc:Q').axis(format='%'), alt.Color('sex:N') ).transform_filter( alt.datum.job == 'Welder' ) altair-5.0.1/tests/examples_methods_syntax/line_with_ci.py000066400000000000000000000010031443422213100240770ustar00rootroot00000000000000""" Line Chart with Confidence Interval Band ---------------------------------------- How to make a line chart with a bootstrapped 95% confidence interval band. """ # category: uncertainties and trends import altair as alt from vega_datasets import data source = data.cars() line = alt.Chart(source).mark_line().encode( x='Year', y='mean(Miles_per_Gallon)' ) band = alt.Chart(source).mark_errorband(extent='ci').encode( x='Year', y=alt.Y('Miles_per_Gallon').title('Miles/Gallon'), ) band + line altair-5.0.1/tests/examples_methods_syntax/line_with_last_value_labeled.py000066400000000000000000000016721443422213100273270ustar00rootroot00000000000000""" Line Chart with Last Value Labeled ---------------------------------- This chart shows a line chart with a label annotating the final value """ # category: line charts import altair as alt from vega_datasets import data # Import example data source = data.stocks() # Create a common chart object chart = alt.Chart(source).transform_filter( alt.datum.symbol != "IBM" # A reducation of the dataset to clarify our example. Not required. ).encode( alt.Color("symbol").legend(None) ) # Draw the line line = chart.mark_line().encode( x="date:T", y="price:Q" ) # Use the `argmax` aggregate to limit the dataset to the final value label = chart.encode( x='max(date):T', y=alt.Y('price:Q').aggregate(argmax='date'), text='symbol' ) # Create a text label text = label.mark_text(align='left', dx=4) # Create a circle annotation circle = label.mark_circle() # Draw the chart with all the layers combined line + circle + text altair-5.0.1/tests/examples_methods_syntax/line_with_log_scale.py000066400000000000000000000006001443422213100254360ustar00rootroot00000000000000""" Line Chart with Logarithmic Scale --------------------------------- How to make a line chart on a `Logarithmic scale `_. """ # category: line charts import altair as alt from vega_datasets import data source = data.population() alt.Chart(source).mark_line().encode( x='year:O', y=alt.Y('sum(people)').scale(type="log") ) altair-5.0.1/tests/examples_methods_syntax/london_tube.py000066400000000000000000000033521443422213100237630ustar00rootroot00000000000000""" London Tube Lines ================= This example shows the London tube lines against the background of the borough boundaries. It is based on the vega-lite example at https://vega.github.io/vega-lite/examples/geo_layer_line_london.html. """ # category: case studies import altair as alt from vega_datasets import data boroughs = alt.topo_feature(data.londonBoroughs.url, 'boroughs') tubelines = alt.topo_feature(data.londonTubeLines.url, 'line') centroids = data.londonCentroids.url background = alt.Chart(boroughs, width=700, height=500).mark_geoshape( stroke='white', strokeWidth=2 ).encode( color=alt.value('#eee'), ) labels = alt.Chart(centroids).mark_text().encode( longitude='cx:Q', latitude='cy:Q', text='bLabel:N', size=alt.value(8), opacity=alt.value(0.6) ).transform_calculate( "bLabel", "indexof (datum.name,' ') > 0 ? substring(datum.name,0,indexof(datum.name, ' ')) : datum.name" ) line_scale = alt.Scale(domain=["Bakerloo", "Central", "Circle", "District", "DLR", "Hammersmith & City", "Jubilee", "Metropolitan", "Northern", "Piccadilly", "Victoria", "Waterloo & City"], range=["rgb(137,78,36)", "rgb(220,36,30)", "rgb(255,206,0)", "rgb(1,114,41)", "rgb(0,175,173)", "rgb(215,153,175)", "rgb(106,114,120)", "rgb(114,17,84)", "rgb(0,0,0)", "rgb(0,24,168)", "rgb(0,160,226)", "rgb(106,187,170)"]) lines = alt.Chart(tubelines).mark_geoshape( filled=False, strokeWidth=2 ).encode( alt.Color('id:N') .title(None) .legend(orient='bottom-right', offset=0) .scale(line_scale) ) background + labels + lines altair-5.0.1/tests/examples_methods_syntax/mosaic_with_labels.py000066400000000000000000000044271443422213100253070ustar00rootroot00000000000000""" Mosaic Chart with Labels ------------------------ """ # category: tables import altair as alt from vega_datasets import data source = data.cars() base = ( alt.Chart(source) .transform_aggregate(count_="count()", groupby=["Origin", "Cylinders"]) .transform_stack( stack="count_", as_=["stack_count_Origin1", "stack_count_Origin2"], offset="normalize", sort=[alt.SortField("Origin", "ascending")], groupby=[], ) .transform_window( x="min(stack_count_Origin1)", x2="max(stack_count_Origin2)", rank_Cylinders="dense_rank()", distinct_Cylinders="distinct(Cylinders)", groupby=["Origin"], frame=[None, None], sort=[alt.SortField("Cylinders", "ascending")], ) .transform_window( rank_Origin="dense_rank()", frame=[None, None], sort=[alt.SortField("Origin", "ascending")], ) .transform_stack( stack="count_", groupby=["Origin"], as_=["y", "y2"], offset="normalize", sort=[alt.SortField("Cylinders", "ascending")], ) .transform_calculate( ny="datum.y + (datum.rank_Cylinders - 1) * datum.distinct_Cylinders * 0.01 / 3", ny2="datum.y2 + (datum.rank_Cylinders - 1) * datum.distinct_Cylinders * 0.01 / 3", nx="datum.x + (datum.rank_Origin - 1) * 0.01", nx2="datum.x2 + (datum.rank_Origin - 1) * 0.01", xc="(datum.nx+datum.nx2)/2", yc="(datum.ny+datum.ny2)/2", ) ) rect = base.mark_rect().encode( x=alt.X("nx:Q").axis(None), x2="nx2", y="ny:Q", y2="ny2", color=alt.Color("Origin:N").legend(None), opacity=alt.Opacity("Cylinders:Q").legend(None), tooltip=["Origin:N", "Cylinders:Q"], ) text = base.mark_text(baseline="middle").encode( alt.X("xc:Q").axis(None), alt.Y("yc:Q").title("Cylinders"), text="Cylinders:N" ) mosaic = rect + text origin_labels = base.mark_text(baseline="middle", align="center").encode( alt.X("min(xc):Q").title("Origin").axis(orient="top"), alt.Color("Origin").legend(None), text="Origin", ) ( (origin_labels & mosaic) .resolve_scale(x="shared") .configure_view(stroke="") .configure_concat(spacing=10) .configure_axis(domain=False, ticks=False, labels=False, grid=False) ) altair-5.0.1/tests/examples_methods_syntax/multifeature_scatter_plot.py000066400000000000000000000006471443422213100267500ustar00rootroot00000000000000""" Multifeature Scatter Plot ========================= This example shows how to make a scatter plot with multiple feature encodings. """ # category: scatter plots import altair as alt from vega_datasets import data source = data.iris() alt.Chart(source).mark_circle().encode( alt.X('sepalLength').scale(zero=False), alt.Y('sepalWidth').scale(zero=False, padding=1), color='species', size='petalWidth' ) altair-5.0.1/tests/examples_methods_syntax/multiline_highlight.py000066400000000000000000000015111443422213100254770ustar00rootroot00000000000000""" Multi-Line Highlight ==================== This multi-line chart uses an invisible Voronoi tessellation to handle mouseover to identify the nearest point and then highlight the line on which the point falls. It is adapted from the Vega-Lite example found at https://bl.ocks.org/amitkaps/fe4238e716db53930b2f1a70d3401701 """ # category: interactive charts import altair as alt from vega_datasets import data source = data.stocks() highlight = alt.selection_point(on='mouseover', fields=['symbol'], nearest=True) base = alt.Chart(source).encode( x='date:T', y='price:Q', color='symbol:N' ) points = base.mark_circle().encode( opacity=alt.value(0) ).add_params( highlight ).properties( width=600 ) lines = base.mark_line().encode( size=alt.condition(~highlight, alt.value(1), alt.value(3)) ) points + lines altair-5.0.1/tests/examples_methods_syntax/multiline_tooltip.py000066400000000000000000000041131443422213100252230ustar00rootroot00000000000000""" Multi-Line Tooltip ================== This example shows how you can use selections and layers to create a tooltip-like behavior tied to the x position of the cursor. If you are looking for more standard tooltips, it is recommended to use the tooltip encoding channel as shown in the `Scatter Plot With Tooltips `_ example. The following example employs a little trick to isolate the x-position of the cursor: we add some transparent points with only an x encoding (no y encoding) and tie a *nearest* selection to these, tied to the "x" field. """ # category: interactive charts import altair as alt import pandas as pd import numpy as np np.random.seed(42) source = pd.DataFrame( np.cumsum(np.random.randn(100, 3), 0).round(2), columns=['A', 'B', 'C'], index=pd.RangeIndex(100, name='x') ) source = source.reset_index().melt('x', var_name='category', value_name='y') # Create a selection that chooses the nearest point & selects based on x-value nearest = alt.selection_point(nearest=True, on='mouseover', fields=['x'], empty=False) # The basic line line = alt.Chart(source).mark_line(interpolate='basis').encode( x='x:Q', y='y:Q', color='category:N' ) # Transparent selectors across the chart. This is what tells us # the x-value of the cursor selectors = alt.Chart(source).mark_point().encode( x='x:Q', opacity=alt.value(0), ).add_params( nearest ) # Draw points on the line, and highlight based on selection points = line.mark_point().encode( opacity=alt.condition(nearest, alt.value(1), alt.value(0)) ) # Draw text labels near the points, and highlight based on selection text = line.mark_text(align='left', dx=5, dy=-5).encode( text=alt.condition(nearest, 'y:Q', alt.value(' ')) ) # Draw a rule at the location of the selection rules = alt.Chart(source).mark_rule(color='gray').encode( x='x:Q', ).transform_filter( nearest ) # Put the five layers into a chart and bind the data alt.layer( line, selectors, points, rules, text ).properties( width=600, height=300 ) altair-5.0.1/tests/examples_methods_syntax/multiple_interactions.py000066400000000000000000000055001443422213100260650ustar00rootroot00000000000000""" Multiple Interactions ===================== This example shows how multiple user inputs can be layered onto a chart. The four inputs have functionality as follows: * Dropdown: Filters the movies by genre * Radio Buttons: Highlights certain films by Worldwide Gross * Mouse Drag and Scroll: Zooms the x and y scales to allow for panning. """ # category: interactive charts import altair as alt from vega_datasets import data movies = alt.UrlData( data.movies.url, format=alt.DataFormat(parse={"Release_Date":"date"}) ) ratings = ['G', 'NC-17', 'PG', 'PG-13', 'R'] genres = [ 'Action', 'Adventure', 'Black Comedy', 'Comedy', 'Concert/Performance', 'Documentary', 'Drama', 'Horror', 'Musical', 'Romantic Comedy', 'Thriller/Suspense', 'Western' ] base = alt.Chart(movies, width=200, height=200).mark_point(filled=True).transform_calculate( Rounded_IMDB_Rating = "floor(datum.IMDB_Rating)", Hundred_Million_Production = "datum.Production_Budget > 100000000.0 ? 100 : 10", Release_Year = "year(datum.Release_Date)" ).transform_filter( alt.datum.IMDB_Rating > 0 ).transform_filter( alt.FieldOneOfPredicate(field='MPAA_Rating', oneOf=ratings) ).encode( x=alt.X('Worldwide_Gross:Q').scale(domain=(100000,10**9), clamp=True), y='IMDB_Rating:Q', tooltip="Title:N" ) # A slider filter year_slider = alt.binding_range(min=1969, max=2018, step=1, name="Release Year") slider_selection = alt.selection_point(bind=year_slider, fields=['Release_Year']) filter_year = base.add_params( slider_selection ).transform_filter( slider_selection ).properties(title="Slider Filtering") # A dropdown filter genre_dropdown = alt.binding_select(options=genres, name="Genre") genre_select = alt.selection_point(fields=['Major_Genre'], bind=genre_dropdown) filter_genres = base.add_params( genre_select ).transform_filter( genre_select ).properties(title="Dropdown Filtering") #color changing marks rating_radio = alt.binding_radio(options=ratings, name="Rating") rating_select = alt.selection_point(fields=['MPAA_Rating'], bind=rating_radio) rating_color_condition = alt.condition( rating_select, alt.Color('MPAA_Rating:N').legend(None), alt.value('lightgray') ) highlight_ratings = base.add_params( rating_select ).encode( color=rating_color_condition ).properties(title="Radio Button Highlighting") # Boolean selection for format changes input_checkbox = alt.binding_checkbox(name="Big Budget Films ") checkbox_selection = alt.param(bind=input_checkbox) size_checkbox_condition = alt.condition( checkbox_selection, alt.Size('Hundred_Million_Production:Q'), alt.SizeValue(25) ) budget_sizing = base.add_params( checkbox_selection ).encode( size=size_checkbox_condition ).properties(title="Checkbox Formatting") (filter_year | filter_genres) & (highlight_ratings | budget_sizing) altair-5.0.1/tests/examples_methods_syntax/natural_disasters.py000066400000000000000000000025311443422213100252000ustar00rootroot00000000000000""" Global Deaths from Natural Disasters ------------------------------------ This example shows a proportional symbols visualization of deaths from natural disasters by year and type. """ # category: case studies import altair as alt from vega_datasets import data source = data.disasters.url alt.Chart(source).transform_filter( alt.datum.Entity != 'All natural disasters' ).mark_circle( opacity=0.8, stroke='black', strokeWidth=1, strokeOpacity=0.4 ).encode( alt.X('Year:T') .title(None) .scale(domain=['1899','2018']), alt.Y('Entity:N') .title(None) .sort(field="Deaths", op="sum", order='descending'), alt.Size('Deaths:Q') .scale(range=[0, 2500]) .title('Deaths') .legend(clipHeight=30, format='s'), alt.Color('Entity:N').legend(None), tooltip=[ "Entity:N", alt.Tooltip("Year:T", format='%Y'), alt.Tooltip("Deaths:Q", format='~s') ], ).properties( width=450, height=320, title=alt.Title( text="Global Deaths from Natural Disasters (1900-2017)", subtitle="The size of the bubble represents the total death count per year, by type of disaster", anchor='start' ) ).configure_axisY( domain=False, ticks=False, offset=10 ).configure_axisX( grid=False, ).configure_view( stroke=None ) altair-5.0.1/tests/examples_methods_syntax/normalized_stacked_area_chart.py000066400000000000000000000005641443422213100274700ustar00rootroot00000000000000""" Normalized Stacked Area Chart ----------------------------- This example shows how to make a normalized stacked area chart. """ # category: area charts import altair as alt from vega_datasets import data source = data.iowa_electricity() alt.Chart(source).mark_area().encode( x="year:T", y=alt.Y("net_generation:Q").stack("normalize"), color="source:N" ) altair-5.0.1/tests/examples_methods_syntax/normalized_stacked_bar_chart.py000066400000000000000000000006611443422213100273220ustar00rootroot00000000000000""" Normalized Stacked Bar Chart ---------------------------- This is an example of a normalized stacked bar chart using data which contains crop yields over different regions and different years in the 1930s. """ # category: bar charts import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( x=alt.X('sum(yield)').stack("normalize"), y='variety', color='site' ) altair-5.0.1/tests/examples_methods_syntax/pacman_chart.py000066400000000000000000000007371443422213100240770ustar00rootroot00000000000000""" Pacman Chart ------------ Chart made using ``mark_arc`` and constant values. This could also be made using ``alt.Chart(source).mark_arc(color = "gold", theta = (5/8)*np.pi, theta2 = (19/8)*np.pi,radius=100)``. """ # category: circular plots import numpy as np import altair as alt alt.Chart().mark_arc(color="gold").encode( theta=alt.ThetaDatum((5 / 8) * np.pi).scale(None), theta2=alt.Theta2Datum((19 / 8) * np.pi), radius=alt.RadiusDatum(100).scale(None), ) altair-5.0.1/tests/examples_methods_syntax/parallel_coordinates.py000066400000000000000000000014361443422213100256420ustar00rootroot00000000000000""" Parallel Coordinates -------------------- A `Parallel Coordinates `_ chart is a chart that lets you visualize the individual data points by drawing a single line for each of them. Such a chart can be created in Altair by first transforming the data into a suitable representation. This example shows a parallel coordinates chart with the Iris dataset. """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.iris() alt.Chart(source, width=500).transform_window( index='count()' ).transform_fold( ['petalLength', 'petalWidth', 'sepalLength', 'sepalWidth'] ).mark_line().encode( x='key:N', y='value:Q', color='species:N', detail='index:N', opacity=alt.value(0.5) ) altair-5.0.1/tests/examples_methods_syntax/percentage_of_total.py000066400000000000000000000011341443422213100254530ustar00rootroot00000000000000""" Calculating Percentage of Total ------------------------------- This chart demonstrates how to use a joinaggregate transform to display data values as a percentage of total. """ # category: bar charts import altair as alt import pandas as pd source = pd.DataFrame({ 'Activity': ['Sleeping', 'Eating', 'TV', 'Work', 'Exercise'], 'Time': [8, 2, 4, 8, 2] }) alt.Chart(source).transform_joinaggregate( TotalTime='sum(Time)', ).transform_calculate( PercentOfTotal="datum.Time / datum.TotalTime" ).mark_bar().encode( alt.X('PercentOfTotal:Q').axis(format='.0%'), y='Activity:N' ) altair-5.0.1/tests/examples_methods_syntax/pie_chart.py000066400000000000000000000007061443422213100234110ustar00rootroot00000000000000""" Pie Chart --------- This example shows how to make a Pie Chart using ``mark_arc``. This is adapted from a corresponding Vega-Lite Example: `Pie Chart `_. """ # category: circular plots import pandas as pd import altair as alt source = pd.DataFrame({"category": [1, 2, 3, 4, 5, 6], "value": [4, 6, 10, 3, 7, 8]}) alt.Chart(source).mark_arc().encode( theta="value", color="category" ) altair-5.0.1/tests/examples_methods_syntax/pie_chart_with_labels.py000066400000000000000000000012671443422213100257710ustar00rootroot00000000000000""" Pie Chart with Labels --------------------- This example shows how to layer text over arc marks (``mark_arc``) to label pie charts. This is adapted from a corresponding Vega-Lite Example: `Pie Chart with Labels `_. """ # category: circular plots import pandas as pd import altair as alt source = pd.DataFrame( {"category": ["a", "b", "c", "d", "e", "f"], "value": [4, 6, 10, 3, 7, 8]} ) base = alt.Chart(source).encode( alt.Theta("value:Q").stack(True), alt.Color("category:N").legend(None) ) pie = base.mark_arc(outerRadius=120) text = base.mark_text(radius=140, size=20).encode(text="category:N") pie + text altair-5.0.1/tests/examples_methods_syntax/poly_fit_regression.py000066400000000000000000000016341443422213100255410ustar00rootroot00000000000000""" Polynomial Fit Plot with Regression Transform ============================================= This example shows how to overlay data with multiple fitted polynomials using the regression transform. """ # category: uncertainties and trends import numpy as np import pandas as pd import altair as alt # Generate some random data rng = np.random.RandomState(1) x = rng.rand(40) ** 2 y = 10 - 1.0 / (x + 0.1) + rng.randn(40) source = pd.DataFrame({"x": x, "y": y}) # Define the degree of the polynomial fits degree_list = [1, 3, 5] base = alt.Chart(source).mark_circle(color="black").encode( alt.X("x"), alt.Y("y") ) polynomial_fit = [ base.transform_regression( "x", "y", method="poly", order=order, as_=["x", str(order)] ) .mark_line() .transform_fold([str(order)], as_=["degree", "y"]) .encode(alt.Color("degree:N")) for order in degree_list ] alt.layer(base, *polynomial_fit) altair-5.0.1/tests/examples_methods_syntax/pyramid.py000066400000000000000000000012731443422213100231200ustar00rootroot00000000000000""" Pyramid Pie Chart ----------------- Altair reproduction of http://robslink.com/SAS/democd91/pyramid_pie.htm """ # category: case studies import altair as alt import pandas as pd category = ['Sky', 'Shady side of a pyramid', 'Sunny side of a pyramid'] color = ["#416D9D", "#674028", "#DEAC58"] df = pd.DataFrame({'category': category, 'value': [75, 10, 15]}) alt.Chart(df, width=150, height=150).mark_arc(outerRadius=80).encode( alt.Theta('value:Q').scale(range=[2.356, 8.639]), alt.Color('category:N') .title(None) .scale(domain=category, range=color) .legend(orient='none', legendX=160, legendY=50), order='value:Q' ).configure_view( strokeOpacity=0 ) altair-5.0.1/tests/examples_methods_syntax/radial_chart.py000066400000000000000000000012431443422213100240650ustar00rootroot00000000000000""" Radial Chart ------------ This radial plot uses both angular and radial extent to convey multiple dimensions of data. This is adapted from a corresponding Vega-Lite Example: `Radial Plot `_. """ # category: circular plots import pandas as pd import altair as alt source = pd.DataFrame({"values": [12, 23, 47, 6, 52, 19]}) base = alt.Chart(source).encode( alt.Theta("values:Q").stack(True), alt.Radius("values").scale(type="sqrt", zero=True, rangeMin=20), color="values:N", ) c1 = base.mark_arc(innerRadius=20, stroke="#fff") c2 = base.mark_text(radiusOffset=10).encode(text="values:Q") c1 + c2 altair-5.0.1/tests/examples_methods_syntax/ranged_dot_plot.py000066400000000000000000000016701443422213100246200ustar00rootroot00000000000000""" Ranged Dot Plot --------------- This example shows a ranged dot plot to convey changing life expectancy for the five most populous countries (between 1955 and 2000). """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.countries.url chart = alt.Chart( data=source ).transform_filter( filter={"field": 'country', "oneOf": ["China", "India", "United States", "Indonesia", "Brazil"]} ).transform_filter( filter={'field': 'year', "oneOf": [1955, 2000]} ) line = chart.mark_line(color='#db646f').encode( x='life_expect:Q', y='country:N', detail='country:N' ) # Add points for life expectancy in 1955 & 2000 points = chart.mark_point( size=100, opacity=1, filled=True ).encode( x='life_expect:Q', y='country:N', color=alt.Color('year:O').scale(domain=[1955, 2000], range=['#e6959c', '#911a24']) ).interactive() (line + points) altair-5.0.1/tests/examples_methods_syntax/ridgeline_plot.py000066400000000000000000000027751443422213100244630ustar00rootroot00000000000000""" Ridgeline plot -------------- A `Ridgeline plot `_ chart is a chart that lets you visualize distribution of a numeric value for several groups. Such a chart can be created in Altair by first transforming the data into a suitable representation. """ # category: distributions import altair as alt from vega_datasets import data source = data.seattle_weather.url step = 20 overlap = 1 alt.Chart(source, height=step).transform_timeunit( Month='month(date)' ).transform_joinaggregate( mean_temp='mean(temp_max)', groupby=['Month'] ).transform_bin( ['bin_max', 'bin_min'], 'temp_max' ).transform_aggregate( value='count()', groupby=['Month', 'mean_temp', 'bin_min', 'bin_max'] ).transform_impute( impute='value', groupby=['Month', 'mean_temp'], key='bin_min', value=0 ).mark_area( interpolate='monotone', fillOpacity=0.8, stroke='lightgray', strokeWidth=0.5 ).encode( alt.X('bin_min:Q') .bin(True) .title('Maximum Daily Temperature (C)'), alt.Y('value:Q') .axis(None) .scale(range=[step, -step * overlap]), alt.Fill('mean_temp:Q') .legend(None) .scale(domain=[30, 5], scheme='redyellowblue') ).facet( alt.Row('Month:T') .title(None) .header(labelAngle=0, labelAlign='right', format='%B') ).properties( title='Seattle Weather', bounds='flush' ).configure_facet( spacing=0 ).configure_view( stroke=None ).configure_title( anchor='end' ) altair-5.0.1/tests/examples_methods_syntax/scatter_linked_table.py000066400000000000000000000026301443422213100256130ustar00rootroot00000000000000""" Brushing Scatter Plot to Show Data on a Table --------------------------------------------- A scatter plot of the cars dataset, with data tables for horsepower, MPG, and origin. The tables update to reflect the selection on the scatter plot. """ # category: scatter plots import altair as alt from vega_datasets import data source = data.cars() # Brush for selection brush = alt.selection_interval() # Scatter Plot points = alt.Chart(source).mark_point().encode( x='Horsepower:Q', y='Miles_per_Gallon:Q', color=alt.condition(brush, alt.value('steelblue'), alt.value('grey')) ).add_params(brush) # Base chart for data tables ranked_text = alt.Chart(source).mark_text(align='right').encode( y=alt.Y('row_number:O').axis(None) ).transform_filter( brush ).transform_window( row_number='row_number()' ).transform_filter( alt.datum.row_number < 15 ) # Data Tables horsepower = ranked_text.encode(text='Horsepower:N').properties( title=alt.Title(text='Horsepower', align='right') ) mpg = ranked_text.encode(text='Miles_per_Gallon:N').properties( title=alt.Title(text='MPG', align='right') ) origin = ranked_text.encode(text='Origin:N').properties( title=alt.Title(text='Origin', align='right') ) text = alt.hconcat(horsepower, mpg, origin) # Combine data tables # Build chart alt.hconcat( points, text ).resolve_legend( color="independent" ).configure_view( stroke=None ) altair-5.0.1/tests/examples_methods_syntax/scatter_marginal_hist.py000066400000000000000000000025341443422213100260220ustar00rootroot00000000000000""" Facetted Scatter Plot with Marginal Histograms ---------------------------------------------- This example demonstrates how to generate a facetted scatter plot, with marginal facetted histograms, and how to share their respective - x,some y-limits. """ # category: distributions import altair as alt from vega_datasets import data source = data.iris() base = alt.Chart(source) xscale = alt.Scale(domain=(4.0, 8.0)) yscale = alt.Scale(domain=(1.9, 4.55)) bar_args = {'opacity': .3, 'binSpacing': 0} points = base.mark_circle().encode( alt.X('sepalLength').scale(xscale), alt.Y('sepalWidth').scale(yscale), color='species', ) top_hist = base.mark_bar(**bar_args).encode( alt.X('sepalLength:Q') # when using bins, the axis scale is set through # the bin extent, so we do not specify the scale here # (which would be ignored anyway) .bin(maxbins=20, extent=xscale.domain) .stack(None) .title(''), alt.Y('count()').stack(None).title(''), alt.Color('species:N'), ).properties(height=60) right_hist = base.mark_bar(**bar_args).encode( alt.Y('sepalWidth:Q') .bin(maxbins=20, extent=yscale.domain) .stack(None) .title(''), alt.X('count()').stack(None).title(''), alt.Color('species:N'), ).properties(width=60) top_hist & (points | right_hist) altair-5.0.1/tests/examples_methods_syntax/scatter_with_layered_histogram.py000066400000000000000000000032371443422213100277370ustar00rootroot00000000000000""" Interactive Scatter Plot and Linked Layered Histogram ===================================================== This example shows how to link a scatter plot and a histogram together such that clicking on a point in the scatter plot will isolate the distribution corresponding to that point, and vice versa. """ # category: interactive charts import altair as alt import pandas as pd import numpy as np # generate fake data source = pd.DataFrame({ 'gender': ['M']*1000 + ['F']*1000, 'height':np.concatenate(( np.random.normal(69, 7, 1000), np.random.normal(64, 6, 1000) )), 'weight': np.concatenate(( np.random.normal(195.8, 144, 1000), np.random.normal(167, 100, 1000) )), 'age': np.concatenate(( np.random.normal(45, 8, 1000), np.random.normal(51, 6, 1000) )) }) selector = alt.selection_point(fields=['gender']) color_scale = alt.Scale(domain=['M', 'F'], range=['#1FC3AA', '#8624F5']) base = alt.Chart(source).properties( width=250, height=250 ).add_params(selector) points = base.mark_point(filled=True, size=200).encode( x=alt.X('mean(height):Q').scale(domain=[0,84]), y=alt.Y('mean(weight):Q').scale(domain=[0,250]), color=alt.condition( selector, 'gender:N', alt.value('lightgray'), scale=color_scale), ) hists = base.mark_bar(opacity=0.5, thickness=100).encode( x=alt.X('age') .bin(step=5) # step keeps bin size the same .scale(domain=[0,100]), y=alt.Y('count()') .stack(None) .scale(domain=[0,350]), color=alt.Color('gender:N', scale=color_scale) ).transform_filter( selector ) points | hists altair-5.0.1/tests/examples_methods_syntax/scatter_with_minimap.py000066400000000000000000000021071443422213100256620ustar00rootroot00000000000000""" Scatter Plot with Minimap ------------------------- This example shows how to create a miniature version of a plot such that creating a selection in the miniature version adjusts the axis limits in another, more detailed view. """ # category: scatter plots import altair as alt from vega_datasets import data source = data.seattle_weather() zoom = alt.selection_interval(encodings=["x", "y"]) minimap = ( alt.Chart(source) .mark_point() .add_params(zoom) .encode( x="date:T", y="temp_max:Q", color=alt.condition(zoom, "weather", alt.value("lightgray")), ) .properties( width=200, height=200, title="Minimap -- click and drag to zoom in the detail view", ) ) detail = ( alt.Chart(source) .mark_point() .encode( alt.X("date:T").scale(domain={"param": zoom.name, "encoding": "x"}), alt.Y("temp_max:Q").scale(domain={"param": zoom.name, "encoding": "y"}), color="weather", ) .properties(width=600, height=400, title="Seattle weather -- detail view") ) detail | minimap altair-5.0.1/tests/examples_methods_syntax/scatter_with_rolling_mean.py000066400000000000000000000012131443422213100266730ustar00rootroot00000000000000""" Scatter Plot with Rolling Mean ------------------------------ A scatter plot with a rolling mean overlay. In this example a 30 day window is used to calculate the mean of the maximum temperature around each date. """ # category: scatter plots import altair as alt from vega_datasets import data source = data.seattle_weather() line = alt.Chart(source).mark_line( color='red', size=3 ).transform_window( rolling_mean='mean(temp_max)', frame=[-15, 15] ).encode( x='date:T', y='rolling_mean:Q' ) points = alt.Chart(source).mark_point().encode( x='date:T', y=alt.Y('temp_max:Q').title('Max Temp') ) points + line altair-5.0.1/tests/examples_methods_syntax/seattle_weather_interactive.py000066400000000000000000000031231443422213100272240ustar00rootroot00000000000000""" Seattle Weather Interactive =========================== This chart provides an interactive exploration of Seattle weather over the course of the year. It includes a one-axis brush selection to easily see the distribution of weather types in a particular date range. """ # category: case studies import altair as alt from vega_datasets import data source = data.seattle_weather() scale = alt.Scale( domain=['sun', 'fog', 'drizzle', 'rain', 'snow'], range=['#e7ba52', '#a7a7a7', '#aec7e8', '#1f77b4', '#9467bd'] ) color = alt.Color('weather:N', scale=scale) # We create two selections: # - a brush that is active on the top panel # - a multi-click that is active on the bottom panel brush = alt.selection_interval(encodings=['x']) click = alt.selection_point(encodings=['color']) # Top panel is scatter plot of temperature vs time points = alt.Chart().mark_point().encode( alt.X('monthdate(date):T').title('Date'), alt.Y('temp_max:Q') .title('Maximum Daily Temperature (C)') .scale(domain=[-5, 40]), alt.Size('precipitation:Q').scale(range=[5, 200]), color=alt.condition(brush, color, alt.value('lightgray')), ).properties( width=550, height=300 ).add_params( brush ).transform_filter( click ) # Bottom panel is a bar chart of weather type bars = alt.Chart().mark_bar().encode( x='count()', y='weather:N', color=alt.condition(click, color, alt.value('lightgray')), ).transform_filter( brush ).properties( width=550, ).add_params( click ) alt.vconcat( points, bars, data=source, title="Seattle Weather: 2012-2015" ) altair-5.0.1/tests/examples_methods_syntax/select_detail.py000066400000000000000000000035541443422213100242600ustar00rootroot00000000000000""" Selection Detail ================ This example shows a selection that links two views of data: the left panel contains one point per object, and the right panel contains one line per object. Clicking on either the points or lines will select the corresponding objects in both views of the data. The challenge lies in expressing such hierarchical data in a way that Altair can handle. We do this by merging the data into a "long form" dataframe, and aggregating identical metadata for the final plot. """ # category: interactive charts import altair as alt import pandas as pd import numpy as np np.random.seed(0) n_objects = 20 n_times = 50 # Create one (x, y) pair of metadata per object locations = pd.DataFrame({ 'id': range(n_objects), 'x': np.random.randn(n_objects), 'y': np.random.randn(n_objects) }) # Create a 50-element time-series for each object timeseries = pd.DataFrame(np.random.randn(n_times, n_objects).cumsum(0), columns=locations['id'], index=pd.RangeIndex(0, n_times, name='time')) # Melt the wide-form timeseries into a long-form view timeseries = timeseries.reset_index().melt('time') # Merge the (x, y) metadata into the long-form view timeseries['id'] = timeseries['id'].astype(int) # make merge not complain data = pd.merge(timeseries, locations, on='id') # Data is prepared, now make a chart selector = alt.selection_point(fields=['id']) base = alt.Chart(data).properties( width=250, height=250 ).add_params(selector) points = base.mark_point(filled=True, size=200).encode( x='mean(x)', y='mean(y)', color=alt.condition(selector, 'id:O', alt.value('lightgray'), legend=None), ) timeseries = base.mark_line().encode( x='time', y=alt.Y('value').scale(domain=(-15, 15)), color=alt.Color('id:O').legend(None) ).transform_filter( selector ) points | timeseries altair-5.0.1/tests/examples_methods_syntax/simple_scatter_with_errorbars.py000066400000000000000000000015611443422213100276050ustar00rootroot00000000000000""" Simple Scatter Plot with Errorbars ---------------------------------- A simple scatter plot of a data set with errorbars. """ # category: uncertainties and trends import altair as alt import pandas as pd import numpy as np # generate some data points with uncertainties np.random.seed(0) x = [1, 2, 3, 4, 5] y = np.random.normal(10, 0.5, size=len(x)) yerr = 0.2 # set up data frame source = pd.DataFrame({"x": x, "y": y, "yerr": yerr}) # the base chart base = alt.Chart(source).transform_calculate( ymin="datum.y-datum.yerr", ymax="datum.y+datum.yerr" ) # generate the points points = base.mark_point( filled=True, size=50, color='black' ).encode( alt.X('x').scale(domain=(0, 6)), alt.Y('y').scale(zero=False) ) # generate the error bars errorbars = base.mark_errorbar().encode( x="x", y="ymin:Q", y2="ymax:Q" ) points + errorbars altair-5.0.1/tests/examples_methods_syntax/sorted_error_bars_with_ci.py000066400000000000000000000013261443422213100267000ustar00rootroot00000000000000""" Sorted Error Bars showing Confidence Interval ============================================= This example shows how to show error bars using confidence intervals, while also sorting the y-axis based on x-axis values. """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.barley() points = alt.Chart(source).mark_point( filled=True, color='black' ).encode( x=alt.X('mean(yield)').title('Barley Yield'), y=alt.Y('variety').sort( field='yield', op='mean', order='descending' ) ).properties( width=400, height=250 ) error_bars = points.mark_rule().encode( x='ci0(yield)', x2='ci1(yield)', ) points + error_bars altair-5.0.1/tests/examples_methods_syntax/stacked_bar_chart_sorted_segments.py000066400000000000000000000007361443422213100303660ustar00rootroot00000000000000""" Stacked Bar Chart with Sorted Segments -------------------------------------- This is an example of a stacked-bar chart with the segments of each bar resorted. """ # category: bar charts import altair as alt from vega_datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( x='sum(yield)', y='variety', color='site', order=alt.Order( # Sort the segments of the bars by this field 'site', sort='ascending' ) ) altair-5.0.1/tests/examples_methods_syntax/stacked_bar_chart_with_text.py000066400000000000000000000013471443422213100271770ustar00rootroot00000000000000""" Stacked Bar Chart with Text Overlay =================================== This example shows how to overlay text on a stacked bar chart. For both the bar and text marks, we use the ``stack`` argument in the ``x`` encoding to cause the values to be stacked horizontally. """ # category: bar charts import altair as alt from vega_datasets import data source=data.barley() bars = alt.Chart(source).mark_bar().encode( x=alt.X('sum(yield):Q').stack('zero'), y=alt.Y('variety:N'), color=alt.Color('site') ) text = alt.Chart(source).mark_text(dx=-15, dy=3, color='white').encode( x=alt.X('sum(yield):Q').stack('zero'), y=alt.Y('variety:N'), detail='site:N', text=alt.Text('sum(yield):Q', format='.1f') ) bars + text altair-5.0.1/tests/examples_methods_syntax/stem_and_leaf.py000066400000000000000000000017011443422213100242300ustar00rootroot00000000000000""" Stem and Leaf Plot ------------------ This example shows how to make a stem and leaf plot. """ # category: advanced calculations import altair as alt import pandas as pd import numpy as np np.random.seed(42) # Generating random data source = pd.DataFrame({'samples': np.random.normal(50, 15, 100).astype(int).astype(str)}) # Splitting stem and leaf source['stem'] = source['samples'].str[:-1] source['leaf'] = source['samples'].str[-1] source = source.sort_values(by=['stem', 'leaf']) # Determining leaf position source['position'] = source.groupby('stem').cumcount().add(1) # Creating stem and leaf plot alt.Chart(source).mark_text( align='left', baseline='middle', dx=-5 ).encode( alt.X('position:Q') .title('') .axis(ticks=False, labels=False, grid=False), alt.Y('stem:N') .title('') .axis(tickSize=0), text='leaf:N', ).configure_axis( labelFontSize=20 ).configure_text( fontSize=20 ) altair-5.0.1/tests/examples_methods_syntax/streamgraph.py000066400000000000000000000007171443422213100237720ustar00rootroot00000000000000""" Streamgraph ----------------- This example shows the streamgraph from vega-lite examples. """ # category: area charts import altair as alt from vega_datasets import data source = data.unemployment_across_industries.url alt.Chart(source).mark_area().encode( alt.X('yearmonth(date):T').axis(format='%Y', domain=False, tickSize=0), alt.Y('sum(count):Q').stack('center').axis(None), alt.Color('series:N').scale(scheme='category20b') ).interactive() altair-5.0.1/tests/examples_methods_syntax/strip_plot_jitter.py000066400000000000000000000024261443422213100252340ustar00rootroot00000000000000""" Strip Plot with Jitter ---------------------- In this chart, we encode the ``Major_Genre`` column from the ``movies`` dataset in the ``y``-channel. In the default presentation of this data, it would be difficult to gauge the relative frequencies with which different values occur because there would be so much overlap. To address this, we use the ``yOffset`` channel to incorporate a random offset (jittering). The example is shown twice, on the left side using normally distributed and on the right side using uniformally distributed jitter. """ # category: distributions import altair as alt from vega_datasets import data source = data.movies.url gaussian_jitter = alt.Chart(source, title='Normally distributed jitter').mark_circle(size=8).encode( y="Major_Genre:N", x="IMDB_Rating:Q", yOffset="jitter:Q", color=alt.Color('Major_Genre:N').legend(None) ).transform_calculate( # Generate Gaussian jitter with a Box-Muller transform jitter="sqrt(-2*log(random()))*cos(2*PI*random())" ) uniform_jitter = gaussian_jitter.transform_calculate( # Generate uniform jitter jitter='random()' ).encode( alt.Y('Major_Genre:N').axis(None) ).properties( title='Uniformly distributed jitter' ) (gaussian_jitter | uniform_jitter).resolve_scale(yOffset='independent') altair-5.0.1/tests/examples_methods_syntax/top_k_items.py000066400000000000000000000012341443422213100237650ustar00rootroot00000000000000""" Top K Items ----------- This example shows how to use the window and transformation filter to display the Top items of a long list of items in decreasing order. Here we sort the top 10 highest ranking movies of IMDB. """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.movies.url # Top 10 movies by IMBD rating alt.Chart( source, ).mark_bar().encode( alt.X('Title:N').sort('-y'), alt.Y('IMDB_Rating:Q'), alt.Color('IMDB_Rating:Q') ).transform_window( rank='rank(IMDB_Rating)', sort=[alt.SortField('IMDB_Rating', order='descending')] ).transform_filter( (alt.datum.rank < 10) ) altair-5.0.1/tests/examples_methods_syntax/top_k_letters.py000066400000000000000000000026431443422213100243330ustar00rootroot00000000000000""" Top K Letters ------------- This example shows how to use a window transform in order to display only the top K categories by number of entries. In this case, we rank the characters in the first paragraph of Dickens' *A Tale of Two Cities* by number of occurances. """ # category: advanced calculations import altair as alt import pandas as pd import numpy as np # Excerpt from A Tale of Two Cities; public domain text text = """ It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way - in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only. """ source = pd.DataFrame( {'letters': np.array([c for c in text if c.isalpha()])} ) alt.Chart(source).transform_aggregate( count='count()', groupby=['letters'] ).transform_window( rank='rank(count)', sort=[alt.SortField('count', order='descending')] ).transform_filter( alt.datum.rank < 10 ).mark_bar().encode( y=alt.Y('letters:N').sort('-x'), x='count:Q', ) altair-5.0.1/tests/examples_methods_syntax/top_k_with_others.py000066400000000000000000000016651443422213100252130ustar00rootroot00000000000000""" Top-K Plot with Others ---------------------- This example shows how to use aggregate, window, and calculate transfromations to display the top-k directors by average worldwide gross while grouping the remaining directors as 'All Others'. """ # category: advanced calculations import altair as alt from vega_datasets import data source = data.movies.url alt.Chart(source).mark_bar().encode( alt.X("aggregate_gross:Q").aggregate("mean").title(None), alt.Y("ranked_director:N") .sort(op="mean", field="aggregate_gross", order="descending") .title(None) ).transform_aggregate( aggregate_gross='mean(Worldwide_Gross)', groupby=["Director"], ).transform_window( rank='row_number()', sort=[alt.SortField("aggregate_gross", order="descending")], ).transform_calculate( ranked_director="datum.rank < 10 ? datum.Director : 'All Others'" ).properties( title="Top Directors by Average Worldwide Gross", ) altair-5.0.1/tests/examples_methods_syntax/trellis_area_sort_array.py000066400000000000000000000010301443422213100263550ustar00rootroot00000000000000''' Trellis Area Sort Chart ----------------------- This example shows small multiples of an area chart. Stock prices of four large companies sorted by `['MSFT', 'AAPL', 'IBM', 'AMZN']` ''' # category: area charts import altair as alt from vega_datasets import data source = data.stocks() alt.Chart(source).transform_filter( alt.datum.symbol != 'GOOG' ).mark_area().encode( x='date:T', y='price:Q', color='symbol:N', row=alt.Row('symbol:N').sort(['MSFT', 'AAPL', 'IBM', 'AMZN']) ).properties(height=50, width=400) altair-5.0.1/tests/examples_methods_syntax/trellis_histogram.py000066400000000000000000000005721443422213100252070ustar00rootroot00000000000000""" Trellis Histogram ----------------- This example shows how to make a basic trellis histogram. https://vega.github.io/vega-lite/examples/trellis_bar_histogram.html """ # category: distributions import altair as alt from vega_datasets import data source = data.cars() alt.Chart(source).mark_bar().encode( alt.X("Horsepower:Q").bin(), y='count()', row='Origin' ) altair-5.0.1/tests/examples_methods_syntax/us_employment.py000066400000000000000000000025441443422213100243550ustar00rootroot00000000000000""" The U.S. Employment Crash During the Great Recession ---------------------------------------------------- This example is a fully developed bar chart with negative values using the sample dataset of U.S. employment changes during the Great Recession. """ # category: case studies import altair as alt import pandas as pd from vega_datasets import data source = data.us_employment() presidents = pd.DataFrame([ { "start": "2006-01-01", "end": "2009-01-19", "president": "Bush" }, { "start": "2009-01-20", "end": "2015-12-31", "president": "Obama" } ]) bars = alt.Chart( source, title="The U.S. employment crash during the Great Recession" ).mark_bar().encode( alt.X("month:T").title(""), alt.Y("nonfarm_change:Q").title("Change in non-farm employment (in thousands)"), color=alt.condition( alt.datum.nonfarm_change > 0, alt.value("steelblue"), alt.value("orange") ) ) rule = alt.Chart(presidents).mark_rule( color="black", strokeWidth=2 ).encode( x='end:T' ).transform_filter(alt.datum.president == "Bush") text = alt.Chart(presidents).mark_text( align='left', baseline='middle', dx=7, dy=-135, size=11 ).encode( x='start:T', text='president', color=alt.value('#000000') ) (bars + rule + text).properties(width=600) altair-5.0.1/tests/examples_methods_syntax/us_population_over_time.py000066400000000000000000000021131443422213100264170ustar00rootroot00000000000000""" US Population by Age and Sex ============================ This chart visualizes the age distribution of the US population over time. It uses a slider widget that is bound to the year to visualize the age distribution over time. """ # category: case studies import altair as alt from vega_datasets import data source = data.population.url select_year = alt.selection_point( name="Year", fields=["year"], bind=alt.binding_range(min=1900, max=2000, step=10, name="Year"), value={"year": 2000}, ) alt.Chart(source).mark_bar().encode( alt.X("sex:N").title('').axis(labels=False, ticks=False), alt.Y("people:Q").scale(domain=(0, 12000000)).title("Population"), alt.Color("sex:N") .scale(domain=("Male", "Female"), range=["steelblue", "salmon"]) .title("Sex"), alt.Column("age:O").title("Age") ).properties( width=20, title="U.S. Population by Age and Sex" ).add_params( select_year ).transform_calculate( "sex", alt.expr.if_(alt.datum.sex == 1, "Male", "Female") ).transform_filter( select_year ).configure_facet( spacing=8 ) altair-5.0.1/tests/examples_methods_syntax/us_population_over_time_facet.py000066400000000000000000000010411443422213100275600ustar00rootroot00000000000000""" US Population: Wrapped Facet ============================ This chart visualizes the age distribution of the US population over time, using a wrapped faceting of the data by decade. """ # category: case studies import altair as alt from vega_datasets import data source = data.population.url alt.Chart(source).mark_area().encode( x='age:O', y=alt.Y('sum(people):Q').title('Population').axis(format='~s'), facet=alt.Facet('year:O').columns(5), ).properties( title='US Age Distribution By Year', width=90, height=80 ) altair-5.0.1/tests/examples_methods_syntax/us_population_pyramid_over_time.py000066400000000000000000000030361443422213100301510ustar00rootroot00000000000000''' US Population Pyramid Over Time =============================== A population pyramid shows the distribution of age groups within a population. It uses a slider widget that is bound to the year to visualize the age distribution over time. ''' # category: case studies import altair as alt from vega_datasets import data source = data.population.url slider = alt.binding_range(min=1850, max=2000, step=10) select_year = alt.selection_point(name='year', fields=['year'], bind=slider, value={'year': 2000}) base = alt.Chart(source).add_params( select_year ).transform_filter( select_year ).transform_calculate( gender=alt.expr.if_(alt.datum.sex == 1, 'Male', 'Female') ).properties( width=250 ) color_scale = alt.Scale(domain=['Male', 'Female'], range=['#1f77b4', '#e377c2']) left = base.transform_filter( alt.datum.gender == 'Female' ).encode( alt.Y('age:O').axis(None), alt.X('sum(people):Q') .title('population') .sort('descending'), alt.Color('gender:N') .scale(color_scale) .legend(None) ).mark_bar().properties(title='Female') middle = base.encode( alt.Y('age:O').axis(None), alt.Text('age:Q'), ).mark_text().properties(width=20) right = base.transform_filter( alt.datum.gender == 'Male' ).encode( alt.Y('age:O').axis(None), alt.X('sum(people):Q').title('population'), alt.Color('gender:N').scale(color_scale).legend(None) ).mark_bar().properties(title='Male') alt.concat(left, middle, right, spacing=5) altair-5.0.1/tests/examples_methods_syntax/us_state_capitals.py000066400000000000000000000021071443422213100251570ustar00rootroot00000000000000""" U.S. State Capitals Overlayed on a Map of the U.S ------------------------------------------------- This is a layered geographic visualization that shows US capitals overlayed on a map. """ # category: case studies import altair as alt from vega_datasets import data states = alt.topo_feature(data.us_10m.url, 'states') capitals = data.us_state_capitals.url # US states background background = alt.Chart(states).mark_geoshape( fill='lightgray', stroke='white' ).properties( title='US State Capitols', width=650, height=400 ).project('albersUsa') # Points and text hover = alt.selection_point(on='mouseover', nearest=True, fields=['lat', 'lon']) base = alt.Chart(capitals).encode( longitude='lon:Q', latitude='lat:Q', ) text = base.mark_text(dy=-5, align='right').encode( alt.Text('city:N'), opacity=alt.condition(~hover, alt.value(0), alt.value(1)) ) points = base.mark_point().encode( color=alt.value('black'), size=alt.condition(~hover, alt.value(30), alt.value(100)) ).add_params(hover) background + points + text altair-5.0.1/tests/examples_methods_syntax/violin_plot.py000066400000000000000000000013771443422213100240160ustar00rootroot00000000000000""" Violin Plot ----------- This example shows how to make a Violin Plot using Altair's density transform. """ # category: distributions import altair as alt from vega_datasets import data alt.Chart(data.cars(), width=100).transform_density( 'Miles_per_Gallon', as_=['Miles_per_Gallon', 'density'], extent=[5, 50], groupby=['Origin'] ).mark_area(orient='horizontal').encode( alt.X('density:Q') .stack('center') .impute(None) .title(None) .axis(labels=False, values=[0], grid=False, ticks=True), alt.Y('Miles_per_Gallon:Q'), alt.Color('Origin:N'), alt.Column('Origin:N') .spacing(0) .header(titleOrient='bottom', labelOrient='bottom', labelPadding=0) ).configure_view( stroke=None ) altair-5.0.1/tests/examples_methods_syntax/wheat_wages.py000066400000000000000000000033711443422213100237520ustar00rootroot00000000000000""" Wheat and Wages --------------- A recreation of William Playfair's classic chart visualizing the price of wheat, the wages of a mechanic, and the reigning British monarch. This is a more polished version of the simpler chart in :ref:`gallery_bar_and_line_with_dual_axis`. """ # category: case studies import altair as alt from vega_datasets import data base_wheat = alt.Chart(data.wheat.url).transform_calculate( year_end="+datum.year + 5") base_monarchs = alt.Chart(data.monarchs.url).transform_calculate( offset="((!datum.commonwealth && datum.index % 2) ? -1: 1) * 2 + 95", off2="((!datum.commonwealth && datum.index % 2) ? -1: 1) + 95", y="95", x="+datum.start + (+datum.end - +datum.start)/2" ) bars = base_wheat.mark_bar(**{"fill": "#aaa", "stroke": "#999"}).encode( alt.X("year:Q").axis(format='d', tickCount=5), alt.Y("wheat:Q").axis(zindex=1), alt.X2("year_end") ) area = base_wheat.mark_area(**{"color": "#a4cedb", "opacity": 0.7}).encode( alt.X("year:Q"), alt.Y("wages:Q") ) area_line_1 = area.mark_line(**{"color": "#000", "opacity": 0.7}) area_line_2 = area.mark_line(**{"yOffset": -2, "color": "#EE8182"}) top_bars = base_monarchs.mark_bar(stroke="#000").encode( alt.X("start:Q"), alt.X2("end"), alt.Y("y:Q"), alt.Y2("offset"), alt.Fill("commonwealth:N").legend(None).scale(range=["black", "white"]) ) top_text = base_monarchs.mark_text(**{"yOffset": 14, "fontSize": 9, "fontStyle": "italic"}).encode( alt.X("x:Q"), alt.Y("off2:Q"), alt.Text("name:N") ) (bars + area + area_line_1 + area_line_2 + top_bars + top_text).properties( width=900, height=400 ).configure_axis( title=None, gridColor="white", gridOpacity=0.25, domain=False ).configure_view( stroke="transparent" ) altair-5.0.1/tests/examples_methods_syntax/wilkinson-dot-plot.py000066400000000000000000000010271443422213100252250ustar00rootroot00000000000000""" Wilkinson Dot Plot ------------------ An example of a `Wilkinson Dot Plot `_ """ # category: advanced calculations import altair as alt import pandas as pd source = pd.DataFrame( {"data":[1,1,1,1,1,1,1,1,1,1, 2,2,2, 3,3, 4,4,4,4,4,4] } ) alt.Chart(source, height=100).mark_circle(opacity=1).transform_window( id='rank()', groupby=['data'] ).encode( alt.X('data:O'), alt.Y('id:O').axis(None).sort('descending') ) altair-5.0.1/tests/examples_methods_syntax/wind_vector_map.py000066400000000000000000000031251443422213100246310ustar00rootroot00000000000000""" Wind Vector Map --------------- An example showing a vector array map showing wind speed and direction using ``wedge`` as shape for ``mark_point`` and ``angle`` encoding for the wind direction. This is adapted from this corresponding Vega-Lite Example: `Wind Vector Map `_ with an added base map. """ # category: maps import altair as alt from vega_datasets import data df_wind = data.windvectors() data_world = alt.topo_feature(data.world_110m.url, "countries") wedge = alt.Chart(df_wind).mark_point(shape="wedge", filled=True).encode( alt.Latitude("latitude"), alt.Longitude("longitude"), alt.Color("dir") .scale(domain=[0, 360], scheme="rainbow") .legend(None), alt.Angle("dir").scale(domain=[0, 360], range=[180, 540]), alt.Size("speed").scale(rangeMax=500) ).project("equalEarth") xmin, xmax, ymin, ymax = ( df_wind.longitude.min(), df_wind.longitude.max(), df_wind.latitude.min(), df_wind.latitude.max(), ) # extent as feature or featurecollection extent = { "type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[ [xmax, ymax], [xmax, ymin], [xmin, ymin], [xmin, ymax], [xmax, ymax]]] }, "properties": {} } # use fit combined with clip=True base = ( alt.Chart(data_world) .mark_geoshape(clip=True, fill="lightgray", stroke="black", strokeWidth=0.5) .project(type="equalEarth", fit=extent) ) base + wedge altair-5.0.1/tests/examples_methods_syntax/window_rank.py000066400000000000000000000031711443422213100237740ustar00rootroot00000000000000""" Window Rank Line Chart ---------------------- This example shows the Group F rankings in the 2018 World Cup after each matchday. A window transformation is used to rank each after each match day, sorting by points and difference. """ # category: line charts import altair as alt import pandas as pd source = pd.DataFrame( [ {"team": "Germany", "matchday": 1, "point": 0, "diff": -1}, {"team": "Germany", "matchday": 2, "point": 3, "diff": 0}, {"team": "Germany", "matchday": 3, "point": 3, "diff": -2}, {"team": "Mexico", "matchday": 1, "point": 3, "diff": 1}, {"team": "Mexico", "matchday": 2, "point": 6, "diff": 2}, {"team": "Mexico", "matchday": 3, "point": 6, "diff": -1}, {"team": "South Korea", "matchday": 1, "point": 0, "diff": -1}, {"team": "South Korea", "matchday": 2, "point": 0, "diff": -2}, {"team": "South Korea", "matchday": 3, "point": 3, "diff": 0}, {"team": "Sweden", "matchday": 1, "point": 3, "diff": 1}, {"team": "Sweden", "matchday": 2, "point": 3, "diff": 0}, {"team": "Sweden", "matchday": 3, "point": 6, "diff": 3}, ] ) color_scale = alt.Scale( domain=["Germany", "Mexico", "South Korea", "Sweden"], range=["#000000", "#127153", "#C91A3C", "#0C71AB"], ) alt.Chart(source).mark_line().encode( x="matchday:O", y="rank:O", color=alt.Color("team:N").scale(color_scale) ).transform_window( rank="rank()", sort=[ alt.SortField("point", order="descending"), alt.SortField("diff", order="descending"), ], groupby=["matchday"], ).properties(title="World Cup 2018: Group F Rankings") altair-5.0.1/tests/expr/000077500000000000000000000000001443422213100151055ustar00rootroot00000000000000altair-5.0.1/tests/expr/__init__.py000066400000000000000000000000001443422213100172040ustar00rootroot00000000000000altair-5.0.1/tests/expr/test_expr.py000066400000000000000000000071411443422213100174770ustar00rootroot00000000000000import operator import pytest from altair import expr from altair import datum from altair import ExprRef from jsonschema.exceptions import ValidationError def test_unary_operations(): OP_MAP = {"-": operator.neg, "+": operator.pos} for op, func in OP_MAP.items(): z = func(datum.xxx) assert repr(z) == "({}datum.xxx)".format(op) def test_binary_operations(): OP_MAP = { "+": operator.add, "-": operator.sub, "*": operator.mul, "/": operator.truediv, "%": operator.mod, "===": operator.eq, "<": operator.lt, "<=": operator.le, ">": operator.gt, ">=": operator.ge, "!==": operator.ne, "&&": operator.and_, "||": operator.or_, } # When these are on the RHS, the opposite is evaluated instead. INEQ_REVERSE = { ">": "<", "<": ">", "<=": ">=", ">=": "<=", "===": "===", "!==": "!==", } for op, func in OP_MAP.items(): z1 = func(datum.xxx, 2) assert repr(z1) == "(datum.xxx {} 2)".format(op) z2 = func(2, datum.xxx) if op in INEQ_REVERSE: assert repr(z2) == "(datum.xxx {} 2)".format(INEQ_REVERSE[op]) else: assert repr(z2) == "(2 {} datum.xxx)".format(op) z3 = func(datum.xxx, datum.yyy) assert repr(z3) == "(datum.xxx {} datum.yyy)".format(op) def test_abs(): z = abs(datum.xxx) assert repr(z) == "abs(datum.xxx)" def test_expr_funcs(): """test all functions defined in expr.funcs""" name_map = {val: key for key, val in expr.funcs.NAME_MAP.items()} for funcname in expr.funcs.__all__: func = getattr(expr, funcname) z = func(datum.xxx) assert repr(z) == "{}(datum.xxx)".format(name_map.get(funcname, funcname)) def test_expr_consts(): """Test all constants defined in expr.consts""" name_map = {val: key for key, val in expr.consts.NAME_MAP.items()} for constname in expr.consts.__all__: const = getattr(expr, constname) z = const * datum.xxx assert repr(z) == "({} * datum.xxx)".format(name_map.get(constname, constname)) def test_json_reprs(): """Test JSON representations of special values""" assert repr(datum.xxx == None) == "(datum.xxx === null)" # noqa: E711 assert repr(datum.xxx == False) == "(datum.xxx === false)" # noqa: E712 assert repr(datum.xxx == True) == "(datum.xxx === true)" # noqa: E712 def test_to_dict(): ex = datum.xxx * 2 > datum.yyy assert ex.to_dict() == repr(ex) def test_copy(): ex = datum.xxx * 2 > abs(datum.yyy) ex_copy = ex.copy() assert ex.to_dict() == ex_copy.to_dict() def test_datum_getattr(): x = datum["foo"] assert repr(x) == "datum['foo']" magic_attr = "__magic__" with pytest.raises(AttributeError): getattr(datum, magic_attr) def test_expression_getitem(): x = datum.foo[0] assert repr(x) == "datum.foo[0]" def test_expression_function_expr(): # test including a expr. should return an ExprRef er = expr(expr.PI * 2) assert isinstance(er, ExprRef) assert repr(er) == "ExprRef({\n expr: (PI * 2)\n})" def test_expression_function_string(): # expr() can only work with str er = expr("2 * 2") assert isinstance(er, ExprRef) assert repr(er) == "ExprRef({\n expr: '2 * 2'\n})" def test_expression_function_nostring(): # expr() can only work with str otherwise # should raise a SchemaValidationError with pytest.raises(ValidationError): expr(2 * 2) with pytest.raises(ValidationError): expr(["foo", "bah"]) altair-5.0.1/tests/test_examples.py000066400000000000000000000077051443422213100173670ustar00rootroot00000000000000import io import pkgutil import pytest import altair as alt from altair.utils.execeval import eval_block from tests import examples_arguments_syntax from tests import examples_methods_syntax try: import altair_saver # noqa: F401 except ImportError: altair_saver = None try: import vl_convert as vlc # noqa: F401 except ImportError: vlc = None def iter_examples_filenames(syntax_module): for _importer, modname, ispkg in pkgutil.iter_modules(syntax_module.__path__): if ispkg or modname.startswith("_"): continue yield modname + ".py" @pytest.mark.parametrize( "syntax_module", [examples_arguments_syntax, examples_methods_syntax] ) def test_render_examples_to_chart(syntax_module): for filename in iter_examples_filenames(syntax_module): source = pkgutil.get_data(syntax_module.__name__, filename) chart = eval_block(source) if chart is None: raise ValueError( f"Example file {filename} should define chart in its final " "statement." ) try: assert isinstance(chart.to_dict(), dict) except Exception as err: raise AssertionError( f"Example file {filename} raised an exception when " f"converting to a dict: {err}" ) from err @pytest.mark.parametrize( "syntax_module", [examples_arguments_syntax, examples_methods_syntax] ) def test_from_and_to_json_roundtrip(syntax_module): """Tests if the to_json and from_json (and by extension to_dict and from_dict) work for all examples in the Example Gallery. """ for filename in iter_examples_filenames(syntax_module): source = pkgutil.get_data(syntax_module.__name__, filename) chart = eval_block(source) if chart is None: raise ValueError( f"Example file {filename} should define chart in its final " "statement." ) try: first_json = chart.to_json() reconstructed_chart = alt.Chart.from_json(first_json) # As the chart objects are not # necessarily the same - they could use different objects to encode the same # information - we do not test for equality of the chart objects, but rather # for equality of the json strings. second_json = reconstructed_chart.to_json() assert first_json == second_json except Exception as err: raise AssertionError( f"Example file {filename} raised an exception when " f"doing a json conversion roundtrip: {err}" ) from err # We do not apply the save_engine mark to this test. This mark is used in # the build GitHub Action workflow to select the tests which should be rerun # with some of the saving engines uninstalled. This would not make sense for this test # as here it is only interesting to run it with all saving engines installed. # Furthermore, the runtime of this test is rather long. @pytest.mark.parametrize("engine", ["vl-convert", "altair_saver"]) @pytest.mark.parametrize( "syntax_module", [examples_arguments_syntax, examples_methods_syntax] ) def test_render_examples_to_png(engine, syntax_module): for filename in iter_examples_filenames(syntax_module): if engine == "vl-convert" and vlc is None: pytest.skip("vl_convert not importable; cannot run mimebundle tests") elif engine == "altair_saver": if altair_saver is None: pytest.skip("altair_saver not importable; cannot run png tests") if "png" not in altair_saver.available_formats("vega-lite"): pytest.skip("altair_saver not configured to save to png") source = pkgutil.get_data(syntax_module.__name__, filename) chart = eval_block(source) out = io.BytesIO() chart.save(out, format="png", engine=engine) assert out.getvalue().startswith(b"\x89PNG") altair-5.0.1/tests/test_magics.py000066400000000000000000000040221443422213100170010ustar00rootroot00000000000000import json import pytest try: from IPython import InteractiveShell IPYTHON_AVAILABLE = True except ImportError: IPYTHON_AVAILABLE = False pass from altair.vegalite.v5 import VegaLite DATA_RECORDS = [ {"amount": 28, "category": "A"}, {"amount": 55, "category": "B"}, {"amount": 43, "category": "C"}, {"amount": 91, "category": "D"}, {"amount": 81, "category": "E"}, {"amount": 53, "category": "F"}, {"amount": 19, "category": "G"}, {"amount": 87, "category": "H"}, ] if IPYTHON_AVAILABLE: _ipshell = InteractiveShell.instance() _ipshell.run_cell("%load_ext altair") _ipshell.run_cell( """ import pandas as pd table = pd.DataFrame.from_records({}) the_data = table """.format( DATA_RECORDS ) ) VEGALITE_SPEC = { "$schema": "https://vega.github.io/schema/vega-lite/v5.json", "data": {"values": DATA_RECORDS}, "description": "A simple bar chart with embedded data.", "encoding": { "x": {"field": "category", "type": "ordinal"}, "y": {"field": "amount", "type": "quantitative"}, }, "mark": {"type": "bar"}, } @pytest.mark.skipif(not IPYTHON_AVAILABLE, reason="requires ipython") def test_vegalite_magic_data_included(): result = _ipshell.run_cell("%%vegalite\n" + json.dumps(VEGALITE_SPEC)) assert isinstance(result.result, VegaLite) assert VEGALITE_SPEC == result.result.spec @pytest.mark.skipif(not IPYTHON_AVAILABLE, reason="requires ipython") def test_vegalite_magic_json_flag(): result = _ipshell.run_cell("%%vegalite --json\n" + json.dumps(VEGALITE_SPEC)) assert isinstance(result.result, VegaLite) assert VEGALITE_SPEC == result.result.spec @pytest.mark.skipif(not IPYTHON_AVAILABLE, reason="requires ipython") def test_vegalite_magic_pandas_data(): spec = {key: val for key, val in VEGALITE_SPEC.items() if key != "data"} result = _ipshell.run_cell("%%vegalite table\n" + json.dumps(spec)) assert isinstance(result.result, VegaLite) assert VEGALITE_SPEC == result.result.spec altair-5.0.1/tests/test_toplevel.py000066400000000000000000000012111443422213100173650ustar00rootroot00000000000000import sys from os.path import abspath, join, dirname import altair as alt current_dir = dirname(__file__) sys.path.insert(0, abspath(join(current_dir, ".."))) from tools import update_init_file # noqa: E402 def test_completeness_of__all__(): relevant_attributes = [ x for x in alt.__dict__ if update_init_file._is_relevant_attribute(x) ] relevant_attributes.sort() # If the assert statement fails below, there are probably either new objects # in the top-level Altair namespace or some were removed. # In that case, run tools/update_init_file.py to update __all__ assert alt.__all__ == relevant_attributes altair-5.0.1/tests/utils/000077500000000000000000000000001443422213100152675ustar00rootroot00000000000000altair-5.0.1/tests/utils/__init__.py000066400000000000000000000000001443422213100173660ustar00rootroot00000000000000altair-5.0.1/tests/utils/test_core.py000066400000000000000000000213351443422213100176340ustar00rootroot00000000000000import types import numpy as np import pandas as pd import pytest import altair as alt from altair.utils.core import parse_shorthand, update_nested, infer_encoding_types from altair.utils.core import infer_dtype FAKE_CHANNELS_MODULE = ''' """Fake channels module for utility tests.""" from altair.utils import schemapi class FieldChannel: def __init__(self, shorthand, **kwargs): kwargs['shorthand'] = shorthand return super(FieldChannel, self).__init__(**kwargs) class ValueChannel: def __init__(self, value, **kwargs): kwargs['value'] = value return super(ValueChannel, self).__init__(**kwargs) class X(FieldChannel, schemapi.SchemaBase): _schema = {} _encoding_name = "x" class XValue(ValueChannel, schemapi.SchemaBase): _schema = {} _encoding_name = "x" class Y(FieldChannel, schemapi.SchemaBase): _schema = {} _encoding_name = "y" class YValue(ValueChannel, schemapi.SchemaBase): _schema = {} _encoding_name = "y" class StrokeWidth(FieldChannel, schemapi.SchemaBase): _schema = {} _encoding_name = "strokeWidth" class StrokeWidthValue(ValueChannel, schemapi.SchemaBase): _schema = {} _encoding_name = "strokeWidth" ''' @pytest.mark.parametrize( "value,expected_type", [ ([1, 2, 3], "integer"), ([1.0, 2.0, 3.0], "floating"), ([1, 2.0, 3], "mixed-integer-float"), (["a", "b", "c"], "string"), (["a", "b", np.nan], "mixed"), ], ) def test_infer_dtype(value, expected_type): assert infer_dtype(value) == expected_type def test_parse_shorthand(): def check(s, **kwargs): assert parse_shorthand(s) == kwargs check("") # Fields alone check("foobar", field="foobar") check(r"blah\:(fd ", field=r"blah\:(fd ") # Fields with type check("foobar:quantitative", type="quantitative", field="foobar") check("foobar:nominal", type="nominal", field="foobar") check("foobar:ordinal", type="ordinal", field="foobar") check("foobar:temporal", type="temporal", field="foobar") check("foobar:geojson", type="geojson", field="foobar") check("foobar:Q", type="quantitative", field="foobar") check("foobar:N", type="nominal", field="foobar") check("foobar:O", type="ordinal", field="foobar") check("foobar:T", type="temporal", field="foobar") check("foobar:G", type="geojson", field="foobar") # Fields with aggregate and/or type check("average(foobar)", field="foobar", aggregate="average") check("min(foobar):temporal", type="temporal", field="foobar", aggregate="min") check("sum(foobar):Q", type="quantitative", field="foobar", aggregate="sum") # check that invalid arguments are not split-out check("invalid(blah)", field="invalid(blah)") check(r"blah\:invalid", field=r"blah\:invalid") check(r"invalid(blah)\:invalid", field=r"invalid(blah)\:invalid") # check parsing in presence of strange characters check( r"average(a b\:(c\nd):Q", aggregate="average", field=r"a b\:(c\nd", type="quantitative", ) # special case: count doesn't need an argument check("count()", aggregate="count", type="quantitative") check("count():O", aggregate="count", type="ordinal") # time units: check("month(x)", field="x", timeUnit="month", type="temporal") check("year(foo):O", field="foo", timeUnit="year", type="ordinal") check("date(date):quantitative", field="date", timeUnit="date", type="quantitative") check( "yearmonthdate(field)", field="field", timeUnit="yearmonthdate", type="temporal" ) def test_parse_shorthand_with_data(): def check(s, data, **kwargs): assert parse_shorthand(s, data) == kwargs data = pd.DataFrame( { "x": [1, 2, 3, 4, 5], "y": ["A", "B", "C", "D", "E"], "z": pd.date_range("2018-01-01", periods=5, freq="D"), "t": pd.date_range("2018-01-01", periods=5, freq="D").tz_localize("UTC"), } ) check("x", data, field="x", type="quantitative") check("y", data, field="y", type="nominal") check("z", data, field="z", type="temporal") check("t", data, field="t", type="temporal") check("count(x)", data, field="x", aggregate="count", type="quantitative") check("count()", data, aggregate="count", type="quantitative") check("month(z)", data, timeUnit="month", field="z", type="temporal") check("month(t)", data, timeUnit="month", field="t", type="temporal") def test_parse_shorthand_all_aggregates(): aggregates = alt.Root._schema["definitions"]["AggregateOp"]["enum"] for aggregate in aggregates: shorthand = "{aggregate}(field):Q".format(aggregate=aggregate) assert parse_shorthand(shorthand) == { "aggregate": aggregate, "field": "field", "type": "quantitative", } def test_parse_shorthand_all_timeunits(): timeUnits = [] for loc in ["Local", "Utc"]: for typ in ["Single", "Multi"]: defn = loc + typ + "TimeUnit" timeUnits.extend(alt.Root._schema["definitions"][defn]["enum"]) for timeUnit in timeUnits: shorthand = "{timeUnit}(field):Q".format(timeUnit=timeUnit) assert parse_shorthand(shorthand) == { "timeUnit": timeUnit, "field": "field", "type": "quantitative", } def test_parse_shorthand_window_count(): shorthand = "count()" dct = parse_shorthand( shorthand, parse_aggregates=False, parse_window_ops=True, parse_timeunits=False, parse_types=False, ) assert dct == {"op": "count"} def test_parse_shorthand_all_window_ops(): window_ops = alt.Root._schema["definitions"]["WindowOnlyOp"]["enum"] aggregates = alt.Root._schema["definitions"]["AggregateOp"]["enum"] for op in window_ops + aggregates: shorthand = "{op}(field)".format(op=op) dct = parse_shorthand( shorthand, parse_aggregates=False, parse_window_ops=True, parse_timeunits=False, parse_types=False, ) assert dct == {"field": "field", "op": op} def test_update_nested(): original = {"x": {"b": {"foo": 2}, "c": 4}} update = {"x": {"b": {"foo": 5}, "d": 6}, "y": 40} output = update_nested(original, update, copy=True) assert output is not original assert output == {"x": {"b": {"foo": 5}, "c": 4, "d": 6}, "y": 40} output2 = update_nested(original, update) assert output2 is original assert output == output2 @pytest.fixture def channels(): channels = types.ModuleType("channels") exec(FAKE_CHANNELS_MODULE, channels.__dict__) return channels def _getargs(*args, **kwargs): return args, kwargs def test_infer_encoding_types(channels): expected = { "x": channels.X("xval"), "y": channels.YValue("yval"), "strokeWidth": channels.StrokeWidthValue(value=4), } # All positional args args, kwds = _getargs( channels.X("xval"), channels.YValue("yval"), channels.StrokeWidthValue(4) ) assert infer_encoding_types(args, kwds, channels) == expected # All keyword args args, kwds = _getargs(x="xval", y=alt.value("yval"), strokeWidth=alt.value(4)) assert infer_encoding_types(args, kwds, channels) == expected # Mixed positional & keyword args, kwds = _getargs( channels.X("xval"), channels.YValue("yval"), strokeWidth=alt.value(4) ) assert infer_encoding_types(args, kwds, channels) == expected def test_infer_encoding_types_with_condition(): channels = alt.channels args, kwds = _getargs( size=alt.condition("pred1", alt.value(1), alt.value(2)), color=alt.condition("pred2", alt.value("red"), "cfield:N"), opacity=alt.condition("pred3", "ofield:N", alt.value(0.2)), ) expected = { "size": channels.SizeValue( 2, condition=alt.ConditionalPredicateValueDefnumberExprRef( value=1, test=alt.Predicate("pred1") ), ), "color": channels.Color( "cfield:N", condition=alt.ConditionalPredicateValueDefGradientstringnullExprRef( value="red", test=alt.Predicate("pred2") ), ), "opacity": channels.OpacityValue( 0.2, condition=alt.ConditionalPredicateMarkPropFieldOrDatumDef( field=alt.FieldName("ofield"), test=alt.Predicate("pred3"), type=alt.StandardType("nominal"), ), ), } assert infer_encoding_types(args, kwds, channels) == expected def test_invalid_data_type(): with pytest.raises( ValueError, match=r'"\(fd " is not one of the valid encoding data types' ): parse_shorthand(r"blah:(fd ") altair-5.0.1/tests/utils/test_data.py000066400000000000000000000074751443422213100176260ustar00rootroot00000000000000import os import pytest import pandas as pd from toolz import pipe from altair.utils.data import ( limit_rows, MaxRowsError, sample, to_values, to_json, to_csv, ) def _create_dataframe(N): data = pd.DataFrame({"x": range(N), "y": range(N)}) return data def _create_data_with_values(N): data = {"values": [{"x": i, "y": i + 1} for i in range(N)]} return data def test_limit_rows(): """Test the limit_rows data transformer.""" data = _create_dataframe(10) result = limit_rows(data, max_rows=20) assert data is result with pytest.raises(MaxRowsError): pipe(data, limit_rows(max_rows=5)) data = _create_data_with_values(10) result = pipe(data, limit_rows(max_rows=20)) assert data is result with pytest.raises(MaxRowsError): limit_rows(data, max_rows=5) def test_sample(): """Test the sample data transformer.""" data = _create_dataframe(20) result = pipe(data, sample(n=10)) assert len(result) == 10 assert isinstance(result, pd.DataFrame) data = _create_data_with_values(20) result = sample(data, n=10) assert isinstance(result, dict) assert "values" in result assert len(result["values"]) == 10 data = _create_dataframe(20) result = pipe(data, sample(frac=0.5)) assert len(result) == 10 assert isinstance(result, pd.DataFrame) data = _create_data_with_values(20) result = sample(data, frac=0.5) assert isinstance(result, dict) assert "values" in result assert len(result["values"]) == 10 def test_to_values(): """Test the to_values data transformer.""" data = _create_dataframe(10) result = pipe(data, to_values) assert result == {"values": data.to_dict(orient="records")} def test_type_error(): """Ensure that TypeError is raised for types other than dict/DataFrame.""" for f in (sample, limit_rows, to_values): with pytest.raises(TypeError): pipe(0, f) def test_dataframe_to_json(): """Test to_json - make certain the filename is deterministic - make certain the file contents match the data """ data = _create_dataframe(10) try: result1 = pipe(data, to_json) result2 = pipe(data, to_json) filename = result1["url"] output = pd.read_json(filename) finally: os.remove(filename) assert result1 == result2 assert output.equals(data) def test_dict_to_json(): """Test to_json - make certain the filename is deterministic - make certain the file contents match the data """ data = _create_data_with_values(10) try: result1 = pipe(data, to_json) result2 = pipe(data, to_json) filename = result1["url"] output = pd.read_json(filename).to_dict(orient="records") finally: os.remove(filename) assert result1 == result2 assert data == {"values": output} def test_dataframe_to_csv(): """Test to_csv with dataframe input - make certain the filename is deterministic - make certain the file contents match the data """ data = _create_dataframe(10) try: result1 = pipe(data, to_csv) result2 = pipe(data, to_csv) filename = result1["url"] output = pd.read_csv(filename) finally: os.remove(filename) assert result1 == result2 assert output.equals(data) def test_dict_to_csv(): """Test to_csv with dict input - make certain the filename is deterministic - make certain the file contents match the data """ data = _create_data_with_values(10) try: result1 = pipe(data, to_csv) result2 = pipe(data, to_csv) filename = result1["url"] output = pd.read_csv(filename).to_dict(orient="records") finally: os.remove(filename) assert result1 == result2 assert data == {"values": output} altair-5.0.1/tests/utils/test_deprecation.py000066400000000000000000000012621443422213100211760ustar00rootroot00000000000000import pytest import altair as alt from altair.utils import AltairDeprecationWarning from altair.utils.deprecation import _deprecate, deprecated def test_deprecated_class(): OldChart = _deprecate(alt.Chart, "OldChart") with pytest.warns(AltairDeprecationWarning) as record: OldChart() assert "alt.OldChart" in record[0].message.args[0] assert "alt.Chart" in record[0].message.args[0] def test_deprecation_decorator(): @deprecated(message="func is deprecated") def func(x): return x + 1 with pytest.warns(AltairDeprecationWarning) as record: y = func(1) assert y == 2 assert record[0].message.args[0] == "func is deprecated" altair-5.0.1/tests/utils/test_execeval.py000066400000000000000000000010351443422213100204730ustar00rootroot00000000000000from altair.utils.execeval import eval_block HAS_RETURN = """ x = 4 y = 2 * x 3 * y """ NO_RETURN = """ x = 4 y = 2 * x z = 3 * y """ def test_eval_block_with_return(): _globals = {} result = eval_block(HAS_RETURN, _globals) assert result == 24 assert _globals["x"] == 4 assert _globals["y"] == 8 def test_eval_block_without_return(): _globals = {} result = eval_block(NO_RETURN, _globals) assert result is None assert _globals["x"] == 4 assert _globals["y"] == 8 assert _globals["z"] == 24 altair-5.0.1/tests/utils/test_html.py000066400000000000000000000026641443422213100176540ustar00rootroot00000000000000import pytest from altair.utils.html import spec_to_html @pytest.fixture def spec(): return { "data": {"url": "data.json"}, "mark": {"type": "point"}, "encoding": { "x": {"field": "x", "type": "quantitative"}, "y": {"field": "y", "type": "quantitative"}, }, } @pytest.mark.parametrize("requirejs", [True, False]) @pytest.mark.parametrize("fullhtml", [True, False]) def test_spec_to_html(requirejs, fullhtml, spec): # We can't test that the html actually renders, but we'll test aspects of # it to make certain that the keywords are respected. vegaembed_version = ("3.12",) vegalite_version = ("3.0",) vega_version = "4.0" html = spec_to_html( spec, mode="vega-lite", requirejs=requirejs, fullhtml=fullhtml, vegalite_version=vegalite_version, vegaembed_version=vegaembed_version, vega_version=vega_version, ) html = html.strip() if fullhtml: assert html.startswith("") assert html.endswith("") else: assert html.startswith("