使用 vanilla javascript API 和自定义 IPywidgets 系统解决了这个问题。如果您复制并粘贴此代码,请注意单元格的显示顺序不正确。代码可在此处获得。
用法
(细胞#3)
import shapely.geometry as geo
some_nodes = [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2]]
some_boxes = []
some_boxes.append([some_nodes[0], some_nodes[3], some_nodes[4], some_nodes[1]])
some_boxes.append([some_nodes[1], some_nodes[4], some_nodes[5], some_nodes[2]])
m_polygon = geo.MultiPolygon(geo.Polygon(box) for box in some_boxes)
poly_selector = PolygonSelector(m_polygon._repr_svg_()) # PolygonSelector defined below
poly_selector # display the selector below cell, use the tool
工具看起来像这样:
使用该工具后,您可以通过复制选择器工具实例的属性来获取当前选中的多边形索引groups_dict
,即“live”:
(单元#4)
polygon_indexes = poly_selector.groups_dict.copy()
polygon_indexes
代码
工作仍在进行中,但下面是我最终所做的说明。这里也是nbviewer 上笔记本的链接(该工具在此处不可见)。
我把它放在这里部分是为了我自己的参考,但它是其他人可以学习(和改进)的概念证明。有些事情并没有按我想要的方式工作——例如在选择对象时更改对象的颜色。但是主要功能,选择和保存点击的多边形,是有效的。
下面是逐个单元格的代码,因为我在上面的链接版本中有它。
Python代码
(单元#1)
import ipywidgets.widgets as widgets
from traitlets import Unicode, Dict, List
from random import randint
class PolygonSelector(widgets.DOMWidget):
_view_name = Unicode('PolygonSelectorView').tag(sync=True)
_view_module = Unicode('polygonselector').tag(sync=True)
groups_dict = Dict().tag(sync=True)
current_list = List().tag(sync=True)
content = Unicode().tag(sync=True)
html_template = '''
<style>
# polygonGeometry path{{
fill: 'pink';
}}
# polygonGeometry .selectedPolygon {{
fill: {fill_selected!r};
}}
# polygonGeometry path:hover {{
fill: {fill_hovered!r};
}}
{selection_styles}
</style>
<button id = "clearBtn"> Clear </button>
<input placeholder = "Name this collection" id = "name" />
<button id = "saveBtn"> Save </button>
<div id = "polygonGeometry">{svg}</div>
'''
# provide some default colors; can override if desired
fill_selected = "plum"
fill_hovered = "lavender"
group_colors = ["#{:06X}".format(randint(0,0xFFFFFF)) for _ in range(100)]
def __init__(self, svg):
super().__init__()
self.update_content(svg)
def update_content(self, svg):
self.content = self.html_template.format(
fill_selected = self.fill_selected,
fill_hovered = self.fill_hovered,
selection_styles = self.selection_styles,
svg = svg
)
@property
def selection_styles(self):
return "".join(f'''
# polygonGeometry .selection_{group_idx} {{
fill: {self.group_colors[group_idx]!r};
}}
''' for group_idx in range(len(self.groups_dict)))
Javascript代码
(单元#2)
%%javascript
require.undef('polygonselector');
define('polygonselector', ["@jupyter-widgets/base"], function(widgets) {
var PolygonSelectorView = widgets.DOMWidgetView.extend({
initialized: 0,
init_render: function(){
},
// Add item to selection list
add: function(id) {
this.current_list.push(id);
console.log('pushed #', id);
},
// Remove item from selection list
remove: function(id) {
this.current_list = this.current_list.filter(function(_id) {
return _id !== id;
})
console.log('filtered #', id);
},
// Remove all items, closure
clear: function(thisView) {
return function() {
// `this` is the button element
console.log('clear() clicked');
thisView.el.querySelector('#name').value = '';
thisView.current_list.length = 0;
Array.from(thisView.el.querySelectorAll('.selectedPolygon')).forEach(function(path) {
console.log("path classList is: ", path.classList)
path.classList.remove('selectedPolygon');
})
console.log('Data cleared');
console.log(thisView.current_list)
};
},
// Add current selection to groups_dict, closure
save: function(thisView) {
return function() {
// `this` is the button element
console.log('save() clicked');
const newName = thisView.el.querySelector('#name').value;
console.log('Current name: ', newName)
if (!newName || thisView.current_list.length < 1) {
console.log("Can't save, newName: ", newName, " list length: ", thisView.current_list.length)
alert('A new selection must have a name and selected polygons');
}
else {
console.log('Attempting to save....')
thisView.groups_dict[newName] = thisView.current_list.slice(0)
console.log('You saved some data');
console.log("Selection Name: ", newName);
console.log(thisView.groups_dict[newName]);
thisView.model.trigger('change:groups_dict');
}
}
},
render: function() {
PolygonSelectorView.__super__.render.apply(this, arguments);
this.groups_dict = this.model.get('groups_dict')
this.current_list = this.model.get('current_list')
this.content_changed();
this.el.innerHTML = `${this.model.get('content')}`;
this.model.on('change:content', this.content_changed, this);
this.model.on('change:current_list', this.content_changed, this);
this.model.on('change:groups_dict', this.content_changed, this);
// Each path element is a polygon
const polygons = this.el.querySelectorAll('#polygonGeometry path');
// Add click event to polygons
console.log('iterating through polygons');
var thisView = this
let arr = Array.from(polygons)
console.log('created array:', arr)
arr.forEach(function(path, i) {
console.log("Array item #", i)
path.addEventListener('click', function() {
console.log('path object clicked')
if (thisView.current_list.includes(i)) {
path.classList.remove('selectedPolygon')
thisView.remove(i);
console.log('path #', i, ' removed');
} else {
path.classList.add('selectedPolygon')
thisView.add(i);
console.log('path #', i, ' added');
}
thisView.content_changed();
});
console.log('path #', i, ' click set');
});
// Attach functions to buttons
this.el.querySelector('#clearBtn').addEventListener('click', this.clear(this));
console.log('clearBtn action set to current view context');
this.el.querySelector('#saveBtn').addEventListener('click', this.save(this));
console.log('saveBtn action set to current view context');
console.log('render exit')
},
content_changed: function() {
console.log('content changed');
this.model.save();
console.log("Current list: ", this.current_list);
console.log("Groups dict: ", this.groups_dict);
},
});
return {
PolygonSelectorView : PolygonSelectorView
};
});