Class: Wads::GraphWidget
Overview
Given a single node or a graph data structure, this widget displays a visualization of the graph using one of the available node widget classes. There are different display modes that control what nodes within the graph are shown. The default display mode, GRAPH_DISPLAY_ALL, shows all nodes as the name implies. GRAPH_DISPLAY_TREE assumes an acyclic graph and renders the graph in a tree-like structure. GRAPH_DISPLAY_EXPLORER has a chosen center focus node with connected nodes circled around it based on the depth or distance from that node. This mode also allows the user to click on different nodes to navigate the graph and change focus nodes.
Instance Attribute Summary collapse
Attributes inherited from Widget
#base_z, #children, #gui_theme, #height, #is_selected, #layout, #overlay_widget, #override_color, #text_input_fields, #visible, #width, #x, #y
Instance Method Summary
collapse
-
#get_node_color(node) ⇒ Object
-
#handle_mouse_down(mouse_x, mouse_y) ⇒ Object
-
#handle_mouse_up(mouse_x, mouse_y) ⇒ Object
-
#handle_update(update_count, mouse_x, mouse_y) ⇒ Object
-
#initialize(x, y, width, height, graph, display_mode = GRAPH_DISPLAY_ALL) ⇒ GraphWidget
constructor
A new instance of GraphWidget.
-
#move_text_for_node(rendered_node) ⇒ Object
-
#overlaps_with_a_node(text) ⇒ Object
-
#populate_rendered_nodes(center_node = nil) ⇒ Object
-
#prevent_text_overlap ⇒ Object
-
#render ⇒ Object
-
#scale_node_size ⇒ Object
-
#set_all_nodes_for_display ⇒ Object
-
#set_center_node(center_node, max_depth = -1)) ⇒ Object
-
#set_explorer_display(center_node = nil) ⇒ Object
-
#set_tree_display ⇒ Object
-
#set_tree_recursive(current_node, start_x, end_x, y_level) ⇒ Object
Methods inherited from Widget
#add, #add_axis_lines, #add_button, #add_child, #add_delete_button, #add_document, #add_graph_display, #add_image, #add_multi_select_table, #add_overlay, #add_panel, #add_plot, #add_single_select_table, #add_table, #add_text, #border_color, #bottom_edge, #button_down, #button_up, #center_children, #center_x, #center_y, #clear_children, #contains_click, #debug, #disable_background, #disable_border, #draw, #draw_background, #draw_border, #enable_background, #enable_border, #error, #get_layout, #get_theme, #graphics_color, #handle_key_press, #handle_right_mouse, #info, #intercept_widget_event, #left_edge, #move_recursive_absolute, #move_recursive_delta, #overlaps_with, #relative_x, #relative_y, #relative_z_order, #remove_child, #remove_children, #remove_children_by_type, #right_edge, #selection_color, #set_absolute_position, #set_dimensions, #set_layout, #set_selected, #set_theme, #text_color, #top_edge, #unset_selected, #update, #uses_layout, #warn, #widget_z, #x_pixel_to_screen, #y_pixel_to_screen, #z_order
Constructor Details
#initialize(x, y, width, height, graph, display_mode = GRAPH_DISPLAY_ALL) ⇒ GraphWidget
Returns a new instance of GraphWidget.
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
|
# File 'lib/wads/widgets.rb', line 2958
def initialize(x, y, width, height, graph, display_mode = GRAPH_DISPLAY_ALL)
super(x, y)
set_dimensions(width, height)
if graph.is_a? Node
@graph = Graph.new(graph)
else
@graph = graph
end
@size_by_connections = false
@is_explorer = false
if [GRAPH_DISPLAY_ALL, GRAPH_DISPLAY_TREE, GRAPH_DISPLAY_EXPLORER].include? display_mode
debug("Displaying graph in #{display_mode} mode")
else
raise "#{display_mode} is not a valid display mode for Graph Widget"
end
if display_mode == GRAPH_DISPLAY_ALL
set_all_nodes_for_display
elsif display_mode == GRAPH_DISPLAY_TREE
set_tree_display
else
set_explorer_display
end
end
|
Instance Attribute Details
#graph ⇒ Object
Returns the value of attribute graph.
2951
2952
2953
|
# File 'lib/wads/widgets.rb', line 2951
def graph
@graph
end
|
#is_explorer ⇒ Object
Returns the value of attribute is_explorer.
2956
2957
2958
|
# File 'lib/wads/widgets.rb', line 2956
def is_explorer
@is_explorer
end
|
#selected_node ⇒ Object
Returns the value of attribute selected_node.
2952
2953
2954
|
# File 'lib/wads/widgets.rb', line 2952
def selected_node
@selected_node
end
|
#selected_node_x_offset ⇒ Object
Returns the value of attribute selected_node_x_offset.
2953
2954
2955
|
# File 'lib/wads/widgets.rb', line 2953
def selected_node_x_offset
@selected_node_x_offset
end
|
#selected_node_y_offset ⇒ Object
Returns the value of attribute selected_node_y_offset.
2954
2955
2956
|
# File 'lib/wads/widgets.rb', line 2954
def selected_node_y_offset
@selected_node_y_offset
end
|
#size_by_connections ⇒ Object
Returns the value of attribute size_by_connections.
2955
2956
2957
|
# File 'lib/wads/widgets.rb', line 2955
def size_by_connections
@size_by_connections
end
|
Instance Method Details
#get_node_color(node) ⇒ Object
3209
3210
3211
3212
3213
3214
3215
|
# File 'lib/wads/widgets.rb', line 3209
def get_node_color(node)
color_tag = node.get_tag(COLOR_TAG)
if color_tag.nil?
return @color
end
color_tag
end
|
#handle_mouse_down(mouse_x, mouse_y) ⇒ Object
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
|
# File 'lib/wads/widgets.rb', line 2989
def handle_mouse_down mouse_x, mouse_y
if @rendered_nodes
@rendered_nodes.values.each do |rn|
if rn.contains_click(mouse_x, mouse_y)
@selected_node = rn
@selected_node_x_offset = mouse_x - rn.x
@selected_node_y_offset = mouse_y - rn.y
@click_timestamp = Time.now
end
end
end
WidgetResult.new(false)
end
|
#handle_mouse_up(mouse_x, mouse_y) ⇒ Object
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
|
# File 'lib/wads/widgets.rb', line 3004
def handle_mouse_up mouse_x, mouse_y
if @selected_node
if @is_explorer
time_between_mouse_up_down = Time.now - @click_timestamp
if time_between_mouse_up_down < 0.2
set_explorer_display(@selected_node.data_node)
end
end
@selected_node = nil
end
end
|
#handle_update(update_count, mouse_x, mouse_y) ⇒ Object
2982
2983
2984
2985
2986
2987
|
# File 'lib/wads/widgets.rb', line 2982
def handle_update update_count, mouse_x, mouse_y
if contains_click(mouse_x, mouse_y) and @selected_node
@selected_node.move_recursive_absolute(mouse_x - @selected_node_x_offset,
mouse_y - @selected_node_y_offset)
end
end
|
#move_text_for_node(rendered_node) ⇒ Object
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
|
# File 'lib/wads/widgets.rb', line 3110
def move_text_for_node(rendered_node)
text = rendered_node.get_text_widget
if text.nil?
return
end
radians_between_attempts = DEG_360 / 24
current_radians = 0.05
done = false
while not done
text_x = rendered_node.center_x + ((rendered_node.width / 2) * Math.cos(current_radians))
text_y = rendered_node.center_y - ((rendered_node.height / 2) * Math.sin(current_radians))
if text_x < @x
text_x = @x + 1
elsif text_x > right_edge - 20
text_x = right_edge - 20
end
if text_y < @y
text_y = @y + 1
elsif text_y > bottom_edge - 26
text_y = bottom_edge - 26
end
text.x = text_x
text.y = text_y
current_radians = current_radians + radians_between_attempts
if overlaps_with_a_node(text)
if current_radians > DEG_360
done = true
error("ERROR: could not find a spot to put the text")
end
else
done = true
end
end
end
|
#overlaps_with_a_node(text) ⇒ Object
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
|
# File 'lib/wads/widgets.rb', line 3148
def overlaps_with_a_node(text)
@rendered_nodes.values.each do |rn|
if text.label == rn.label
else
if rn.overlaps_with(text)
return true
end
end
end
false
end
|
#populate_rendered_nodes(center_node = nil) ⇒ Object
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
|
# File 'lib/wads/widgets.rb', line 3241
def populate_rendered_nodes(center_node = nil)
stats = Stats.new("NodesPerDepth")
@visible_data_nodes.values.each do |n|
stats.increment(n.depth)
end
current_radians = []
radians_increment = []
(1..4).each do |n|
number_of_nodes_at_depth = stats.count(n)
radians_increment[n] = DEG_360 / number_of_nodes_at_depth.to_f
current_radians[n] = 0.05
end
padding = 100
size_of_x_band = (@width - padding) / 6
size_of_y_band = (@height - padding) / 6
random_x = size_of_x_band / 8
random_y = size_of_y_band / 8
half_random_x = random_x / 2
half_random_y = random_y / 2
band_center_x = padding + (size_of_x_band / 2)
band_center_y = padding + (size_of_y_band / 2)
bands_x = [0, band_center_x]
bands_x << band_center_x + size_of_x_band
bands_x << band_center_x + size_of_x_band + size_of_x_band
bands_y = [0, band_center_y]
bands_y << band_center_y + size_of_y_band
bands_y << band_center_y + size_of_y_band + size_of_y_band
@visible_data_nodes.each do |node_name, data_node|
process_this_node = true
if center_node
if node_name == center_node.name
process_this_node = false
end
end
if process_this_node
scale_to_use = 1
if stats.count(1) > 0 and stats.count(2) == 0
elsif data_node.depth < 4
scale_to_use = 5 - data_node.depth
end
if @is_explorer
band_index = 4 - scale_to_use
distance_from_center_x = bands_x[band_index] + rand(random_x) - half_random_x
distance_from_center_y = bands_y[band_index] + rand(random_y) - half_random_y
else
distance_from_center_x = 80 + rand(200)
distance_from_center_y = 40 + rand(100)
end
radians_to_use = current_radians[data_node.depth]
radians_to_use = radians_to_use + (rand(radians_increment[data_node.depth]) / 2)
current_radians[data_node.depth] = current_radians[data_node.depth] + radians_increment[data_node.depth]
node_x = center_x + (distance_from_center_x * Math.cos(radians_to_use))
node_y = center_y - (distance_from_center_y * Math.sin(radians_to_use))
if node_x < @x
node_x = @x + 1
elsif node_x > right_edge - 20
node_x = right_edge - 20
end
if node_y < @y
node_y = @y + 1
elsif node_y > bottom_edge - 26
node_y = bottom_edge - 26
end
if @gui_theme.use_icons
@rendered_nodes[data_node.name] = NodeIconWidget.new(
node_x,
node_y,
data_node,
get_node_color(data_node),
scale_to_use,
@is_explorer)
else
@rendered_nodes[data_node.name] = NodeWidget.new(
node_x,
node_y,
data_node,
get_node_color(data_node),
scale_to_use,
@is_explorer)
end
end
end
@rendered_nodes.values.each do |rn|
rn.base_z = @base_z
end
end
|
#prevent_text_overlap ⇒ Object
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
|
# File 'lib/wads/widgets.rb', line 3087
def prevent_text_overlap
@rendered_nodes.values.each do |rn|
text = rn.get_text_widget
if text
if overlaps_with_a_node(text)
move_text_for_node(rn)
else
move_in_bounds = false
if text.x < @x or text.right_edge > right_edge
move_in_bounds = true
elsif text.y < @y or text.bottom_edge > bottom_edge
move_in_bounds = true
end
if move_in_bounds
debug("#{text.label} was out of bounds")
move_text_for_node(rn)
end
end
end
end
end
|
#render ⇒ Object
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
|
# File 'lib/wads/widgets.rb', line 3352
def render
if @rendered_nodes
@rendered_nodes.values.each do |vn|
vn.draw
end
@visible_data_nodes.values.each do |data_node|
data_node.outputs.each do |connected_data_node|
if connected_data_node.is_a? Edge
connected_data_node = connected_data_node.destination
end
rendered_node = @rendered_nodes[data_node.name]
connected_rendered_node = @rendered_nodes[connected_data_node.name]
if connected_rendered_node.nil?
else
if @is_explorer and (rendered_node.is_background or connected_rendered_node.is_background)
Gosu::draw_line rendered_node.center_x, rendered_node.center_y, COLOR_LIGHT_GRAY,
connected_rendered_node.center_x, connected_rendered_node.center_y, COLOR_LIGHT_GRAY,
relative_z_order(Z_ORDER_GRAPHIC_ELEMENTS)
else
Gosu::draw_line rendered_node.center_x, rendered_node.center_y, rendered_node.graphics_color,
connected_rendered_node.center_x, connected_rendered_node.center_y, connected_rendered_node.graphics_color,
relative_z_order(Z_ORDER_GRAPHIC_ELEMENTS)
end
end
end
end
end
end
|
#scale_node_size ⇒ Object
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
|
# File 'lib/wads/widgets.rb', line 3067
def scale_node_size
range = @graph.get_number_of_connections_range
bins = range.bin_max_values(4)
@visible_data_nodes.values.each do |node|
num_links = node.number_of_links
index = 0
while index < bins.size
if num_links <= bins[index]
@rendered_nodes[node.name].set_scale(index + 1, @is_explorer)
index = bins.size
end
index = index + 1
end
end
end
|
#set_all_nodes_for_display ⇒ Object
3199
3200
3201
3202
3203
3204
3205
3206
3207
|
# File 'lib/wads/widgets.rb', line 3199
def set_all_nodes_for_display
@visible_data_nodes = @graph.node_map
@rendered_nodes = {}
populate_rendered_nodes
if @size_by_connections
scale_node_size
end
prevent_text_overlap
end
|
#set_center_node(center_node, max_depth = -1)) ⇒ Object
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
|
# File 'lib/wads/widgets.rb', line 3217
def set_center_node(center_node, max_depth = -1)
@graph.reset_visited
@visible_data_nodes = @graph.traverse_and_collect_nodes(center_node, max_depth)
@rendered_nodes = {}
if @gui_theme.use_icons
@rendered_nodes[center_node.name] = NodeIconWidget.new(
center_x, center_y, center_node, get_node_color(center_node))
else
@rendered_nodes[center_node.name] = NodeWidget.new(center_x, center_y,
center_node, get_node_color(center_node), get_node_color(center_node))
end
populate_rendered_nodes(center_node)
if @size_by_connections
scale_node_size
end
prevent_text_overlap
end
|
#set_explorer_display(center_node = nil) ⇒ Object
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
|
# File 'lib/wads/widgets.rb', line 3018
def set_explorer_display(center_node = nil)
if center_node.nil?
center_node = @graph.node_with_most_connections
end
@graph.reset_visited
@visible_data_nodes = {}
center_node.bfs(4) do |n|
@visible_data_nodes[n.name] = n
end
@size_by_connections = false
@is_explorer = true
@rendered_nodes = {}
populate_rendered_nodes
prevent_text_overlap
end
|
#set_tree_display ⇒ Object
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
|
# File 'lib/wads/widgets.rb', line 3039
def set_tree_display
@graph.reset_visited
@visible_data_nodes = @graph.node_map
@rendered_nodes = {}
root_nodes = @graph.root_nodes
number_of_root_nodes = root_nodes.size
width_for_each_root_tree = @width / number_of_root_nodes
start_x = 0
y_level = 20
root_nodes.each do |root|
set_tree_recursive(root, start_x, start_x + width_for_each_root_tree - 1, y_level)
start_x = start_x + width_for_each_root_tree
y_level = y_level + 40
end
@rendered_nodes.values.each do |rn|
rn.base_z = @base_z
end
if @size_by_connections
scale_node_size
end
prevent_text_overlap
end
|
#set_tree_recursive(current_node, start_x, end_x, y_level) ⇒ Object
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
|
# File 'lib/wads/widgets.rb', line 3161
def set_tree_recursive(current_node, start_x, end_x, y_level)
if current_node.visited
return
end
current_node.visited = true
if @gui_theme.use_icons
@rendered_nodes[current_node.name] = NodeIconWidget.new(
x_pixel_to_screen(start_x + ((end_x - start_x) / 2)),
y_pixel_to_screen(y_level),
current_node,
get_node_color(current_node))
else
@rendered_nodes[current_node.name] = NodeWidget.new(
x_pixel_to_screen(start_x + ((end_x - start_x) / 2)),
y_pixel_to_screen(y_level),
current_node,
get_node_color(current_node))
end
number_of_child_nodes = current_node.outputs.size
if number_of_child_nodes == 0
return
end
width_for_each_child_tree = (end_x - start_x) / number_of_child_nodes
start_child_x = start_x + 5
current_node.outputs.each do |child|
if child.is_a? Edge
child = child.destination
end
set_tree_recursive(child, start_child_x, start_child_x + width_for_each_child_tree - 1, y_level + 40)
start_child_x = start_child_x + width_for_each_child_tree
end
end
|