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.
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()
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}
)