8

I am working with a new Python web framework called justpy which lets you build both the backend and the frontend of a web app using Python only. The framework also integrates with the javascript Highcharts library. Here's how to build a web app that contains a Highcharts plot:

import justpy as jp
import pandas as pd


wm = pd.read_csv('https://elimintz.github.io/women_majors.csv').round(2)
# Create list of majors which start under 20% women students
wm_under_20 = list(wm.loc[0, wm.loc[0] < 20].index)

def women_majors():
    wp = jp.WebPage()
    wm.jp.plot(0, wm_under_20, kind='spline', a=wp, title='The gender gap is transitory - even for extreme cases',
               subtitle='Percentage of Bachelors conferred to women form 1970 to 2011 in the US for extreme cases where the percentage was less than 20% in 1970',
                classes='m-2 p-2 w-3/4')
    return wp

jp.justpy(women_majors)

that will load the webapp on localhost:8000:

enter image description here

I am now trying to figure out how to display the Highcharts plot only, without having to build a web app.

If I modify the above code to this:

import justpy as jp
import pandas as pd


wm = pd.read_csv('https://elimintz.github.io/women_majors.csv').round(2)
# Create list of majors which start under 20% women students
wm_under_20 = list(wm.loc[0, wm.loc[0] < 20].index)

fig = wm.jp.plot(0, wm_under_20, kind='spline', title='The gender gap is transitory - even for extreme cases',
               subtitle='Percentage of Bachelors conferred to women form 1970 to 2011 in the US for extreme cases where the percentage was less than 20% in 1970',
                classes='m-2 p-2 w-3/4')
print(fig)

That will return the following output:

 HighCharts(id: 1, vue_type: chart, chart options: {'series': [{'data': [4.23,...

How can I make an image file out of that HighCharts object (or show the plot in a Jupyter notebook) without having to build a web app?

Wolfgang Fahl
  • 15,016
  • 11
  • 93
  • 186
multigoodverse
  • 7,638
  • 19
  • 64
  • 106
  • It is recommended to use the Node Export Server to render a graph without the web app, but I don't know how to configure it with the Jupyter. – Sebastian Wędzel Mar 19 '21 at 12:12
  • I haven't used Hichart, but I think the introduction of this library will solve the problem. See here for [highchartexport](https://pypi.org/project/highchartexport/). – r-beginners Mar 21 '21 at 02:35
  • why not use matplotlib? – Piyush Singh Mar 21 '21 at 19:30
  • 1
    @PiyushSingh because interactive charts are much better - they convey information more efficiently to viewers. I think matplotlib is only is good if you want to generate graphs in batch. – multigoodverse Mar 22 '21 at 11:09
  • If you just want to show the plot in a Jupyter notebook, this link[https://nbviewer.jupyter.org/github/arnoutaertgeerts/python-highcharts/blob/master/Tutorial.ipynb] would help you! – Abdul Wahab Mar 22 '21 at 15:29
  • "I am now trying to figure out how to display the Highcharts plot only, without having to build a web app." Can you elaborate? Highcharts is a JS library that works in browser. What do you mean by "build a web app"? – Arman Ordookhani Mar 23 '21 at 14:31
  • Not sure about the whole Jupyter notebook thing, but if you want pure html/javascript file you can use [pandas-highcharts](https://pypi.org/project/pandas-highcharts). I have been using it with pandas quite recently even if this library is outdated... – tgrandje Mar 23 '21 at 17:19

2 Answers2

3

There are two ways of doing it. One as an image and the other as an interactive chart/hacky.

Image. You will need to import requests,Image and json. The fig.options generated by justpy will be sent as a payload to the highcharts export server and will return an image.

import requests
from IPython.display import Image
import json

#using the fig output from the justpy.plot extension
#fig = wm.jp.plot(0,  ......

payload = {"async": True,
           "constr": "Chart",
           "infile": fig.options,
           "scale": False,
           "type": "image/png",
           "width": False}

response = requests.post("""https://export.highcharts.com/""" ,json=payload)

Image(url='https://export.highcharts.com/'+response.text)

Jupyter Notebook Interactive/Hacky way of doing it for Jupyter as interactive. I copied the approach here Embedding d3.js

You will need to import 2 things and then use the %%javascript cell magic. These are needed since the charts for Highcharts need Javascript to be rendered.

Cell 1

#import needed
IPython.display import Javascript
import json

Cell 2

#using the fig output from the justpy.plot extension
#fig = wm.jp.plot(0,  ......

#this converts the dict(addict is actually whats used by justpy) into json
Javascript("""
           window.dataForchart={};
           """.format(json.dumps(fig.options)))

Cell 3
this runs the javascript code that renders the chart and displays it in the notebook

%%javascript
require.config({
    packages: [{
        name: 'highcharts',
        main: 'highcharts'
    }],
    paths: {
        'highcharts': 'https://code.highcharts.com'
    }
});
$("#chart1").remove();
element.append(`<div id="chart1"></div>`);
require([
    'highcharts',
    'highcharts/modules/exporting',
    'highcharts/modules/accessibility'
], function (Highcharts){Highcharts.chart("chart1", window.dataForchart)});

Jupyter Lab Interactive/Hacky

Cell 1

from IPython.display import Javascript,HTML
import json
import requests

Cell 2

#loads highcharts into the notebook. Succeeding calls for 
#Highchart will work if you open this notebook.
response = requests.get('https://code.highcharts.com/highcharts.js')
Javascript(response.text)

Cell 3

Javascript("""
           window.dataForchart={};
           """.format(json.dumps(fig.options)))

Cell 4

#the HTML function has to be in the last line of the cell
#for this to work. Also this become the output cell
HTML('<div id="chart123"></div>')

Cell 5

#make sure that the chart id for the divs you make are unique so they
#dont overwrite each other
Javascript('Highcharts.chart("chart123", window.dataForchart);')

The image below is for the Fruit Chart example

fruit_chart

This one is for your specific example your_example

fcsr
  • 921
  • 10
  • 17
  • Ok, I first ran the cell containing my code. Then, I ran your cell 1, 2, and 3, but when I ran cell 3, I got the error `Javascript Error: require is not defined`. Do you know how to fix that? Update: The error is only occurring in Jupyter Lab. It works on Jupyter Notebook. – multigoodverse Mar 24 '21 at 15:31
  • Jupyter Lab doesnt come with `require.js` apparently. https://github.com/jupyterlab/jupyterlab/issues/8219. – fcsr Mar 24 '21 at 16:13
  • @multigoodverse added the Jupyter Lab version – fcsr Mar 28 '21 at 08:47
0

They have a question like this on the High Charts website

https://www.highcharts.com/forum/viewtopic.php?t=32634 Which says

One option is to use getSVG - export image without any server: http://api.highcharts.com/highcharts#Chart.getSVG Second option would be to use Highcharts default exporting server - post chart's options and get image: http://www.highcharts.com/docs/export-m ... e-overview Third option is to set up your own exporting server:

http://www.highcharts.com/docs/export-m ... the-server

https://www.highcharts.com/blog/news/52-serverside-generated-charts/

Main code segment:

function(chart) {
    chart.renderer.arc(200, 150, 100, 50, -Math.PI, 0).attr({
        fill : '#FCFFC5',
        stroke : 'black',
        'stroke-width' : 1
     }).add();
}

and on stackoverflow:

How to save an image of the chart on the server with highcharts?

This shows a variety of methods and adapts the questions code to

var system = require('system');
var page = require('webpage').create();
var fs = require('fs');

// load JS libraries
page.injectJs("js/jquery.min.js");
page.injectJs("js/highcharts/highcharts.js");
page.injectJs("js/highcharts/exporting.js");

// chart demo
var args = {
width: 600,
height: 500
};

var svg = page.evaluate(function(opt){
$('body').prepend('<div id="container"></div>');

var chart = new Highcharts.Chart({
    chart: {
        renderTo: 'container',
        width: opt.width,
        height: opt.height
    },
    exporting: {
        enabled: false
    },
    title: {
        text: 'Combination chart'
    },
    xAxis: {
        categories: ['Apples', 'Oranges', 'Pears', 'Bananas', 'Plums']
    },
    yAxis: {
        title: {
            text: 'Y-values'
        }
    },
    labels: {
        items: [{
            html: 'Total fruit consumption',
            style: {
                left: '40px',
                top: '8px',
                color: 'black'
            }
        }]
    },
    plotOptions: {
        line: {
            dataLabels: {
                enabled: true
            },
            enableMouseTracking: false
        },
        series: {
            enableMouseTracking: false, 
            shadow: false, 
            animation: false
        }
    },
    series: [{
        type: 'column',
        name: 'Andrii',
        data: [3, 2, 1, 3, 4]
    }, {
        type: 'column',
        name: 'Fabian',
        data: [2, 3, 5, 7, 6]
    }, {
        type: 'column',
        name: 'Joan',
        data: [4, 3, 3, 9, 0]
    }, {
        type: 'spline',
        name: 'Average',
        data: [3, 2.67, 3, 6.33, 3.33],
        marker: {
            lineWidth: 2,
            lineColor: 'white'
        }
    }, {
        type: 'pie',
        name: 'Total consumption',
        data: [{
            name: 'Andrii',
            y: 13,
            color: '#4572A7'
        }, {
            name: 'Fabian',
            y: 23,
            color: '#AA4643'
        }, {
            name: 'Joan',
            y: 19,
            color: '#89A54E'
        }],
        center: [100, 80],
        size: 100,
        showInLegend: false,
        dataLabels: {
            enabled: false
        }
    }]
});

return chart.getSVG();
},  args);

// Saving SVG to a file
fs.write("demo.svg", svg);
// Saving diagram as PDF
page.render('demo.pdf');

phantom.exit();