class Ellington::Visualizer

Constants

CLUSTER_COLOR
CLUSTER_FILLCOLOR
CLUSTER_PENCOLOR
CLUSTER_STYLE
EDGE_COLOR_PASSENGER_HIT
EDGE_PENWIDTH_PASSENGER_HIT
EDGE_STYLE_PASSENGER_MISS
FONTNAME
NODE_COLOR
NODE_COLOR_LINE_GOAL
NODE_COLOR_PASSENGER_HIT
NODE_COLOR_ROUTE_GOAL
NODE_COLOR_VIRTUAL
NODE_FILLCOLOR
NODE_FILLCOLOR_LINE_GOAL
NODE_FILLCOLOR_ROUTE_GOAL
NODE_FONTCOLOR_VIRTUAL
NODE_PENWIDTH_PASSENGER_HIT
NODE_SHAPE
NODE_STYLE
NODE_STYLE_VIRTUAL
RANKSEP

Attributes

format[R]
route[R]
short_names[R]

Public Class Methods

new(route, format: :svg, short_names: true) click to toggle source
# File lib/ellington/visualizer.rb, line 38
def initialize(route, format: :svg, short_names: true)
  @route = route
  @format = format
  @short_names = short_names
end

Public Instance Methods

class_label(obj) click to toggle source
# File lib/ellington/visualizer.rb, line 67
def class_label(obj)
  klass = obj
  klass = klass.class unless klass.is_a?(Class)
  return klass.name unless short_names
  klass.name.split("::").last
end
graph_route(passenger=nil) click to toggle source
# File lib/ellington/visualizer.rb, line 139
def graph_route(passenger=nil)
  g = Node.new(nil, GraphViz.new("GraphRoute"))
  set_graph_defaults g.viz
  g.viz[:label] = "#{class_label(route)} Lines"
  g.viz[:ranksep] = 0.8

  route.lines.each_with_index do |line, index|
    line_cluster = g.add(Node.new(line, g.viz.add_graph("cluster#{index}")))
    set_cluster_defaults line_cluster.viz
    line_cluster.viz[:label] = class_label(line)
    add_state_nodes_for_line line_cluster, line, passenger
  end

  viz = g.viz.add_nodes(route.initial_state, :label => state_label(route.initial_state))
  rendered_edges = {}

  if passenger
    passenger_nodes = g.reduce([]) do |memo, line_cluster|
      line_cluster.children.each do |node|
        if node.viz[:color].to_s.gsub(/\W/, "") == NODE_COLOR_PASSENGER_HIT
          memo << node
        end
      end
      memo
    end
    previous_node = nil
    passenger_nodes.each do |node|
      from_viz = previous_node.nil? ? viz : previous_node.viz
      rendered_edges[from_viz.id + node.viz.id] = true
      edge = g.viz.add_edges(from_viz, node.viz)
      edge["color"] = EDGE_COLOR_PASSENGER_HIT
      edge["penwidth"] = EDGE_PENWIDTH_PASSENGER_HIT
      previous_node = node
    end
  end

  route.states.each do |from_state, to_states|
    (to_states || []).each do |to_state|
      from_line = route.lines.to_a.select{ |l| l.states.keys.include?(from_state) }.first
      from_node = g.find(from_line) if from_line
      from_viz = from_node.viz.get_node(from_state) if from_node
      from_viz ||= g.viz.get_node(from_state)
      to_line = route.lines.to_a.select{ |l| l.states.keys.include?(to_state) }.first
      to_node = g.find(to_line) if to_line
      to_viz = to_node.viz.get_node(to_state) if to_node
      to_viz ||= g.viz.get_node(to_state)

      if from_viz && to_viz && !rendered_edges[from_viz.id + to_viz.id]
        rendered_edges[from_viz.id + to_viz.id] = true
        edge = g.viz.add_edges(
          from_viz,
          to_viz
        )
        edge[:style] = EDGE_STYLE_PASSENGER_MISS if passenger
      end
    end
  end

  g.viz.output(format => String)
end
graph_route_basic(passenger=nil) click to toggle source
# File lib/ellington/visualizer.rb, line 79
def graph_route_basic(passenger=nil)
  g = Node.new(nil, GraphViz.new("GraphRouteBasic"))
  set_graph_defaults g.viz
  g.viz[:ranksep] = 1
  g.viz[:label] = "#{class_label(route)} Route - basic"

  route.lines.each_with_index do |line, index|
    line_cluster = g.add(Node.new(line, g.viz.add_graph("cluster#{index}")))
    set_cluster_defaults line_cluster.viz
    line_cluster.viz[:label] = class_label(line)

    %w{PASS FAIL ERROR}.each do |state|
      state_node = line_cluster.add(Node.new(state, line_cluster.viz.add_nodes("#{line.class.name}#{state}", "label" => state)))
      states = line.stations.map{ |s| "#{state} #{s.name}" }
      style_node_for_line(state_node, line, *states)
      style_node_for_route(state_node, route, *states)
      style_node_for_passenger(state_node, passenger, *line.send("#{state.downcase}ed"))
    end
  end

  route.connections.each do |connection|
    to_node = g.find(connection.line)
    to_line = to_node.base

    combos = {}
    g.to_a.each do |node|
      states = node.base.state_names(connection.states)
      states.each do |state|
        (combos[state] ||= []) << node
      end
    end

    if connection.strict
      combos.each do |state, nodes|
        node_name = nodes.map{ |n| n.base.class.name }.join + state
        node_label = nodes.map{ |n| state_label(state) }.join("\n")
        viz = g.viz.add_nodes(node_name, :label => node_label)
        g.viz.add_edges(
          viz,
          to_node.viz.get_node("#{connection.line.class.name}#{state}"),
          "lhead" => to_node.viz.id
        )
      end
    else
      combos.each do |state, nodes|
        nodes.each do |node|
          from_line = node.base
          g.viz.add_edges(
            node.viz.get_node("#{from_line.class.name}#{state}"),
            to_node.viz.get_node("#{to_line.class.name}PASS"),
            "lhead" => to_node.viz.id
          )
        end
      end
    end
  end

  g.viz.output(format => String)
end
state_label(state) click to toggle source
# File lib/ellington/visualizer.rb, line 74
def state_label(state)
  return state unless short_names
  state.split(" ").map { |part| part.split("::").last }.join(" | ")
end

Protected Instance Methods

add_state_nodes_for_line(cluster, line, passenger) click to toggle source
# File lib/ellington/visualizer.rb, line 202
def add_state_nodes_for_line(cluster, line, passenger)
  line.states.keys.each do |state|
    node = cluster.add(Node.new(state, cluster.viz.add_nodes(state, :label => state_label(state))))
    style_node_for_line(node, line, state)
    style_node_for_route(node, route, state)
    style_node_for_passenger(node, passenger, state)
  end
end
color_name(graphviz_color) click to toggle source
# File lib/ellington/visualizer.rb, line 233
def color_name(graphviz_color)
  graphviz_color.to_s.gsub("\"", "")
end
set_cluster_defaults(cluster) click to toggle source
# File lib/ellington/visualizer.rb, line 248
def set_cluster_defaults(cluster)
  cluster[:style]     = CLUSTER_STYLE
  cluster[:color]     = CLUSTER_COLOR
  cluster[:fillcolor] = CLUSTER_FILLCOLOR
  cluster[:pencolor]  = CLUSTER_PENCOLOR
end
set_graph_defaults(graph) click to toggle source
# File lib/ellington/visualizer.rb, line 237
def set_graph_defaults(graph)
  graph[:compound]       = true
  graph[:ranksep]        = RANKSEP
  graph[:fontname]       = FONTNAME
  graph.node[:fontname]  = FONTNAME
  graph.node[:shape]     = NODE_SHAPE
  graph.node[:style]     = NODE_STYLE
  graph.node[:color]     = NODE_COLOR
  graph.node[:fillcolor] = NODE_FILLCOLOR
end
style_node(node, color) click to toggle source
# File lib/ellington/visualizer.rb, line 211
def style_node(node, color)
  node.viz[:color] = color
  node.viz[:fillcolor] = color
end
style_node_for_line(node, line, *states) click to toggle source
# File lib/ellington/visualizer.rb, line 216
def style_node_for_line(node, line, *states)
  return if (line.goal & states).empty?
  style_node node, NODE_COLOR_LINE_GOAL
end
style_node_for_passenger(node, passenger, *states) click to toggle source
# File lib/ellington/visualizer.rb, line 226
def style_node_for_passenger(node, passenger, *states)
  return if passenger.nil?
  return if (passenger.state_history & states).empty?
  node.viz[:color] = NODE_COLOR_PASSENGER_HIT
  node.viz[:penwidth] = NODE_PENWIDTH_PASSENGER_HIT
end
style_node_for_route(node, route, *states) click to toggle source
# File lib/ellington/visualizer.rb, line 221
def style_node_for_route(node, route, *states)
  return if (route.goal & states).empty?
  style_node node, NODE_COLOR_ROUTE_GOAL
end