我想将包含补丁(来自a GeoJSONDataSource
)的图表与折线图链接,但我无法获取所选补丁的属性.
它基本上是一个显示多边形的图,当选择多边形时,我想用该多边形的数据时间序列更新折线图.折线图由法线驱动ColumnDataSource
.
我可以通过添加一个回调结合得到所选补丁的索引geo_source.selected['1d']['indices']
.但是,我如何获得与该索引相对应的数据/属性?我需要的属性,我可以再使用更新线图一个"关键".
在GeoJSONDataSource
没有data
属性的,我可以查找数据本身.Bokeh可以使用像着色/工具提示等东西的属性,所以我认为必须有一种方法来解决这些问题GeoJSONDataSource
,我很遗憾地发现它.
这是一个工作的玩具示例,展示了迄今为止我所拥有的.
import pandas as pd
import numpy as np
from bokeh import events
from bokeh.models import (Select, Column, Row, ColumnDataSource, HoverTool,
Range1d, LinearAxis, GeoJSONDataSource)
from bokeh.plotting import figure
from bokeh.io import curdoc
import os
import datetime
from collections import OrderedDict
def make_plot(src):
# function to create the line chart
p = figure(width=500, height=200, x_axis_type='datetime', title='Some parameter',
tools=['xwheel_zoom', 'xpan'], logo=None, toolbar_location='below', toolbar_sticky=False)
p.circle('index', 'var1', color='black', fill_alpha=0.2, size=10, source=src)
return p
def make_geo_plot(src):
# function to create the spatial plot with polygons
p = figure(width=300, height=300, title="Select area", tools=['tap', 'pan', 'box_zoom', 'wheel_zoom','reset'], logo=None)
p.patches('xs', 'ys', fill_alpha=0.2, fill_color='black',
line_color='black', line_width=0.5, source=src)
p.on_event(events.SelectionGeometry, update_plot_from_geo)
return p
def update_plot_from_geo(event):
# update the line chart based on the selected polygon
selected = geo_source.selected['1d']['indices']
if (len(selected) > 0):
first = selected[0]
print(geo_source.selected['1d']['indices'])
def update_plot(attrname, old, new):
# Callback for the dropdown menu which updates the line chart
new_src = get_source(df, area_select.value)
src.data.update(new_src.data)
def get_source(df, fieldid):
# function to get a subset of the multi-hierarchical DataFrame
# slice 'out' the selected area
dfsub = df.xs(fieldid, axis=1, level=0)
src = ColumnDataSource(dfsub)
return src
# example timeseries
n_points = 100
df = pd.DataFrame({('area_a','var1'): np.sin(np.linspace(0,5,n_points)) + np.random.rand(100)*0.1,
('area_b','var1'): np.sin(np.linspace(0,2,n_points)) + np.random.rand(100)*0.1,
('area_c','var1'): np.sin(np.linspace(0,3,n_points)) + np.random.rand(100)*0.1,
('area_d','var1'): np.sin(np.linspace(0,4,n_points)) + np.random.rand(100)*0.1},
index=pd.DatetimeIndex(start='2017-01-01', freq='D', periods=100))
# example polygons
geojson = """{
"type":"FeatureCollection",
"crs":{"type":"name","properties":{"name":"urn:ogc:def:crs:OGC:1.3:CRS84"}},
"features":[
{"type":"Feature","properties":{"key":"area_a"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-108.8,42.7],[-104.5,42.0],[-108.3,39.3],[-108.8,42.7]]]]}},
{"type":"Feature","properties":{"key":"area_b"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-106.3,44.0],[-106.2,42.6],[-103.3,42.6],[-103.4,44.0],[-106.3,44.0]]]]}},
{"type":"Feature","properties":{"key":"area_d"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-104.3,41.0],[-101.5,41.0],[-102.9,37.8],[-104.3,41.0]]]]}},
{"type":"Feature","properties":{"key":"area_c"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-105.8,40.3],[-108.3,37.7],[-104.0,37.4],[-105.8,40.3]]]]}}
]
}"""
geo_source = GeoJSONDataSource(geojson=geojson)
# populate a drop down menu with the area's
area_ids = sorted(df.columns.get_level_values(0).unique().values.tolist())
area_ids = [str(x) for x in area_ids]
area_select = Select(value=area_ids[0], title='Select area', options=area_ids)
area_select.on_change('value', update_plot)
src = get_source(df, area_select.value)
p = make_plot(src)
pgeo = make_geo_plot(geo_source)
# add to document
curdoc().add_root(Row(Column(area_select, p), pgeo))
Run Code Online (Sandbox Code Playgroud)
将代码保存在.py
文件中并加载bokeh serve example.py --show
您应该为 GeoJSONDataSource 编写自定义扩展
这是 GeoJSONDataSource 的咖啡脚本https://github.com/bokeh/bokeh/blob/master/bokehjs/src/coffee/models/sources/geojson_data_source.coffee
我不太擅长自定义扩展。所以我完全复制了 GeoJSONDataSource 并将其命名为 CustomGeo。我刚刚将“数据”从@internal 移至@define。然后宾果,你得到了一个带有“data”属性的 GeoJSONDataSource。
在下面的示例中,我使用“key”列表进行回调,但由于您现在拥有这样的数据,如果您担心洗牌,您可以编写一些内容来仔细检查它是否对应于适当的多边形
import pandas as pd
import numpy as np
from bokeh.core.properties import Instance, Dict, JSON, Any
from bokeh import events
from bokeh.models import (Select, Column, Row, ColumnDataSource, HoverTool,
Range1d, LinearAxis, GeoJSONDataSource, ColumnarDataSource)
from bokeh.plotting import figure
from bokeh.io import curdoc
import os
import datetime
from collections import OrderedDict
def make_plot(src):
# function to create the line chart
p = figure(width=500, height=200, x_axis_type='datetime', title='Some parameter',
tools=['xwheel_zoom', 'xpan'], logo=None, toolbar_location='below', toolbar_sticky=False)
p.circle('index', 'var1', color='black', fill_alpha=0.2, size=10, source=src)
return p
def make_geo_plot(src):
# function to create the spatial plot with polygons
p = figure(width=300, height=300, title="Select area", tools=['tap', 'pan', 'box_zoom', 'wheel_zoom','reset'], logo=None)
a=p.patches('xs', 'ys', fill_alpha=0.2, fill_color='black',
line_color='black', line_width=0.5, source=src,name='poly')
p.on_event(events.SelectionGeometry, update_plot_from_geo)
return p
def update_plot_from_geo(event):
# update the line chart based on the selected polygon
try:
selected = geo_source.selected['1d']['indices'][0]
except IndexError:
return
print geo_source.data
print geo_source.data['key'][selected]
new_src = get_source(df,geo_source.data['key'][selected])
src.data.update(new_src.data)
def update_plot(attrname, old, new):
# Callback for the dropdown menu which updates the line chart
print area_select.value
new_src = get_source(df, area_select.value)
src.data.update(new_src.data)
def get_source(df, fieldid):
# function to get a subset of the multi-hierarchical DataFrame
# slice 'out' the selected area
dfsub = df.xs(fieldid, axis=1, level=0)
src = ColumnDataSource(dfsub)
return src
# example timeseries
n_points = 100
df = pd.DataFrame({('area_a','var1'): np.sin(np.linspace(0,5,n_points)) + np.random.rand(100)*0.1,
('area_b','var1'): np.sin(np.linspace(0,2,n_points)) + np.random.rand(100)*0.1,
('area_c','var1'): np.sin(np.linspace(0,3,n_points)) + np.random.rand(100)*0.1,
('area_d','var1'): np.sin(np.linspace(0,4,n_points)) + np.random.rand(100)*0.1},
index=pd.DatetimeIndex(start='2017-01-01', freq='D', periods=100))
# example polygons
geojson = """{
"type":"FeatureCollection",
"crs":{"type":"name","properties":{"name":"urn:ogc:def:crs:OGC:1.3:CRS84"}},
"features":[
{"type":"Feature","properties":{"key":"area_a"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-108.8,42.7],[-104.5,42.0],[-108.3,39.3],[-108.8,42.7]]]]}},
{"type":"Feature","properties":{"key":"area_b"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-106.3,44.0],[-106.2,42.6],[-103.3,42.6],[-103.4,44.0],[-106.3,44.0]]]]}},
{"type":"Feature","properties":{"key":"area_d"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-104.3,41.0],[-101.5,41.0],[-102.9,37.8],[-104.3,41.0]]]]}},
{"type":"Feature","properties":{"key":"area_c"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-105.8,40.3],[-108.3,37.7],[-104.0,37.4],[-105.8,40.3]]]]}}
]
}"""
implementation = """
import {ColumnarDataSource} from "models/sources/columnar_data_source"
import {logger} from "core/logging"
import * as p from "core/properties"
export class CustomGeo extends ColumnarDataSource
type: 'CustomGeo'
@define {
geojson: [ p.Any ] # TODO (bev)
data: [ p.Any, {} ]
}
initialize: (options) ->
super(options)
@_update_data()
@connect(@properties.geojson.change, () => @_update_data())
_update_data: () -> @data = @geojson_to_column_data()
_get_new_list_array: (length) -> ([] for i in [0...length])
_get_new_nan_array: (length) -> (NaN for i in [0...length])
_flatten_function: (accumulator, currentItem) ->
return accumulator.concat([[NaN, NaN, NaN]]).concat(currentItem)
_add_properties: (item, data, i, item_count) ->
for property of item.properties
if !data.hasOwnProperty(property)
data[property] = @_get_new_nan_array(item_count)
data[property][i] = item.properties[property]
_add_geometry: (geometry, data, i) ->
switch geometry.type
when "Point"
coords = geometry.coordinates
data.x[i] = coords[0]
data.y[i] = coords[1]
data.z[i] = coords[2] ? NaN
when "LineString"
coord_list = geometry.coordinates
for coords, j in coord_list
data.xs[i][j] = coords[0]
data.ys[i][j] = coords[1]
data.zs[i][j] = coords[2] ? NaN
when "Polygon"
if geometry.coordinates.length > 1
logger.warn('Bokeh does not support Polygons with holes in, only exterior ring used.')
exterior_ring = geometry.coordinates[0]
for coords, j in exterior_ring
data.xs[i][j] = coords[0]
data.ys[i][j] = coords[1]
data.zs[i][j] = coords[2] ? NaN
when "MultiPoint"
logger.warn('MultiPoint not supported in Bokeh')
when "MultiLineString"
flattened_coord_list = geometry.coordinates.reduce(@_flatten_function)
for coords, j in flattened_coord_list
data.xs[i][j] = coords[0]
data.ys[i][j] = coords[1]
data.zs[i][j] = coords[2] ? NaN
when "MultiPolygon"
exterior_rings = []
for polygon in geometry.coordinates
if polygon.length > 1
logger.warn('Bokeh does not support Polygons with holes in, only exterior ring used.')
exterior_rings.push(polygon[0])
flattened_coord_list = exterior_rings.reduce(@_flatten_function)
for coords, j in flattened_coord_list
data.xs[i][j] = coords[0]
data.ys[i][j] = coords[1]
data.zs[i][j] = coords[2] ? NaN
else
throw new Error('Invalid type ' + geometry.type)
_get_items_length: (items) ->
count = 0
for item, i in items
geometry = if item.type == 'Feature' then item.geometry else item
if geometry.type == 'GeometryCollection'
for g, j in geometry.geometries
count += 1
else
count += 1
return count
geojson_to_column_data: () ->
geojson = JSON.parse(@geojson)
if geojson.type not in ['GeometryCollection', 'FeatureCollection']
throw new Error('Bokeh only supports type GeometryCollection and FeatureCollection at top level')
if geojson.type == 'GeometryCollection'
if not geojson.geometries?
throw new Error('No geometries found in GeometryCollection')
if geojson.geometries.length == 0
throw new Error('geojson.geometries must have one or more items')
items = geojson.geometries
if geojson.type == 'FeatureCollection'
if not geojson.features?
throw new Error('No features found in FeaturesCollection')
if geojson.features.length == 0
throw new Error('geojson.features must have one or more items')
items = geojson.features
item_count = @_get_items_length(items)
data = {
'x': @_get_new_nan_array(item_count),
'y': @_get_new_nan_array(item_count),
'z': @_get_new_nan_array(item_count),
'xs': @_get_new_list_array(item_count),
'ys': @_get_new_list_array(item_count),
'zs': @_get_new_list_array(item_count)
}
arr_index = 0
for item, i in items
geometry = if item.type == 'Feature' then item.geometry else item
if geometry.type == 'GeometryCollection'
for g, j in geometry.geometries
@_add_geometry(g, data, arr_index)
if item.type == 'Feature'
@_add_properties(item, data, arr_index, item_count)
arr_index += 1
else
# Now populate based on Geometry type
@_add_geometry(geometry, data, arr_index)
if item.type == 'Feature'
@_add_properties(item, data, arr_index, item_count)
arr_index += 1
return data
"""
class CustomGeo(ColumnarDataSource):
__implementation__ = implementation
geojson = JSON(help="""
GeoJSON that contains features for plotting. Currently GeoJSONDataSource can
only process a FeatureCollection or GeometryCollection.
""")
data = Dict(Any,Any,default={},help="wooo")
geo_source = CustomGeo(geojson=geojson)
# populate a drop down menu with the area's
area_ids = sorted(df.columns.get_level_values(0).unique().values.tolist())
area_ids = [str(x) for x in area_ids]
area_select = Select(value=area_ids[0], title='Select area', options=area_ids)
area_select.on_change('value', update_plot)
src = get_source(df, area_select.value)
p = make_plot(src)
pgeo = make_geo_plot(geo_source)
# add to document
curdoc().add_root(Row(Column(area_select, p), pgeo))
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
728 次 |
最近记录: |