├── requirements.txt ├── requirements-dev.txt ├── docs └── index.rst ├── branca ├── plugins │ └── __init__.py ├── __init__.py ├── scheme_base_codes.json ├── six.py ├── scheme_info.json ├── templates │ └── color_scale.js ├── _cnames.json ├── utilities.py ├── colormap.py ├── _schemes.json └── element.py ├── MANIFEST.in ├── tests ├── test_utilities.py ├── test_iframe.py ├── test_notebooks.py └── test_colormap.py ├── CHANGES.txt ├── .gitignore ├── README.rst ├── LICENSE.txt ├── .travis.yml ├── setup.py └── examples └── Elements.ipynb /requirements.txt: -------------------------------------------------------------------------------- 1 | Jinja2 2 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | flake8 2 | pytest 3 | selenium 4 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Branca 2 | ###### 3 | 4 | TODO : Doc 5 | 6 | -------------------------------------------------------------------------------- /branca/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Branca plugins 4 | -------------- 5 | 6 | Add different objects/effects in a branca webpage. 7 | """ 8 | 9 | __all__ = [] 10 | -------------------------------------------------------------------------------- /branca/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import 4 | 5 | import branca.colormap as colormap 6 | import branca.element as element 7 | 8 | __version__ = '0.2.0' 9 | 10 | __all__ = [ 11 | 'colormap', 12 | 'element', 13 | ] 14 | -------------------------------------------------------------------------------- /branca/scheme_base_codes.json: -------------------------------------------------------------------------------- 1 | {"codes": ["Spectral", "RdYlGn", "PuBu", "Accent", "OrRd", "Set1", "Set2", "Set3", "BuPu", "Dark2", "RdBu", "Oranges", "BuGn", "PiYG", "YlOrBr", "YlGn", "Pastel2", "RdPu", "Greens", "PRGn", "YlGnBu", "RdYlBu", "Paired", "BrBG", "Purples", "Reds", "Pastel1", "GnBu", "Greys", "RdGy", "YlOrRd", "PuOr", "PuRd", "Blues", "PuBuGn"]} -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | include README.rst 3 | include branca/_cnames.json 4 | include branca/_schemes.json 5 | include branca/scheme_info.json 6 | include branca/scheme_base_codes.json 7 | 8 | recursive-include branca *.py 9 | recursive-include branca *.js 10 | recursive-include branca/plugins * 11 | recursive-include branca/templates * 12 | -------------------------------------------------------------------------------- /tests/test_utilities.py: -------------------------------------------------------------------------------- 1 | import branca.utilities as ut 2 | 3 | 4 | def test_color_brewer_base(): 5 | scheme = ut.color_brewer('YlGnBu', 9) 6 | assert scheme == [ 7 | '#ffffd9', '#edf8b1', '#c7e9b4', 8 | '#7fcdbb', '#41b6c4', '#1d91c0', 9 | '#225ea8', '#253494', '#081d58' 10 | ] 11 | 12 | 13 | def test_color_brewer_reverse(): 14 | scheme = ut.color_brewer('YlGnBu') 15 | scheme_r = ut.color_brewer('YlGnBu_r') 16 | assert scheme[::-1] == scheme_r 17 | -------------------------------------------------------------------------------- /branca/six.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | PY3 = sys.version_info[0] == 3 4 | 5 | if PY3: 6 | text_type = str 7 | binary_type = bytes 8 | else: 9 | text_type = unicode # noqa 10 | binary_type = str 11 | 12 | if PY3: 13 | def iteritems(d, **kw): 14 | return iter(d.items(**kw)) 15 | else: 16 | def iteritems(d, **kw): 17 | return iter(d.iteritems(**kw)) 18 | 19 | if PY3: 20 | import urllib.request 21 | urlopen = urllib.request.urlopen 22 | else: 23 | import urllib 24 | urlopen = urllib.urlopen 25 | -------------------------------------------------------------------------------- /branca/scheme_info.json: -------------------------------------------------------------------------------- 1 | {"Spectral": "Diverging", "RdYlGn": "Diverging", "Set2": "Qualitative", "Accent": "Qualitative", "OrRd": "Sequential", "Set1": "Qualitative", "PuBu": "Sequential", "Set3": "Qualitative", "BuPu": "Sequential", "Dark2": "Qualitative", "RdBu": "Diverging", "BuGn": "Sequential", "PiYG": "Diverging", "YlOrBr": "Sequential", "YlGn": "Sequential", "RdPu": "Sequential", "PRGn": "Diverging", "YlGnBu": "Sequential", "RdYlBu": "Diverging", "Paired": "Qualitative", "Pastel2": "Qualitative", "Pastel1": "Qualitative", "GnBu": "Sequential", "RdGy": "Diverging", "YlOrRd": "Sequential", "PuOr": "Diverging", "PuRd": "Sequential", "BrBg": "Diverging", "PuBuGn": "Sequential"} -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | - Added class for hosting step colormap (matsavage https://github.com/python-visualization/branca/pull/25) 2 | 3 | 0.2.0 4 | ~~~~~ 5 | - Correct rendering utf-8 IFrame (knil-sama https://github.com/python-visualization/branca/pull/18) 6 | - Remove embedded IFrame's border (deelaka https://github.com/python-visualization/branca/pull/17) 7 | - Let IFrame contents go fullscreen (sanga https://github.com/python-visualization/branca/pull/13) 8 | - Add HTML Popup Class to element.py (samchorlton https://github.com/python-visualization/branca/pull/6) 9 | 10 | 0.1.0 11 | ~~~~~ 12 | - Separate branca from folium (bibmartin d678357) 13 | - Enable HTML embedding in Html (samchorlton 90f6b13) 14 | -------------------------------------------------------------------------------- /tests/test_iframe.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """" 3 | Folium Element Module class IFrame 4 | ---------------------- 5 | """ 6 | import branca.element as elem 7 | from selenium import webdriver 8 | 9 | 10 | def test_create_empty_iframe(): 11 | iframe = elem.IFrame() 12 | iframe.render() 13 | 14 | 15 | def test_create_iframe(): 16 | iframe = elem.IFrame(html="

test content

", width=60, height=45) 17 | iframe.render() 18 | 19 | 20 | def test_rendering_utf8_iframe(): 21 | iframe = elem.IFrame(html=u"

Cerrahpaşa Tıp Fakültesi

") 22 | driver = webdriver.PhantomJS() 23 | driver.get("data:text/html," + iframe.render()) 24 | driver.switch_to.frame(0) 25 | assert u"Cerrahpaşa Tıp Fakültesi" in driver.page_source 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | include 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | 38 | #Mac 39 | *.DS_Store 40 | 41 | # IPython Notebook Checkpoints 42 | .ipynb_checkpoints 43 | 44 | #Virtualenv 45 | ENV 46 | .env 47 | 48 | # Tests products 49 | .cache 50 | data.png 51 | map.html 52 | examples/foo.html 53 | 54 | # documentation builds 55 | docs/_build 56 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://badge.fury.io/py/branca.png 2 | :target: http://badge.fury.io/py/branca 3 | .. image:: https://api.travis-ci.org/python-visualization/branca.png?branch=master 4 | :target: https://travis-ci.org/python-visualization/branca 5 | .. image:: https://badges.gitter.im/Join%20Chat.svg 6 | :target: https://gitter.im/python-visualization/folium?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 7 | 8 | Branca 9 | ====== 10 | 11 | This library is a spinoff from `folium`_, that would host the non-map-specific features. 12 | 13 | It may become a HTML+JS generation library in the future. 14 | 15 | It is based on Jinja2 only. 16 | 17 | There's no documentation, but you can browse the `examples`_ gallery. 18 | 19 | .. _`examples`: http://nbviewer.jupyter.org/github/python-visualization/branca/tree/master/examples 20 | .. _`folium`: https://github.com/python-visualization/folium 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013, Martin Journois 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | sudo: false 4 | 5 | matrix: 6 | fast_finish: true 7 | include: 8 | - python: 2.7 9 | env: TEST_TARGET=default 10 | - python: 3.5 11 | env: TEST_TARGET=default 12 | - python: 3.6 13 | env: TEST_TARGET=default 14 | - python: 3.6 15 | env: TEST_TARGET=coding_standards 16 | 17 | 18 | before_install: 19 | - wget http://bit.ly/miniconda -O miniconda.sh 20 | - bash miniconda.sh -b -p $HOME/miniconda 21 | - export PATH="$HOME/miniconda/bin:$PATH" 22 | - conda config --add channels conda-forge --force 23 | - conda update conda --yes 24 | - travis_retry conda create --yes -n TEST python=$TRAVIS_PYTHON_VERSION --file requirements.txt --file requirements-dev.txt 25 | - source activate TEST 26 | 27 | # Test source distribution. 28 | install: 29 | - python setup.py sdist && version=$(python setup.py --version) && pushd dist && pip install branca-${version}.tar.gz && popd 30 | 31 | script: 32 | - if [[ $TEST_TARGET == 'default' ]]; then 33 | python setup.py test ; 34 | fi 35 | - if [[ $TEST_TARGET == 'coding_standards' ]]; then 36 | find . -type f -name "*.py" ! -name 'conf.py' | xargs flake8 --max-line-length=100 ; 37 | fi 38 | -------------------------------------------------------------------------------- /tests/test_notebooks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Branca Notebooks Tests 4 | ---------------------- 5 | 6 | Here we try to execute all notebooks that are in `branca/examples`. 7 | """ 8 | 9 | import os 10 | import sys 11 | import branca.utilities 12 | 13 | if sys.version_info[:2] == (3, 4): 14 | import nbconvert 15 | 16 | rootpath = os.path.abspath(os.path.dirname(__file__)) 17 | 18 | class NotebookTester(object): 19 | def __init__(self, filename): 20 | self.filename = filename 21 | 22 | def __call__(self, exporter=None, filename=None): 23 | raw_nb = nbconvert.exporters.Exporter().from_filename(self.filename) 24 | raw_nb[0].metadata.setdefault('kernelspec', {})['name'] = 'python' 25 | exec_nb = nbconvert.preprocessors.ExecutePreprocessor().preprocess(*raw_nb) 26 | 27 | if exporter is not None: 28 | out_nb = nbconvert.exporters.MarkdownExporter().from_notebook_node(*exec_nb) 29 | if filename is None: 30 | assert self.filename.endswith('.ipynb') 31 | filename = self.filename[:-6] + exporter.file_extension 32 | open(filename, 'w').write(out_nb[0].encode('utf-8')) 33 | 34 | class TestNotebooks(object): 35 | _filepath = rootpath.rstrip('/')+'/../examples/' 36 | _nblist = [x for x in os.listdir(_filepath) if x.endswith('.ipynb')] 37 | 38 | for fn in TestNotebooks._nblist: 39 | setattr(TestNotebooks, 40 | 'test_'+branca.utilities._camelify(fn[:-6]), 41 | NotebookTester(TestNotebooks._filepath+fn).__call__ 42 | ) 43 | -------------------------------------------------------------------------------- /tests/test_colormap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """" 3 | Folium Colormap Module 4 | ---------------------- 5 | """ 6 | import branca.colormap as cm 7 | 8 | 9 | def test_simple_step(): 10 | step = cm.StepColormap(['green', 'yellow', 'red'], 11 | vmin=3., vmax=10., 12 | index=[3, 4, 8, 10], caption='step') 13 | step = cm.StepColormap(['r', 'y', 'g', 'c', 'b', 'm']) 14 | step._repr_html_() 15 | 16 | 17 | def test_simple_linear(): 18 | linear = cm.LinearColormap(['green', 'yellow', 'red'], vmin=3., vmax=10.) 19 | linear = cm.LinearColormap(['red', 'orange', 'yellow', 'green'], 20 | index=[0, 0.1, 0.9, 1.]) 21 | linear._repr_html_() 22 | 23 | 24 | def test_linear_to_step(): 25 | some_list = [30.6, 50, 51, 52, 53, 54, 55, 60, 70, 100] 26 | lc = cm.linear.YlOrRd_06 27 | lc.to_step(n=12) 28 | lc.to_step(index=[0, 2, 4, 6, 8, 10]) 29 | lc.to_step(data=some_list, n=12) 30 | lc.to_step(data=some_list, n=12, method='linear') 31 | lc.to_step(data=some_list, n=12, method='log') 32 | lc.to_step(data=some_list, n=30, method='quantiles') 33 | lc.to_step(data=some_list, quantiles=[0, 0.3, 0.7, 1]) 34 | lc.to_step(data=some_list, quantiles=[0, 0.3, 0.7, 1], round_method='int') 35 | lc.to_step(data=some_list, quantiles=[0, 0.3, 0.7, 1], 36 | round_method='log10') 37 | 38 | 39 | def test_step_to_linear(): 40 | step = cm.StepColormap(['green', 'yellow', 'red'], 41 | vmin=3., vmax=10., 42 | index=[3, 4, 8, 10], caption='step') 43 | step.to_linear() 44 | 45 | 46 | def test_linear_object(): 47 | cm.linear.OrRd_06._repr_html_() 48 | cm.linear.PuBu_06.to_step(12) 49 | cm.linear.YlGn_06.scale(3, 12) 50 | cm.linear._repr_html_() 51 | 52 | 53 | def test_step_object(): 54 | cm.step.OrRd_06._repr_html_() 55 | cm.step.PuBu_06.to_linear() 56 | cm.step.YlGn_06.scale(3, 12) 57 | cm.step._repr_html_() 58 | -------------------------------------------------------------------------------- /branca/templates/color_scale.js: -------------------------------------------------------------------------------- 1 | {% macro script(this, kwargs) %} 2 | var {{this.get_name()}} = {}; 3 | 4 | {%if this.color_range %} 5 | {{this.get_name()}}.color = d3.scale.threshold() 6 | .domain({{this.color_domain}}) 7 | .range({{this.color_range}}); 8 | {%else%} 9 | {{this.get_name()}}.color = d3.scale.threshold() 10 | .domain([{{ this.color_domain[0] }}, {{ this.color_domain[-1] }}]) 11 | .range(['{{ this.fill_color }}', '{{ this.fill_color }}']); 12 | {%endif%} 13 | 14 | {{this.get_name()}}.x = d3.scale.linear() 15 | .domain([{{ this.color_domain[0] }}, {{ this.color_domain[-1] }}]) 16 | .range([0, 400]); 17 | 18 | {{this.get_name()}}.legend = L.control({position: 'topright'}); 19 | {{this.get_name()}}.legend.onAdd = function (map) {var div = L.DomUtil.create('div', 'legend'); return div}; 20 | {{this.get_name()}}.legend.addTo({{this._parent.get_name()}}); 21 | 22 | {{this.get_name()}}.xAxis = d3.svg.axis() 23 | .scale({{this.get_name()}}.x) 24 | .orient("top") 25 | .tickSize(1) 26 | .tickValues({{ this.tick_labels }}); 27 | 28 | {{this.get_name()}}.svg = d3.select(".legend.leaflet-control").append("svg") 29 | .attr("id", 'legend') 30 | .attr("width", 450) 31 | .attr("height", 40); 32 | 33 | {{this.get_name()}}.g = {{this.get_name()}}.svg.append("g") 34 | .attr("class", "key") 35 | .attr("transform", "translate(25,16)"); 36 | 37 | {{this.get_name()}}.g.selectAll("rect") 38 | .data({{this.get_name()}}.color.range().map(function(d, i) { 39 | return { 40 | x0: i ? {{this.get_name()}}.x({{this.get_name()}}.color.domain()[i - 1]) : {{this.get_name()}}.x.range()[0], 41 | x1: i < {{this.get_name()}}.color.domain().length ? {{this.get_name()}}.x({{this.get_name()}}.color.domain()[i]) : {{this.get_name()}}.x.range()[1], 42 | z: d 43 | }; 44 | })) 45 | .enter().append("rect") 46 | .attr("height", 10) 47 | .attr("x", function(d) { return d.x0; }) 48 | .attr("width", function(d) { return d.x1 - d.x0; }) 49 | .style("fill", function(d) { return d.z; }); 50 | 51 | {{this.get_name()}}.g.call({{this.get_name()}}.xAxis).append("text") 52 | .attr("class", "caption") 53 | .attr("y", 21) 54 | .text('{{ this.caption }}'); 55 | {% endmacro %} -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | from setuptools import setup 6 | from setuptools.command.test import test as TestCommand 7 | 8 | rootpath = os.path.abspath(os.path.dirname(__file__)) 9 | 10 | 11 | class PyTest(TestCommand): 12 | def finalize_options(self): 13 | TestCommand.finalize_options(self) 14 | self.verbose = True 15 | 16 | def run_tests(self): 17 | import pytest 18 | errno = pytest.main(self.test_args) 19 | sys.exit(errno) 20 | 21 | 22 | def read(*parts): 23 | return open(os.path.join(rootpath, *parts), 'r').read() 24 | 25 | 26 | def extract_version(module='branca'): 27 | version = None 28 | fname = os.path.join(rootpath, module, '__init__.py') 29 | with open(fname) as f: 30 | for line in f: 31 | if (line.startswith('__version__')): 32 | _, version = line.split('=') 33 | version = version.strip()[1:-1] # Remove quotation characters. 34 | break 35 | return version 36 | 37 | 38 | def walk_subpkg(name): 39 | data_files = [] 40 | package_dir = 'branca' 41 | for parent, dirs, files in os.walk(os.path.join(package_dir, name)): 42 | # Remove package_dir from the path. 43 | sub_dir = os.sep.join(parent.split(os.sep)[1:]) 44 | for f in files: 45 | data_files.append(os.path.join(sub_dir, f)) 46 | return data_files 47 | 48 | 49 | pkg_data = {'': ['*.js', 50 | 'plugins/*.js', 51 | 'plugins/*.html', 52 | 'plugins/*.css', 53 | 'plugins/*.tpl', 54 | 'templates/*.html', 55 | 'templates/*.js', 56 | 'templates/*.txt']} 57 | pkgs = ['branca', ] 58 | 59 | LICENSE = read('LICENSE.txt') 60 | long_description = '{}\n{}'.format(read('README.rst'), read('CHANGES.txt')) 61 | 62 | # Dependencies. 63 | with open('requirements.txt') as f: 64 | tests_require = f.readlines() 65 | install_requires = [t.strip() for t in tests_require] 66 | 67 | 68 | config = dict(name='branca', 69 | version=extract_version(), 70 | description='Generate complex HTML+JS pages with Python', 71 | long_description=long_description, 72 | author='Martin Journois', 73 | url='https://github.com/python-visualization/branca', 74 | keywords='data visualization', 75 | classifiers=['Programming Language :: Python :: 2.7', 76 | 'Programming Language :: Python :: 3.4', 77 | 'Programming Language :: Python :: 3.5', 78 | 'License :: OSI Approved :: MIT License', 79 | 'Development Status :: 5 - Production/Stable'], 80 | packages=pkgs, 81 | package_data=pkg_data, 82 | include_package_data=True, 83 | cmdclass=dict(test=PyTest), 84 | tests_require=['pytest'], 85 | license=LICENSE, 86 | install_requires=install_requires, 87 | zip_safe=False) 88 | 89 | 90 | setup(**config) 91 | -------------------------------------------------------------------------------- /branca/_cnames.json: -------------------------------------------------------------------------------- 1 | {"indigo": "#4B0082", "gold": "#FFD700", "hotpink": "#FF69B4", "firebrick": "#B22222", "indianred": "#CD5C5C", "sage": "#87AE73", "yellow": "#FFFF00", "mistyrose": "#FFE4E1", "darkolivegreen": "#556B2F", "olive": "#808000", "darkseagreen": "#8FBC8F", "pink": "#FFC0CB", "tomato": "#FF6347", "lightcoral": "#F08080", "orangered": "#FF4500", "navajowhite": "#FFDEAD", "lime": "#00FF00", "palegreen": "#98FB98", "greenyellow": "#ADFF2F", "burlywood": "#DEB887", "seashell": "#FFF5EE", "mediumspringgreen": "#00FA9A", "fuchsia": "#FF00FF", "papayawhip": "#FFEFD5", "blanchedalmond": "#FFEBCD", "chartreuse": "#7FFF00", "dimgray": "#696969", "black": "#000000", "peachpuff": "#FFDAB9", "springgreen": "#00FF7F", "aquamarine": "#7FFFD4", "white": "#FFFFFF", "b": "#0000FF", "orange": "#FFA500", "lightsalmon": "#FFA07A", "darkslategray": "#2F4F4F", "brown": "#A52A2A", "ivory": "#FFFFF0", "dodgerblue": "#1E90FF", "peru": "#CD853F", "lawngreen": "#7CFC00", "chocolate": "#D2691E", "crimson": "#DC143C", "forestgreen": "#228B22", "slateblue": "#6A5ACD", "lightseagreen": "#20B2AA", "cyan": "#00FFFF", "mintcream": "#F5FFFA", "silver": "#C0C0C0", "antiquewhite": "#FAEBD7", "mediumorchid": "#BA55D3", "skyblue": "#87CEEB", "gray": "#808080", "darkturquoise": "#00CED1", "goldenrod": "#DAA520", "darkgreen": "#006400", "floralwhite": "#FFFAF0", "darkviolet": "#9400D3", "darkgray": "#A9A9A9", "moccasin": "#FFE4B5", "saddlebrown": "#8B4513", "darkslateblue": "#483D8B", "lightskyblue": "#87CEFA", "lightpink": "#FFB6C1", "mediumvioletred": "#C71585", "r": "#FF0000", "red": "#FF0000", "deeppink": "#FF1493", "limegreen": "#32CD32", "k": "#000000", "darkmagenta": "#8B008B", "palegoldenrod": "#EEE8AA", "plum": "#DDA0DD", "turquoise": "#40E0D0", "m": "#FF00FF", "lightgoldenrodyellow": "#FAFAD2", "darkgoldenrod": "#B8860B", "lavender": "#E6E6FA", "maroon": "#800000", "yellowgreen": "#9ACD32", "sandybrown": "#FAA460", "thistle": "#D8BFD8", "violet": "#EE82EE", "navy": "#000080", "magenta": "#FF00FF", "tan": "#D2B48C", "rosybrown": "#BC8F8F", "olivedrab": "#6B8E23", "blue": "#0000FF", "lightblue": "#ADD8E6", "ghostwhite": "#F8F8FF", "honeydew": "#F0FFF0", "cornflowerblue": "#6495ED", "linen": "#FAF0E6", "darkblue": "#00008B", "powderblue": "#B0E0E6", "seagreen": "#2E8B57", "darkkhaki": "#BDB76B", "snow": "#FFFAFA", "sienna": "#A0522D", "mediumblue": "#0000CD", "royalblue": "#4169E1", "lightcyan": "#E0FFFF", "green": "#008000", "mediumpurple": "#9370DB", "midnightblue": "#191970", "cornsilk": "#FFF8DC", "paleturquoise": "#AFEEEE", "bisque": "#FFE4C4", "slategray": "#708090", "darkcyan": "#008B8B", "khaki": "#F0E68C", "wheat": "#F5DEB3", "teal": "#008080", "darkorchid": "#9932CC", "deepskyblue": "#00BFFF", "salmon": "#FA8072", "y": "#FFFF00", "darkred": "#8B0000", "steelblue": "#4682B4", "g": "#008000", "palevioletred": "#DB7093", "lightslategray": "#778899", "aliceblue": "#F0F8FF", "lightgreen": "#90EE90", "orchid": "#DA70D6", "gainsboro": "#DCDCDC", "mediumseagreen": "#3CB371", "lightgray": "#D3D3D3", "c": "#00FFFF", "mediumturquoise": "#48D1CC", "darksage": "#598556", "lemonchiffon": "#FFFACD", "cadetblue": "#5F9EA0", "lightyellow": "#FFFFE0", "lavenderblush": "#FFF0F5", "coral": "#FF7F50", "purple": "#800080", "aqua": "#00FFFF", "lightsage": "#BCECAC", "whitesmoke": "#F5F5F5", "mediumslateblue": "#7B68EE", "darkorange": "#FF8C00", "mediumaquamarine": "#66CDAA", "darksalmon": "#E9967A", "beige": "#F5F5DC", "w": "#FFFFFF", "blueviolet": "#8A2BE2", "azure": "#F0FFFF", "lightsteelblue": "#B0C4DE", "oldlace": "#FDF5E6"} -------------------------------------------------------------------------------- /branca/utilities.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Utilities 4 | ------- 5 | 6 | Utility module for Folium helper functions. 7 | 8 | """ 9 | 10 | from __future__ import absolute_import 11 | from __future__ import print_function 12 | from __future__ import division 13 | 14 | import math 15 | import zlib 16 | import struct 17 | import json 18 | import pkg_resources 19 | import base64 20 | from jinja2 import Environment, PackageLoader 21 | 22 | try: 23 | import pandas as pd 24 | except ImportError: 25 | pd = None 26 | 27 | try: 28 | import numpy as np 29 | except ImportError: 30 | np = None 31 | 32 | from branca.six import text_type, binary_type 33 | 34 | 35 | def get_templates(): 36 | """Get Jinja templates.""" 37 | return Environment(loader=PackageLoader('branca', 'templates')) 38 | 39 | 40 | def legend_scaler(legend_values, max_labels=10.0): 41 | """ 42 | Downsamples the number of legend values so that there isn't a collision 43 | of text on the legend colorbar (within reason). The colorbar seems to 44 | support ~10 entries as a maximum. 45 | 46 | """ 47 | if len(legend_values) < max_labels: 48 | legend_ticks = legend_values 49 | else: 50 | spacer = int(math.ceil(len(legend_values)/max_labels)) 51 | legend_ticks = [] 52 | for i in legend_values[::spacer]: 53 | legend_ticks += [i] 54 | legend_ticks += ['']*(spacer-1) 55 | return legend_ticks 56 | 57 | 58 | def linear_gradient(hexList, nColors): 59 | """ 60 | Given a list of hexcode values, will return a list of length 61 | nColors where the colors are linearly interpolated between the 62 | (r, g, b) tuples that are given. 63 | 64 | Examples 65 | -------- 66 | >>> linear_gradient([(0, 0, 0), (255, 0, 0), (255, 255, 0)], 100) 67 | 68 | """ 69 | def _scale(start, finish, length, i): 70 | """ 71 | Return the value correct value of a number that is in between start 72 | and finish, for use in a loop of length *length*. 73 | 74 | """ 75 | base = 16 76 | 77 | fraction = float(i) / (length - 1) 78 | raynge = int(finish, base) - int(start, base) 79 | thex = hex(int(int(start, base) + fraction * raynge)).split('x')[-1] 80 | if len(thex) != 2: 81 | thex = '0' + thex 82 | return thex 83 | 84 | allColors = [] 85 | # Separate (R, G, B) pairs. 86 | for start, end in zip(hexList[:-1], hexList[1:]): 87 | # Linearly intepolate between pair of hex ###### values and 88 | # add to list. 89 | nInterpolate = 765 90 | for index in range(nInterpolate): 91 | r = _scale(start[1:3], end[1:3], nInterpolate, index) 92 | g = _scale(start[3:5], end[3:5], nInterpolate, index) 93 | b = _scale(start[5:7], end[5:7], nInterpolate, index) 94 | allColors.append(''.join(['#', r, g, b])) 95 | 96 | # Pick only nColors colors from the total list. 97 | result = [] 98 | for counter in range(nColors): 99 | fraction = float(counter) / (nColors - 1) 100 | index = int(fraction * (len(allColors) - 1)) 101 | result.append(allColors[index]) 102 | return result 103 | 104 | 105 | def color_brewer(color_code, n=6): 106 | """ 107 | Generate a colorbrewer color scheme of length 'len', type 'scheme. 108 | Live examples can be seen at http://colorbrewer2.org/ 109 | 110 | """ 111 | maximum_n = 253 112 | minimum_n = 3 113 | 114 | # Raise an error if the n requested is greater than the maximum. 115 | if n > maximum_n: 116 | raise ValueError("The maximum number of colors in a" 117 | " ColorBrewer sequential color series is 253") 118 | if n < minimum_n: 119 | raise ValueError("The minimum number of colors in a" 120 | " ColorBrewer sequential color series is 3") 121 | 122 | if color_code[-2:] == '_r': 123 | base_code = color_code[:-2] 124 | core_color_code = base_code + '_' + str(n).zfill(2) 125 | color_reverse = True 126 | else: 127 | base_code = color_code 128 | core_color_code = base_code + '_' + str(n).zfill(2) 129 | color_reverse = False 130 | 131 | resource_package = __name__ 132 | resource_path_schemes = '/_schemes.json' 133 | resource_path_scheme_info = '/_cnames.json' 134 | resource_path_scheme_base_codes = '/scheme_base_codes.json' 135 | 136 | schemes_string = pkg_resources.resource_stream( 137 | resource_package, resource_path_schemes).read().decode() 138 | schemes = json.loads(schemes_string) 139 | 140 | scheme_info_string = pkg_resources.resource_stream( 141 | resource_package, resource_path_scheme_info).read().decode() 142 | scheme_info = json.loads(scheme_info_string) 143 | 144 | core_schemes_string = pkg_resources.resource_stream( 145 | resource_package, resource_path_scheme_base_codes).read().decode() 146 | core_schemes = json.loads(core_schemes_string)['codes'] 147 | 148 | if base_code not in core_schemes: 149 | raise ValueError(base_code + " is not a valid ColorBrewer code") 150 | 151 | try: 152 | schemes[core_color_code] 153 | explicit_scheme = True 154 | except KeyError: 155 | explicit_scheme = False 156 | 157 | # Only if n is greater than the scheme length do we interpolate values. 158 | if not explicit_scheme: 159 | # Check to make sure that it is not a qualitative scheme. 160 | if scheme_info[base_code] == 'Qualitative': 161 | matching_quals = [] 162 | for key in schemes: 163 | if base_code + '_' in key: 164 | matching_quals.append(int(key.split('_')[1])) 165 | 166 | raise ValueError("Expanded color support is not available" 167 | " for Qualitative schemes; restrict the" 168 | " number of colors for the " + base_code + 169 | " code to between " + str(min(matching_quals)) + 170 | " and " + str(max(matching_quals)) 171 | ) 172 | else: 173 | if not color_reverse: 174 | color_scheme = linear_gradient(schemes.get(core_color_code), n) 175 | else: 176 | color_scheme = linear_gradient(schemes.get(core_color_code)[::-1], n) 177 | else: 178 | if not color_reverse: 179 | color_scheme = schemes.get(core_color_code, None) 180 | else: 181 | color_scheme = schemes.get(core_color_code, None)[::-1] 182 | return color_scheme 183 | 184 | 185 | def split_six(series=None): 186 | """ 187 | Given a Pandas Series, get a domain of values from zero to the 90% quantile 188 | rounded to the nearest order-of-magnitude integer. For example, 2100 is 189 | rounded to 2000, 2790 to 3000. 190 | 191 | Parameters 192 | ---------- 193 | series: Pandas series, default None 194 | 195 | Returns 196 | ------- 197 | list 198 | 199 | """ 200 | if pd is None: 201 | raise ImportError("The Pandas package is required" 202 | " for this functionality") 203 | if np is None: 204 | raise ImportError("The NumPy package is required" 205 | " for this functionality") 206 | 207 | def base(x): 208 | if x > 0: 209 | base = pow(10, math.floor(math.log10(x))) 210 | return round(x/base)*base 211 | else: 212 | return 0 213 | 214 | quants = [0, 50, 75, 85, 90] 215 | # Some weirdness in series quantiles a la 0.13. 216 | arr = series.values 217 | return [base(np.percentile(arr, x)) for x in quants] 218 | 219 | 220 | def image_to_url(image, colormap=None, origin='upper'): 221 | """Infers the type of an image argument and transforms it into a URL. 222 | 223 | Parameters 224 | ---------- 225 | image: string, file or array-like object 226 | * If string, it will be written directly in the output file. 227 | * If file, it's content will be converted as embedded in the 228 | output file. 229 | * If array-like, it will be converted to PNG base64 string and 230 | embedded in the output. 231 | origin : ['upper' | 'lower'], optional, default 'upper' 232 | Place the [0, 0] index of the array in the upper left or 233 | lower left corner of the axes. 234 | colormap : callable, used only for `mono` image. 235 | Function of the form [x -> (r,g,b)] or [x -> (r,g,b,a)] 236 | for transforming a mono image into RGB. 237 | It must output iterables of length 3 or 4, with values between 238 | 0. and 1. Hint : you can use colormaps from `matplotlib.cm`. 239 | """ 240 | if hasattr(image, 'read'): 241 | # We got an image file. 242 | if hasattr(image, 'name'): 243 | # We try to get the image format from the file name. 244 | fileformat = image.name.lower().split('.')[-1] 245 | else: 246 | fileformat = 'png' 247 | url = "data:image/{};base64,{}".format( 248 | fileformat, base64.b64encode(image.read()).decode('utf-8')) 249 | elif (not (isinstance(image, text_type) or 250 | isinstance(image, binary_type))) and hasattr(image, '__iter__'): 251 | # We got an array-like object. 252 | png = write_png(image, origin=origin, colormap=colormap) 253 | url = "data:image/png;base64," + base64.b64encode(png).decode('utf-8') 254 | else: 255 | # We got an URL. 256 | url = json.loads(json.dumps(image)) 257 | 258 | return url.replace('\n', ' ') 259 | 260 | 261 | def write_png(data, origin='upper', colormap=None): 262 | """ 263 | Transform an array of data into a PNG string. 264 | This can be written to disk using binary I/O, or encoded using base64 265 | for an inline PNG like this: 266 | 267 | >>> png_str = write_png(array) 268 | >>> "data:image/png;base64,"+png_str.encode('base64') 269 | 270 | Inspired from 271 | http://stackoverflow.com/questions/902761/saving-a-numpy-array-as-an-image 272 | 273 | Parameters 274 | ---------- 275 | data: numpy array or equivalent list-like object. 276 | Must be NxM (mono), NxMx3 (RGB) or NxMx4 (RGBA) 277 | 278 | origin : ['upper' | 'lower'], optional, default 'upper' 279 | Place the [0,0] index of the array in the upper left or lower left 280 | corner of the axes. 281 | 282 | colormap : callable, used only for `mono` image. 283 | Function of the form [x -> (r,g,b)] or [x -> (r,g,b,a)] 284 | for transforming a mono image into RGB. 285 | It must output iterables of length 3 or 4, with values between 286 | 0. and 1. Hint: you can use colormaps from `matplotlib.cm`. 287 | 288 | Returns 289 | ------- 290 | PNG formatted byte string 291 | """ 292 | if np is None: 293 | raise ImportError("The NumPy package is required" 294 | " for this functionality") 295 | 296 | if colormap is None: 297 | def colormap(x): 298 | return (x, x, x, 1) 299 | 300 | array = np.atleast_3d(data) 301 | height, width, nblayers = array.shape 302 | 303 | if nblayers not in [1, 3, 4]: 304 | raise ValueError("Data must be NxM (mono), " 305 | "NxMx3 (RGB), or NxMx4 (RGBA)") 306 | assert array.shape == (height, width, nblayers) 307 | 308 | if nblayers == 1: 309 | array = np.array(list(map(colormap, array.ravel()))) 310 | nblayers = array.shape[1] 311 | if nblayers not in [3, 4]: 312 | raise ValueError("colormap must provide colors of" 313 | "length 3 (RGB) or 4 (RGBA)") 314 | array = array.reshape((height, width, nblayers)) 315 | assert array.shape == (height, width, nblayers) 316 | 317 | if nblayers == 3: 318 | array = np.concatenate((array, np.ones((height, width, 1))), axis=2) 319 | nblayers = 4 320 | assert array.shape == (height, width, nblayers) 321 | assert nblayers == 4 322 | 323 | # Normalize to uint8 if it isn't already. 324 | if array.dtype != 'uint8': 325 | array = array * 255./array.max(axis=(0, 1)).reshape((1, 1, 4)) 326 | array = array.astype('uint8') 327 | 328 | # Eventually flip the image. 329 | if origin == 'lower': 330 | array = array[::-1, :, :] 331 | 332 | # Transform the array to bytes. 333 | raw_data = b''.join([b'\x00' + array[i, :, :].tobytes() 334 | for i in range(height)]) 335 | 336 | def png_pack(png_tag, data): 337 | chunk_head = png_tag + data 338 | return (struct.pack("!I", len(data)) + 339 | chunk_head + 340 | struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))) 341 | 342 | return b''.join([ 343 | b'\x89PNG\r\n\x1a\n', 344 | png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)), 345 | png_pack(b'IDAT', zlib.compress(raw_data, 9)), 346 | png_pack(b'IEND', b'')]) 347 | 348 | 349 | def _camelify(out): 350 | return (''.join(["_" + x.lower() if i < len(out)-1 and x.isupper() and out[i+1].islower() # noqa 351 | else x.lower() + "_" if i < len(out)-1 and x.islower() and out[i+1].isupper() # noqa 352 | else x.lower() for i, x in enumerate(list(out))])).lstrip('_').replace('__', '_') # noqa 353 | 354 | 355 | def _parse_size(value): 356 | try: 357 | if isinstance(value, int) or isinstance(value, float): 358 | value_type = 'px' 359 | value = float(value) 360 | assert value > 0 361 | else: 362 | value_type = '%' 363 | value = float(value.strip('%')) 364 | assert 0 <= value <= 100 365 | except: 366 | msg = "Cannot parse value {!r} as {!r}".format 367 | raise ValueError(msg(value, value_type)) 368 | return value, value_type 369 | 370 | 371 | def _locations_mirror(x): 372 | """Mirrors the points in a list-of-list-of-...-of-list-of-points. 373 | For example: 374 | >>> _locations_mirror([[[1, 2], [3, 4]], [5, 6], [7, 8]]) 375 | [[[2, 1], [4, 3]], [6, 5], [8, 7]] 376 | 377 | """ 378 | if hasattr(x, '__iter__'): 379 | if hasattr(x[0], '__iter__'): 380 | return list(map(_locations_mirror, x)) 381 | else: 382 | return list(x[::-1]) 383 | else: 384 | return x 385 | 386 | 387 | def _locations_tolist(x): 388 | """Transforms recursively a list of iterables into a list of list. 389 | """ 390 | if hasattr(x, '__iter__'): 391 | return list(map(_locations_tolist, x)) 392 | else: 393 | return x 394 | 395 | 396 | def none_min(x, y): 397 | if x is None: 398 | return y 399 | elif y is None: 400 | return x 401 | else: 402 | return min(x, y) 403 | 404 | 405 | def none_max(x, y): 406 | if x is None: 407 | return y 408 | elif y is None: 409 | return x 410 | else: 411 | return max(x, y) 412 | 413 | 414 | def iter_points(x): 415 | """Iterates over a list representing a feature, and returns a list of points, 416 | whatever the shape of the array (Point, MultiPolyline, etc). 417 | """ 418 | if isinstance(x, (list, tuple)): 419 | if len(x): 420 | if isinstance(x[0], (list, tuple)): 421 | out = [] 422 | for y in x: 423 | out += iter_points(y) 424 | return out 425 | else: 426 | return [x] 427 | else: 428 | return [] 429 | else: 430 | raise ValueError('List/tuple type expected. Got {!r}.'.format(x)) 431 | -------------------------------------------------------------------------------- /branca/colormap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Colormap 4 | ------- 5 | 6 | Utility module for dealing with colormaps. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | import math 12 | import json 13 | import pkg_resources 14 | from jinja2 import Template 15 | from branca.six import text_type, binary_type 16 | from branca.element import MacroElement, Figure, JavascriptLink 17 | from branca.utilities import legend_scaler 18 | 19 | 20 | resource_package = __name__ 21 | resource_path_schemes = '/_schemes.json' 22 | resource_path_cnames = '/_cnames.json' 23 | 24 | cnames_string = pkg_resources.resource_stream( 25 | resource_package, resource_path_cnames).read().decode() 26 | _cnames = json.loads(cnames_string) 27 | 28 | schemes_string = pkg_resources.resource_stream( 29 | resource_package, resource_path_schemes).read().decode() 30 | _schemes = json.loads(schemes_string) 31 | 32 | 33 | def _parse_color(x): 34 | """ 35 | """ 36 | if isinstance(x, (tuple, list)): 37 | color_tuple = tuple(x)[:4] 38 | elif isinstance(x, (text_type, binary_type)): 39 | if x.startswith('#') and len(x) == 7: 40 | # Color of the form #RRGGBB 41 | color_tuple = (int(x[1:3], 16), 42 | int(x[3:5], 16), 43 | int(x[5:7], 16)) 44 | else: 45 | color_code = _cnames.get(x.lower(), None) 46 | if color_code is None: 47 | raise ValueError('Unknown color {!r}.'.format(x)) 48 | color_tuple = (int(color_code[1:3], 16), 49 | int(color_code[3:5], 16), 50 | int(color_code[5:7], 16)) 51 | if max(color_tuple) > 1.: 52 | color_tuple = tuple(u/255. for u in color_tuple) 53 | return tuple(map(float, (color_tuple+(1.,))[:4])) 54 | 55 | 56 | def _base(x): 57 | if x > 0: 58 | base = pow(10, math.floor(math.log10(x))) 59 | return round(x/base)*base 60 | else: 61 | return 0 62 | 63 | 64 | class ColorMap(MacroElement): 65 | """A generic class for creating colormaps. 66 | 67 | Parameters 68 | ---------- 69 | vmin: float 70 | The left bound of the color scale. 71 | vmax: float 72 | The right bound of the color scale. 73 | caption: str 74 | A caption to draw with the colormap. 75 | """ 76 | def __init__(self, vmin=0., vmax=1., caption=""): 77 | super(ColorMap, self).__init__() 78 | self._name = 'ColorMap' 79 | 80 | self.vmin = vmin 81 | self.vmax = vmax 82 | self.caption = caption 83 | self.index = [vmin, vmax] 84 | 85 | self._template = self._env.get_template('color_scale.js') 86 | 87 | def render(self, **kwargs): 88 | """Renders the HTML representation of the element.""" 89 | self.color_domain = [self.vmin + (self.vmax-self.vmin) * k/499. for 90 | k in range(500)] 91 | self.color_range = [self.__call__(x) for x in self.color_domain] 92 | self.tick_labels = legend_scaler(self.index) 93 | 94 | super(ColorMap, self).render(**kwargs) 95 | 96 | figure = self.get_root() 97 | assert isinstance(figure, Figure), ("You cannot render this Element " 98 | "if it's not in a Figure.") 99 | 100 | figure.header.add_child(JavascriptLink("https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"), name='d3') # noqa 101 | 102 | def rgba_floats_tuple(self, x): 103 | """ 104 | This class has to be implemented for each class inheriting from 105 | Colormap. This has to be a function of the form float -> 106 | (float, float, float, float) describing for each input float x, 107 | the output color in RGBA format; 108 | Each output value being between 0 and 1. 109 | """ 110 | raise NotImplementedError 111 | 112 | def rgba_bytes_tuple(self, x): 113 | """Provides the color corresponding to value `x` in the 114 | form of a tuple (R,G,B,A) with int values between 0 and 255. 115 | """ 116 | return tuple(int(u*255.9999) for u in self.rgba_floats_tuple(x)) 117 | 118 | def rgb_bytes_tuple(self, x): 119 | """Provides the color corresponding to value `x` in the 120 | form of a tuple (R,G,B) with int values between 0 and 255. 121 | """ 122 | return self.rgba_bytes_tuple(x)[:3] 123 | 124 | def rgb_hex_str(self, x): 125 | """Provides the color corresponding to value `x` in the 126 | form of a string of hewadecimal values "#RRGGBB". 127 | """ 128 | return "#%02x%02x%02x" % self.rgb_bytes_tuple(x) 129 | 130 | def __call__(self, x): 131 | """Provides the color corresponding to value `x` in the 132 | form of a string of hewadecimal values "#RRGGBB". 133 | """ 134 | return self.rgb_hex_str(x) 135 | 136 | def _repr_html_(self): 137 | return ( 138 | '' + 139 | ''.join( 140 | [('').format( 142 | i=i*1, 143 | color=self.rgb_hex_str(self.vmin + 144 | (self.vmax-self.vmin)*i/499.)) 145 | for i in range(500)]) + 146 | '{}'.format(self.vmin) + 147 | '{}'.format( 148 | self.vmax) + 149 | '') 150 | 151 | 152 | class LinearColormap(ColorMap): 153 | """Creates a ColorMap based on linear interpolation of a set of colors 154 | over a given index. 155 | 156 | Parameters 157 | ---------- 158 | 159 | colors : list-like object 160 | The set of colors to be used for interpolation. 161 | Colors can be provided in the form: 162 | * tuples of int between 0 and 255 (e.g: `(255,255,0)` or 163 | `(255, 255, 0, 255)`) 164 | * tuples of floats between 0. and 1. (e.g: `(1.,1.,0.)` or 165 | `(1., 1., 0., 1.)`) 166 | * HTML-like string (e.g: `"#ffff00`) 167 | * a color name or shortcut (e.g: `"y"` or `"yellow"`) 168 | index : list of floats, default None 169 | The values corresponding to each color. 170 | It has to be sorted, and have the same length as `colors`. 171 | If None, a regular grid between `vmin` and `vmax` is created. 172 | vmin : float, default 0. 173 | The minimal value for the colormap. 174 | Values lower than `vmin` will be bound directly to `colors[0]`. 175 | vmax : float, default 1. 176 | The maximal value for the colormap. 177 | Values higher than `vmax` will be bound directly to `colors[-1]`.""" 178 | 179 | def __init__(self, colors, index=None, vmin=0., vmax=1., caption=""): 180 | super(LinearColormap, self).__init__(vmin=vmin, vmax=vmax, 181 | caption=caption) 182 | 183 | n = len(colors) 184 | if n < 2: 185 | raise ValueError('You must provide at least 2 colors.') 186 | if index is None: 187 | self.index = [vmin + (vmax-vmin)*i*1./(n-1) for i in range(n)] 188 | else: 189 | self.index = [x for x in index] 190 | self.colors = [_parse_color(x) for x in colors] 191 | 192 | def rgba_floats_tuple(self, x): 193 | """Provides the color corresponding to value `x` in the 194 | form of a tuple (R,G,B,A) with float values between 0. and 1. 195 | """ 196 | if x <= self.index[0]: 197 | return self.colors[0] 198 | if x >= self.index[-1]: 199 | return self.colors[-1] 200 | 201 | i = len([u for u in self.index if u < x]) # 0 < i < n. 202 | if self.index[i-1] < self.index[i]: 203 | p = (x - self.index[i-1])*1./(self.index[i]-self.index[i-1]) 204 | elif self.index[i-1] == self.index[i]: 205 | p = 1. 206 | else: 207 | raise ValueError('Thresholds are not sorted.') 208 | 209 | return tuple((1.-p) * self.colors[i-1][j] + p*self.colors[i][j] for j 210 | in range(4)) 211 | 212 | def to_step(self, n=None, index=None, data=None, method=None, 213 | quantiles=None, round_method=None): 214 | """Splits the LinearColormap into a StepColormap. 215 | 216 | Parameters 217 | ---------- 218 | n : int, default None 219 | The number of expected colors in the ouput StepColormap. 220 | This will be ignored if `index` is provided. 221 | index : list of floats, default None 222 | The values corresponding to each color bounds. 223 | It has to be sorted. 224 | If None, a regular grid between `vmin` and `vmax` is created. 225 | data : list of floats, default None 226 | A sample of data to adapt the color map to. 227 | method : str, default 'linear' 228 | The method used to create data-based colormap. 229 | It can be 'linear' for linear scale, 'log' for logarithmic, 230 | or 'quant' for data's quantile-based scale. 231 | quantiles : list of floats, default None 232 | Alternatively, you can provide explicitely the quantiles you 233 | want to use in the scale. 234 | round_method : str, default None 235 | The method used to round thresholds. 236 | * If 'int', all values will be rounded to the nearest integer. 237 | * If 'log10', all values will be rounded to the nearest 238 | order-of-magnitude integer. For example, 2100 is rounded to 239 | 2000, 2790 to 3000. 240 | 241 | Returns 242 | ------- 243 | A StepColormap with `n=len(index)-1` colors. 244 | 245 | Examples: 246 | >> lc.to_step(n=12) 247 | >> lc.to_step(index=[0, 2, 4, 6, 8, 10]) 248 | >> lc.to_step(data=some_list, n=12) 249 | >> lc.to_step(data=some_list, n=12, method='linear') 250 | >> lc.to_step(data=some_list, n=12, method='log') 251 | >> lc.to_step(data=some_list, n=12, method='quantiles') 252 | >> lc.to_step(data=some_list, quantiles=[0, 0.3, 0.7, 1]) 253 | >> lc.to_step(data=some_list, quantiles=[0, 0.3, 0.7, 1], 254 | ... round_method='log10') 255 | 256 | """ 257 | msg = 'You must specify either `index` or `n`' 258 | if index is None: 259 | if data is None: 260 | if n is None: 261 | raise ValueError(msg) 262 | else: 263 | index = [self.vmin + (self.vmax-self.vmin)*i*1./n for 264 | i in range(1+n)] 265 | scaled_cm = self 266 | else: 267 | max_ = max(data) 268 | min_ = min(data) 269 | scaled_cm = self.scale(vmin=min_, vmax=max_) 270 | method = ('quantiles' if quantiles is not None 271 | else method if method is not None 272 | else 'linear' 273 | ) 274 | if method.lower().startswith('lin'): 275 | if n is None: 276 | raise ValueError(msg) 277 | index = [min_ + i*(max_-min_)*1./n for i in range(1+n)] 278 | elif method.lower().startswith('log'): 279 | if n is None: 280 | raise ValueError(msg) 281 | if min_ <= 0: 282 | msg = ('Log-scale works only with strictly ' 283 | 'positive values.') 284 | raise ValueError(msg) 285 | index = [math.exp( 286 | math.log(min_) + i * (math.log(max_) - 287 | math.log(min_)) * 1./n 288 | ) for i in range(1+n)] 289 | elif method.lower().startswith('quant'): 290 | if quantiles is None: 291 | if n is None: 292 | msg = ('You must specify either `index`, `n` or' 293 | '`quantiles`.') 294 | raise ValueError(msg) 295 | else: 296 | quantiles = [i*1./n for i in range(1+n)] 297 | p = len(data)-1 298 | s = sorted(data) 299 | index = [s[int(q*p)] * (1.-(q*p) % 1) + 300 | s[min(int(q*p) + 1, p)] * ((q*p) % 1) for 301 | q in quantiles] 302 | else: 303 | raise ValueError('Unknown method {}'.format(method)) 304 | else: 305 | scaled_cm = self.scale(vmin=min(index), vmax=max(index)) 306 | 307 | n = len(index)-1 308 | 309 | if round_method == 'int': 310 | index = [round(x) for x in index] 311 | 312 | if round_method == 'log10': 313 | index = [_base(x) for x in index] 314 | 315 | colors = [scaled_cm.rgba_floats_tuple(index[i] * (1.-i/(n-1.)) + 316 | index[i+1] * i/(n-1.)) for 317 | i in range(n)] 318 | 319 | return StepColormap(colors, index=index, vmin=index[0], vmax=index[-1]) 320 | 321 | def scale(self, vmin=0., vmax=1.): 322 | """Transforms the colorscale so that the minimal and maximal values 323 | fit the given parameters. 324 | """ 325 | return LinearColormap( 326 | self.colors, 327 | index=[vmin + (vmax-vmin)*(x-self.vmin)*1./(self.vmax-self.vmin) for x in self.index], # noqa 328 | vmin=vmin, 329 | vmax=vmax, 330 | ) 331 | 332 | 333 | class StepColormap(ColorMap): 334 | """Creates a ColorMap based on linear interpolation of a set of colors 335 | over a given index. 336 | 337 | Parameters 338 | ---------- 339 | colors : list-like object 340 | The set of colors to be used for interpolation. 341 | Colors can be provided in the form: 342 | * tuples of int between 0 and 255 (e.g: `(255,255,0)` or 343 | `(255, 255, 0, 255)`) 344 | * tuples of floats between 0. and 1. (e.g: `(1.,1.,0.)` or 345 | `(1., 1., 0., 1.)`) 346 | * HTML-like string (e.g: `"#ffff00`) 347 | * a color name or shortcut (e.g: `"y"` or `"yellow"`) 348 | index : list of floats, default None 349 | The values corresponding to each color. 350 | It has to be sorted, and have the same length as `colors`. 351 | If None, a regular grid between `vmin` and `vmax` is created. 352 | vmin : float, default 0. 353 | The minimal value for the colormap. 354 | Values lower than `vmin` will be bound directly to `colors[0]`. 355 | vmax : float, default 1. 356 | The maximal value for the colormap. 357 | Values higher than `vmax` will be bound directly to `colors[-1]`. 358 | 359 | """ 360 | def __init__(self, colors, index=None, vmin=0., vmax=1., caption=""): 361 | super(StepColormap, self).__init__(vmin=vmin, vmax=vmax, 362 | caption=caption) 363 | 364 | n = len(colors) 365 | if n < 1: 366 | raise ValueError('You must provide at least 1 colors.') 367 | if index is None: 368 | self.index = [vmin + (vmax-vmin)*i*1./n for i in range(n+1)] 369 | else: 370 | self.index = [x for x in index] 371 | self.colors = [_parse_color(x) for x in colors] 372 | 373 | def rgba_floats_tuple(self, x): 374 | """ 375 | Provides the color corresponding to value `x` in the 376 | form of a tuple (R,G,B,A) with float values between 0. and 1. 377 | 378 | """ 379 | if x <= self.index[0]: 380 | return self.colors[0] 381 | if x >= self.index[-1]: 382 | return self.colors[-1] 383 | 384 | i = len([u for u in self.index if u < x]) # 0 < i < n. 385 | return tuple(self.colors[i-1]) 386 | 387 | def to_linear(self, index=None): 388 | """ 389 | Transforms the StepColormap into a LinearColormap. 390 | 391 | Parameters 392 | ---------- 393 | index : list of floats, default None 394 | The values corresponding to each color in the output colormap. 395 | It has to be sorted. 396 | If None, a regular grid between `vmin` and `vmax` is created. 397 | 398 | """ 399 | if index is None: 400 | n = len(self.index)-1 401 | index = [self.index[i]*(1.-i/(n-1.))+self.index[i+1]*i/(n-1.) for 402 | i in range(n)] 403 | 404 | colors = [self.rgba_floats_tuple(x) for x in index] 405 | return LinearColormap(colors, index=index, 406 | vmin=self.vmin, vmax=self.vmax) 407 | 408 | def scale(self, vmin=0., vmax=1.): 409 | """Transforms the colorscale so that the minimal and maximal values 410 | fit the given parameters. 411 | """ 412 | return StepColormap( 413 | self.colors, 414 | index=[vmin + (vmax-vmin)*(x-self.vmin)*1./(self.vmax-self.vmin) for x in self.index], # noqa 415 | vmin=vmin, 416 | vmax=vmax, 417 | ) 418 | 419 | 420 | class _LinearColormaps(object): 421 | """A class for hosting the list of built-in linear colormaps.""" 422 | def __init__(self): 423 | self._schemes = _schemes.copy() 424 | self._colormaps = {key: LinearColormap(val) for 425 | key, val in _schemes.items()} 426 | for key, val in _schemes.items(): 427 | setattr(self, key, LinearColormap(val)) 428 | 429 | def _repr_html_(self): 430 | return Template(""" 431 | 432 | {% for key,val in this._colormaps.items() %} 433 | 434 | {% endfor %}
{{key}}{{val._repr_html_()}}
435 | """).render(this=self) 436 | 437 | 438 | linear = _LinearColormaps() 439 | 440 | 441 | class _StepColormaps(object): 442 | """A class for hosting the list of built-in step colormaps.""" 443 | def __init__(self): 444 | self._schemes = _schemes.copy() 445 | self._colormaps = {key: StepColormap(val) for 446 | key, val in _schemes.items()} 447 | for key, val in _schemes.items(): 448 | setattr(self, key, StepColormap(val)) 449 | 450 | def _repr_html_(self): 451 | return Template(""" 452 | 453 | {% for key,val in this._colormaps.items() %} 454 | 455 | {% endfor %}
{{key}}{{val._repr_html_()}}
456 | """).render(this=self) 457 | 458 | 459 | step = _StepColormaps() 460 | -------------------------------------------------------------------------------- /branca/_schemes.json: -------------------------------------------------------------------------------- 1 | {"Pastel1_03": ["#fbb4ae", "#b3cde3", "#ccebc5"], "Pastel1_05": ["#fbb4ae", "#b3cde3", "#ccebc5", "#decbe4", "#fed9a6"], "Pastel1_04": ["#fbb4ae", "#b3cde3", "#ccebc5", "#decbe4"], "Pastel1_07": ["#fbb4ae", "#b3cde3", "#ccebc5", "#decbe4", "#fed9a6", "#ffffcc", "#e5d8bd"], "YlOrRd_04": ["#ffffb2", "#fecc5c", "#fd8d3c", "#e31a1c"], "Pastel1_09": ["#fbb4ae", "#b3cde3", "#ccebc5", "#decbe4", "#fed9a6", "#ffffcc", "#e5d8bd", "#fddaec", "#f2f2f2"], "Pastel1_08": ["#fbb4ae", "#b3cde3", "#ccebc5", "#decbe4", "#fed9a6", "#ffffcc", "#e5d8bd", "#fddaec"], "Spectral_07": ["#d53e4f", "#fc8d59", "#fee08b", "#ffffbf", "#e6f598", "#99d594", "#3288bd"], "RdYlBu_05": ["#d7191c", "#fdae61", "#ffffbf", "#abd9e9", "#2c7bb6"], "PuBuGn_03": ["#ece2f0", "#a6bddb", "#1c9099"], "Set1_08": ["#e41a1c", "#377eb8", "#4daf4a", "#984ea3", "#ff7f00", "#ffff33", "#a65628", "#f781bf"], "PuBuGn_05": ["#f6eff7", "#bdc9e1", "#67a9cf", "#1c9099", "#016c59"], "PuBuGn_04": ["#f6eff7", "#bdc9e1", "#67a9cf", "#02818a"], "PuBuGn_07": ["#f6eff7", "#d0d1e6", "#a6bddb", "#67a9cf", "#3690c0", "#02818a", "#016450"], "PuBuGn_06": ["#f6eff7", "#d0d1e6", "#a6bddb", "#67a9cf", "#1c9099", "#016c59"], "PuBuGn_09": ["#fff7fb", "#ece2f0", "#d0d1e6", "#a6bddb", "#67a9cf", "#3690c0", "#02818a", "#016c59", "#014636"], "PuBuGn_08": ["#fff7fb", "#ece2f0", "#d0d1e6", "#a6bddb", "#67a9cf", "#3690c0", "#02818a", "#016450"], "YlOrBr_04": ["#ffffd4", "#fed98e", "#fe9929", "#cc4c02"], "YlOrBr_05": ["#ffffd4", "#fed98e", "#fe9929", "#d95f0e", "#993404"], "Set1_07": ["#e41a1c", "#377eb8", "#4daf4a", "#984ea3", "#ff7f00", "#ffff33", "#a65628"], "YlOrBr_03": ["#fff7bc", "#fec44f", "#d95f0e"], "Set1_05": ["#e41a1c", "#377eb8", "#4daf4a", "#984ea3", "#ff7f00"], "YlOrRd_03": ["#ffeda0", "#feb24c", "#f03b20"], "PuOr_06": ["#b35806", "#f1a340", "#fee0b6", "#d8daeb", "#998ec3", "#542788"], "PuOr_07": ["#b35806", "#f1a340", "#fee0b6", "#f7f7f7", "#d8daeb", "#998ec3", "#542788"], "PuOr_04": ["#e66101", "#fdb863", "#b2abd2", "#5e3c99"], "PuOr_05": ["#e66101", "#fdb863", "#f7f7f7", "#b2abd2", "#5e3c99"], "PuOr_03": ["#f1a340", "#f7f7f7", "#998ec3"], "Purples_09": ["#fcfbfd", "#efedf5", "#dadaeb", "#bcbddc", "#9e9ac8", "#807dba", "#6a51a3", "#54278f", "#3f007d"], "Set2_06": ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f"], "RdYlBu_11": ["#a50026", "#d73027", "#f46d43", "#fdae61", "#fee090", "#ffffbf", "#e0f3f8", "#abd9e9", "#74add1", "#4575b4", "#313695"], "PuOr_08": ["#b35806", "#e08214", "#fdb863", "#fee0b6", "#d8daeb", "#b2abd2", "#8073ac", "#542788"], "PuOr_09": ["#b35806", "#e08214", "#fdb863", "#fee0b6", "#f7f7f7", "#d8daeb", "#b2abd2", "#8073ac", "#542788"], "Paired_03": ["#a6cee3", "#1f78b4", "#b2df8a"], "RdBu_03": ["#ef8a62", "#f7f7f7", "#67a9cf"], "RdYlBu_10": ["#a50026", "#d73027", "#f46d43", "#fdae61", "#fee090", "#e0f3f8", "#abd9e9", "#74add1", "#4575b4", "#313695"], "Paired_07": ["#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c", "#fdbf6f"], "Paired_06": ["#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c"], "Paired_05": ["#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99"], "Paired_04": ["#a6cee3", "#1f78b4", "#b2df8a", "#33a02c"], "Paired_09": ["#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c", "#fdbf6f", "#ff7f00", "#cab2d6"], "Paired_08": ["#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c", "#fdbf6f", "#ff7f00"], "RdGy_03": ["#ef8a62", "#ffffff", "#999999"], "PiYG_04": ["#d01c8b", "#f1b6da", "#b8e186", "#4dac26"], "Accent_03": ["#7fc97f", "#beaed4", "#fdc086"], "BuGn_08": ["#f7fcfd", "#e5f5f9", "#ccece6", "#99d8c9", "#66c2a4", "#41ae76", "#238b45", "#005824"], "BuGn_09": ["#f7fcfd", "#e5f5f9", "#ccece6", "#99d8c9", "#66c2a4", "#41ae76", "#238b45", "#006d2c", "#00441b"], "BuGn_04": ["#edf8fb", "#b2e2e2", "#66c2a4", "#238b45"], "BuGn_05": ["#edf8fb", "#b2e2e2", "#66c2a4", "#2ca25f", "#006d2c"], "BuGn_06": ["#edf8fb", "#ccece6", "#99d8c9", "#66c2a4", "#2ca25f", "#006d2c"], "BuGn_07": ["#edf8fb", "#ccece6", "#99d8c9", "#66c2a4", "#41ae76", "#238b45", "#005824"], "BuGn_03": ["#e5f5f9", "#99d8c9", "#2ca25f"], "YlGnBu_07": ["#ffffcc", "#c7e9b4", "#7fcdbb", "#41b6c4", "#1d91c0", "#225ea8", "#0c2c84"], "YlGnBu_06": ["#ffffcc", "#c7e9b4", "#7fcdbb", "#41b6c4", "#2c7fb8", "#253494"], "YlGnBu_05": ["#ffffcc", "#a1dab4", "#41b6c4", "#2c7fb8", "#253494"], "YlGnBu_04": ["#ffffcc", "#a1dab4", "#41b6c4", "#225ea8"], "YlGnBu_03": ["#edf8b1", "#7fcdbb", "#2c7fb8"], "RdBu_06": ["#b2182b", "#ef8a62", "#fddbc7", "#d1e5f0", "#67a9cf", "#2166ac"], "RdBu_05": ["#ca0020", "#f4a582", "#f7f7f7", "#92c5de", "#0571b0"], "RdBu_04": ["#ca0020", "#f4a582", "#92c5de", "#0571b0"], "Accent_08": ["#7fc97f", "#beaed4", "#fdc086", "#ffff99", "#386cb0", "#f0027f", "#bf5b17", "#666666"], "RdBu_09": ["#b2182b", "#d6604d", "#f4a582", "#fddbc7", "#f7f7f7", "#d1e5f0", "#92c5de", "#4393c3", "#2166ac"], "RdBu_08": ["#b2182b", "#d6604d", "#f4a582", "#fddbc7", "#d1e5f0", "#92c5de", "#4393c3", "#2166ac"], "Set2_04": ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3"], "YlGnBu_09": ["#ffffd9", "#edf8b1", "#c7e9b4", "#7fcdbb", "#41b6c4", "#1d91c0", "#225ea8", "#253494", "#081d58"], "YlGnBu_08": ["#ffffd9", "#edf8b1", "#c7e9b4", "#7fcdbb", "#41b6c4", "#1d91c0", "#225ea8", "#0c2c84"], "Blues_08": ["#f7fbff", "#deebf7", "#c6dbef", "#9ecae1", "#6baed6", "#4292c6", "#2171b5", "#084594"], "Blues_09": ["#f7fbff", "#deebf7", "#c6dbef", "#9ecae1", "#6baed6", "#4292c6", "#2171b5", "#08519c", "#08306b"], "RdPu_09": ["#fff7f3", "#fde0dd", "#fcc5c0", "#fa9fb5", "#f768a1", "#dd3497", "#ae017e", "#7a0177", "#49006a"], "RdPu_08": ["#fff7f3", "#fde0dd", "#fcc5c0", "#fa9fb5", "#f768a1", "#dd3497", "#ae017e", "#7a0177"], "Set3_07": ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69"], "Set3_06": ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462"], "RdPu_05": ["#feebe2", "#fbb4b9", "#f768a1", "#c51b8a", "#7a0177"], "RdPu_04": ["#feebe2", "#fbb4b9", "#f768a1", "#ae017e"], "RdPu_07": ["#feebe2", "#fcc5c0", "#fa9fb5", "#f768a1", "#dd3497", "#ae017e", "#7a0177"], "RdPu_06": ["#feebe2", "#fcc5c0", "#fa9fb5", "#f768a1", "#c51b8a", "#7a0177"], "Blues_06": ["#eff3ff", "#c6dbef", "#9ecae1", "#6baed6", "#3182bd", "#08519c"], "Blues_07": ["#eff3ff", "#c6dbef", "#9ecae1", "#6baed6", "#4292c6", "#2171b5", "#084594"], "RdPu_03": ["#fde0dd", "#fa9fb5", "#c51b8a"], "Blues_05": ["#eff3ff", "#bdd7e7", "#6baed6", "#3182bd", "#08519c"], "Paired_10": ["#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c", "#fdbf6f", "#ff7f00", "#cab2d6", "#6a3d9a"], "Paired_11": ["#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c", "#fdbf6f", "#ff7f00", "#cab2d6", "#6a3d9a", "#ffff99"], "Paired_12": ["#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c", "#fdbf6f", "#ff7f00", "#cab2d6", "#6a3d9a", "#ffff99", "#b15928"], "PuBu_06": ["#f1eef6", "#d0d1e6", "#a6bddb", "#74a9cf", "#2b8cbe", "#045a8d"], "PuBu_07": ["#f1eef6", "#d0d1e6", "#a6bddb", "#74a9cf", "#3690c0", "#0570b0", "#034e7b"], "PuBu_04": ["#f1eef6", "#bdc9e1", "#74a9cf", "#0570b0"], "PuBu_05": ["#f1eef6", "#bdc9e1", "#74a9cf", "#2b8cbe", "#045a8d"], "PuRd_05": ["#f1eef6", "#d7b5d8", "#df65b0", "#dd1c77", "#980043"], "PuBu_03": ["#ece7f2", "#a6bddb", "#2b8cbe"], "PuRd_07": ["#f1eef6", "#d4b9da", "#c994c7", "#df65b0", "#e7298a", "#ce1256", "#91003f"], "PuRd_06": ["#f1eef6", "#d4b9da", "#c994c7", "#df65b0", "#dd1c77", "#980043"], "PuRd_09": ["#f7f4f9", "#e7e1ef", "#d4b9da", "#c994c7", "#df65b0", "#e7298a", "#ce1256", "#980043", "#67001f"], "PuRd_08": ["#f7f4f9", "#e7e1ef", "#d4b9da", "#c994c7", "#df65b0", "#e7298a", "#ce1256", "#91003f"], "Set2_07": ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f", "#e5c494"], "PuBu_08": ["#fff7fb", "#ece7f2", "#d0d1e6", "#a6bddb", "#74a9cf", "#3690c0", "#0570b0", "#034e7b"], "PuBu_09": ["#fff7fb", "#ece7f2", "#d0d1e6", "#a6bddb", "#74a9cf", "#3690c0", "#0570b0", "#045a8d", "#023858"], "RdBu_10": ["#67001f", "#b2182b", "#d6604d", "#f4a582", "#fddbc7", "#d1e5f0", "#92c5de", "#4393c3", "#2166ac", "#053061"], "RdBu_11": ["#67001f", "#b2182b", "#d6604d", "#f4a582", "#fddbc7", "#f7f7f7", "#d1e5f0", "#92c5de", "#4393c3", "#2166ac", "#053061"], "Accent_06": ["#7fc97f", "#beaed4", "#fdc086", "#ffff99", "#386cb0", "#f0027f"], "Set3_03": ["#8dd3c7", "#ffffb3", "#bebada"], "Set3_05": ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3"], "Set3_12": ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", "#d9d9d9", "#bc80bd", "#ccebc5", "#ffed6f"], "Set3_10": ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", "#d9d9d9", "#bc80bd"], "Set3_04": ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072"], "RdGy_11": ["#67001f", "#b2182b", "#d6604d", "#f4a582", "#fddbc7", "#ffffff", "#e0e0e0", "#bababa", "#878787", "#4d4d4d", "#1a1a1a"], "RdGy_10": ["#67001f", "#b2182b", "#d6604d", "#f4a582", "#fddbc7", "#e0e0e0", "#bababa", "#878787", "#4d4d4d", "#1a1a1a"], "Set1_03": ["#e41a1c", "#377eb8", "#4daf4a"], "Set1_09": ["#e41a1c", "#377eb8", "#4daf4a", "#984ea3", "#ff7f00", "#ffff33", "#a65628", "#f781bf", "#999999"], "Set3_09": ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", "#d9d9d9"], "BuPu_08": ["#f7fcfd", "#e0ecf4", "#bfd3e6", "#9ebcda", "#8c96c6", "#8c6bb1", "#88419d", "#6e016b"], "BuPu_09": ["#f7fcfd", "#e0ecf4", "#bfd3e6", "#9ebcda", "#8c96c6", "#8c6bb1", "#88419d", "#810f7c", "#4d004b"], "RdYlGn_11": ["#a50026", "#d73027", "#f46d43", "#fdae61", "#fee08b", "#ffffbf", "#d9ef8b", "#a6d96a", "#66bd63", "#1a9850", "#006837"], "Blues_03": ["#deebf7", "#9ecae1", "#3182bd"], "Set2_05": ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854"], "BuPu_03": ["#e0ecf4", "#9ebcda", "#8856a7"], "BuPu_06": ["#edf8fb", "#bfd3e6", "#9ebcda", "#8c96c6", "#8856a7", "#810f7c"], "BuPu_07": ["#edf8fb", "#bfd3e6", "#9ebcda", "#8c96c6", "#8c6bb1", "#88419d", "#6e016b"], "BuPu_04": ["#edf8fb", "#b3cde3", "#8c96c6", "#88419d"], "BuPu_05": ["#edf8fb", "#b3cde3", "#8c96c6", "#8856a7", "#810f7c"], "Accent_04": ["#7fc97f", "#beaed4", "#fdc086", "#ffff99"], "YlOrRd_05": ["#ffffb2", "#fecc5c", "#fd8d3c", "#f03b20", "#bd0026"], "YlOrBr_08": ["#ffffe5", "#fff7bc", "#fee391", "#fec44f", "#fe9929", "#ec7014", "#cc4c02", "#8c2d04"], "Oranges_08": ["#fff5eb", "#fee6ce", "#fdd0a2", "#fdae6b", "#fd8d3c", "#f16913", "#d94801", "#8c2d04"], "Oranges_09": ["#fff5eb", "#fee6ce", "#fdd0a2", "#fdae6b", "#fd8d3c", "#f16913", "#d94801", "#a63603", "#7f2704"], "Oranges_06": ["#feedde", "#fdd0a2", "#fdae6b", "#fd8d3c", "#e6550d", "#a63603"], "Oranges_07": ["#feedde", "#fdd0a2", "#fdae6b", "#fd8d3c", "#f16913", "#d94801", "#8c2d04"], "Oranges_04": ["#feedde", "#fdbe85", "#fd8d3c", "#d94701"], "YlOrBr_09": ["#ffffe5", "#fff7bc", "#fee391", "#fec44f", "#fe9929", "#ec7014", "#cc4c02", "#993404", "#662506"], "Oranges_03": ["#fee6ce", "#fdae6b", "#e6550d"], "YlOrBr_06": ["#ffffd4", "#fee391", "#fec44f", "#fe9929", "#d95f0e", "#993404"], "Dark2_06": ["#1b9e77", "#d95f02", "#7570b3", "#e7298a", "#66a61e", "#e6ab02"], "Blues_04": ["#eff3ff", "#bdd7e7", "#6baed6", "#2171b5"], "YlOrBr_07": ["#ffffd4", "#fee391", "#fec44f", "#fe9929", "#ec7014", "#cc4c02", "#8c2d04"], "RdYlGn_05": ["#d7191c", "#fdae61", "#ffffbf", "#a6d96a", "#1a9641"], "Set3_08": ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5"], "YlOrRd_06": ["#ffffb2", "#fed976", "#feb24c", "#fd8d3c", "#f03b20", "#bd0026"], "Dark2_03": ["#1b9e77", "#d95f02", "#7570b3"], "Accent_05": ["#7fc97f", "#beaed4", "#fdc086", "#ffff99", "#386cb0"], "RdYlGn_08": ["#d73027", "#f46d43", "#fdae61", "#fee08b", "#d9ef8b", "#a6d96a", "#66bd63", "#1a9850"], "RdYlGn_09": ["#d73027", "#f46d43", "#fdae61", "#fee08b", "#ffffbf", "#d9ef8b", "#a6d96a", "#66bd63", "#1a9850"], "PuOr_11": ["#7f3b08", "#b35806", "#e08214", "#fdb863", "#fee0b6", "#f7f7f7", "#d8daeb", "#b2abd2", "#8073ac", "#542788", "#2d004b"], "YlOrRd_07": ["#ffffb2", "#fed976", "#feb24c", "#fd8d3c", "#fc4e2a", "#e31a1c", "#b10026"], "Spectral_11": ["#9e0142", "#d53e4f", "#f46d43", "#fdae61", "#fee08b", "#ffffbf", "#e6f598", "#abdda4", "#66c2a5", "#3288bd", "#5e4fa2"], "RdGy_08": ["#b2182b", "#d6604d", "#f4a582", "#fddbc7", "#e0e0e0", "#bababa", "#878787", "#4d4d4d"], "RdGy_09": ["#b2182b", "#d6604d", "#f4a582", "#fddbc7", "#ffffff", "#e0e0e0", "#bababa", "#878787", "#4d4d4d"], "RdGy_06": ["#b2182b", "#ef8a62", "#fddbc7", "#e0e0e0", "#999999", "#4d4d4d"], "RdGy_07": ["#b2182b", "#ef8a62", "#fddbc7", "#ffffff", "#e0e0e0", "#999999", "#4d4d4d"], "RdGy_04": ["#ca0020", "#f4a582", "#bababa", "#404040"], "RdGy_05": ["#ca0020", "#f4a582", "#ffffff", "#bababa", "#404040"], "RdYlGn_04": ["#d7191c", "#fdae61", "#a6d96a", "#1a9641"], "PiYG_09": ["#c51b7d", "#de77ae", "#f1b6da", "#fde0ef", "#f7f7f7", "#e6f5d0", "#b8e186", "#7fbc41", "#4d9221"], "RdYlGn_06": ["#d73027", "#fc8d59", "#fee08b", "#d9ef8b", "#91cf60", "#1a9850"], "RdYlGn_07": ["#d73027", "#fc8d59", "#fee08b", "#ffffbf", "#d9ef8b", "#91cf60", "#1a9850"], "Spectral_04": ["#d7191c", "#fdae61", "#abdda4", "#2b83ba"], "Spectral_05": ["#d7191c", "#fdae61", "#ffffbf", "#abdda4", "#2b83ba"], "Spectral_06": ["#d53e4f", "#fc8d59", "#fee08b", "#e6f598", "#99d594", "#3288bd"], "PiYG_08": ["#c51b7d", "#de77ae", "#f1b6da", "#fde0ef", "#e6f5d0", "#b8e186", "#7fbc41", "#4d9221"], "Set2_03": ["#66c2a5", "#fc8d62", "#8da0cb"], "Spectral_03": ["#fc8d59", "#ffffbf", "#99d594"], "Reds_08": ["#fff5f0", "#fee0d2", "#fcbba1", "#fc9272", "#fb6a4a", "#ef3b2c", "#cb181d", "#99000d"], "Set1_04": ["#e41a1c", "#377eb8", "#4daf4a", "#984ea3"], "Spectral_08": ["#d53e4f", "#f46d43", "#fdae61", "#fee08b", "#e6f598", "#abdda4", "#66c2a5", "#3288bd"], "Spectral_09": ["#d53e4f", "#f46d43", "#fdae61", "#fee08b", "#ffffbf", "#e6f598", "#abdda4", "#66c2a5", "#3288bd"], "Set2_08": ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f", "#e5c494", "#b3b3b3"], "Reds_09": ["#fff5f0", "#fee0d2", "#fcbba1", "#fc9272", "#fb6a4a", "#ef3b2c", "#cb181d", "#a50f15", "#67000d"], "Greys_07": ["#f7f7f7", "#d9d9d9", "#bdbdbd", "#969696", "#737373", "#525252", "#252525"], "Greys_06": ["#f7f7f7", "#d9d9d9", "#bdbdbd", "#969696", "#636363", "#252525"], "Greys_05": ["#f7f7f7", "#cccccc", "#969696", "#636363", "#252525"], "Greys_04": ["#f7f7f7", "#cccccc", "#969696", "#525252"], "Greys_03": ["#f0f0f0", "#bdbdbd", "#636363"], "PuOr_10": ["#7f3b08", "#b35806", "#e08214", "#fdb863", "#fee0b6", "#d8daeb", "#b2abd2", "#8073ac", "#542788", "#2d004b"], "Accent_07": ["#7fc97f", "#beaed4", "#fdc086", "#ffff99", "#386cb0", "#f0027f", "#bf5b17"], "Reds_06": ["#fee5d9", "#fcbba1", "#fc9272", "#fb6a4a", "#de2d26", "#a50f15"], "Greys_09": ["#ffffff", "#f0f0f0", "#d9d9d9", "#bdbdbd", "#969696", "#737373", "#525252", "#252525", "#000000"], "Greys_08": ["#ffffff", "#f0f0f0", "#d9d9d9", "#bdbdbd", "#969696", "#737373", "#525252", "#252525"], "Reds_07": ["#fee5d9", "#fcbba1", "#fc9272", "#fb6a4a", "#ef3b2c", "#cb181d", "#99000d"], "RdYlBu_08": ["#d73027", "#f46d43", "#fdae61", "#fee090", "#e0f3f8", "#abd9e9", "#74add1", "#4575b4"], "RdYlBu_09": ["#d73027", "#f46d43", "#fdae61", "#fee090", "#ffffbf", "#e0f3f8", "#abd9e9", "#74add1", "#4575b4"], "BrBG_09": ["#8c510a", "#bf812d", "#dfc27d", "#f6e8c3", "#f5f5f5", "#c7eae5", "#80cdc1", "#35978f", "#01665e"], "BrBG_08": ["#8c510a", "#bf812d", "#dfc27d", "#f6e8c3", "#c7eae5", "#80cdc1", "#35978f", "#01665e"], "BrBG_07": ["#8c510a", "#d8b365", "#f6e8c3", "#f5f5f5", "#c7eae5", "#5ab4ac", "#01665e"], "BrBG_06": ["#8c510a", "#d8b365", "#f6e8c3", "#c7eae5", "#5ab4ac", "#01665e"], "BrBG_05": ["#a6611a", "#dfc27d", "#f5f5f5", "#80cdc1", "#018571"], "BrBG_04": ["#a6611a", "#dfc27d", "#80cdc1", "#018571"], "BrBG_03": ["#d8b365", "#f5f5f5", "#5ab4ac"], "PiYG_06": ["#c51b7d", "#e9a3c9", "#fde0ef", "#e6f5d0", "#a1d76a", "#4d9221"], "Reds_03": ["#fee0d2", "#fc9272", "#de2d26"], "Set3_11": ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", "#d9d9d9", "#bc80bd", "#ccebc5"], "Set1_06": ["#e41a1c", "#377eb8", "#4daf4a", "#984ea3", "#ff7f00", "#ffff33"], "PuRd_03": ["#e7e1ef", "#c994c7", "#dd1c77"], "PiYG_07": ["#c51b7d", "#e9a3c9", "#fde0ef", "#f7f7f7", "#e6f5d0", "#a1d76a", "#4d9221"], "RdBu_07": ["#b2182b", "#ef8a62", "#fddbc7", "#f7f7f7", "#d1e5f0", "#67a9cf", "#2166ac"], "Pastel1_06": ["#fbb4ae", "#b3cde3", "#ccebc5", "#decbe4", "#fed9a6", "#ffffcc"], "Spectral_10": ["#9e0142", "#d53e4f", "#f46d43", "#fdae61", "#fee08b", "#e6f598", "#abdda4", "#66c2a5", "#3288bd", "#5e4fa2"], "PuRd_04": ["#f1eef6", "#d7b5d8", "#df65b0", "#ce1256"], "OrRd_03": ["#fee8c8", "#fdbb84", "#e34a33"], "PiYG_03": ["#e9a3c9", "#f7f7f7", "#a1d76a"], "Oranges_05": ["#feedde", "#fdbe85", "#fd8d3c", "#e6550d", "#a63603"], "OrRd_07": ["#fef0d9", "#fdd49e", "#fdbb84", "#fc8d59", "#ef6548", "#d7301f", "#990000"], "OrRd_06": ["#fef0d9", "#fdd49e", "#fdbb84", "#fc8d59", "#e34a33", "#b30000"], "OrRd_05": ["#fef0d9", "#fdcc8a", "#fc8d59", "#e34a33", "#b30000"], "OrRd_04": ["#fef0d9", "#fdcc8a", "#fc8d59", "#d7301f"], "Reds_04": ["#fee5d9", "#fcae91", "#fb6a4a", "#cb181d"], "Reds_05": ["#fee5d9", "#fcae91", "#fb6a4a", "#de2d26", "#a50f15"], "OrRd_09": ["#fff7ec", "#fee8c8", "#fdd49e", "#fdbb84", "#fc8d59", "#ef6548", "#d7301f", "#b30000", "#7f0000"], "OrRd_08": ["#fff7ec", "#fee8c8", "#fdd49e", "#fdbb84", "#fc8d59", "#ef6548", "#d7301f", "#990000"], "BrBG_10": ["#543005", "#8c510a", "#bf812d", "#dfc27d", "#f6e8c3", "#c7eae5", "#80cdc1", "#35978f", "#01665e", "#003c30"], "BrBG_11": ["#543005", "#8c510a", "#bf812d", "#dfc27d", "#f6e8c3", "#f5f5f5", "#c7eae5", "#80cdc1", "#35978f", "#01665e", "#003c30"], "PiYG_05": ["#d01c8b", "#f1b6da", "#f7f7f7", "#b8e186", "#4dac26"], "YlOrRd_08": ["#ffffcc", "#ffeda0", "#fed976", "#feb24c", "#fd8d3c", "#fc4e2a", "#e31a1c", "#b10026"], "GnBu_04": ["#f0f9e8", "#bae4bc", "#7bccc4", "#2b8cbe"], "GnBu_05": ["#f0f9e8", "#bae4bc", "#7bccc4", "#43a2ca", "#0868ac"], "GnBu_06": ["#f0f9e8", "#ccebc5", "#a8ddb5", "#7bccc4", "#43a2ca", "#0868ac"], "GnBu_07": ["#f0f9e8", "#ccebc5", "#a8ddb5", "#7bccc4", "#4eb3d3", "#2b8cbe", "#08589e"], "Purples_08": ["#fcfbfd", "#efedf5", "#dadaeb", "#bcbddc", "#9e9ac8", "#807dba", "#6a51a3", "#4a1486"], "GnBu_03": ["#e0f3db", "#a8ddb5", "#43a2ca"], "Purples_06": ["#f2f0f7", "#dadaeb", "#bcbddc", "#9e9ac8", "#756bb1", "#54278f"], "Purples_07": ["#f2f0f7", "#dadaeb", "#bcbddc", "#9e9ac8", "#807dba", "#6a51a3", "#4a1486"], "Purples_04": ["#f2f0f7", "#cbc9e2", "#9e9ac8", "#6a51a3"], "Purples_05": ["#f2f0f7", "#cbc9e2", "#9e9ac8", "#756bb1", "#54278f"], "GnBu_08": ["#f7fcf0", "#e0f3db", "#ccebc5", "#a8ddb5", "#7bccc4", "#4eb3d3", "#2b8cbe", "#08589e"], "GnBu_09": ["#f7fcf0", "#e0f3db", "#ccebc5", "#a8ddb5", "#7bccc4", "#4eb3d3", "#2b8cbe", "#0868ac", "#084081"], "YlOrRd_09": ["#ffffcc", "#ffeda0", "#fed976", "#feb24c", "#fd8d3c", "#fc4e2a", "#e31a1c", "#bd0026", "#800026"], "Purples_03": ["#efedf5", "#bcbddc", "#756bb1"], "RdYlBu_04": ["#d7191c", "#fdae61", "#abd9e9", "#2c7bb6"], "PRGn_09": ["#762a83", "#9970ab", "#c2a5cf", "#e7d4e8", "#f7f7f7", "#d9f0d3", "#a6dba0", "#5aae61", "#1b7837"], "PRGn_08": ["#762a83", "#9970ab", "#c2a5cf", "#e7d4e8", "#d9f0d3", "#a6dba0", "#5aae61", "#1b7837"], "PRGn_07": ["#762a83", "#af8dc3", "#e7d4e8", "#f7f7f7", "#d9f0d3", "#7fbf7b", "#1b7837"], "PRGn_06": ["#762a83", "#af8dc3", "#e7d4e8", "#d9f0d3", "#7fbf7b", "#1b7837"], "PRGn_05": ["#7b3294", "#c2a5cf", "#f7f7f7", "#a6dba0", "#008837"], "PRGn_04": ["#7b3294", "#c2a5cf", "#a6dba0", "#008837"], "PRGn_03": ["#af8dc3", "#f7f7f7", "#7fbf7b"], "RdYlBu_06": ["#d73027", "#fc8d59", "#fee090", "#e0f3f8", "#91bfdb", "#4575b4"], "RdYlGn_10": ["#a50026", "#d73027", "#f46d43", "#fdae61", "#fee08b", "#d9ef8b", "#a6d96a", "#66bd63", "#1a9850", "#006837"], "YlGn_08": ["#ffffe5", "#f7fcb9", "#d9f0a3", "#addd8e", "#78c679", "#41ab5d", "#238443", "#005a32"], "YlGn_09": ["#ffffe5", "#f7fcb9", "#d9f0a3", "#addd8e", "#78c679", "#41ab5d", "#238443", "#006837", "#004529"], "RdYlBu_07": ["#d73027", "#fc8d59", "#fee090", "#ffffbf", "#e0f3f8", "#91bfdb", "#4575b4"], "PiYG_10": ["#8e0152", "#c51b7d", "#de77ae", "#f1b6da", "#fde0ef", "#e6f5d0", "#b8e186", "#7fbc41", "#4d9221", "#276419"], "PiYG_11": ["#8e0152", "#c51b7d", "#de77ae", "#f1b6da", "#fde0ef", "#f7f7f7", "#e6f5d0", "#b8e186", "#7fbc41", "#4d9221", "#276419"], "YlGn_03": ["#f7fcb9", "#addd8e", "#31a354"], "YlGn_04": ["#ffffcc", "#c2e699", "#78c679", "#238443"], "YlGn_05": ["#ffffcc", "#c2e699", "#78c679", "#31a354", "#006837"], "YlGn_06": ["#ffffcc", "#d9f0a3", "#addd8e", "#78c679", "#31a354", "#006837"], "YlGn_07": ["#ffffcc", "#d9f0a3", "#addd8e", "#78c679", "#41ab5d", "#238443", "#005a32"], "Dark2_05": ["#1b9e77", "#d95f02", "#7570b3", "#e7298a", "#66a61e"], "Dark2_04": ["#1b9e77", "#d95f02", "#7570b3", "#e7298a"], "Dark2_07": ["#1b9e77", "#d95f02", "#7570b3", "#e7298a", "#66a61e", "#e6ab02", "#a6761d"], "Pastel2_03": ["#b3e2cd", "#fdcdac", "#cbd5e8"], "Pastel2_04": ["#b3e2cd", "#fdcdac", "#cbd5e8", "#f4cae4"], "Pastel2_05": ["#b3e2cd", "#fdcdac", "#cbd5e8", "#f4cae4", "#e6f5c9"], "Pastel2_06": ["#b3e2cd", "#fdcdac", "#cbd5e8", "#f4cae4", "#e6f5c9", "#fff2ae"], "Pastel2_07": ["#b3e2cd", "#fdcdac", "#cbd5e8", "#f4cae4", "#e6f5c9", "#fff2ae", "#f1e2cc"], "Pastel2_08": ["#b3e2cd", "#fdcdac", "#cbd5e8", "#f4cae4", "#e6f5c9", "#fff2ae", "#f1e2cc", "#cccccc"], "RdYlBu_03": ["#fc8d59", "#ffffbf", "#91bfdb"], "Dark2_08": ["#1b9e77", "#d95f02", "#7570b3", "#e7298a", "#66a61e", "#e6ab02", "#a6761d", "#666666"], "RdYlGn_03": ["#fc8d59", "#ffffbf", "#91cf60"], "PRGn_11": ["#40004b", "#762a83", "#9970ab", "#c2a5cf", "#e7d4e8", "#f7f7f7", "#d9f0d3", "#a6dba0", "#5aae61", "#1b7837", "#00441b"], "Greens_08": ["#f7fcf5", "#e5f5e0", "#c7e9c0", "#a1d99b", "#74c476", "#41ab5d", "#238b45", "#005a32"], "Greens_09": ["#f7fcf5", "#e5f5e0", "#c7e9c0", "#a1d99b", "#74c476", "#41ab5d", "#238b45", "#006d2c", "#00441b"], "Greens_06": ["#edf8e9", "#c7e9c0", "#a1d99b", "#74c476", "#31a354", "#006d2c"], "Greens_07": ["#edf8e9", "#c7e9c0", "#a1d99b", "#74c476", "#41ab5d", "#238b45", "#005a32"], "Greens_04": ["#edf8e9", "#bae4b3", "#74c476", "#238b45"], "Greens_05": ["#edf8e9", "#bae4b3", "#74c476", "#31a354", "#006d2c"], "PRGn_10": ["#40004b", "#762a83", "#9970ab", "#c2a5cf", "#e7d4e8", "#d9f0d3", "#a6dba0", "#5aae61", "#1b7837", "#00441b"], "Greens_03": ["#e5f5e0", "#a1d99b", "#31a354"]} -------------------------------------------------------------------------------- /branca/element.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Element 4 | ------- 5 | 6 | A generic class for creating Elements. 7 | """ 8 | import warnings 9 | from uuid import uuid4 10 | 11 | from jinja2 import Environment, PackageLoader, Template 12 | from collections import OrderedDict 13 | import json 14 | import base64 15 | 16 | from .six import urlopen, text_type, binary_type 17 | from .utilities import _camelify, _parse_size, none_min, none_max 18 | 19 | 20 | ENV = Environment(loader=PackageLoader('branca', 'templates')) 21 | 22 | 23 | class Element(object): 24 | """Basic Element object that does nothing. 25 | Other Elements may inherit from this one. 26 | 27 | Parameters 28 | ---------- 29 | template: str, default None 30 | A jinaj2-compatible template string for rendering the element. 31 | If None, template will be: 32 | {% for name, element in this._children.items() %} 33 | {{element.render(**kwargs)}} 34 | {% endfor %} 35 | so that all the element's children are rendered. 36 | template_name: str, default None 37 | If no template is provided, you can also provide a filename. 38 | """ 39 | def __init__(self, template=None, template_name=None): 40 | self._name = 'Element' 41 | self._id = uuid4().hex 42 | self._env = ENV 43 | self._children = OrderedDict() 44 | self._parent = None 45 | self._template = Template(template) if template is not None\ 46 | else ENV.get_template(template_name) if template_name is not None\ 47 | else Template( 48 | "{% for name, element in this._children.items() %}\n" 49 | " {{element.render(**kwargs)}}" 50 | "{% endfor %}" 51 | ) 52 | 53 | def get_name(self): 54 | """Returns a string representation of the object. 55 | This string has to be unique and to be a python and 56 | javascript-compatible 57 | variable name. 58 | """ 59 | return _camelify(self._name) + '_' + self._id 60 | 61 | def _get_self_bounds(self): 62 | """Computes the bounds of the object itself (not including it's children) 63 | in the form [[lat_min, lon_min], [lat_max, lon_max]] 64 | """ 65 | return [[None, None], [None, None]] 66 | 67 | def get_bounds(self): 68 | """Computes the bounds of the object and all it's children 69 | in the form [[lat_min, lon_min], [lat_max, lon_max]]. 70 | """ 71 | bounds = self._get_self_bounds() 72 | 73 | for child in self._children.values(): 74 | child_bounds = child.get_bounds() 75 | bounds = [ 76 | [ 77 | none_min(bounds[0][0], child_bounds[0][0]), 78 | none_min(bounds[0][1], child_bounds[0][1]), 79 | ], 80 | [ 81 | none_max(bounds[1][0], child_bounds[1][0]), 82 | none_max(bounds[1][1], child_bounds[1][1]), 83 | ], 84 | ] 85 | return bounds 86 | 87 | def add_children(self, child, name=None, index=None): 88 | """Add a child.""" 89 | warnings.warn("Method `add_children` is deprecated. Please use `add_child` instead.", 90 | FutureWarning, stacklevel=2) 91 | return self.add_child(child, name=name, index=index) 92 | 93 | def add_child(self, child, name=None, index=None): 94 | """Add a child.""" 95 | if name is None: 96 | name = child.get_name() 97 | if index is None: 98 | self._children[name] = child 99 | else: 100 | items = [item for item in self._children.items() 101 | if item[0] != name] 102 | items.insert(int(index), (name, child)) 103 | self._children = items 104 | child._parent = self 105 | return self 106 | 107 | def add_to(self, parent, name=None, index=None): 108 | """Add element to a parent.""" 109 | parent.add_child(self, name=name, index=index) 110 | return self 111 | 112 | def to_dict(self, depth=-1, ordered=True, **kwargs): 113 | """Returns a dict representation of the object.""" 114 | if ordered: 115 | dict_fun = OrderedDict 116 | else: 117 | dict_fun = dict 118 | out = dict_fun() 119 | out['name'] = self._name 120 | out['id'] = self._id 121 | if depth != 0: 122 | out['children'] = dict_fun([(name, child.to_dict(depth=depth-1)) 123 | for name, child in self._children.items()]) # noqa 124 | return out 125 | 126 | def to_json(self, depth=-1, **kwargs): 127 | """Returns a JSON representation of the object.""" 128 | return json.dumps(self.to_dict(depth=depth, ordered=True), **kwargs) 129 | 130 | def get_root(self): 131 | """Returns the root of the elements tree.""" 132 | if self._parent is None: 133 | return self 134 | else: 135 | return self._parent.get_root() 136 | 137 | def render(self, **kwargs): 138 | """Renders the HTML representation of the element.""" 139 | return self._template.render(this=self, kwargs=kwargs) 140 | 141 | def save(self, outfile, close_file=True, **kwargs): 142 | """Saves an Element into a file. 143 | 144 | Parameters 145 | ---------- 146 | outfile : str or file object 147 | The file (or filename) where you want to output the html. 148 | close_file : bool, default True 149 | Whether the file has to be closed after write. 150 | """ 151 | if isinstance(outfile, text_type) or isinstance(outfile, binary_type): 152 | fid = open(outfile, 'wb') 153 | else: 154 | fid = outfile 155 | 156 | root = self.get_root() 157 | html = root.render(**kwargs) 158 | fid.write(html.encode('utf8')) 159 | if close_file: 160 | fid.close() 161 | 162 | 163 | class Link(Element): 164 | """An abstract class for embedding a link in the HTML.""" 165 | def get_code(self): 166 | """Opens the link and returns the response's content.""" 167 | if self.code is None: 168 | self.code = urlopen(self.url).read() 169 | return self.code 170 | 171 | def to_dict(self, depth=-1, **kwargs): 172 | """Returns a dict representation of the object.""" 173 | out = super(Link, self).to_dict(depth=-1, **kwargs) 174 | out['url'] = self.url 175 | return out 176 | 177 | 178 | class JavascriptLink(Link): 179 | """Create a JavascriptLink object based on a url. 180 | Parameters 181 | ---------- 182 | url : str 183 | The url to be linked 184 | download : bool, default False 185 | Whether the target document shall be loaded right now. 186 | """ 187 | def __init__(self, url, download=False): 188 | super(JavascriptLink, self).__init__() 189 | self._name = 'JavascriptLink' 190 | self.url = url 191 | self.code = None 192 | if download: 193 | self.get_code() 194 | 195 | self._template = Template( 196 | '{% if kwargs.get("embedded",False) %}' 197 | '' 198 | '{% else %}' 199 | '' 200 | '{% endif %}' 201 | ) 202 | 203 | 204 | class CssLink(Link): 205 | """Create a CssLink object based on a url. 206 | Parameters 207 | ---------- 208 | url : str 209 | The url to be linked 210 | download : bool, default False 211 | Whether the target document shall be loaded right now. 212 | """ 213 | def __init__(self, url, download=False): 214 | super(CssLink, self).__init__() 215 | self._name = 'CssLink' 216 | self.url = url 217 | self.code = None 218 | if download: 219 | self.get_code() 220 | 221 | self._template = Template( 222 | '{% if kwargs.get("embedded",False) %}' 223 | '' 224 | '{% else %}' 225 | '' 226 | '{% endif %}' 227 | ) 228 | 229 | 230 | class Figure(Element): 231 | """Create a Figure object, to plot things into it. 232 | 233 | Parameters 234 | ---------- 235 | width : str, default "100%" 236 | The width of the Figure. 237 | It may be a percentage or pixel value (like "300px"). 238 | height : str, default None 239 | The height of the Figure. 240 | It may be a percentage or a pixel value (like "300px"). 241 | ratio : str, default "60%" 242 | A percentage defining the aspect ratio of the Figure. 243 | It will be ignored if height is not None. 244 | figsize : tuple of two int, default None 245 | If you're a matplotlib addict, you can overwrite width and 246 | height. Values will be converted into pixels in using 60 dpi. 247 | For example figsize=(10, 5) will result in 248 | width="600px", height="300px". 249 | """ 250 | def __init__(self, width="100%", height=None, ratio="60%", figsize=None): 251 | super(Figure, self).__init__() 252 | self._name = 'Figure' 253 | self.header = Element() 254 | self.html = Element() 255 | self.script = Element() 256 | 257 | self.header._parent = self 258 | self.html._parent = self 259 | self.script._parent = self 260 | 261 | self.width = width 262 | self.height = height 263 | self.ratio = ratio 264 | if figsize is not None: 265 | self.width = str(60*figsize[0])+'px' 266 | self.height = str(60*figsize[1])+'px' 267 | 268 | self._template = Template( 269 | '\n' 270 | '' 271 | ' {{this.header.render(**kwargs)}}\n' 272 | '\n' 273 | '' 274 | ' {{this.html.render(**kwargs)}}\n' 275 | '\n' 276 | '\n' 279 | ) 280 | 281 | # Create the meta tag. 282 | self.header.add_child(Element( 283 | ''), # noqa 284 | name='meta_http') 285 | 286 | def to_dict(self, depth=-1, **kwargs): 287 | """Returns a dict representation of the object.""" 288 | out = super(Figure, self).to_dict(depth=depth, **kwargs) 289 | out['header'] = self.header.to_dict(depth=depth-1, **kwargs) 290 | out['html'] = self.html.to_dict(depth=depth-1, **kwargs) 291 | out['script'] = self.script.to_dict(depth=depth-1, **kwargs) 292 | return out 293 | 294 | def get_root(self): 295 | """Returns the root of the elements tree.""" 296 | return self 297 | 298 | def render(self, **kwargs): 299 | """Renders the HTML representation of the element.""" 300 | for name, child in self._children.items(): 301 | child.render(**kwargs) 302 | return self._template.render(this=self, kwargs=kwargs) 303 | 304 | def _repr_html_(self, **kwargs): 305 | """Displays the Figure in a Jupyter notebook. 306 | 307 | Parameters 308 | ---------- 309 | 310 | """ 311 | html = self.render(**kwargs) 312 | html = "data:text/html;charset=utf-8;base64," + base64.b64encode(html.encode('utf8')).decode('utf8') # noqa 313 | 314 | if self.height is None: 315 | iframe = ( 316 | '
' 317 | '
' # noqa 318 | '' 322 | '
').format 323 | iframe = iframe(html=html, 324 | width=self.width, 325 | ratio=self.ratio) 326 | else: 327 | iframe = ('').format 331 | iframe = iframe(html=html, width=self.width, height=self.height) 332 | return iframe 333 | 334 | def add_subplot(self, x, y, n, margin=0.05): 335 | """Creates a div child subplot in a matplotlib.figure.add_subplot style. 336 | 337 | Parameters 338 | ---------- 339 | x : int 340 | The number of rows in the grid. 341 | y : int 342 | The number of columns in the grid. 343 | n : int 344 | The cell number in the grid, counted from 1 to x*y. 345 | 346 | Example: 347 | >>> fig.add_subplot(3,2,5) 348 | # Create a div in the 5th cell of a 3rows x 2columns 349 | grid(bottom-left corner). 350 | """ 351 | width = 1./y 352 | height = 1./x 353 | left = ((n-1) % y)*width 354 | top = ((n-1)//y)*height 355 | 356 | left = left+width*margin 357 | top = top+height*margin 358 | width = width*(1-2.*margin) 359 | height = height*(1-2.*margin) 360 | 361 | div = Div(position='absolute', 362 | width="{}%".format(100.*width), 363 | height="{}%".format(100.*height), 364 | left="{}%".format(100.*left), 365 | top="{}%".format(100.*top), 366 | ) 367 | self.add_child(div) 368 | return div 369 | 370 | 371 | class Html(Element): 372 | """Create an HTML div object for embedding data. 373 | 374 | Parameters 375 | ---------- 376 | data : str 377 | The HTML data to be embedded. 378 | script : bool 379 | If True, data will be embedded without escaping 380 | (suitable for embedding html-ready code) 381 | width : int or str, default '100%' 382 | The width of the output div element. 383 | Ex: 120 , '120px', '80%' 384 | height : int or str, default '100%' 385 | The height of the output div element. 386 | Ex: 120 , '120px', '80%' 387 | """ 388 | 389 | def __init__(self, data, script=False, width="100%", height="100%"): 390 | super(Html, self).__init__() 391 | self._name = 'Html' 392 | self.script = script 393 | self.data = data 394 | 395 | self.width = _parse_size(width) 396 | self.height = _parse_size(height) 397 | 398 | self._template = Template( 399 | '
' # noqa 401 | '{% if this.script %}{{this.data}}{% else %}{{this.data|e}}{% endif %}
' 402 | ) # noqa 403 | 404 | 405 | class Div(Figure): 406 | """Create a Div to be embedded in a Figure. 407 | 408 | Parameters 409 | ---------- 410 | width: int or str, default '100%' 411 | The width of the div in pixels (int) or percentage (str). 412 | height: int or str, default '100%' 413 | The height of the div in pixels (int) or percentage (str). 414 | left: int or str, default '0%' 415 | The left-position of the div in pixels (int) or percentage (str). 416 | top: int or str, default '0%' 417 | The top-position of the div in pixels (int) or percentage (str). 418 | position: str, default 'relative' 419 | The position policy of the div. 420 | Usual values are 'relative', 'absolute', 'fixed', 'static'. 421 | """ 422 | def __init__(self, width='100%', height='100%', 423 | left="0%", top="0%", position='relative'): 424 | super(Figure, self).__init__() 425 | self._name = 'Div' 426 | 427 | # Size Parameters. 428 | self.width = _parse_size(width) 429 | self.height = _parse_size(height) 430 | self.left = _parse_size(left) 431 | self.top = _parse_size(top) 432 | self.position = position 433 | 434 | self.header = Element() 435 | self.html = Element( 436 | '{% for name, element in this._children.items() %}' 437 | '{{element.render(**kwargs)}}' 438 | '{% endfor %}' 439 | ) 440 | self.script = Element() 441 | 442 | self.header._parent = self 443 | self.html._parent = self 444 | self.script._parent = self 445 | 446 | self._template = Template( 447 | '{% macro header(this, kwargs) %}' 448 | '' 455 | '{% endmacro %}' 456 | '{% macro html(this, kwargs) %}' 457 | '
{{this.html.render(**kwargs)}}
' 458 | '{% endmacro %}' 459 | ) 460 | 461 | def get_root(self): 462 | """Returns the root of the elements tree.""" 463 | return self 464 | 465 | def render(self, **kwargs): 466 | """Renders the HTML representation of the element.""" 467 | figure = self._parent 468 | assert isinstance(figure, Figure), ("You cannot render this Element " 469 | "if it's not in a Figure.") 470 | 471 | for name, element in self._children.items(): 472 | element.render(**kwargs) 473 | 474 | for name, element in self.header._children.items(): 475 | figure.header.add_child(element, name=name) 476 | 477 | for name, element in self.script._children.items(): 478 | figure.script.add_child(element, name=name) 479 | 480 | header = self._template.module.__dict__.get('header', None) 481 | if header is not None: 482 | figure.header.add_child(Element(header(self, kwargs)), 483 | name=self.get_name()) 484 | 485 | html = self._template.module.__dict__.get('html', None) 486 | if html is not None: 487 | figure.html.add_child(Element(html(self, kwargs)), 488 | name=self.get_name()) 489 | 490 | script = self._template.module.__dict__.get('script', None) 491 | if script is not None: 492 | figure.script.add_child(Element(script(self, kwargs)), 493 | name=self.get_name()) 494 | 495 | def _repr_html_(self, **kwargs): 496 | """Displays the Div in a Jupyter notebook.""" 497 | if self._parent is None: 498 | self.add_to(Figure()) 499 | out = self._parent._repr_html_(**kwargs) 500 | self._parent = None 501 | else: 502 | out = self._parent._repr_html_(**kwargs) 503 | return out 504 | 505 | 506 | class IFrame(Element): 507 | """Create a Figure object, to plot things into it. 508 | 509 | Parameters 510 | ---------- 511 | html : str, default None 512 | Eventual HTML code that you want to put in the frame. 513 | width : str, default "100%" 514 | The width of the Figure. 515 | It may be a percentage or pixel value (like "300px"). 516 | height : str, default None 517 | The height of the Figure. 518 | It may be a percentage or a pixel value (like "300px"). 519 | ratio : str, default "60%" 520 | A percentage defining the aspect ratio of the Figure. 521 | It will be ignored if height is not None. 522 | figsize : tuple of two int, default None 523 | If you're a matplotlib addict, you can overwrite width and 524 | height. Values will be converted into pixels in using 60 dpi. 525 | For example figsize=(10, 5) will result in 526 | width="600px", height="300px". 527 | """ 528 | def __init__(self, html=None, width="100%", height=None, ratio="60%", 529 | figsize=None): 530 | super(IFrame, self).__init__() 531 | self._name = 'IFrame' 532 | 533 | self.width = width 534 | self.height = height 535 | self.ratio = ratio 536 | if figsize is not None: 537 | self.width = str(60*figsize[0])+'px' 538 | self.height = str(60*figsize[1])+'px' 539 | 540 | if isinstance(html, text_type) or isinstance(html, binary_type): 541 | self.add_child(Element(html)) 542 | elif html is not None: 543 | self.add_child(html) 544 | 545 | def render(self, **kwargs): 546 | """Renders the HTML representation of the element.""" 547 | html = super(IFrame, self).render(**kwargs) 548 | html = "data:text/html;charset=utf-8;base64," + base64.b64encode(html.encode('utf8')).decode('utf8') # noqa 549 | 550 | if self.height is None: 551 | iframe = ( 552 | '
' 553 | '
' # noqa 554 | '' 557 | '
').format 558 | iframe = iframe(html=html, 559 | width=self.width, 560 | ratio=self.ratio) 561 | else: 562 | iframe = ('').format 564 | iframe = iframe(html=html, width=self.width, height=self.height) 565 | return iframe 566 | 567 | 568 | class MacroElement(Element): 569 | """This is a parent class for Elements defined by a macro template. 570 | To compute your own element, all you have to do is: 571 | 572 | * To inherit from this class 573 | * Overwrite the '_name' attribute 574 | * Overwrite the '_template' attribute with something of the form:: 575 | 576 | {% macro header(this, kwargs) %} 577 | ... 578 | {% endmacro %} 579 | 580 | {% macro html(this, kwargs) %} 581 | ... 582 | {% endmacro %} 583 | 584 | {% macro script(this, kwargs) %} 585 | ... 586 | {% endmacro %} 587 | 588 | """ 589 | def __init__(self): 590 | super(MacroElement, self).__init__() 591 | self._name = 'MacroElement' 592 | 593 | self._template = Template(u"") 594 | 595 | def render(self, **kwargs): 596 | """Renders the HTML representation of the element.""" 597 | figure = self.get_root() 598 | assert isinstance(figure, Figure), ("You cannot render this Element " 599 | "if it's not in a Figure.") 600 | 601 | header = self._template.module.__dict__.get('header', None) 602 | if header is not None: 603 | figure.header.add_child(Element(header(self, kwargs)), 604 | name=self.get_name()) 605 | 606 | html = self._template.module.__dict__.get('html', None) 607 | if html is not None: 608 | figure.html.add_child(Element(html(self, kwargs)), 609 | name=self.get_name()) 610 | 611 | script = self._template.module.__dict__.get('script', None) 612 | if script is not None: 613 | figure.script.add_child(Element(script(self, kwargs)), 614 | name=self.get_name()) 615 | 616 | for name, element in self._children.items(): 617 | element.render(**kwargs) 618 | -------------------------------------------------------------------------------- /examples/Elements.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import sys\n", 12 | "sys.path.insert(0, '..')\n", 13 | "\n", 14 | "from branca.element import *" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "## Element" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "This is the base brick of `branca`. You can create an `Element` in providing a template string:" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 2, 34 | "metadata": { 35 | "collapsed": false 36 | }, 37 | "outputs": [], 38 | "source": [ 39 | "e = Element(\"This is fancy text\")" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "Each element has an attribute `_name` and a unique `_id`. You also have a method `get_name` to get a unique string representation of the element." 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 3, 52 | "metadata": { 53 | "collapsed": false 54 | }, 55 | "outputs": [ 56 | { 57 | "name": "stdout", 58 | "output_type": "stream", 59 | "text": [ 60 | "Element a1d0f648f7444f96b526931944247fd6\n", 61 | "element_a1d0f648f7444f96b526931944247fd6\n" 62 | ] 63 | } 64 | ], 65 | "source": [ 66 | "print(e._name, e._id)\n", 67 | "print(e.get_name())" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "You can render an `Element` using the method `render`:" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": 4, 80 | "metadata": { 81 | "collapsed": false 82 | }, 83 | "outputs": [ 84 | { 85 | "data": { 86 | "text/plain": [ 87 | "'This is fancy text'" 88 | ] 89 | }, 90 | "execution_count": 4, 91 | "metadata": {}, 92 | "output_type": "execute_result" 93 | } 94 | ], 95 | "source": [ 96 | "e.render()" 97 | ] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "metadata": {}, 102 | "source": [ 103 | "In the template, you can use keyword `this` for accessing the object itself ; and the keyword `kwargs` for accessing any keyword argument provided in the `render` method:" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 5, 109 | "metadata": { 110 | "collapsed": false 111 | }, 112 | "outputs": [ 113 | { 114 | "data": { 115 | "text/plain": [ 116 | "'Hello World, my name is `element_6f17661abddb45c7bf2aa794cadd327d`.'" 117 | ] 118 | }, 119 | "execution_count": 5, 120 | "metadata": {}, 121 | "output_type": "execute_result" 122 | } 123 | ], 124 | "source": [ 125 | "e = Element(\"Hello {{kwargs['you']}}, my name is `{{this.get_name()}}`.\")\n", 126 | "e.render(you='World')" 127 | ] 128 | }, 129 | { 130 | "cell_type": "markdown", 131 | "metadata": {}, 132 | "source": [ 133 | "Well, this is not really cool for now. What makes elements useful lies in the fact that you can create trees out of them. To do so, you can either use the method `add_child` or the method `add_to`." 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": 6, 139 | "metadata": { 140 | "collapsed": false 141 | }, 142 | "outputs": [], 143 | "source": [ 144 | "child = Element('This is the child.')\n", 145 | "parent = Element('This is the parent.').add_child(child)\n", 146 | "\n", 147 | "parent = Element('This is the parent.')\n", 148 | "child = Element('This is the child.').add_to(parent)" 149 | ] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "metadata": {}, 154 | "source": [ 155 | "Now in the example above, embedding the one in the other does not change anything." 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": 7, 161 | "metadata": { 162 | "collapsed": false 163 | }, 164 | "outputs": [ 165 | { 166 | "name": "stdout", 167 | "output_type": "stream", 168 | "text": [ 169 | "This is the parent. This is the child.\n" 170 | ] 171 | } 172 | ], 173 | "source": [ 174 | "print(parent.render(), child.render())" 175 | ] 176 | }, 177 | { 178 | "cell_type": "markdown", 179 | "metadata": {}, 180 | "source": [ 181 | "But you can use the tree structure in the template." 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": 8, 187 | "metadata": { 188 | "collapsed": false 189 | }, 190 | "outputs": [ 191 | { 192 | "data": { 193 | "text/plain": [ 194 | "''" 195 | ] 196 | }, 197 | "execution_count": 8, 198 | "metadata": {}, 199 | "output_type": "execute_result" 200 | } 201 | ], 202 | "source": [ 203 | "parent = Element(\"{% for child in this._children.values() %}{{child.render()}}{% endfor %}\")\n", 204 | "Element('').add_to(parent)\n", 205 | "Element('').add_to(parent)\n", 206 | "parent.render()" 207 | ] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "metadata": {}, 212 | "source": [ 213 | "As you can see, the child of an element are referenced in the `_children` attibute in the form of an `OrderedDict`. You can choose the key of each child in specifying a `name` in the `add_child` (or `add_to`) method:" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": 9, 219 | "metadata": { 220 | "collapsed": false 221 | }, 222 | "outputs": [ 223 | { 224 | "data": { 225 | "text/plain": [ 226 | "OrderedDict([('child_1', )])" 227 | ] 228 | }, 229 | "execution_count": 9, 230 | "metadata": {}, 231 | "output_type": "execute_result" 232 | } 233 | ], 234 | "source": [ 235 | "parent = Element(\"{% for child in this._children.values() %}{{child.render()}}{% endfor %}\")\n", 236 | "Element('').add_to(parent, name='child_1')\n", 237 | "parent._children" 238 | ] 239 | }, 240 | { 241 | "cell_type": "markdown", 242 | "metadata": {}, 243 | "source": [ 244 | "That way, it's possible to overwrite a child in specifying the same name:" 245 | ] 246 | }, 247 | { 248 | "cell_type": "code", 249 | "execution_count": 10, 250 | "metadata": { 251 | "collapsed": false 252 | }, 253 | "outputs": [ 254 | { 255 | "data": { 256 | "text/plain": [ 257 | "''" 258 | ] 259 | }, 260 | "execution_count": 10, 261 | "metadata": {}, 262 | "output_type": "execute_result" 263 | } 264 | ], 265 | "source": [ 266 | "Element('').add_to(parent, name='child_1')\n", 267 | "parent.render()" 268 | ] 269 | }, 270 | { 271 | "cell_type": "markdown", 272 | "metadata": {}, 273 | "source": [ 274 | "I hope you start to find it useful.\n", 275 | "\n", 276 | "In fact, the real interest of `Element` lies in the classes that inherit from it. The most important one is `Figure` described in the next section." 277 | ] 278 | }, 279 | { 280 | "cell_type": "markdown", 281 | "metadata": {}, 282 | "source": [ 283 | "## Figure\n", 284 | "\n", 285 | "A `Figure` represents an HTML document. It's composed of 3 parts (attributes):\n", 286 | "\n", 287 | "* `header` : corresponds to the `` part of the HTML document,\n", 288 | "* `html` : corresponds to the `` part,\n", 289 | "* `script` : corresponds to a `\n" 311 | ] 312 | } 313 | ], 314 | "source": [ 315 | "f = Figure()\n", 316 | "print(f.render())" 317 | ] 318 | }, 319 | { 320 | "cell_type": "markdown", 321 | "metadata": {}, 322 | "source": [ 323 | "You can for example create a beatiful cyan \"hello-world\" webpage in doing:" 324 | ] 325 | }, 326 | { 327 | "cell_type": "code", 328 | "execution_count": 12, 329 | "metadata": { 330 | "collapsed": false 331 | }, 332 | "outputs": [ 333 | { 334 | "name": "stdout", 335 | "output_type": "stream", 336 | "text": [ 337 | "\n", 338 | " \n", 339 | " \n", 340 | " \n", 341 | "\n", 342 | " \n", 343 | "

Hello world

\n", 344 | "\n", 345 | "\n" 347 | ] 348 | } 349 | ], 350 | "source": [ 351 | "f.header.add_child(Element(\"\"))\n", 352 | "f.html.add_child(Element(\"

Hello world

\"))\n", 353 | "print(f.render())" 354 | ] 355 | }, 356 | { 357 | "cell_type": "markdown", 358 | "metadata": {}, 359 | "source": [ 360 | "You can simply save the content of the `Figure` to a file, thanks to the `save` method:" 361 | ] 362 | }, 363 | { 364 | "cell_type": "code", 365 | "execution_count": 13, 366 | "metadata": { 367 | "collapsed": false 368 | }, 369 | "outputs": [ 370 | { 371 | "name": "stdout", 372 | "output_type": "stream", 373 | "text": [ 374 | "\n", 375 | " \n", 376 | " \n", 377 | " \n", 378 | "\n", 379 | " \n", 380 | "

Hello world

\n", 381 | "\n", 382 | "\n" 384 | ] 385 | } 386 | ], 387 | "source": [ 388 | "f.save('foo.html')\n", 389 | "print(open('foo.html').read())" 390 | ] 391 | }, 392 | { 393 | "cell_type": "markdown", 394 | "metadata": {}, 395 | "source": [ 396 | "If you want to visualize it in the notebook, you can let `Figure._repr_html_` method do it's job in typing: " 397 | ] 398 | }, 399 | { 400 | "cell_type": "code", 401 | "execution_count": 14, 402 | "metadata": { 403 | "collapsed": false 404 | }, 405 | "outputs": [ 406 | { 407 | "data": { 408 | "text/html": [ 409 | "
" 410 | ], 411 | "text/plain": [ 412 | "" 413 | ] 414 | }, 415 | "execution_count": 14, 416 | "metadata": {}, 417 | "output_type": "execute_result" 418 | } 419 | ], 420 | "source": [ 421 | "f" 422 | ] 423 | }, 424 | { 425 | "cell_type": "markdown", 426 | "metadata": {}, 427 | "source": [ 428 | "If this rendering is too large for you, you can force it's width and height:" 429 | ] 430 | }, 431 | { 432 | "cell_type": "code", 433 | "execution_count": 15, 434 | "metadata": { 435 | "collapsed": false 436 | }, 437 | "outputs": [ 438 | { 439 | "data": { 440 | "text/html": [ 441 | "" 442 | ], 443 | "text/plain": [ 444 | "" 445 | ] 446 | }, 447 | "execution_count": 15, 448 | "metadata": {}, 449 | "output_type": "execute_result" 450 | } 451 | ], 452 | "source": [ 453 | "f.width = 300\n", 454 | "f.height = 200\n", 455 | "f" 456 | ] 457 | }, 458 | { 459 | "cell_type": "markdown", 460 | "metadata": {}, 461 | "source": [ 462 | "Note that you can also define a `Figure`'s size in a matplotlib way:" 463 | ] 464 | }, 465 | { 466 | "cell_type": "code", 467 | "execution_count": 16, 468 | "metadata": { 469 | "collapsed": false 470 | }, 471 | "outputs": [ 472 | { 473 | "data": { 474 | "text/html": [ 475 | "" 476 | ], 477 | "text/plain": [ 478 | "" 479 | ] 480 | }, 481 | "execution_count": 16, 482 | "metadata": {}, 483 | "output_type": "execute_result" 484 | } 485 | ], 486 | "source": [ 487 | "Figure(figsize=(5,5))" 488 | ] 489 | }, 490 | { 491 | "cell_type": "markdown", 492 | "metadata": {}, 493 | "source": [ 494 | "## MacroElement" 495 | ] 496 | }, 497 | { 498 | "cell_type": "markdown", 499 | "metadata": {}, 500 | "source": [ 501 | "It happens you need to create elements that have multiple effects on a Figure. For this, you can use `MacroElement` whose template contains macros ; each macro writes something into the parent Figure's header, body and script." 502 | ] 503 | }, 504 | { 505 | "cell_type": "code", 506 | "execution_count": 17, 507 | "metadata": { 508 | "collapsed": false 509 | }, 510 | "outputs": [ 511 | { 512 | "name": "stdout", 513 | "output_type": "stream", 514 | "text": [ 515 | "\n", 516 | " \n", 517 | " \n", 518 | " This is header of macro_element_ea36a310ab8a4212a8c7ca754a4140fc\n", 519 | "\n", 520 | " \n", 521 | " This is html of macro_element_ea36a310ab8a4212a8c7ca754a4140fc\n", 522 | "\n", 523 | "\n" 526 | ] 527 | } 528 | ], 529 | "source": [ 530 | "macro = MacroElement()\n", 531 | "macro._template = Template(\n", 532 | " '{% macro header(this, kwargs) %}'\n", 533 | " 'This is header of {{this.get_name()}}'\n", 534 | " '{% endmacro %}'\n", 535 | "\n", 536 | " '{% macro html(this, kwargs) %}'\n", 537 | " 'This is html of {{this.get_name()}}'\n", 538 | " '{% endmacro %}'\n", 539 | "\n", 540 | " '{% macro script(this, kwargs) %}'\n", 541 | " 'This is script of {{this.get_name()}}'\n", 542 | " '{% endmacro %}'\n", 543 | " )\n", 544 | "\n", 545 | "print(Figure().add_child(macro).render())" 546 | ] 547 | }, 548 | { 549 | "cell_type": "markdown", 550 | "metadata": {}, 551 | "source": [ 552 | "## Link" 553 | ] 554 | }, 555 | { 556 | "cell_type": "markdown", 557 | "metadata": {}, 558 | "source": [ 559 | "To embed javascript and css links in the header, you can use these class:" 560 | ] 561 | }, 562 | { 563 | "cell_type": "code", 564 | "execution_count": 18, 565 | "metadata": { 566 | "collapsed": false 567 | }, 568 | "outputs": [ 569 | { 570 | "data": { 571 | "text/plain": [ 572 | "''" 573 | ] 574 | }, 575 | "execution_count": 18, 576 | "metadata": {}, 577 | "output_type": "execute_result" 578 | } 579 | ], 580 | "source": [ 581 | "js_link = JavascriptLink('https://example.com/javascript.js')\n", 582 | "js_link.render()" 583 | ] 584 | }, 585 | { 586 | "cell_type": "code", 587 | "execution_count": 19, 588 | "metadata": { 589 | "collapsed": false 590 | }, 591 | "outputs": [ 592 | { 593 | "data": { 594 | "text/plain": [ 595 | "''" 596 | ] 597 | }, 598 | "execution_count": 19, 599 | "metadata": {}, 600 | "output_type": "execute_result" 601 | } 602 | ], 603 | "source": [ 604 | "css_link = CssLink('https://example.com/style.css')\n", 605 | "css_link.render()" 606 | ] 607 | }, 608 | { 609 | "cell_type": "markdown", 610 | "metadata": {}, 611 | "source": [ 612 | "## Html" 613 | ] 614 | }, 615 | { 616 | "cell_type": "markdown", 617 | "metadata": {}, 618 | "source": [ 619 | "An `Html` element enables you to create custom div to put in the *body* of your page." 620 | ] 621 | }, 622 | { 623 | "cell_type": "code", 624 | "execution_count": 26, 625 | "metadata": { 626 | "collapsed": false 627 | }, 628 | "outputs": [ 629 | { 630 | "data": { 631 | "text/plain": [ 632 | "'
Hello world
'" 633 | ] 634 | }, 635 | "execution_count": 26, 636 | "metadata": {}, 637 | "output_type": "execute_result" 638 | } 639 | ], 640 | "source": [ 641 | "html = Html('Hello world')\n", 642 | "html.render()" 643 | ] 644 | }, 645 | { 646 | "cell_type": "markdown", 647 | "metadata": {}, 648 | "source": [ 649 | "It's designed to render the text *as you gave it*, so it won't work directly it you want to embed HTML code inside the div." 650 | ] 651 | }, 652 | { 653 | "cell_type": "code", 654 | "execution_count": 25, 655 | "metadata": { 656 | "collapsed": false 657 | }, 658 | "outputs": [ 659 | { 660 | "data": { 661 | "text/plain": [ 662 | "'
<b>Hello world</b>
'" 663 | ] 664 | }, 665 | "execution_count": 25, 666 | "metadata": {}, 667 | "output_type": "execute_result" 668 | } 669 | ], 670 | "source": [ 671 | "Html('Hello world').render()" 672 | ] 673 | }, 674 | { 675 | "cell_type": "markdown", 676 | "metadata": {}, 677 | "source": [ 678 | "For this, you have to set `script=True` and it will work:" 679 | ] 680 | }, 681 | { 682 | "cell_type": "code", 683 | "execution_count": 28, 684 | "metadata": { 685 | "collapsed": false 686 | }, 687 | "outputs": [ 688 | { 689 | "data": { 690 | "text/plain": [ 691 | "'
Hello world
'" 692 | ] 693 | }, 694 | "execution_count": 28, 695 | "metadata": {}, 696 | "output_type": "execute_result" 697 | } 698 | ], 699 | "source": [ 700 | "Html('Hello world', script=True).render()" 701 | ] 702 | }, 703 | { 704 | "cell_type": "markdown", 705 | "metadata": {}, 706 | "source": [ 707 | "## IFrame" 708 | ] 709 | }, 710 | { 711 | "cell_type": "markdown", 712 | "metadata": {}, 713 | "source": [ 714 | "If you need to embed a full webpage (with separate javascript environment), you can use `IFrame`." 715 | ] 716 | }, 717 | { 718 | "cell_type": "code", 719 | "execution_count": 21, 720 | "metadata": { 721 | "collapsed": false 722 | }, 723 | "outputs": [ 724 | { 725 | "data": { 726 | "text/plain": [ 727 | "'
'" 728 | ] 729 | }, 730 | "execution_count": 21, 731 | "metadata": {}, 732 | "output_type": "execute_result" 733 | } 734 | ], 735 | "source": [ 736 | "iframe = IFrame('Hello World')\n", 737 | "iframe.render()" 738 | ] 739 | }, 740 | { 741 | "cell_type": "markdown", 742 | "metadata": {}, 743 | "source": [ 744 | "As you can see, it will embed the full content of the iframe in a *base64* string so that the ouput looks like:" 745 | ] 746 | }, 747 | { 748 | "cell_type": "code", 749 | "execution_count": 22, 750 | "metadata": { 751 | "collapsed": false 752 | }, 753 | "outputs": [ 754 | { 755 | "data": { 756 | "text/html": [ 757 | "" 758 | ], 759 | "text/plain": [ 760 | "" 761 | ] 762 | }, 763 | "execution_count": 22, 764 | "metadata": {}, 765 | "output_type": "execute_result" 766 | } 767 | ], 768 | "source": [ 769 | "f = Figure(height=180)\n", 770 | "f.html.add_child(Element(\"Before the frame\"))\n", 771 | "f.html.add_child(IFrame('In the frame', height='100px'))\n", 772 | "f.html.add_child(Element(\"After the frame\"))\n", 773 | "f" 774 | ] 775 | }, 776 | { 777 | "cell_type": "markdown", 778 | "metadata": {}, 779 | "source": [ 780 | "## Div" 781 | ] 782 | }, 783 | { 784 | "cell_type": "markdown", 785 | "metadata": {}, 786 | "source": [ 787 | "At last, you have the `Div` element that behaves almost like `Html` with a few differences:\n", 788 | "\n", 789 | "* The style is put in the header, while `Html`'s style is embedded inline.\n", 790 | "* `Div` inherits from `MacroElement` so that:\n", 791 | " * It cannot be rendered unless it's embedded in a `Figure`.\n", 792 | " * It is a useful object toinherit from when you create new classes." 793 | ] 794 | }, 795 | { 796 | "cell_type": "code", 797 | "execution_count": 29, 798 | "metadata": { 799 | "collapsed": false 800 | }, 801 | "outputs": [ 802 | { 803 | "name": "stdout", 804 | "output_type": "stream", 805 | "text": [ 806 | "\n", 807 | " \n", 808 | " \n", 809 | " \n", 816 | "\n", 817 | " \n", 818 | "
Hello world
\n", 819 | "\n", 820 | "\n" 822 | ] 823 | } 824 | ], 825 | "source": [ 826 | "div = Div()\n", 827 | "div.html.add_child(Element('Hello world'))\n", 828 | "print(Figure().add_child(div).render())" 829 | ] 830 | } 831 | ], 832 | "metadata": { 833 | "kernelspec": { 834 | "display_name": "Python 3", 835 | "language": "python", 836 | "name": "python3" 837 | }, 838 | "language_info": { 839 | "codemirror_mode": { 840 | "name": "ipython", 841 | "version": 3 842 | }, 843 | "file_extension": ".py", 844 | "mimetype": "text/x-python", 845 | "name": "python", 846 | "nbconvert_exporter": "python", 847 | "pygments_lexer": "ipython3", 848 | "version": "3.5.1" 849 | } 850 | }, 851 | "nbformat": 4, 852 | "nbformat_minor": 0 853 | } 854 | --------------------------------------------------------------------------------