Search code examples
pythonplotlyplotly-pythonr-plotlyplotly.js

Is there a way to resize the legend items of a discrete choropleth created using Plotly?


I'm using Python, if helpful, though I believe any solution would be language agnostic. I've created a discrete choropleth mapbox using plotly.express, but when exporting at high resolutions the legend items (both font and coloring) become effectively unreadable. If I recall, Plotly creates discrete figures by overlaying multiple traces, so not sure whether there's something we can do at the trace or figure level to fix.

Happy to share code if helpful, though I'm not actually updating the legend in anyway yet, so I don't think it adds anything.

Hi-res map output

Std map output

Code:

DISCRETE = 11

def gen_colorscale(obs, color="viridis"):
    color = px.colors.sample_colorscale(color, obs)
    p1 = tuple(zip(np.linspace(0, 1, obs+1)[:-1], color))
    p2 = tuple(zip(np.linspace(0, 1, obs+1)[1:], color))
    cs = []
    for a, b in zip(p1, p2):
        cs.append(a)
        cs.append(b)
    return cs

cs = gen_colorscale(DISCRETE)

# color range
cr = [0, 10000]
# tick vals
v = np.linspace(*cr, DISCRETE)
vt = (
    pd.DataFrame(v, columns=["v"])
    .apply(lambda v: (v / 10 ** 3).round(1))
    .apply(lambda v: v.astype(str) + "k to " + v.shift(-1).astype(str) + "k")
    .values
)
vt[0] = v[0].astype(str) + " to " + (v[1] / 10 ** 3).round(1).astype(str) + "k"
vt[-1] = ">" + (v[-1] / 10 ** 3).round(1).astype(str) + "k"

fig = px.choropleth_mapbox(
    df,
    geojson=counties,
    locations="fips",
    color="migration",
    range_color=[cr[0], cr[1] + cr[1]/(DISCRETE-1)],
    color_continuous_scale=cs,
    labels={"migration": "Migration (k)"},
    center={"lat": 37.0902, "lon": -95.7129},
    zoom=4.2,
    opacity=1.0,
    mapbox_style="white-bg",
)
fig.update_layout(
    mapbox_style="mapbox://styles/ryangilland/ckwqzs8ck0h5f14nybww9c5ts",
    mapbox_accesstoken=token,
    coloraxis_colorbar=dict(
        tickvals=np.linspace(cr[0]+cr[1]/(DISCRETE-1)/2,cr[1] + cr[1]/(DISCRETE-1)/2,DISCRETE),
        ticktext=vt,
        len=0.8,
        thickness=50,
        xanchor="right",
        x=1.0,
        bgcolor="rgba(22,33,49,1)",
        tickfont=dict(color="rgba(255,255,255,1)"),
    ),
    margin=dict(l=0, r=0, b=50, t=75, pad=4),
    paper_bgcolor="rgba(8,18,23,1)",
    plot_bgcolor="rgba(8,18,23,1)",
)
fig.show()

Sample Image


Solution

  • import geopandas as gpd
    import numpy as np
    import pandas as pd
    import plotly.express as px
    
    gdf = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres")).set_index("iso_a3")
    
    DISCRETE = 6
    # build discrete continuous colorscale
    cs = [
        (a, px.colors.sample_colorscale("viridis", b)[0])
        for a, b in zip(
            np.repeat(np.linspace(0, 1, DISCRETE + 1), 2)[1:-1],
            np.repeat(np.linspace(0, 1, DISCRETE + 1), 2),
        )
    ]
    # color range
    cr = [0, gdf["pop_est"].quantile(.95)]
    # tick vals
    v = np.linspace(*cr, DISCRETE+1)
    vt = (
        pd.DataFrame(v, columns=["v"])
        .apply(lambda v: (v / 10 ** 6).round(0).astype(int))
        .apply(lambda v: v.astype(str) + "M to " + v.shift(-1).astype(str) + "M")
        .values
    )
    px.choropleth_mapbox(
        gdf,
        geojson=gdf.__geo_interface__,
        locations=gdf.index,
        color="pop_est",
        color_continuous_scale=cs,
        range_color=cr,
    ).update_layout(
        mapbox={"style": "carto-positron", "zoom": .5},
        coloraxis={"colorbar": {"tickvals": v[1:] - v[1]/2, "ticktext":vt}},
        margin={"l":0,"r":0,"t":0,"b":0}
    )
    

    enter image description here