Plotly 图形对象中线条的离散色标 (Python)

Discrete colour scale for lines in Plotly graph objects (Python)

我正在将 plotly.graph_objects.Scattermapbox 中的线映射到 fig.add_trace。理想情况下,我想更改线条的颜色,使其对应于另一列中的属性。例如:

Line Attribute
LINE01 Highway
LINE02 River
LINE03 Highway

我不知道该怎么做。当我创建我的情节时,每一行都显示为图例条目(LINE01 显示为 trace 0,等等)这是我的代码:

import plotly.graph_objects as go
fig = go.Figure()


for i in range(len(df)):
    fig.add_trace(go.Scattermapbox(
        mode = "lines",
        lon = [df['lon.start'][i], df['lon.end'][i]],
        lat = [df['lat.start'][i], df['lat.end'][i]],
        line = dict(width = 3)
        )
    )

如何更改它,使我的图例按 Attribute 列分组以创建离散色标?

  • 你的问题没有道路和河流的样本数据。从英国政府来源获取英国河流和道路
  • 有两种方法可以将线层添加到绘图图形
    1. 作为地图框图层。这样做的好处是您可以利用 plotlygeopandas geojson 功能更简单地使用来自第三方来源的参考映射信息
    2. as traces - 您一直在使用的方法。您已经使用了这些行的开始和结束,这意味着您丢失了
    3. 之间的所有坐标
  • 我已经使用这个答案 How to plot visualize a Linestring over a map with Python? 关于如何在 mapbox 图上生成线条。
  • 使用了数据的子集,只是因为图形生成时间对全套河流和道路很重要

来源示例河流和道路数据

import urllib
from pathlib import Path
from zipfile import ZipFile
import geopandas as gpd
import pandas as pd

# get some river and road geometry....
src = [
    {
        "name": "rivers",
        "suffix": ".shp",
        "color": "blue",
        "width": 1.5,
        "url": "https://environment.data.gov.uk/UserDownloads/interactive/023ce3a412b84aca949cad6dcf6c5338191808/EA_StatutoryMainRiverMap_SHP_Full.zip",
    },
    {
        "name": "roads",
        "suffix": ".shp",
        "color": "red",
        "width": 3,
        "url": "https://maps.dft.gov.uk/major-road-network-shapefile/Major_Road_Network_2018_Open_Roads.zip",
    },
]
data = {}
for s in src:
    f = Path.cwd().joinpath(urllib.parse.urlparse(s["url"]).path.split("/")[-1])
    if not f.exists():
        r = requests.get(s["url"],stream=True,)
        with open(f, "wb") as fd:
            for chunk in r.iter_content(chunk_size=128):
                fd.write(chunk)

    fz = ZipFile(f)
    fz.extractall(f.parent.joinpath(f.stem))

    data[s["name"]] = gpd.read_file(
        f.parent.joinpath(f.stem).joinpath(
            [
                f.filename
                for f in fz.infolist()
                if Path(f.filename).suffix == s["suffix"]
            ][0]
        )
    ).assign(source_name=s["name"])
gdf = pd.concat(data.values()).to_crs("EPSG:4326")

使用 mapbox 图层

import plotly.graph_objects as go
import json

# let's work with longer rivers and smaller numer of random roads
gdf2 = gdf.loc[gdf["length_km"].gt(50).fillna(False) | gdf["roadNumber"].isin(gdf["roadNumber"].fillna("").sample(50).unique())  ]
fig = go.Figure(go.Scattermapbox())

# use geopandas and plotly geojson layer capabilities,  keep full definition of line strings
fig.update_layout(
    margin={"l": 0, "r": 0, "t": 0, "b": 0},
    mapbox={
        "style": "carto-positron",
        "zoom": 4,
        "center": {
            "lon": gdf.total_bounds[[0, 2]].mean(),
            "lat": gdf.total_bounds[[1, 3]].mean(),
        },
        "layers": [
            {
                "source": json.loads(gdf2.loc[gdf2["source_name"].eq(s["name"])].geometry.to_json()),
                "below": "traces",
                "type": "line",
                "color": s["color"],
                "line": {"width": s["width"]},
            }
            for s in src
        ],
    },
)

使用 mapbox 线

import numpy as np

# plotly takes array delimited with None between lines. Use numpy padding and shaping to generate this array
# from pair of features
def line_array(df, cols):
    return np.pad(
        df.loc[:, cols].values, [(0, 0), (0, 1)], constant_values=None
    ).reshape(1, (len(df) * 3))[0]


# map to question columns.... looses all detail of a linestring
gdf3 = gdf2.join(
    gdf2.geometry.bounds.rename(
        columns={
            "minx": "lon.start",
            "miny": "lat.start",
            "maxx": "lon.end",
            "maxy": "lat.end",
        }
    )
)

fig = go.Figure(
    [
        go.Scattermapbox(
            name=g[0],
            lat=line_array(g[1], ["lat.start", "lat.end"]),
            lon=line_array(g[1], ["lon.start", "lon.end"]),
            mode="lines",
        )
        for g in gdf3.groupby("source_name")
    ]
)
fig.update_layout(
    margin={"l": 0, "r": 0, "t": 15, "b": 0},
    mapbox={
        "style": "carto-positron",
        "zoom": 4,
        "center": {
            "lon": gdf3.loc[:, ["lon.start", "lon.end"]].mean().mean(),
            "lat": gdf3.loc[:, ["lat.start", "lat.end"]].mean().mean(),
        },
    },
)