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:

  1. Configure your data visualizations in Python.

  2. Supply data you have in Python to your data visualizations.

  3. Programmatically produce the Highcharts Maps JavaScript code that will actually render your data visualization.

  4. 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:

  1. All Highcharts for Python classes follow the Pythonic PascalCase class-naming convention.

  2. All Highcharts for Python properties and methods follow the Pythonic snake_case property/method/variable/function-naming convention.

  3. All inputs to properties support both snake_case and camelCase (aka mixedCase) 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 use snake_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 natural camelCase convention OR Highcharts for Python’s snake_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 expect snake_case properties to be supplied as keywords.

  4. All outputs from serialization methods (e.g. to_dict() or to_js_literal()) will produce outputs that are Highcharts JS-compatible, meaning that they apply the camelCase 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 a str containing the JavaScript object, or a path to a file which consists of the object.

  • allow_snake_case (bool) – If True, allows keys in as_string_or_file to apply the snake_case convention. If False, will ignore keys that apply the snake_case convention and only process keys that use the camelCase convention. Defaults to True.

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 or bytes form, or as a file name) into a Highcharts for Python object, typically descended from HighchartsMeta.

Parameters:
  • cls (type) – The class object itself.

  • as_json_or_file (str or bytes) – The JSON object you wish to convert, or a filename that contains the JSON object that you wish to convert.

  • allow_snake_case (bool) – If True, allows keys in as_json to apply the snake_case convention. If False, will ignore keys that apply the snake_case convention and only process keys that use the camelCase convention. Defaults to True.

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 from HighchartsMeta.

Parameters:
  • cls (type) – The class object itself.

  • as_dict (dict) – The dict representation of the object.

  • allow_snake_case (bool) – If True, allows keys in as_dict to apply the snake_case convention. If False, will ignore keys that apply the snake_case convention and only process keys that use the camelCase convention. Defaults to True.

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:
  • filename (Path-like or None) – If supplied, persists the JavaScript code to the file indicated. Defaults to None.

  • encoding (str) – Indicates the character encoding to use when producing the JavaScript literal string. Defaults to 'utf-8'.

Returns:

Highcharts Maps-compatible JavaScript code using JavaScript object literal notation.

Return type:

str

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:
  • filename (Path-like or None) – If supplied, persists the JSON is persisted to the file indicated. Defaults to None.

  • encoding (str) – Indicates the character encoding to use when producing the JSON. Defaults to 'utf-8'.

Returns:

Highcharts Maps-compatible JSON representation of the object.

Return type:

str or bytes

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.

to_dict(self)

Convert the Highcharts Maps for Python object into a Highcharts Maps-compatible dict object.

Returns:

Highcharts Maps-compatible dict object

Return type:

dict

Other Convenience Methods

copy(self, other, overwrite=True, **kwargs)

Copy the properties from self to other.

Parameters:
  • other (HighchartsMeta) – The target instance to which the properties of this instance should be copied.

  • overwrite (bool) – if True, properties in other that are already set will be overwritten by their counterparts in self. Defaults to True.

  • 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.

Note

There’s an item on the Highcharts for Python roadmap (#7) to optionally surface defaults when explicitly requested. Not sure when it will be implemented, but we’ll get there at some point.

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

Use Shared Options

One of the most challenging aspects of Highcharts Core and Highcharts Maps are their sheer breadth of functionality and configurability. That’s simultaneously their greatest strength, and their greatest weakness. This is because it can be quite challenging to wrangle thousands of properties - especially when even a single visualization can use hundreds of those properties!

This is a challenge that we are keenly aware of, and one which we’ve given some thought to in the design of the Highcharts for Python Toolkit. A core principle you should use throughout your project is to practice DRY programming.

If your application will be generating multiple visualizations, they will likely need some consistent configurations.

For example, you will want their title position to be consistent, their color schemes to be consistent, their font sizing to be consistent, etc. In your code you want these configuration settings to be defined once and then applied to all of the visualizations you are producing.

This can be facilitated using the SharedMapsOptions class. This generates a single set of global options which - when serialized to JavaScript - apply its configuration settings consistently across all data visualizations on the same page.

Warning

SharedMapsOptions is a sub-class of SharedOptions which extends its properties and methods with properties/methods that are only available in the Highcharts Maps API.

However, this class is fully backwards-compatible with the Highcharts Core API if you leave the Maps-specific methods and properties set to None (their default).

As with all Highcharts for Python objects, you can instantiate them in several ways:

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 SharedMapsOptions 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
| ———— bar-template-01.js
| ———— bar-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

You’ll notice that the organization has a project_resources folder. This is where you would put the various files that your application wlil reference, like your static images, or the files that contain data you might be using in your application. It also contains a highcharts_config folder, which contains several files with a .js extension. Of particular note is the file in bold, shared_options.js. This file should contain a JavaScript object literal version of the configuration settings you want to apply to all of your visualizations. This file might look something like this:

{
  chart: {
        backgroundColor: {
            linearGradient: {
              x1: 0,
              x2: 0,
              y1: 1,
              y2: 1
            },
            stops: [
                [0, 'rgb(255, 255, 255)'],
                [1, 'rgb(240, 240, 255)']
            ]
        },
        borderWidth: 2,
        plotBackgroundColor: 'rgba(255, 255, 255, .9)',
        plotBorderWidth: 1
  },
  caption: {
      align: 'center',
      floating: true,
      margin: 20,
      verticalAlign: 'top'
  },
  credits: {
      enabled: true,
      href: 'https://www.somewhere.com',
      style: {
          color: '#cccccc',
          fontSize: '8px'
      },
      text: 'Highcharts for Python'
  }
}

Now with this file, you can easily create a SharedMapsOptions instance by executing:

from highcharts_maps.highcharts import SharedMapsOptions

my_shared_options = SharedMapsOptions.from_js_literal('../../project_resources/highcharts_config/shared_options.js')

And that’s it! Now you have a SharedMapsOptions instance that can be used to apply your configuration standards to all of your charts. You can do that by delivering its JavaScript output to your front-end by calling:

js_code_snippet = my_shared_options.to_js_literal()

which will produce a string as follows:

Highcharts.setOptions({
  caption: {
    align: 'center',
    floating: true,
    margin: 20,
    verticalAlign: 'top'
  },
  chart: {
    backgroundColor: {
      linearGradient: {
        x1: 0.0,
        x2: 0.0,
        y1: 1.0,
        y2: 1.0
      },
      stops: [
        [0, 'rgb(255, 255, 255)'],
        [1, 'rgb(240, 240, 255)']
      ]
    },
    borderWidth: 2,
    plotBackgroundColor: 'rgba(255, 255, 255, .9)',
    plotBorderWidth: 1
  },
  credits: {
    enabled: true,
    href: 'https://www.somewhere.com',
    style: {
      color: '#cccccc',
      fontSize: '8px'
    },
    text: 'Highcharts for Python'
  }
});

And now you can deliver js_code_snippet to your HTML template or wherever it will be rendered.

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.


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.

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 the url (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.


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

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:

Expand Method Signature
classmethod .from_array(cls, value)

Creates a collection of data point instances, parsing the contents of value as an array (iterable). This method is specifically used to parse data that is input to Highcharts for Python without property names, in an array-organized structure as described in the Highcharts JS documentation.

See also

The specific structure of the expected array is highly dependent on the type of data point that the series needs, which itself is dependent on the series type itself.

Please review the detailed series documentation for series type-specific details of relevant array structures.

Note

An example of how this works for a simple LineSeries (which uses CartesianData data points) would be:

my_series = LineSeries()

# A simple array of numerical values which correspond to the Y value of the data
# point
my_series.data = [0, 5, 3, 5]

# An array containing 2-member arrays (corresponding to the X and Y values of the
# data point)
my_series.data = [
    [0, 0],
    [1, 5],
    [2, 3],
    [3, 5]
]

# An array of dict with named values
my_series.data = [
  {
      'x': 0,
      'y': 0,
      'name': 'Point1',
      'color': '#00FF00'
  },
  {
      'x': 1,
      'y': 5,
      'name': 'Point2',
      'color': '#CCC'
  },
  {
      'x': 2,
      'y': 3,
      'name': 'Point3',
      'color': '#999'
  },
  {
      'x': 3,
      'y': 5,
      'name': 'Point4',
      'color': '#000'
  }
]
Parameters:

value (iterable) –

The value that should contain the data which will be converted into data point instances.

Note

If value is not an iterable, it will be converted into an iterable to be further de-serialized correctly.

Returns:

Collection of data point instances (descended from DataBase)

Return type:

list of DataBase-descendant instances

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 Python HighchartsOptions class, and is fully backwards-compatible with it. This means that you can use them interchangeably when using Highcharts Maps for Python, as the HighchartsMapsOptions 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)

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]
#          ]
#      }
#   });
# });

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 or None) – The shared options to use when rendering the chart. Defaults to None

  • container (str or None) –

    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. The Chart 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 a HighchartsDependencyError.

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 to None.

  • server_instance (ExportServer or None) – Provide an already-configured ExportServer instance to use to programmatically produce the exported chart. Defaults to None, which causes Highcharts for Python to instantiate a new ExportServer 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 or None) – The username to use to authenticate against the Export Server, using basic authentication. Defaults to None.

  • auth_password (str or None) – The password to use to authenticate against the Export Server (using basic authentication). Defaults to None.

  • 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 the timeout value. Defaults to 0.5.

  • global_options (HighchartsMapsOptions, HighchartsOptions or None) – The global options which will be passed to the (JavaScript) Highcharts.setOptions() method, and which will be applied to the exported chart. Defaults to None.

Note

All other keyword arguments are as per the ExportServer constructor.

Returns:

The exported chart image, either as a bytes binary object or as a base-64 encoded string (depending on the use_base64 keyword argument).

Return type:

bytes or str

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 class

  • have 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.