Using Highcharts Maps for Python
Introduction to Highcharts Maps and Highcharts for Python
Highcharts Maps is the gold-standard in JavaScript data visualization libraries for map and GIS data, enabling you to design rich, beautiful, and highly interactive data visualizations of (almost) any kind imaginable, and to render those visualizations in your web or mobile applications. Take a look at some of the customer showcases and demo gallery to see some examples of what you can do with Highcharts Maps.
Highcharts Maps for Python is a Python wrapper for the Highcharts Maps JavaScript library, which means that it is designed to give developers working in Python a simple and Pythonic way of interacting with Highcharts Maps (JS).
Highcharts Maps for Python will not render data visualizations itself - that’s what Highcharts Maps (JS) does - but it will allow you to:
Configure your data visualizations in Python.
Supply data you have in Python to your data visualizations.
Programmatically produce the Highcharts Maps JavaScript code that will actually render your data visualization.
Programmatically download a static version of your visualization (as needed) within Python.
Tip
Think of Highcharts Maps for Python as a translator to bridge your data visualization needs between Python and JavaScript.
Key Design Patterns in Highcharts for Python
Highcharts is a large, robust, and complicated JavaScript library. If in doubt, take a look at its extensive documentation and in particular its API reference [2].
Because the Highcharts for Python Toolkit wraps the Highcharts (JS) API, its design is heavily shaped by Highcharts JS’ own design - as one should expect.
However, one of the main goals of the Python toolkit is to make it easier for Python developers to leverage the Highcharts JavaScript libraries - in particular by providing a more Pythonic way of interacting with the framework.
Here are the notable design patterns that have been adopted that you should be aware of:
Code Style: Python vs JavaScript Naming Conventions
There are only two hard things in Computer Science: cache invalidation and naming things. – Phil Karlton
Highcharts Maps is a JavaScript library, and as such it adheres to the code conventions
that are popular (practically standard) when working in JavaScript. Chief among these
conventions is that variables and object properties (keys) are typically written in
camelCase
.
A lot of (digital) ink has been spilled writing about the pros and cons of camelCase
vs snake_case
. While we have a scientific evidence-based opinion on the matter, in
practice it is simply a convention that developers adopt in a particular programming
language. The issue, however, is that while JavaScript has adopted the camelCase
convention, Python generally skews towards the snake_case
convention.
For most Python developers, using snake_case
is the “default” mindset. Most of your
Python code will use snake_case
. So having to switch into camelcase
to interact
with Highcharts Maps forces us to context switch, increases cognitive load, and is an
easy place for us to overlook things and make a mistake that can be quite annoying to
track down and fix later.
Therefore, when designing the Highcharts for Python Toolkit, we made several carefully considered design choices when it comes to naming conventions:
All Highcharts for Python classes follow the Pythonic
PascalCase
class-naming convention.All Highcharts for Python properties and methods follow the Pythonic
snake_case
property/method/variable/function-naming convention.All inputs to properties support both
snake_case
andcamelCase
(akamixedCase
) convention by default. This means that you can take something directly from Highcharts JavaScript code and supply it to the Highcharts for Python Toolkit without having to convert case or conventions. But if you are constructing and configuring something directly in Python using explicit deserialization methods, you can usesnake_case
if you prefer (and most Python developers will prefer).For example, if you supply a JSON file to a
from_json()
method, that file can leverage Highcharts JS naturalcamelCase
convention OR Highcharts for Python’ssnake_case
convention.Warning
Note that this dual-convention support only applies to deserialization methods and does not apply to the Highcharts for Python
__init__()
class constructors. All__init__()
methods expectsnake_case
properties to be supplied as keywords.All outputs from serialization methods (e.g.
to_dict()
orto_js_literal()
) will produce outputs that are Highcharts JS-compatible, meaning that they apply thecamelCase
convention.
Tip
Best Practice
If you are using external files to provide templates or themes for your Highcharts
data visualizations, produce those external files using Highcharts JS’ natural
camelCase
convention. That will make it easier to re-use them elsewhere within a
JavaScript context if you need to in the future.
Standard Methods
Every single object supported by the Highcharts Maps API corresponds to a Python class in Highcharts Maps for Python. You can find the complete list in our comprehensive Highcharts Maps for Python API Reference.
These classes generally inherit from the
HighchartsMeta
metaclass, which
provides each class with a number of standard methods. These methods are the “workhorses”
of Highcharts Maps for Python and you will be relying heavily on them when
using the library. Thankfully, their signatures and behavior is generally consistent -
even if what happens “under the hood” is class-specific at times.
The standard methods exposed by the classes are:
Deserialization Methods
- classmethod from_js_literal(cls, as_string_or_file, allow_snake_case=True)
Convert a JavaScript object defined using JavaScript object literal notation into a Highcharts for Python Python object, typically descended from
HighchartsMeta
.
- Parameters:
cls (
type
) – The class object itself.as_string_or_file (
str
) – The JavaScript object you wish to convert. Expects either astr
containing the JavaScript object, or a path to a file which consists of the object.allow_snake_case (
bool
) – IfTrue
, allows keys inas_string_or_file
to apply thesnake_case
convention. IfFalse
, will ignore keys that apply thesnake_case
convention and only process keys that use thecamelCase
convention. Defaults toTrue
.- Returns:
A Highcharts for Python object corresponding to the JavaScript object supplied in
as_string_or_file
.- Return type:
Descendent of
HighchartsMeta
- classmethod from_json(cls, as_json_or_file, allow_snake_case=True)
Convert a Highcharts JS object represented as JSON (in either
str
orbytes
form, or as a file name) into a Highcharts for Python object, typically descended fromHighchartsMeta
.
- Parameters:
cls (
type
) – The class object itself.as_json_or_file (
str
orbytes
) – The JSON object you wish to convert, or a filename that contains the JSON object that you wish to convert.allow_snake_case (
bool
) – IfTrue
, allows keys inas_json
to apply thesnake_case
convention. IfFalse
, will ignore keys that apply thesnake_case
convention and only process keys that use thecamelCase
convention. Defaults toTrue
.- Returns:
A Highcharts for Python Python object corresponding to the JSON object supplied in
as_json
.- Return type:
Descendent of
HighchartsMeta
- classmethod from_dict(cls, as_dict, allow_snake_case=True)
Convert a
dict
representation of a Highcharts JS object into a Python object representation, typically descended fromHighchartsMeta
.
Serialization Methods
- to_js_literal(self, filename=None, encoding='utf-8')
Convert the Highcharts Maps for Python instance to Highcharts Maps-compatible JavaScript code using JavaScript object literal notation.
- Parameters:
- Returns:
Highcharts Maps-compatible JavaScript code using JavaScript object literal notation.
- Return type:
- to_json(self, filename=None, encoding='utf-8')
Convert the Highcharts Maps for Python instance to Highcharts Maps-compatible JSON.
Warning
While similar, JSON is inherently different from JavaScript object literal notation. In particular, it cannot include JavaScript functions. This means if you try to convert a Highcharts for Python object to JSON, any properties that are
CallbackFunction
instances will not be included. If you want to convert those functions, please use.to_js_literal()
instead.
- Parameters:
- Returns:
Highcharts Maps-compatible JSON representation of the object.
- Return type:
Note
Highcharts Maps for Python works with different JSON encoders. If your environment has orjson, for example, the result will be returned as a
bytes
instance. Otherwise, the library will fallback to various other JSON encoders until finally falling back to the Python standard library’s JSON encoder/decoder.
Other Convenience Methods
- copy(self, other, overwrite=True, **kwargs)
Copy the properties from
self
toother
.
- Parameters:
other (
HighchartsMeta
) – The target instance to which the properties of this instance should be copied.overwrite (
bool
) – ifTrue
, properties inother
that are already set will be overwritten by their counterparts inself
. Defaults toTrue
.kwargs – Additional keyword arguments. Some special descendants of
HighchartsMeta
may have special implementations of this method which rely on additional keyword arguments.- Returns:
A mutated version of
other
with new property values- Raises:
HighchartsValueError – if
other
is not the same class as (or subclass of)self
Handling Default Values
Explicit is better than implicit. – The Zen of Python
Highcharts Maps has a lot of default
values. These default values are generally applied if a JavaScript property is
undefined
(missing or otherwise not specified), which is different from the JavaScript
value of null
.
While our Pythonic instinct is to:
indicate those default values explicitly in the Highcharts for Python code as keyword argument defaults, and
return those default values in the serialized form of any Highcharts for Python objects
doing so would introduce a massive problem: It would bloat data transferred on the wire unnecessarily.
The way that Highcharts Maps handles defaults is an elegant compromise between explicitness and the practical reality of making your code readable. Why make a property explicit in a configuration string if you don’t care about it? Purity is only valuable to a point. And with thousands of properties across the Highcharts JS and Highcharts Maps libraries, nobody wants to transmit or maintain thousands of property configurations if it can be avoided.
For that reason, the Highcharts for Python Toolkit explicitly breaks Pythonic
convention: when an object’s property returns None
, that has the
equivalent meaning of “Highcharts JS/Maps will apply the Highcharts default for this
property”. These properties will not be serialized, either to a JS literal, nor to a
dict
, nor to JSON. This has the advantage of maintaining consistent
behavior with Highcharts JS and
Highcharts Maps while still providing an
internally consistent logic to follow.
Module Structure
The structure of the Highcharts Maps for Python library closely matches the structure of the Highcharts Maps options object (see the relevant reference documentation).
At the root of the library - importable from highcharts_maps
- you will find the
highcharts_maps.highcharts
module. This module is a catch-all importable module,
which allows you to easily access the most-commonly-used Highcharts Maps for Python
classes and modules.
Note
Whlie you can access all of the Highcharts Maps for Python classes from
highcharts_maps.highcharts
, if you want to more precisely navigate to specific
class definitions you can do fairly easily using the module organization and naming
conventions used in the library.
This is the recommended best practice to maximize performance.
In the root of the highcharts_maps
library you can find universally-shared
class definitions, like .metaclasses
which
contains the HighchartsMeta
and JavaScriptDict
definitions, or .decorators
which define
method/property decorators that are used throughout the library.
The .utility_classes
module contains class
definitions for classes that are referenced or used throughout the other class
definitions.
And you can find the Highcharts Maps options
object and all of its
properties defined in the .options
module, with
specific (complicated or extensive) sub-modules providing property-specific classes
(e.g. the .options.plot_options
module defines all of the different configuration options for different series types,
while the .options.series
module defines all
of the classes that represent series of data in a given chart).
Class Structures and Inheritance
Highcharts Maps objects re-use many of
the same properties. This is one of the strengths of the Highcharts API, in that it is
internally consistent and that behavior configured on one object should be readily
transferrable to a second object provided it shares the same properties. However,
Highcharts Maps has a lot of properties. For example, we estimate that
the options.plotOptions
objects and their sub-properties have close to 2,000
properties. But because they are heavily repeated, those 2,000 or so properties can be
reduced to only 345 unique property names. That’s almost an 83% reduction.
DRY is an important principle in software development. Can you imagine propagating changes in seven places (on average) in your code? That would be a maintenance nightmare! And it is exactly the kind of maintenance nightmare that class inheritance was designed to fix.
For that reason, the Highcharts for Python Toolkit’s classes have a deeply nested
inheritance structure. This is important to understand both for evaluating
isinstance()
checks in your code, or for understanding how to
further subclass Highcharts for Python components.
See also
For more details, please review the API documentation, in particular the class inheritance diagrams included for each documented class.
Warning
Certain sections of the Highcharts Maps for Python library - in particular the
options.series
classes - rely heavily on
multiple inheritance. This is a known anti-pattern in Python development as it runs the
risk of encountering the diamond of death inheritance problem. This complicates
the process of inheriting methods or properties from parent classes when properties or
methods share names across multiple parents.
We know the diamond of death is an anti-pattern, but it was a necessary one to minimize code duplication and maximize consistency. For that reason, we implemented it properly despite the anti-pattern, using some advanced Python concepts to navigate the Python MRO (Method Resolution Order) system cleanly. However, an awareness of the pattern used may prove helpful if your code inherits from the Highcharts for Python classes.
See also
For a more in-depth discussion of how the anti-pattern was implemented safely and reliably, please review the Contributor Guidelines.
Organizing Your Highcharts for Python Project
Highcharts Maps for Python is a utility that can integrate with - quite literally - any frontend framework. Whether your Python application is relying on iPython (e.g. Jupyter Notebook [3] or Jupyter Labs [3]), Flask, Django, FastAPI, Pyramid, Tornado, or some completely home-grown solution all Highcharts Maps for Python needs is a place where Highcharts Maps JavaScript code can be executed.
All of those frameworks mentioned have their own best practices for organizing their application structures, and those should always take priority. Even in a data-centric application that will be relying heavily on Highcharts Maps for Python, your application’s core business logic will be doing most of the heavy lifting and so your project’s organization should reflect that.
All of those frameworks mentioned have their own best practices for organizing their application structures, and those best practices should always take priority. Even in a data-centric application that will be relying heavily on Highcharts for Python, your application’s core business logic will be doing most of the heavy lifting and so your project’s organization should reflect that.
However, there are a number of best practices that we recommend for organizing your files and code to work with the Highcharts for Python Toolkit:
Warning
There are nine and sixty ways of constructing a tribal lay, and every single one of them is right! – Rudyard Kipling, In the Neolithic Age
The organizational model described below is just a suggestion, and you can (and likely will) depart from its principles and practices as you gain more experience using Highcharts Maps for Python. There’s nothing wrong with that! It’s just a set of best practices that we’ve found work for us and which we therefore recommend.
Importing Highcharts Maps for Python
Tip
Best Practice!
This method of importing Highcharts Maps for Python objects yields the fastest
performance for the import
statement. However, it is more verbose and requires
you to navigate the extensive Highcharts Maps for Python API.
# Import classes using precise module indications. For example:
from highcharts_maps.chart import Chart
from highcharts_maps.global_options.shared_options import SharedMapsOptions
from highcharts_maps.options import HighchartsMapsOptions
from highcharts_maps.options.plot_options.map import MapOptions
from highcharts_maps.options.series.map import MapSeries
Caution
This method of importing Highcharts Maps for Python classes has relatively slow performance because it imports hundreds of different classes from across the entire library. This performance impact may be acceptable to you in your use-case, but do use at your own risk.
# Import objects from the catch-all ".highcharts" module.
from highcharts_maps import highcharts
# You can now access specific classes without individual import statements.
highcharts.Chart
highcharts.SharedMapsOptions
highcharts.HighchartsMapsOptions
highcharts.MapOptions
highcharts.MapSeries
Use Templates to Get Started
While shared options are applied to all charts that are rendered
on the same web page with the shared options JS code, certain types of visualizations
may need special treatment. Sure, you can use the
plot_options
settings to configure chart
type-specific options, but how can you efficiently use multiple charts of the same type
that have different settings?
For example, let’s say you used shared options to set universal bar chart settings. But what happens if you know you’ll have different data shown in different bar charts? You can use a similar templating pattern for different sub-types of your charts.
Tip
Best practice!
We really like to use JS literals written as separate files in our codebase. It makes it super simple to instantiate a Highcharts Maps for Python instance with one method call.
Let’s say you organize your files like so:
my_repository/| — docs/| — my_project/| —— project_resources/| ——— image_files/| ——— data_files/| ———— data-file-01.csv| ———— data-file-02.csv| ———— data-file-03.csv| ——— highcharts_config/| ———— shared_options.js| ———— map-template-01.js| ———— map-template-02.js| ———— line-template.js| ———— packed-bubble-template.js| —— some_package/| ——— __init__.py| ——— package_module.py| ——— another_module.py| —— __init__.py| —— __version__.py| —— some_module.py| — tests/| — .gitignore| — requirements.txt
As you can see, there are two JS literal files named map-template-01.js
and
map-template-02.js
respectively. These template files can be used to significantly
accelerate the configuration of our bar charts. Each template corresponds to one
sub-type of bar chart that we know we will need. These sub-types may have different
event functions, or more frequently use different formatting functions to make the
data look the way we want it to look.
Now with these template files, we can easily create a pair of
Chart
instances by executing:
from highcharts_maps.highcharts import Chart from highcharts_maps.options.series.map import MapSeries type_1_chart = Chart.from_js_literal( '../../project_resources/highcharts_config/map-template-01.js' ) type_2_chart = Chart.from_js_literal( '../../project_resources/highcharts_config/map-template-02.js' )
And that’s it! Now you have two chart instances which you can further modify. For example, you can add data to them by calling:
type_1_chart.container = 'chart1_div' type_2_chart.container = 'chart2_div' type_1_chart.add_series(MapSeries.from_csv('../../project_resources/data_files/data-file-01.csv')) type_2_chart.add_series(MapSeries.from_csv('../../project_resources/data_files/data-file-02.csv'))
And then you can create the relevant JavaScript code to render the chart using:
type_1_chart_js = type_1_chart.to_js_literal() type_2_chart_js = type_2_chart.to_js_literal()
And now you can deliver type_1_chart_js
and type_2_chart_js
to your HTML
template or wherever it will be rendered.
You can use the same exact pattern as using a JS literal with a JSON file instead. We don’t really think there’s an advantage to this - but there might be one significant disadvantage: JSON files cannot be used to provide JavaScript functions to your Highcharts configuration. This means that formatters, event handlers, etc. will not be applied through your shared options if you use a JSON file.
If your chart templates don’t require JavaScript functions? Then by all means, feel
free to use a JSON file and the .from_json()
method instead of the
.from_js_literal()
method.
Tip
In practice, we find that most chart templates differ in their formatter functions and event handlers. This makes JSON a particularly weak tool for templating those charts. We strongly prefer the JS literal method described above.
If you are hoping to configure a simple set of template settings, one of the fastest
ways to do so in your Python code is to instantiate your
Chart
instance from a simple
dict
using the .from_dict()
method.
Tip
This method is particularly helpful and easy to maintain if you are only using a very small subset of the Highcharts Maps configuration options.
If you have an existing Highcharts for Python instance, you can copy its
properties to another object using the .copy()
method. You can therefore set up
one chart, and then copy its properties to other chart objects with one method call.
type_1_chart = Chart.from_js_literal('../../project_resources/highcharts_config/line-template-01.js') other_chart = type_1_chart.copy(other_chart, overwrite = True)Tip
The
Chart.copy()
method supports a special keyword argument,preverse_data
which if set toTrue
will copy properties (unlessoverwrite = False
) but will not overwrite any data. This can be very useful to replicating the configuration of your chart across multiple charts that have different series and data.other_chart = Chart() other_chart.add_series( LineSeries.from_csv('../../project_resources/data_files/data-file-02.csv') ) other_chart = type_1_chart.copy(other_chart, preserve_data = True)
Working with Highcharts Maps Features
Highcharts Maps extends Highcharts Core with numerous features that add significant interactivity to your visualizations. These key features include:
When configuring your map visualization, obviously you need to configure the actual “map” your visualization will be rendering. All maps are defined by their geometries, which is a fancy way of saying they are defined by a very precise definition of the lines and shapes that make up the map.
Typically, your map definition will be stored in either GeoJSON, TopoJSON, or ESRI Shapefile files. Highcharts for Maps natively supports these formats, automatically rendering the maps defined by their content.
The map used in your visualization can be defined in two separate places:
When configuring your visualization, you can set your chart’s basic configuration
settings in the Chart.options
option, specifically in the
Chart.options.chart
property.
There, you will find the
ChartOptions.map
property
which is where you supply your map definition.
This property accepts either a
MapData
instance
or an
AsyncMapData
instance which contains the GeoJSON, TopoJSON, or
Shapefile definition of your map geometry.
The map defined in this property will be the default map used for all series rendered on your chart. Since most map visualizations will be rendering all series on one map, this is the most common use case.
Tip
Best practice!
It is recommended to use options.chart.map
to configure your visualization’s
map. This is because laying out a single visualization that has multiple series
represented on multiple maps is a very complicated configuration, and is
rarely necessary.
When defining a map series (descended from
MapSeriesBase
, e.g.
MapSeries
or
MapBubbleSeries
),
you can configure the map in the series
.map_data
property.
As with options.chart.map
, this property takes either a
MapData
instance
or an
AsyncMapData
instance which contains the GeoJSON, TopoJSON, or
Shapefile definition of your map geometry.
Your map itself is defined using either GeoJSON, Topojson, or Shapefiles formats. The most important decision you will need to make is whether you wish to load your map data synchronously within Highcharts Maps for Python and then supply the chart definition and the map definition to your (JavaScript) client, or whether you would prefer to load the map definition asynchronously from your (JavaScript) client:
Tip
Best practice!
Because map data can be verbose and relatively large on the wire, we prefer to rely on the asynchronous method, but there are plenty of valid use cases where the synchronous approach is the best choice.
You can configure your visualization to load your map data asynchronously by
supplying an
AsyncMapData
instance to either .options.chart.map
or .map_data
as described above.
The
AsyncMapData
instance contains a configuration that tells Highcharts Maps for Python how to have
your (JavaScript) client download (using JavaScript’s fetch()
) your map data.
The
AsyncMapData
instance is configured by supplying it with three pieces of information:
The
url
from where your map data should be downloaded. This should be the URL to a single file which contains either GeoJSON, Topojson, or Shapefile data.An optional
selector
(JavaScript) function which you can use to have your (JavaScript) code modify, change, or sub-select data from your asynchronously fetched map file before rendering your chart.An optional
fetch_configuration
which you can use to configure the details of how your (JavaScript) code will execute the (JavaScript)fetch()
request from theurl
(typically used to supply credentials against a backend API, for example).
If you have configured an asynchronous map, Highcharts Maps for Python will
automatically serialize it to JavaScript (when calling
Chart.to_js_literal()
)
using (JavaScript) async/await
and the fetch()
API.
Tip
Best practice!
This approach is recommended because - in practice - it minimizes the amount of data transferred over the wire between your Python backend and your (JavaScript) client. This is particularly helpful because map geometries can be verbose and occupy a (relatively) large amount of space on the wire.
You can supply your map geometries directly within Python
as well, and that map data will then be serialized to JavaScript along with your
chart definition when you call
Chart.to_js_literal()
.
Within Highcharts Maps for Python, synchronous map data is represented as a
MapData
instance.
This object can most easily be created by calling one of its deserializer methods:
Each of these class methods will return a
MapData
instance
whose
.topology
property will now be populated with your map geometry.
from highcharts_maps.options.series.data.map_data import MapData
# Load Map Data from a TopoJSON file
my_map_data = MapData.from_topojson('my-map-data.topo.json')
# Load Map Data from a TopoJSON string "my_topojson_string"
my_map_data = MapData.from_topojson(my_topojson_string)
See also
from highcharts_maps.options.series.data.map_data import MapData
# Load Map Data from a GeoJSON file
my_map_data = MapData.from_geojson('my-map-data.geo.json')
# Load Map Data from a GeoJSON string "my_geojson_string"
my_map_data = MapData.from_geojson(my_geojson_string)
See also
from highcharts_maps.options.series.data.map_data import MapData
# Load Map Data from a GeoPandas GeoDataFrame "gdf"
my_map_data = MapData.from_geodataframe(gdf)
See also
Method Signature
- classmethod .from_geodataframe(cls, as_gdf, prequantize = False, \*\*kwargs)
Create a
MapData
instance from ageopandas.GeoDataFrame
.- Parameters:
as_gdf (
geopandas.GeoDataFrame
) – Thegeopandas.GeoDataFrame
containing the map geometry.prequantize (
bool
) – IfTrue
, will perform the TopoJSON optimizations (“quantizing the topology”) before generating theTopology
instance. Defaults toFalse
.kwargs (
dict
) – additional keyword arguments which are passed to theTopology
constructor
- Return type:
from highcharts_maps.options.series.data.map_data import MapData
# Load Map Data from an ESRI Shapefile
my_map_data = MapData.from_shapefile('my-shapefile.shp')
# Load Map Data from an ESRI Shapefile ZIP
my_map_data = MapData.from_shapefile('my-shapefile.zip')
See also
Method Signature
- classmethod .from_shapefile(cls, shp_filename)
Create a
MapData
instance from an ESRI Shapefile.- Parameters:
The full filename of an ESRI Shapefile to load.
Note
ESRI Shapefiles are actually composed of three files each, with one file receiving the
.shp
extension, one with a.dbf
extension, and one (optional) file with a.shx
extension.Highcharts Maps for Python will resolve all three files given a single base filename. Thus:
/my-shapefiles-folder/my_shapefile.shp
will successfully load data from the three files:
/my-shapefiles-folder/my_shapefile.shp
/my-shapefiles-folder/my_shapefile.dbf
/my-shapefiles-folder/my_shapefile.shx
Tip
Highcharts for Python will also correctly load and unpack shapefiles that are grouped together within a ZIP file.
- Return type:
Note
The MapData
instance will automatically convert your map geometry to
TopoJSON. This is useful because TopoJSON is a much more
compact format than GeoJSON which minimizes the amount of data
transferred over the wire.
If you absolutely need to have GeoJSON delivered to your (JavaScript) client,
you can force GeoJSON on serialization by setting the
MapData.force_geojson
property to True
(it defaults to False
).
Besides setting up your map itself, you can also configure the map view using the
HighchartsMapsOptions.map_view
property. This property lets you use a
MapViewOptions
to
configure:
any map insets that should be rendered on your map,
the default zoom settings for your map,
the default center / positioning for your map, and
any custom projection that should be applied to your map to render it the way you want to.
Map Insets
Map insets are particularly useful when you wish to render either non-contiguous areas (e.g. Alaska and Hawaii on a map of the United States of America) or to render a blown-up/zoomed-in section of the map with special options (think of this as a “detail section”).
You can configure general settings that will apply to all insets on your map using the
MapViewOptions.inset_options
property. And you can then supply the specific definition of each inset (which can override those general inset options) using theMapViewOptions.insets
property and one or moreInset
instances.
Caution
It is important to note that unlike the rest of Highcharts Maps for Python and Highcharts Maps, insets are defined using GeoJSON geometries and not TopoJSON.
For more information, please see the documentation for the
Inset
class.
Zoom Settings
You can configure your map’s maximum zoom level using the
MapViewOptions.max_zoom
property, and you can configure the default level of zoom using theMapViewOptions.zoom
setting.
Default Center
You can configure where your map will be centered by default using the
MapViewOptions.center
property.See also
Projection
All maps are projections of a three-dimensional globe onto a two-dimensional plane (a map). Any such projection will in some ways distort the proportions of the areas depicted, and you may want to apply a different projection to better communicate insights from your data.
Projections are configured using the
MapViewOptions.projection
property, which takes aProjectionOptions
instance.Highcharts for Maps supports both a number of built-in projections as well as the ability to apply a fully custom projection. The default projections supported are:
'EqualEarth'
'LambertConformalConic'
'Miller'
'Orthographic'
'WebMercator'
which can be compared using Highcharts Projection Explorer demo
If you wish to define a custom projection (which is calculated client-side in your JavaScript code), you can do so by supplying a
CustomProjection
instance toMapViewOptions.custom
.
You can configure how users will navigate your map using the
HighchartsMapsOptions.map_navigation
setting. It allows you to configure how the map zooms in and out in response to user
behavior (clicks, double clicks, mouse wheel, etc.).
See also
Working with Data
Obviously, if you are going to use Highcharts Maps for Python and Highcharts Maps you will need to have data to visualize. Python is rapidly becoming the lingua franca in the world of data manipulation, transformation, and analysis and Highcharts Maps for Python is specifically designed to play well within that ecosystem to make it easy to visualize data from CSV files, from geopandas [4] geodataframes, from ESRI Shapefiles, from `pandas`_ dataframes, or PySpark [1] dataframes.
How Data is Represented
Highcharts (JS) supports two different ways of representing data: as an individual series comprised of individual data points, and as a set of instructions to read data dynamically from a CSV file or an HTML table.
See also
DataBase
class
options.Data
class
Highcharts organizes data into series. You can think of a series as a single line on a graph that shows a set of values. The set of values that make up the series are data points, which are defined by a set of properties that indicate the data point’s position on one or more axes.
As a result, Highcharts (JS) and
Highcharts for Python both represent the data points in series as a list of data point
objects in the data
property within the series:
Highcharts JS |
Highcharts for Python |
---|---|
// Example Series Object
// (for a Line series type):
{
data: [
{
id: 'first-data-point',
x: 1,
y: 123,
// ...
// optional additional properties
// for styling/behavior go here
// ...
},
{
id: 'second-data-point',
x: 2,
y: 456,
// ...
// optional additional properties
// for styling/behavior go here
// ...
},
{
id: 'third-data-point',
x: 3,
y: 789,
// ...
// optional additional properties
// for styling/behavior go here
// ...
}
],
// ...
// other Series properties go here
// to configure styling/behavior
}
|
# Corresponding LineSeries object
my_series = Series(data = [
CartesianData(id = 'first-data-point1',
x = 1,
y = 123),
CartesianData(id = 'second-data-point1',
x = 2,
y = 456),
CartesianData(id = 'third-data-point1',
x = 3,
y = 789),
])
|
As you can see, Highcharts for Python represents its data the same way that Highcharts (JS) does. That should be expected. However, constructing tens, hundreds, or possibly thousands of data points individually in your code would be a nightmare. For that reason, the Highcharts for Python Toolkit provides a number of convenience methods to make it easier to populate your series.
Populating Series Data
Every single Series class in Highcharts Maps for Python features several different methods to either instantiate data points directly, load data (to an existing series instance), or to create a new series instance with data already loaded.
When working with a series instance, you can instantiate data points directly.
These data points are stored in the
.data
setting, which
always accepts/expects a list of data point instances (descended from
DataBase
).
Data points all have the same standard Highcharts for Python deserialization methods, so those make things very easy. However, they also have a special data point-specific deserialization method:
# Given a LineSeries named "my_series", and a CSV file named "updated-data.csv"
my_series.load_from_csv('updated-data.csv')
# For more precise control over how the CSV data is parsed,
# you can supply a mapping of series properties to their CSV column
# either by index position *or* by column header name.
my_series.load_from_csv('updated-data.csv',
property_column_map = {
'x': 0,
'y': 3,
'id': 'id'
})
Method Signature
- .load_from_csv(self, as_string_or_file, property_column_map=None, has_header_row=True, delimiter=',', null_text='None', wrapper_character="'", line_terminator='\r\n', wrap_all_strings=False, double_wrapper_character_when_nested=False, escape_character='\\', series_in_rows='line', series_index=None, **kwargs)
Updates the series instance with a collection of data points (descending from
DataBase
) fromas_string_or_file
by traversing the rows of data and extracting the values from the columns indicated inproperty_column_map
.Warning
This method will overwrite the contents of the series instance’s
data
property.Note
For an example
LineSeries
, the minimum code required would be:my_series = LineSeries() # Minimal code - will attempt to update the line series # taking x-values from the first column, and y-values from # the second column. If there are too many columns in the CSV, # will throw an error. my_series = my_series.from_csv('some-csv-file.csv') # More precise code - will attempt to update the line series # mapping columns in the CSV file to properties on the series # instance. my_series = my_series.from_csv('some-csv-file.csv', property_column_map = { 'x': 0, 'y': 3, 'id': 'id' })
As the example above shows, data is loaded into the
my_series
instance from the CSV file with a filenamesome-csv-file.csv
. Unless otherwise specified, the.x
values for each data point will be taken from the first (index 0) column in the CSV file, while the.y
values will be taken from the second column.If the CSV has more than 2 columns, then this will throw an
HighchartsCSVDeserializationError
because the function is not certain which columns to use to update the series. If this happens, you can precisely specify which columns to use by providing aproperty_column_map
argument, as shown in the second example. In that second example, the.x
values for each data point will be taken from the first (index 0) column in the CSV file. The.y
values will be taken from the fourth (index 3) column in the CSV file. And the.id
values will be taken from a column whose header row is labeled'id'
(regardless of its index).- Parameters:
as_string_or_file (
str
or Path-like) –The CSV data to load, either as a
str
or as the name of a file in the runtime envirnoment. If a file, data will be read from the file.Tip
Unwrapped empty column values are automatically interpreted as null (
None
).property_column_map (
dict
) –An optional
dict
used to indicate which data point property should be set to which CSV column. The keys in thedict
should correspond to properties in the data point class, while the value can either be a numerical index (starting with 0) or astr
indicating the label for the CSV column. Defaults toNone
.has_header_row (
bool
) – IfTrue
, indicates that the first row ofas_string_or_file
contains column labels, rather than actual data. Defaults toTrue
.delimiter (
str
) – The delimiter used between columns. Defaults to,
.wrapper_character (
str
) – The string used to wrap string values when wrapping is applied. Defaults to'
.null_text (
str
) – The string used to indicate an empty value if empty values are wrapped. Defaults to None.line_terminator (
str
) –The string used to indicate the end of a line/record in the CSV data. Defaults to
'\r\n'
.Warning
The Python
csv
module currently ignores theline_terminator
parameter and always applies'\r\n'
, by design. The Python docs say this may change in the future, so for future backwards compatibility we are including it here.wrap_all_strings (
bool
) –If
True
, indicates that the CSV file has all string data values wrapped in quotation marks. Defaults toFalse
.double_wrapper_character_when_nested (
bool
) – IfTrue
, quote character is doubled when appearing within a string value. IfFalse
, theescape_character
is used to prefix quotation marks. Defaults toFalse
.escape_character (
str
) – A one-character string that indicates the character used to escape quotation marks if they appear within a string value that is already wrapped in quotation marks. Defaults to\\
(which is Python for'\'
, which is Python’s native escape character).series_in_rows (
bool
) – ifTrue
, will attempt a streamlined cartesian series with x-values taken from column names, y-values taken from row values, and the series name taken from the row index. Defaults toFalse
.if
None
, will raise aHighchartsCSVDeserializationError
if the CSV data contains more than one series and noproperty_column_map
is provided. Otherwise, will update the instance with the series found in the CSV at theseries_index
value. Defaults toNone
.Tip
This argument is ignored if
property_column_map
is provided.**kwargs –
Remaining keyword arguments will be attempted on the resulting series instance and the data points it contains.
- Returns:
A collection of data points descended from
DataBase
as appropriate for the series class.- Return type:
list
of instances descended fromDataBase
- Raises:
HighchartsDeserializationError – if unable to parse the CSV data correctly
# Given a LineSeries named "my_series", and a Pandas DataFrame variable named "df"
# EXAMPLE 1. The minimum code required to update the series:
my_series.load_from_pandas(df)
# EXAMPLE 2. For more precise control over how the ``df`` is parsed,
# you can supply a mapping of series properties to their dataframe column.
my_series.load_from_pandas(df,
property_map = {
'x': 'date',
'y': 'value',
'id': 'id'
})
# EXAMPLE 3. For more precise control, specify the index of the
# Highcharts for Python series instance to use in updating your series' data.
my_series.load_from_pandas(df, series_index = 3)
Method Signature
- .load_from_pandas(self, df, property_map=None, series_in_rows=False, series_index=None)
Replace the contents of the
.data
property with data points populated from a pandasDataFrame
.- Parameters:
df (
DataFrame
) – TheDataFrame
from which data should be loaded.property_map (
dict
) – Adict
used to indicate which data point property should be set to which column indf
. The keys in thedict
should correspond to properties in the data point class, while the value should indicate the label for theDataFrame
column.series_in_rows (
bool
) – ifTrue
, will attempt a streamlined cartesian series with x-values taken from column names, y-values taken from row values, and the series name taken from the row index. Defaults toFalse
.series_index (
int
, slice, orNone
) –If supplied, return the series that Highcharts for Python generated from
df
at theseries_index
value. Defaults toNone
, which returns all series generated fromdf
.Warning
If
None
and Highcharts for Python generates multiple series, then aHighchartsPandasDeserializationError
will be raised.
- Raises:
HighchartsPandasDeserializationError – if
property_map
references a column that does not exist in the data frameHighchartsPandasDeserializationError – if
series_index
isNone
, and it is ambiguous which series generated from the dataframe should be usedHighchartsDependencyError – if pandas is not available in the runtime environment
# Given a MapSeries named "my_series", and a GeoPandas DataFrame variable named "gdf"
my_series.load_from_geopandas(gdf,
property_map = {
'id': 'id',
'value': 'value'
})
Method Signature
- .load_from_geopandas(self, gdf, property_map)
Replace the contents of the
.data
property with data points and the.map_data
property with geometries populated from a geopandasGeoDataFrame
.- Parameters:
gdf (
GeoDataFrame
) – TheGeoDataFrame
from which data should be loaded.property_map (
dict
) – Adict
used to indicate which data point property should be set to which column ingdf
. The keys in thedict
should correspond to properties in the data point class, while the value should indicate the label for theGeoDataFrame
column.
- Raises:
HighchartsPandasDeserializationError – if
property_map
references a column that does not exist in the data frameHighchartsDependencyError – if geopandas is not available in the runtime environment
# Given a LineSeries named "my_series", and a PySpark DataFrame variable named "df"
my_series.load_from_pyspark(df,
property_map = {
'x': 'date',
'y': 'value',
'id': 'id'
})
Method Signature
- .load_from_pyspark(self, df, property_map)
Replaces the contents of the
.data
property with values from a PySparkDataFrame
.- Parameters:
df (
DataFrame
) – TheDataFrame
from which data should be loaded.property_map (
dict
) – Adict
used to indicate which data point property should be set to which column indf
. The keys in thedict
should correspond to properties in the data point class, while the value should indicate the label for theDataFrame
column.
- Raises:
HighchartsPySparkDeserializationError – if
property_map
references a column that does not exist in the data frameif PySpark is not available in the runtime environment
from highcharts_maps.chart import Chart
from highcharts_maps.options.series.area import LineSeries
# Create one or more LineSeries instances from the CSV file "some-csv-file.csv".
# EXAMPLE 1. The minimum code to produce one series for each
# column in the CSV file (excluding the first column):
my_series = LineSeries.from_csv('some-csv-file.csv')
# EXAMPLE 2. Produces ONE series with more precise configuration:
my_series = LineSeries.from_csv('some-csv-file.csv',
property_column_map = {
'x': 0,
'y': 3,
'id': 'id'
})
# EXAMPLE 3. Produces THREE series instances with
# more precise configuration:
my_series = LineSeries.from_csv('some-csv-file.csv',
property_column_map = {
'x': 0,
'y': [3, 5, 8],
'id': 'id'
})
# Create a chart with one or more LineSeries instances from
# the CSV file "some-csv-file.csv".
# EXAMPLE 1: The minimum code:
my_chart = Chart.from_csv('some-csv-file.csv', series_type = 'line')
# EXAMPLE 2: For more precise configuration and *one* series:
my_chart = Chart.from_csv('some-csv-file.csv',
property_column_map = {
'x': 0,
'y': 3,
'id': 'id'
},
series_type = 'line')
# EXAMPLE 3: For more precise configuration and *multiple* series:
my_chart = Chart.from_csv('some-csv-file.csv',
property_column_map = {
'x': 0,
'y': [3, 5, 8],
'id': 'id'
},
series_type = 'line')
Method Signature
- classmethod .from_csv(cls, as_string_or_file, property_column_map=None, series_kwargs=None, has_header_row=True, delimiter=',', null_text='None', wrapper_character="'", line_terminator='\r\n', wrap_all_strings=False, double_wrapper_character_when_nested=False, escape_character='\\', series_in_rows=False, series_index=None, **kwargs)
Create one or more series instances with
.data
populated from data in a CSV string or file.Note
To produce one or more
LineSeries
instances, the minimum code required would be:# EXAMPLE 1. The minimum code: my_series = LineSeries.from_csv('some-csv-file.csv') # EXAMPLE 2. For more precise configuration and ONE series: my_series = LineSeries.from_csv('some-csv-file.csv', property_column_map = { 'x': 0, 'y': 3, 'id': 'id' }) # EXAMPLE 3. For more precise configuration and MULTIPLE series: my_series = LineSeries.from_csv('some-csv-file.csv', property_column_map = { 'x': 0, 'y': [3, 5, 8], 'id': 'id' })
As the example above shows, data is loaded into the
my_series
instance from the CSV file with a filenamesome-csv-file.csv
.In EXAMPLE 1, the method will return one or more series where each series will default to having its
.x
values taken from the first (index 0) column in the CSV, and oneLineSeries
instance will be created for each subsequent column (which will populate that series’.y
values.In EXAMPLE 2, the chart will contain one series, where the
.x
values for each data point will be taken from the first (index 0) column in the CSV file. The.y
values will be taken from the fourth (index 3) column in the CSV file. And the.id
values will be taken from a column whose header row is labeled'id'
(regardless of its index).In EXAMPLE 3, the chart will contain three series, all of which will have
.x
values taken from the first (index 0) column,.id
values from the column whose header row is labeled'id'
, and whose.y
will be taken from the fourth (index 3) column for the first series, the sixth (index 5) column for the second series, and the ninth (index 8) column for the third series.- Parameters:
as_string_or_file (
str
or Path-like) –The CSV data to use to pouplate data. Accepts either the raw CSV data as a
str
or a path to a file in the runtime environment that contains the CSV data.Tip
Unwrapped empty column values are automatically interpreted as null (
None
).property_column_map (
dict
) –A
dict
used to indicate which data point property should be set to which CSV column. The keys in thedict
should correspond to properties in the data point class, while the value can either be a numerical index (starting with 0) or astr
indicating the label for the CSV column.Note
If any of the values in
property_column_map
contain an iterable, then one series will be produced for each item in the iterable. For example, the following:{ 'x': 0, 'y': [3, 5, 8] }
will return three series, each of which will have its
.x
value populated from the first column (index 0), and whose.y
values will be populated from the fourth, sixth, and ninth columns (indices 3, 5, and 8), respectively.series_type (
str
) –Indicates the series type that should be created from the CSV data. Defaults to
'line'
.Warning
This argument is not supported when calling
.from_csv()
on a series instance. It is only supported when callingChart.from_csv()
.has_header_row (
bool
) – IfTrue
, indicates that the first row ofas_string_or_file
contains column labels, rather than actual data. Defaults toTrue
.series_kwargs (
dict
) –An optional
dict
containing keyword arguments that should be used when instantiating the series instance. Defaults toNone
.Warning
If
series_kwargs
contains adata
key, its value will be overwritten. Thedata
value will be created from the CSV file instead.delimiter (
str
) – The delimiter used between columns. Defaults to,
.wrapper_character (
str
) – The string used to wrap string values when wrapping is applied. Defaults to'
.null_text (
str
) – The string used to indicate an empty value if empty values are wrapped. Defaults to None.line_terminator (
str
) – The string used to indicate the end of a line/record in the CSV data. Defaults to'\r\n'
.line_terminator –
The string used to indicate the end of a line/record in the CSV data. Defaults to
'\r\n'
.Note
The Python
csv
currently ignores theline_terminator
parameter and always applies'\r\n'
, by design. The Python docs say this may change in the future, so for future backwards compatibility we are including it here.wrap_all_strings (
bool
) –If
True
, indicates that the CSV file has all string data values wrapped in quotation marks. Defaults toFalse
.double_wrapper_character_when_nested (
bool
) – IfTrue
, quote character is doubled when appearing within a string value. IfFalse
, theescape_character
is used to prefix quotation marks. Defaults toFalse
.escape_character (
str
) – A one-character string that indicates the character used to escape quotation marks if they appear within a string value that is already wrapped in quotation marks. Defaults to\\
(which is Python for'\'
, which is Python’s native escape character).series_in_rows (
bool
) – ifTrue
, will attempt a streamlined cartesian series with x-values taken from column names, y-values taken from row values, and the series name taken from the row index. Defaults toFalse
.series_index (
int
, slice, orNone
) – ifNone
, will attempt to populate the chart with multiple series from the CSV data. If anint
is supplied, will populate the chart only with the series found atseries_index
.**kwargs –
Remaining keyword arguments will be attempted on the resulting series instance and the data points it contains.
- Returns:
One or more series instances (descended from
SeriesBase
) with its.data
property populated from the CSV data inas_string_or_file
.- Return type:
list
of series instances (descended fromSeriesBase
) orSeriesBase
instance- Raises:
HighchartsCSVDeserializationError – if
property_column_map
references CSV columns by their label, but the CSV data does not contain a header row
# Given a Pandas DataFrame instance named "df"
from highcharts_maps.chart import Chart
from highcharts_maps.options.series.area import LineSeries
# Creating a Series from the DataFrame
## EXAMPLE 1. Minimum code required. Creates one or more series.
my_series = LineSeries.from_pandas(df)
## EXAMPLE 2. More precise configuration. Creates ONE series.
my_series = LineSeries.from_pandas(df, series_index = 2)
## EXAMPLE 3. More precise configuration. Creates ONE series.
my_series = LineSeries.from_pandas(df,
property_map = {
'x': 'date',
'y': 'value',
'id': 'id'
})
## EXAMPLE 4. More precise configuration. Creates THREE series.
my_series = LineSeries.from_pandas(df,
property_map = {
'x': 'date',
'y': ['value1', 'value2', 'value3'],
'id': 'id'
})
## EXAMPLE 5. Minimum code required. Creates one or more series
## from a dataframe where each row in the dataframe is a
## Highcharts series. The two lines of code below are equivalent.
my_series = LineSeries.from_pandas_in_rows(df)
# Creating a Chart with a lineSeries from the DataFrame.
## EXAMPLE 1. Minimum code required. Populates the chart with
## one or more series.
my_chart = Chart.from_pandas(df)
## EXAMPLE 2. More precise configuration. Populates the chart with
## one series.
my_chart = Chart.from_pandas(df, series_index = 2)
## EXAMPLE 3. More precise configuration. Populates the chart with
## ONE series.
my_chart = Chart.from_pandas(df,
property_map = {
'x': 'date',
'y': 'value',
'id': 'id'
},
series_type = 'line')
## EXAMPLE 4. More precise configuration. Populates the chart with
## THREE series.
my_chart = Chart.from_pandas(df,
property_map = {
'x': 'date',
'y': ['value1', 'value2', 'value3'],
'id': 'id'
},
series_type = 'line')
## EXAMPLE 5. Minimum code required. Creates a Chart populated
## with series from a dataframe where each row in the dataframe
## becomes a series on the chart.
my_chart = Chart.from_pandas_in_rows(df)
Method Signature
- classmethod .from_pandas(cls, df, property_map=None, series_kwargs=None, series_in_rows=False, series_index=None, **kwargs)
Create one or more series instances whose
.data
properties are populated from a pandasDataFrame
.- Parameters:
df (
DataFrame
) – TheDataFrame
from which data should be loaded.property_map (
dict
) –An optional
dict
used to indicate which data point property should be set to which column indf
. The keys in thedict
should correspond to properties in the data point class, while the value should indicate the label for theDataFrame
column.Note
If any of the values in
property_map
contain an iterable, then one series will be produced for each item in the iterable. For example, the following:{ 'x': 'timestamp', 'y': ['value1', 'value2', 'value3'] }
will return three series, each of which will have its
.x
value populated from the column labeled'timestamp'
, and whose.y
values will be populated from the columns labeled'value1'
,'value2'
, and'value3'
, respectively.series_type (
str
) –Indicates the series type that should be created from the CSV data. Defaults to
'line'
.Warning
This argument is not supported when calling
.from_pandas()
on a series. It is only supported when callingChart.from_csv()
.series_kwargs (
dict
) –An optional
dict
containing keyword arguments that should be used when instantiating the series instance. Defaults toNone
.Warning
If
series_kwargs
contains adata
key, its value will be overwritten. Thedata
value will be created fromdf
instead.series_in_rows (
bool
) – ifTrue
, will attempt a streamlined cartesian series with x-values taken from column names, y-values taken from row values, and the series name taken from the row index. Defaults toFalse
.False
.series_index (
int
, slice, orNone
) – If supplied, return the series that Highcharts for Python generated fromdf
at theseries_index
value. Defaults toNone
, which returns all series generated fromdf
.**kwargs –
Remaining keyword arguments will be attempted on the resulting series instance and the data points it contains.
- Returns:
One or more series instances (descended from
SeriesBase
) with the.data
property populated from the data indf
.- Return type:
list
of series instances (descended fromSeriesBase
), or aSeriesBase
-descended instance- Raises:
HighchartsPandasDeserializationError – if
property_map
references a column that does not exist in the data frameHighchartsDependencyError – if pandas is not available in the runtime environment
Note
The .from_geopandas()
method is available on all series classes which
support rendering as a map visualization. This includes:
TilemapSeries
allowing you to either assemble a series or an entire chart from a GeoPandas
GeoDataFrame
with only one method call.
# Given a geoPandas DataFrame instance named "gdf"
from highcharts_maps.chart import Chart
from highcharts_maps.options.series.map import MapSeries
# Creating a Series from the GeoDataFrame
my_series = MapSeries.from_geopandas(gdf,
property_map = {
'id': 'state',
'value': 'value'
})
# Creating a Chart with a MapSeries from the GeoDataFrame.
my_chart = Chart.from_geopandas(gdf,
property_map = {
'id': 'state',
'value': 'value'
},
series_type = 'map')
Method Signature
See also
- classmethod .from_geopandas(cls, df, property_map, series_kwargs=None)
Create a series instance whose
.data
property is populated from a geopandasGeoDataFrame
.- Parameters:
gdf (
GeoDataFrame
) – TheGeoDataFrame
from which data should be loaded.property_map (
dict
) – Adict
used to indicate which data point property should be set to which column ingdf
. The keys in thedict
should correspond to properties in the data point class, while the value should indicate the label for theGeoDataFrame
column.series_kwargs (
dict
) –An optional
dict
containing keyword arguments that should be used when instantiating the series instance. Defaults toNone
.Warning
If
series_kwargs
contains adata
ormap_data
key, their values will be overwritten. Thedata
andmap_data
values will be created fromgdf
instead.
- Returns:
A series instance (descended from
MapSeriesBase
) with its.data
and.map_data
properties from the data ingdf`
- Return type:
list
of series instances (descended fromMapSeriesBase
)- Raises:
HighchartsPandasDeserializationError – if
property_map
references a column that does not exist in the data frameHighchartsDependencyError – if geopandas is not available in the runtime environment
# Given a PySpark DataFrame instance named "df"
from highcharts_maps.chart import Chart
from highcharts_maps.options.series.area import LineSeries
# Create a LineSeries from the PySpark DataFrame "df"
my_series = LineSeries.from_pyspark(df,
property_map = {
'x': 'date',
'y': 'value',
'id': 'id'
})
# Create a new Chart witha LineSeries from the DataFrame "df"
my_chart = Chart.from_pyspark(df,
property_map = {
'x': 'date',
'y': 'value',
'id': 'id'
},
series_type = 'line')
Method Signature
See also
- classmethod .from_pyspark(cls, df, property_map, series_kwargs=None)
Create a series instance whose
.data
property is populated from a PySparkDataFrame
.- Parameters:
df (
DataFrame
) – TheDataFrame
from which data should be loaded.property_map (
dict
) – Adict
used to indicate which data point property should be set to which column indf
. The keys in thedict
should correspond to properties in the data point class, while the value should indicate the label for theDataFrame
column.series_kwargs (
dict
) –An optional
dict
containing keyword arguments that should be used when instantiating the series instance. Defaults toNone
.Warning
If
series_kwargs
contains adata
key, its value will be overwritten. Thedata
value will be created fromdf
instead.
- Returns:
A series instance (descended from
SeriesBase
) with its.data
property populated from the data indf
.- Return type:
list
of series instances (descended fromSeriesBase
)- Raises:
HighchartsPySparkDeserializationError – if
property_map
references a column that does not exist in the data frameif PySpark is not available in the runtime environment
Adding Series to Charts
Now that you have constructed your series instances, you can add them to
charts very easily. First, Highcharts for Python represents visualizations as
instances of the Chart
class. This class contains
an options
property, which itself contains
an instance of
HighchartsMapsOptions
.
Note
The
HighchartsMapsOptions
is a sub-class of the Highcharts for PythonHighchartsOptions
class, and is fully backwards-compatible with it. This means that you can use them interchangeably when using Highcharts Maps for Python, as theHighchartsMapsOptions
class merely extends its parent with a number of methods and properties that are specifically supported by Highcharts Maps.Note
This structure - where the chart object contains an options object - is a little nested for some tastes, but it is the structure which Highcharts (JS) has adopted and so for the sake of consistency the Highcharts for Python Toolkit uses it as well.
To be visualized on your chart, you will need to add your series instances to the
Chart.options.series
property. You can do this in several ways:
from highcharts_maps.chart import Chart
from highcharts_maps.options.series.area import LineSeries
from highcharts_maps.options.series.bar import BarSeries
# Create a Chart instance called "my_chart" with an empty set of options
my_chart = Chart(options = {})
# Create a couple Series instances
my_series1 = LineSeries()
my_series2 = BarSeries()
# Populate the options series list with the series you created.
my_chart.options.series = [my_series1, my_series2]
# Make a new one, and append it.
my_series3 = LineSeries()
my_chart.options.series.append(my_series3)
Note
.add_series()
is supported by both theChart
andHighchartsStockOptions
classes
my_chart = Chart()
my_chart.add_series(my_series1, my_series2)
my_series = LineSeries()
my_chart.add_series(my_series)
Method Signature
- .add_series(self, *series)
Adds
series
to theChart.options.series
property.- Parameters:
series (
SeriesBase
or coercable) – One or more series instances (descended fromSeriesBase
) or an instance (e.g.dict
,str
, etc.) coercable to one
Note
.from_series()
is supported by both theChart
andHighchartsMapsOptions
classes
my_series1 = LineSeries()
my_series2 = BarSeries()
my_chart = Chart.from_series(my_series1, my_series2, options = None)
Method Signature
- .from_series(cls, *series, kwargs=None)
Creates a new
Chart
instance populated withseries
.- Parameters:
series (
SeriesBase
or coercable) – One or more series instances (descended fromSeriesBase
) or an instance (e.g.dict
,str
, etc.) coercable to onekwargs (
dict
) –Other properties to use as keyword arguments for the instance to be created.
Warning
If
kwargs
sets theoptions.series
property, that setting will be overridden by the contents ofseries
.
- Returns:
A new
Chart
instance- Return type:
# Given a geoPandas DataFrame instance named "gdf"
from highcharts_maps.chart import Chart
my_chart = Chart.from_geopandas(gdf,
property_map = {
'id': 'state',
'value': 'value'
},
series_type = 'map')
Method Signature
See also
- classmethod .from_geopandas(cls, df, property_map, series_type, series_kwargs=None, options_kwargs=None, chart_kwargs=None)
Create a
Chart
instance whose data is populated from a geopandasGeoDataFrame
.- Parameters:
gdf (
GeoDataFrame
) – TheGeoDataFrame
from which data should be loaded.property_map (
dict
) – Adict
used to indicate which data point property should be set to which column ingdf
. The keys in thedict
should correspond to properties in the data point class, while the value should indicate the label for theGeoDataFrame
column.series_type (
str
) – Indicates the series type that should be created from the data ingdf
.series_kwargs (
dict
) –An optional
dict
containing keyword arguments that should be used when instantiating the series instance. Defaults toNone
.Warning
If
series_kwargs
contains adata
key, its value will be overwritten. Thedata
value will be created fromgdf
instead.options_kwargs (
dict
orNone
) –An optional
dict
containing keyword arguments that should be used when instantiating theHighchartsOptions
instance. Defaults toNone
.Warning
If
options_kwargs
contains aseries
key, theseries
value will be overwritten. Theseries
value will be created from the data ingdf
.An optional
dict
containing keyword arguments that should be used when instantiating theChart
instance. Defaults toNone
.Warning
If
chart_kwargs
contains anoptions
key,options
will be overwritten. Theoptions
value will be created from theoptions_kwargs
and the data ingdf
instead.
- Returns:
A
Chart
instance with its data populated from the data ingdf
.- Return type:
- Raises:
HighchartsPandasDeserializationError – if
property_map
references a column that does not exist in the data frameHighchartsDependencyError – if pandas is not available in the runtime environment
Rendering Your Visualizations
Once you have created your Chart
instance or
instances, you can render them very easily. There are really only two ways to display
your visualizations:
Rendering Highcharts Visualizations in Web Content
Highcharts is a suite of JavaScript libraries designed to enable rendering high-end data visualizations in a web context. They are designed and optimized to operate within a web browser. The Highcharts for Python Toolkit therefore fully supports this capability, and we’ve enabled it using the batteries included principle.
To render a Highcharts Maps for Python visualization, all you need is for the browser
to execute the output of the chart’s
.to_js_literal()
method, which will
return a snippet of JavaScript code which when included in a web page will display the
chart in full.
Warning
The current version of Highcharts Maps for Python assumes that your web content
already has all the <script/>
tags which include the
Highcharts Core and
Highcharts Maps modules your chart
relies on.
This is likely to change in a future version of Highcharts for Python, where the
toolkit will support the production of <script/>
tags (see roadmap #6).
For example:
from highcharts_maps.chart import Chart
from highcharts_maps.options.series.hlc import HLCSeries
my_chart = Chart(container = 'target_div',
options = {
'series': [
HLCSeries(data = [
[2, 0, 4],
[4, 2, 8],
[3, 9, 3]
])
]
},
variable_name = 'myChart',
is_maps_chart = True)
as_js_literal = my_chart.to_js_literal()
# This will produce a string equivalent to:
#
# document.addEventListener('DOMContentLoaded', function() {
# const myChart = Highcharts.stockChart('target_div', {
# series: {
# type: 'hlc',
# data: [
# [2, 0, 4],
# [4, 2, 8],
# [3, 9, 3]
# ]
# }
# });
# });
from highcharts_maps.chart import Chart
from highcharts_maps.options.series.area import LineSeries
my_chart = Chart(data = [0, 5, 3, 5], series_type = 'line')
as_js_literal = my_chart.to_js_literal()
# This will produce a string equivalent to:
#
# document.addEventListener('DOMContentLoaded', function() {
# const myChart = Highcharts.chart('target_div', {
# series: {
# type: 'line',
# data: [0, 5, 3, 5]
# }
# });
# });
Now you can use whatever front-end framework you are using to insert that string into your
application’s HTML output (in an appropriate <script/>
tag, of course).
Tip
The same principle applies to the use of
SharedMapsOptions
.
It is recommended to place the JS literal form of your shared options before any of the charts that you will be visualizing.
Rendering Highcharts for Python in Jupyter Labs or Jupyter Notebooks
You can also render Highcharts Maps for Python visualizations inside your
Jupyter notebook. This is as simple as executing a single
.display()
call on your
Chart
instance:
from highcharts_maps.chart import Chart
from highcharts_maps.global_options.shared_options import SharedOptions
my_chart = Chart(data = [0, 5, 3, 5], series_type = 'line')
# Now this will render the contents of "my_chart" in your Jupyter Notebook
my_chart.display()
# You can also supply shared options to display to make sure that they are applied:
my_shared_options = SharedOptions()
# Now this will render the contents of "my_chart" in your Jupyter Notebook, but applying
# your shared options
my_chart.display(global_options = my_shared_options)
Method Signature
- display(self, global_options=None, container=None, retries=5, interval=1000)
Display the chart in Jupyter Labs or Jupyter Notebooks.
- Parameters:
global_options (
SharedOptions
orNone
) – The shared options to use when rendering the chart. Defaults toNone
The ID to apply to the HTML container when rendered in Jupyter Labs. Defaults to
None
, which applies the.container
property if set, and'highcharts_target_div'
if not set.Note
Highcharts for Python will append a 6-character random string to the value of
container
to ensure uniqueness of the chart’s container when rendering in a Jupyter Notebook/Labs context. TheChart
instance will retain the mapping between container and the random string so long as the instance exists, thus allowing you to easily update the rendered chart by calling the.display()
method again.If you wish to create a new chart from the instance that does not update the existing chart, then you can do so by specifying a new
container
value.retries (
int
) – The number of times to retry rendering the chart. Used to avoid race conditions with the Highcharts script. Defaults to 5.interval (
int
) – The number of milliseconds to wait between retrying rendering the chart. Defaults to 1000 (1 second).
- Raises:
HighchartsDependencyError – if ipython is not available in the runtime environment
You can call the .display()
method
from anywhere within any notebook cell, and it will render the resulting chart in your
notebook’s output. That’s it!
Caution
If iPython is not available in your runtime environment, calling
.display()
will raise aHighchartsDependencyError
.
Maps Chart vs Regular Chart
When using Highcharts Maps for Python you have the choice to render your charts using the Highcharts Maps chart constructor or the standard Highcharts Core chart constructor.
The difference between these two constructors relates to the features available in the
chart. The Highcharts Maps chart will be visualized including a map (configured in
Chart.options.chart.map
,
Chart.options.map_view
,
and
Chart.options.map_navigation
).
A regular Highcharts JS chart cannot be displayed with either of these elements.
However, Highcharts Maps can visualize all of the series types offered by Highcharts Core.
When working with your Chart
object, you can set
the .is_maps_chart
property to
True
to force the chart to be rendered using the (JavaScript)
Highcharts.mapChart()
constructor.
If you wish to force the use of the (JavaScript) Highcharts.chart()
constructor, you can explicitly set
.is_maps_chart
to False
after
populating the chart’s .options
property.
If you do not set this property explicitly, Highcharts Maps for Python will make
a determination based on the contents of the
.options
property. If that that property
is set to a
HighchartsMapsOptions
instance, the .is_maps_chart
property will be set to True
, unless explicitly overridden in your code.
Downloading Your Visualizations
Sometimes you are not looking to produce an interactive web-based visualization of your data, but instead are looking to produce a static image of your visualization that can be downloaded, emailed, or embedded in some other documents.
With Highcharts Maps for Python, that’s as simple as executing the
Chart.download_chart()
method.
When you have defined a Chart
instance, you can
download a static version of that chart or persist it to a file in your runtime
environment. The actual file itself is produced using a
Highcharts Export Server.
from highcharts_maps.chart import Chart
my_chart = Chart(data = [0, 5, 3, 5],
series_type = 'line')
# Download a PNG version of the chart in memory within your Python code.
my_png_image = my_chart.download_chart(format = 'png')
# Download a PNG version of the chart and save it the file "/images/my-chart-file.png"
my_png_image = my_chart.download_chart(
format = 'png',
filename = '/images/my-chart-file.png'
)
Method Signature
- .download_chart(self, filename=None, format='png', server_instance=None, scale=1, width=None, auth_user=None, auth_password=None, timeout=0.5, global_options=None, **kwargs)
Export a downloaded form of the chart using a Highcharts Export Server.
- Parameters:
filename (Path-like or
None
) – The name of the file where the exported chart should (optionally) be persisted. Defaults toNone
.server_instance (
ExportServer
orNone
) – Provide an already-configuredExportServer
instance to use to programmatically produce the exported chart. Defaults toNone
, which causes Highcharts for Python to instantiate a newExportServer
instance with all applicable defaults.format (
str
) –The format in which the exported chart should be returned. Defaults to
'png'
.Accepts:
'png'
'jpeg'
'pdf'
'svg'
scale (numeric) –
The scale factor by which the exported chart image should be scaled. Defaults to
1
.Tip
Use this setting to improve resolution when exporting PNG or JPEG images. For example, setting
scale = 2
on a chart whose width is 600px will produce an image file with a width of 1200px.Warning
If
width
is explicitly set, this setting will be overridden.width (numeric or
None
) –The width that the exported chart should have. Defaults to
None
.Warning
If explicitly set, this setting will override
scale
.auth_user (
str
orNone
) – The username to use to authenticate against the Export Server, using basic authentication. Defaults toNone
.auth_password (
str
orNone
) – The password to use to authenticate against the Export Server (using basic authentication). Defaults toNone
.timeout (numeric or
None
) – The number of seconds to wait before issuing a timeout error. The timeout check is passed if bytes have been received on the socket in less than thetimeout
value. Defaults to0.5
.global_options (
HighchartsMapsOptions
,HighchartsOptions
orNone
) – The global options which will be passed to the (JavaScript)Highcharts.setOptions()
method, and which will be applied to the exported chart. Defaults toNone
.
Note
All other keyword arguments are as per the
ExportServer
constructor.
from highcharts_maps.chart import Chart
from highcharts_maps.headless_export import ExportServer
custom_server = ExportServer(url = 'https://www.mydomain.dev/some_pathname_goes_here')
my_chart = Chart(data = [0, 5, 3, 5],
series_type = 'line')
# Download a PNG version of the chart in memory within your Python code.
my_png_image = my_chart.download_chart(format = 'png',
server_instance = custom_server)
# Download a PNG version of the chart and save it the file "/images/my-chart-file.png"
my_png_image = my_chart.download_chart(
format = 'png',
filename = '/images/my-chart-file.png',
server_instance = custom_server
)
Tip
Best practice!
If you are using a custom export server, it is strongly recommended that you
supply its configuration (e.g. the URL) via environment variables. For more information,
please see
headless_export.ExportServer
.
Method Signature
- .download_chart(self, filename=None, format='png', server_instance=None, scale=1, width=None, auth_user=None, auth_password=None, timeout=0.5, global_options=None, **kwargs)
Export a downloaded form of the chart using a Highcharts Export Server.
- Parameters:
filename (Path-like or
None
) – The name of the file where the exported chart should (optionally) be persisted. Defaults toNone
.server_instance (
ExportServer
orNone
) – Provide an already-configuredExportServer
instance to use to programmatically produce the exported chart. Defaults toNone
, which causes Highcharts for Python to instantiate a newExportServer
instance with all applicable defaults.format (
str
) –The format in which the exported chart should be returned. Defaults to
'png'
.Accepts:
'png'
'jpeg'
'pdf'
'svg'
scale (numeric) –
The scale factor by which the exported chart image should be scaled. Defaults to
1
.Tip
Use this setting to improve resolution when exporting PNG or JPEG images. For example, setting
scale = 2
on a chart whose width is 600px will produce an image file with a width of 1200px.Warning
If
width
is explicitly set, this setting will be overridden.width (numeric or
None
) –The width that the exported chart should have. Defaults to
None
.Warning
If explicitly set, this setting will override
scale
.auth_user (
str
orNone
) – The username to use to authenticate against the Export Server, using basic authentication. Defaults toNone
.auth_password (
str
orNone
) – The password to use to authenticate against the Export Server (using basic authentication). Defaults toNone
.timeout (numeric or
None
) – The number of seconds to wait before issuing a timeout error. The timeout check is passed if bytes have been received on the socket in less than thetimeout
value. Defaults to0.5
.global_options (
HighchartsMapsOptions
,HighchartsOptions
orNone
) – The global options which will be passed to the (JavaScript)Highcharts.setOptions()
method, and which will be applied to the exported chart. Defaults toNone
.
Note
All other keyword arguments are as per the
ExportServer
constructor.
Warning
As of Highcharts for Python v.1.1.0, the Highcharts Export Server does not yet fully
support all of the series types added in Highcharts (JS) v.11. Attempting to programmatically download
one of those new as-yet-unsupported visualizations will generate a
HighchartsUnsupportedExportError
.
Using Custom Projections
All maps are projections of a three-dimensional globe onto a two-dimensional plane (a map). Any such projection will in some ways distort the proportions of the areas depicted, and you may want to apply a different projection to better communicate insights from your data.
Projections are configured using the
MapViewOptions.projection
property, which takes a
ProjectionOptions
instance. You can define a custom projection algorithm to apply using the
ProjectionOptions.custom
property, which takes a
CustomProjection
instance.
A CustomProjection
is a JavaScriptClass
which is used to calculate your map projection to/from a given set of latitude and
longitude coordinates. In order to be valid, it needs to:
be given a
.name
have a
constructor
method which sets a (JavaScript)this.projection
property as a JavaScript function or classhave a
forward
method which accepts a longitude and latitude array (2-member array of longitude and latitude coordinates, respectively)have an
inverse
method which accepts a point array (2-member array of projected horizontal and vertical coordinates, respectively)
An example of how this might be represented, using (JavaScript) d3-geo
to create a
“Robinson” projection would be:
Python |
JavaScript |
---|---|
from highcharts_maps.utility_classes.projections import CustomProjection
from highcharts_maps.utility_classes.javascript_functions import CallbackFunction
robinson_constructor = CallbackFunction(
function_name = 'constructor',
arguments = None,
body = """this.projection = window.d3.geoRobinson().reflectY(true);"""
)
robinson_forward = CallbackFunction(
function_name = 'forward',
arguments = ['lonLat'],
body = """return this.projection(lonLat);"""
)
robinson_inverse = CallbackFunction(
function_name = 'inverse',
arguments = ['point'],
body = """return this.projection.invert(point);"""
)
robinson_projection = CustomProjection(
class_name = 'RobinsonProjectionDefinition',
name = 'Robinson',
methods = [
robinson_constructor,
robinson_forward,
robinson_inverse
]
)
my_chart.set_custom_projection(robinson_projection)
|
class RobinsonProjectionDefinition {
constructor() {
this.projection = window.d3.geoRobinson().reflectY(true);
}
forward(lonLat) {
return this.projection(lonLat);
}
inverse(point) {
return this.projection.invert(point);
}
}
Highcharts.Projection.add('Robinson', RobinsonProjectionDefinition);
Highcharts.mapChart('container', {
// .. OTHER PROPERTIES GO HERE ...
mapView: {
projection: {
name: 'Robinson',
},
// .. OTHER PROPERTIES GO HERE ...
},
// .. OTHER PROPERTIES GO HERE ...
});
|
Warning
You may rely on outside libraries (like d3-geo
) to compute your custom projections.
Be careful to make sure they are imported using appropriate <script/>
tags and
initialized appropriately in your client-side JavaScript. Highcharts Maps for Python
provides no introspection of your JavaScript code, so you will have to make sure
you’ve laid appropriate groundwork in the code into which you will be inserting your
Highcharts Maps for Python serialized JavaScript.
Once you have defined your
CustomProjection
,
you can apply it to your chart either using the
Chart.set_custom_projection()
convenience method or by setting
Chart.options.map_view.custom
directly.