Offline Scattergeo plots not working

Hi everybody,

Im working on Maps plots offline but I have troubles with the scattergeo plot.
It doesnā€™t display the map and I donā€™t understand why.

I saw similar questions on the forum (Scattergeo Plots Offline? & Scattergeo loading map offline from local topojson (with Python) [Solved]) but I donā€™t have any knowledge with json and network.

Is someone can explain to me how to perform my plots please ?

Thank you in advance,
AD

If you can share a simple example of whatā€™s not working for you and how to reproduce it, Iā€™d be glad to help!

I believe I can help you out without an example.

Hereā€™s a list of procedures that should work whenever you have an internet connection that can reach out to plot.ly

  1. Make a scattergeo with valid data
  2. Add geo plot to fig
  3. Run plotly.offline.plot(fig)
  4. The figure should show up with the correct map and everything.

Hereā€™s a list of procedures to get this working completely offline without access to internet or plot.ly.

  1. Visit http://cdn.plot.ly/world_110m.json and save the json (upper left hand corner should say ā€˜Saveā€™ in Firefox browser.

  2. Save the .json file in a folder as ā€œworld_110m.jsonā€. I have mine saved in a directory as follows Flask/plotly/topojson

  3. Install flask and flask-cors via pip or Anaconda

  4. Make a simple Flask app (Save it as ā€œPlotlyFlask.pyā€ in the Flask/ directory that you made where you saved the .json file) that can access these topojson files like the following:

import os
import sys
from flask import Flask
from flask_cors import CORS,cross_origin
import json
import argparse

app = Flask(__name__,template_folder='template')
CORS(app)

file_dir = os.path.dirname(os.path.realpath(__file__))

    
@app.route('/<path:relative_path>',methods=['GET','POST'])
@cross_origin()
def get_topojson(relative_path):
    i = json.load(open(os.path.join(file_dir,relative_path),'r'))
    return json.dumps(i)



if __name__ == "__main__":
    my_parser = argparse.ArgumentParser()
    my_parser.version = '0.1.0'
    my_parser.add_argument('-port',default=5000)
    my_parser.add_argument('-host',default='127.0.0.1')
    args = my_parser.parse_args()
    port = args.port
    host = args.host
    app.run(debug = True, port=port, host=host)
  1. Run the PlotlyFlask.py script from a terminal. This will create a web server for you to be able to access the topojsons that you acquire from plot.ly [This is where the default offline mode fails, hence why this is necessary]. Make note of the Hostname and Port that it is running from. My default is 127.0.0.1 [localhost] and port 5000 [just one that i commonly use for testing].

  2. In a completely different python script, create a scattergeo and add it to fig

  3. at the end of your plotting script do

plotly.offline.plot(fig,config={'topojsonURL':'http://127.0.0.1:5000/plotly/topojson/'})

or more generally if you want to write a function just do something like this:

import plotly.graph_objects as go
import plotly
def PLOT(fig,filename,**kwargs):
    host = kwargs.get('host','127.0.0.1')
    port = kwargs.get('port',5000)
    if isinstance(fig,dict):
        fig = go.Figure(fig)
    plotly.offline.plot(fig,filename=filename,config={'topojsonURL':f'http://{host}:{port}/plotly/topojson/'},**kwargs)

Some things to make note of:

The topojsonURL in the config dictionary needs to match the file structure FROM your PlotlyFlask.py to where your topojsons are located. So if your PlotlyFlask.py file is in

MyRandomFolderName/

and the topojson world_110m.json is in

MyRandomFolderName/See/You/There/world_110m.json

then the config would read

config = {'topojsonURL':f'http://{host}:{port}/See/You/There/'}

and everything should work completely offline. Lemme know if you get stuck.

Basically make a Flask app that serves the topojson files and tell plotly to go look for them on your hosted web server instead of going to some website on the internet.

Note, there are more topojson files that need to be downloaded. For the complete set, just replace ā€œworld_110m.jsonā€ from step 1 with each of the following:

world_50m.json
usa_110m.json
usa_50m.json

and all of those should be all that you need with the standard set of projections. There should be no further need to edit the PlotlyFlask.py to get these topojsons to work. it should just give plotly everything it needs. [This really needs to be in the documentations somewhere because plotly is not TRULY offline out of the box.]

Hi guys ! First of all, thank you @nicolaskruchten for proposing your help !

I think @carlmarl understood what is exactly my problem, thank you very much for this precise answer.

I followed the steps you presented and I think iā€™m missing something because itā€™s still not working.

  1. I downloaded the json and putted it here ā€œMyproject/data/plotly-data/world_110m.jsonā€

  2. I copied the simple flask script without modiying anything, putted it here ā€œMyproject/data/ploty-data/PlotlyFlask.pyā€

  3. I ran the script with the Anaconda prompt because itā€™s the only terminal I have containing my python environement, itā€™s working well ā€œRunning on http://127.0.0.1:5000/ (Press Ctrl+C to quit)ā€

  4. Maybe the error is here but Iā€™m running spyder in another anaconda prompt and finally Iā€™m running the plot code in spyder with the following line of code :

plotly.offline.plot(fig, config={'topojsonURL' : 'http://127.0.0.1:5000/'})

Nevertheless, I still have the same behaviour, the plot is empty and I just have the title and the legend, also, the URL is not mentionning the localhost 127.0.0.1 anywhere.

Do I have to run the PlotlyFlask and the plot code in the same Anaconda Prompt ?

Thank you very much for your help !

Iā€™m not sure of all of the inner workings of Flask, but I believe that the Flask web-server will only connect to directories that are located UNDER the PlotlyFlask.py file. So try putting your .json file 1 level lower.

Myproject/data/plotly-data/topojson/world_110m.json

I think that might fix your problem. :smiley:

You should see in the terminal where you ran PlotlyFlask.py something that indicates that a resource is attempting to be accessed.

To answer your question, I have never had to run the PlotlyFlask and the plotting code from the same terminal. I will say, that spyder and Plotly do not seem to get along with purely showing the figures. I would run both in separate Anaconda terminals.

Thank you angain for helping me @carlmarl.

  1. I putted the json in a subfolder topojson as you suggested and I changed the path in the plotly.offline.plot command;
  2. I added the forgotten f character inside config={ā€˜topojsonURLā€™:fā€™http://127.0.0.1:5000/topojson/'};
  3. I tried to plot threw spyder and directly threw an anaconda promptā€¦

Nevertheless, itā€™s still not working, I have neither the points, neither the map, just title and legend :frowning:

Also, I donā€™t see in the terminal of PlotlyFlask something indicating that a ressource is attempting to be accessed, here is what I have :

  • Serving Flask app ā€œPlotlyFlaskā€ (lazy loading)
  • Environment: production
    WARNING: This is a development server. Do not use it un a production deployment.
  • Use a production WSGI server instead.
  • Debug mode: on
  • Restarting with windowsapi realoader
  • Debugger is active!
  • Debugger PIN: 191-270-134
  • Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

It doesnā€™t display anything else when Iā€™m running the plot code.

Iā€™m still opened to suggestions if someone have an idea.

Can you write the figure out to an html file and open it in notepad++ and search for ā€œtopojsonURLā€ and confirm that it being set to ā€œhttp://127.0.0.1:5000/topojsonā€?

Also you can test to see if the Flask app is running properly by visiting http://127.0.0.1:5000/topojson/world_110m.json in a browser. You should see the json file in your browser, the same as visiting the cdn.plot.ly link from step 1 that I provided.

Also, did you make sure to download all of the other .json files that I suggested? It could be possible that you are using a projection that requires a different .json file.

EDIT: You should see something like this when the plot is being shown if your flask app is working correctly.

 * Serving Flask app "PlotlyFlask" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Restarting with windowsapi reloader
 * Debugger is active!
 * Debugger PIN: 445-777-133
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [18/Aug/2020 17:43:19] "e[37mGET /plotly/topojson/world_110m.json HTTP/1.1e[0m" 200 -

Thank you for your patience !

  1. I opened my html plot in notepad++ and I confirm that I have the path of the json in the local server
Plotly.newPlot(  [...]  {"topojsonURL": "http://127.0.0.1:5000/topojson/world_110m.json", "responsive": true})
  1. When Iā€™m oppening the previous path in my browser, I have an access to the json and a GET line appears in the anaconda prompt as excepted

  2. I downloaded the world_50m.json and not the others ones but Iā€™m pretty sure that Iā€™m just using the world_110m.json projection.

  3. I added a print for the path in the PlotlyFlask.py code and I noticed something strange about the path, I thought it was about the linux/windows paths. Itā€™s this path which is used in the python to load the json and return the json.dumps(i) :

[...]\data\plotly-data\topojson/world_110m.json
  1. I just copied/pasted the content of the json link in a notes file, do you think there is an impact on /n/r on windows that could have any impact on the json ?

No problem! I just want to see this work for you.

I havenā€™t had any OS version issues, since we need our stuff to work on both windows and linux, however, that might be an issue. I cannot remember if I tested on Linux the full extent of this working. You could take the path that you print out and try to load the json file in Spyder. If that works then it should be just fine.

Iā€™m not entirely sure why the GET request is not showing when you try to open the plot though. I need to look into this a little bit more.

So, admittedly, it has been a while since Iā€™ve set all this stuff up and Iā€™ve been trying different things since my initial success with the offline plotting.

What I would recommend do is search the html document again for any ā€œhttp://127.0.0.1:5000ā€ and see if it is trying to load any other files that you do not have.

I have a plotly-lately.min.js file that Iā€™ve put at the same level as my topojson folder. I cannot recall if plotly offline needs this file too in order to work offline. I cannot currently find the correct URL in order to download this file. I also have a jquery folder with the latest jquery javascript file inside. [IIRC this file was something that I was trying to do with Dashā€¦ but im not sure].

It begins to be really painfullā€¦

  1. I tested the path as it is but of course it didnā€™t work because in spyder, we need Linux pathsā€¦
  2. I modified the PlotlyFlask.py code to change the path inside and provide an linux friendly type of path, by performing the following lines of code :
def get_topojson(relative_path):
    path = os.path.join(file_dir, relative_path)
    path = path.replace('\\', '/')
    i = json.load(open(path, 'r'))
    return json.dumps(i)
  1. I donā€™t know if the javascript inside the html file is well called, before the generation of the plot, there is an if condition but iā€™m not sure that the condition is respected :
                
                    window.PLOTLYENV=window.PLOTLYENV || {};
                    
                if (document.getElementById("023743d3-6456-4b13-88c9-6c551fb17550")) {
                    Plotly.newPlot(
                        '023743d3-6456-4b13-88c9-6c551fb17550',
                        [{"lat": [20.0], "lon": [-45.0], "mode": "markers", "name": "JustApoint", "text": "Apoint", "type": "scattergeo"}],
                        {"template": {"data": {"bar": [{"error_x": {"color": "#2a3f5f"}, "error_y": {"color": "#2a3f5f"}, "marker": {"line": {"color": "#E5ECF6", "width": 0.5}}, "type": "bar"}], "barpolar": [{"marker": {"line": {"color": "#E5ECF6", "width": 0.5}}, "type": "barpolar"}], "carpet": [{"aaxis": {"endlinecolor": "#2a3f5f", "gridcolor": "white", "linecolor": "white", "minorgridcolor": "white", "startlinecolor": "#2a3f5f"}, "baxis": {"endlinecolor": "#2a3f5f", "gridcolor": "white", "linecolor": "white", "minorgridcolor": "white", "startlinecolor": "#2a3f5f"}, "type": "carpet"}], "choropleth": [{"colorbar": {"outlinewidth": 0, "ticks": ""}, "type": "choropleth"}], "contour": [{"colorbar": {"outlinewidth": 0, "ticks": ""}, "colorscale": [[0.0, "#0d0887"], [0.1111111111111111, "#46039f"], [0.2222222222222222, "#7201a8"], [0.3333333333333333, "#9c179e"], [0.4444444444444444, "#bd3786"], [0.5555555555555556, "#d8576b"], [0.6666666666666666, "#ed7953"], [0.7777777777777778, "#fb9f3a"], [0.8888888888888888, "#fdca26"], [1.0, "#f0f921"]], "type": "contour"}], "contourcarpet": [{"colorbar": {"outlinewidth": 0, "ticks": ""}, "type": "contourcarpet"}], "heatmap": [{"colorbar": {"outlinewidth": 0, "ticks": ""}, "colorscale": [[0.0, "#0d0887"], [0.1111111111111111, "#46039f"], [0.2222222222222222, "#7201a8"], [0.3333333333333333, "#9c179e"], [0.4444444444444444, "#bd3786"], [0.5555555555555556, "#d8576b"], [0.6666666666666666, "#ed7953"], [0.7777777777777778, "#fb9f3a"], [0.8888888888888888, "#fdca26"], [1.0, "#f0f921"]], "type": "heatmap"}], "heatmapgl": [{"colorbar": {"outlinewidth": 0, "ticks": ""}, "colorscale": [[0.0, "#0d0887"], [0.1111111111111111, "#46039f"], [0.2222222222222222, "#7201a8"], [0.3333333333333333, "#9c179e"], [0.4444444444444444, "#bd3786"], [0.5555555555555556, "#d8576b"], [0.6666666666666666, "#ed7953"], [0.7777777777777778, "#fb9f3a"], [0.8888888888888888, "#fdca26"], [1.0, "#f0f921"]], "type": "heatmapgl"}], "histogram": [{"marker": {"colorbar": {"outlinewidth": 0, "ticks": ""}}, "type": "histogram"}], "histogram2d": [{"colorbar": {"outlinewidth": 0, "ticks": ""}, "colorscale": [[0.0, "#0d0887"], [0.1111111111111111, "#46039f"], [0.2222222222222222, "#7201a8"], [0.3333333333333333, "#9c179e"], [0.4444444444444444, "#bd3786"], [0.5555555555555556, "#d8576b"], [0.6666666666666666, "#ed7953"], [0.7777777777777778, "#fb9f3a"], [0.8888888888888888, "#fdca26"], [1.0, "#f0f921"]], "type": "histogram2d"}], "histogram2dcontour": [{"colorbar": {"outlinewidth": 0, "ticks": ""}, "colorscale": [[0.0, "#0d0887"], [0.1111111111111111, "#46039f"], [0.2222222222222222, "#7201a8"], [0.3333333333333333, "#9c179e"], [0.4444444444444444, "#bd3786"], [0.5555555555555556, "#d8576b"], [0.6666666666666666, "#ed7953"], [0.7777777777777778, "#fb9f3a"], [0.8888888888888888, "#fdca26"], [1.0, "#f0f921"]], "type": "histogram2dcontour"}], "mesh3d": [{"colorbar": {"outlinewidth": 0, "ticks": ""}, "type": "mesh3d"}], "parcoords": [{"line": {"colorbar": {"outlinewidth": 0, "ticks": ""}}, "type": "parcoords"}], "pie": [{"automargin": true, "type": "pie"}], "scatter": [{"marker": {"colorbar": {"outlinewidth": 0, "ticks": ""}}, "type": "scatter"}], "scatter3d": [{"line": {"colorbar": {"outlinewidth": 0, "ticks": ""}}, "marker": {"colorbar": {"outlinewidth": 0, "ticks": ""}}, "type": "scatter3d"}], "scattercarpet": [{"marker": {"colorbar": {"outlinewidth": 0, "ticks": ""}}, "type": "scattercarpet"}], "scattergeo": [{"marker": {"colorbar": {"outlinewidth": 0, "ticks": ""}}, "type": "scattergeo"}], "scattergl": [{"marker": {"colorbar": {"outlinewidth": 0, "ticks": ""}}, "type": "scattergl"}], "scattermapbox": [{"marker": {"colorbar": {"outlinewidth": 0, "ticks": ""}}, "type": "scattermapbox"}], "scatterpolar": [{"marker": {"colorbar": {"outlinewidth": 0, "ticks": ""}}, "type": "scatterpolar"}], "scatterpolargl": [{"marker": {"colorbar": {"outlinewidth": 0, "ticks": ""}}, "type": "scatterpolargl"}], "scatterternary": [{"marker": {"colorbar": {"outlinewidth": 0, "ticks": ""}}, "type": "scatterternary"}], "surface": [{"colorbar": {"outlinewidth": 0, "ticks": ""}, "colorscale": [[0.0, "#0d0887"], [0.1111111111111111, "#46039f"], [0.2222222222222222, "#7201a8"], [0.3333333333333333, "#9c179e"], [0.4444444444444444, "#bd3786"], [0.5555555555555556, "#d8576b"], [0.6666666666666666, "#ed7953"], [0.7777777777777778, "#fb9f3a"], [0.8888888888888888, "#fdca26"], [1.0, "#f0f921"]], "type": "surface"}], "table": [{"cells": {"fill": {"color": "#EBF0F8"}, "line": {"color": "white"}}, "header": {"fill": {"color": "#C8D4E3"}, "line": {"color": "white"}}, "type": "table"}]}, "layout": {"annotationdefaults": {"arrowcolor": "#2a3f5f", "arrowhead": 0, "arrowwidth": 1}, "coloraxis": {"colorbar": {"outlinewidth": 0, "ticks": ""}}, "colorscale": {"diverging": [[0, "#8e0152"], [0.1, "#c51b7d"], [0.2, "#de77ae"], [0.3, "#f1b6da"], [0.4, "#fde0ef"], [0.5, "#f7f7f7"], [0.6, "#e6f5d0"], [0.7, "#b8e186"], [0.8, "#7fbc41"], [0.9, "#4d9221"], [1, "#276419"]], "sequential": [[0.0, "#0d0887"], [0.1111111111111111, "#46039f"], [0.2222222222222222, "#7201a8"], [0.3333333333333333, "#9c179e"], [0.4444444444444444, "#bd3786"], [0.5555555555555556, "#d8576b"], [0.6666666666666666, "#ed7953"], [0.7777777777777778, "#fb9f3a"], [0.8888888888888888, "#fdca26"], [1.0, "#f0f921"]], "sequentialminus": [[0.0, "#0d0887"], [0.1111111111111111, "#46039f"], [0.2222222222222222, "#7201a8"], [0.3333333333333333, "#9c179e"], [0.4444444444444444, "#bd3786"], [0.5555555555555556, "#d8576b"], [0.6666666666666666, "#ed7953"], [0.7777777777777778, "#fb9f3a"], [0.8888888888888888, "#fdca26"], [1.0, "#f0f921"]]}, "colorway": ["#636efa", "#EF553B", "#00cc96", "#ab63fa", "#FFA15A", "#19d3f3", "#FF6692", "#B6E880", "#FF97FF", "#FECB52"], "font": {"color": "#2a3f5f"}, "geo": {"bgcolor": "white", "lakecolor": "white", "landcolor": "#E5ECF6", "showlakes": true, "showland": true, "subunitcolor": "white"}, "hoverlabel": {"align": "left"}, "hovermode": "closest", "mapbox": {"style": "light"}, "paper_bgcolor": "white", "plot_bgcolor": "#E5ECF6", "polar": {"angularaxis": {"gridcolor": "white", "linecolor": "white", "ticks": ""}, "bgcolor": "#E5ECF6", "radialaxis": {"gridcolor": "white", "linecolor": "white", "ticks": ""}}, "scene": {"xaxis": {"backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white"}, "yaxis": {"backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white"}, "zaxis": {"backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white"}}, "shapedefaults": {"line": {"color": "#2a3f5f"}}, "ternary": {"aaxis": {"gridcolor": "white", "linecolor": "white", "ticks": ""}, "baxis": {"gridcolor": "white", "linecolor": "white", "ticks": ""}, "bgcolor": "#E5ECF6", "caxis": {"gridcolor": "white", "linecolor": "white", "ticks": ""}}, "title": {"x": 0.05}, "xaxis": {"automargin": true, "gridcolor": "white", "linecolor": "white", "ticks": "", "title": {"standoff": 15}, "zerolinecolor": "white", "zerolinewidth": 2}, "yaxis": {"automargin": true, "gridcolor": "white", "linecolor": "white", "ticks": "", "title": {"standoff": 15}, "zerolinecolor": "white", "zerolinewidth": 2}}}, "title": {"text": "JustApoint"}, "xaxis": {"title": {"text": "Longitude"}}, "yaxis": {"title": {"text": "Latitude"}}},
                        {"topojsonURL": "http://127.0.0.1:5000/topojson/world_110m.json", "responsive": true}
                    )
                };
                
            </script>
  1. I tried a really simple plot also :
import plotly.graph_objs as go
from plotly.offline import plot

SimpleTrace = go.Scattergeo(lon=[-45.],
                        lat=[20.],
                        text='Apoint',
                        mode='markers',
                        name='JustApoint')
data = [SimpleTrace]
layout = go.Layout(title='JustApoint',
                   xaxis=dict(title='Longitude'),
                   yaxis=dict(title='Latitude'))

fig = dict(data=data, layout=layout)
plot(fig, config={'topojsonURL':'http://127.0.0.1:5000/topojson/world_110m.json'})

Finally, when I open the html with notepad++, their is only one http://127.0.0.1:5000/ like path and the GET is orking well when Iā€™m clicking on the link.

Hey, its been a while. But I have done this so many times now, that Iā€™m not sure why my previous answer didnā€™t work for you. But here is a link to the stackoverflow page in which I just detailed the answer again.