Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] expand edge_renderer glyph to handle more than just multiline #13666

Open
gmerritt123 opened this issue Jan 25, 2024 · 1 comment
Open
Milestone

Comments

@gmerritt123
Copy link

Problem description

This pertains to network graphs --> https://docs.bokeh.org/en/latest/docs/user_guide/topics/graph.html#network-graphs . The current setup is to instantiate a StaticLayoutProvider with a dict that containing node-node connections (e.g. 'start' and 'end'). From what I can tell, this layout provider does some sort of default transform that looks up the corresponding xy coordinates of the start/end nodes, and translates that into xs and ys coords for a multi line.

e.g.

nds = {'x':[5,10,11],'y':[15,12,9]
layout = {'start':[0,0],'end':[1,2]}
# some transform happening that turns the above into this:
edge_renderer_data = {'xs':[ [5,10], [5,11] ] , 'ys': [ [15,12], [15,9] ] }

Then if you want to make these edges NOT simple connectors, you have to manually define explicit paths by essentially hard-coding the xs and ys values on the edge_renderer. The problem with doing this is a) it's inefficient if you want to define your path with a function, and b) it's "dangerous" (and alluded to in the docs) because users are responsible for making their paths actually connect between the nodes.

Feature description

There are actually a lot of glyphs in bokeh that in theory you should be able to use for an edge renderer. Bezier and Segment are obvious to me me but there are probably others. The feature I'm requesting is to provide the ability (and an example implementation) to assign alternate glyphs that use alternate transformations to translate 'start'/'end' node info into information that the selected glyph can use to cleanly and efficiently draw the edges.

Potential alternatives

I drafted a basic example/idea using CustomJSTransform that doesn't use GraphRenderer or StaticLayoutProvider at all -->

import math
from bokeh.plotting import figure, show
from bokeh.models import  Ellipse, Segment, ColumnDataSource, CustomJSTransform
from bokeh.transform import transform
from bokeh.palettes import Spectral8

# list the nodes and initialize a plot
N = 8
node_indices = list(range(N))

plot = figure(title="Graph layout demonstration", x_range=(-1.1,1.1),
              y_range=(-1.1,1.1), tools="", toolbar_location=None)

# create lists of x- and y-coordinates
circ = [i*2*math.pi/8 for i in node_indices]
x = [math.cos(i) for i in circ]
y = [math.sin(i) for i in circ]

node_source = ColumnDataSource(data={'x':x,'y':y,'fill_color':Spectral8})
edge_source = ColumnDataSource(data={'start':[0 for x in node_indices[1:]]
                                     ,'end':[x for x in node_indices[1:]]})

node_glyph = Ellipse(height=0.1, width=0.2, fill_color="fill_color")
node_rend = plot.add_glyph(node_source,node_glyph)

edge_tr_x = CustomJSTransform(args=dict(ns=node_source)
                            ,v_func='''
                            return xs.map(ni=>ns.data['x'][ni])
                            ''')
edge_tr_y = CustomJSTransform(args=dict(ns=node_source)
                            ,v_func='''
                            return xs.map(ni=>ns.data['y'][ni])
                            ''')
edge_glyph = Segment(x0=transform(field_name='start',transform=edge_tr_x)
                     ,y0=transform(field_name='start',transform=edge_tr_y)
                     ,x1=transform(field_name='end',transform=edge_tr_x)
                     ,y1=transform(field_name='end',transform=edge_tr_y)                    
                     )
edge_rend = plot.add_glyph(edge_source,edge_glyph)

show(plot)

But obviously if I want to leverage all the interaction policies and other nice stuff already built into GraphRenderer, I will have to write a lot more CustomJS/CustomJSTransform/CustomJSHover etc..... Furthermore I'm looping through edge_source four times in this example to get x0,x1,y0, and y1 coordinates.... which in theory i should only have to do once (e.g. take the node source and the edge source and transform it into a derivative datasource used to drive Segment.

Additional information

No response

@mattpap
Copy link
Contributor

mattpap commented Jan 25, 2024

I did some work towards this as part of my SDG 2023 work, specifically support for Segment as edge renderer, though that particular work isn't published yet.

@mattpap mattpap added this to the 3.x milestone Jan 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants