lib/rsyntaxtree/svg_graph.rb in rsyntaxtree-1.1.4 vs lib/rsyntaxtree/svg_graph.rb in rsyntaxtree-1.2.0
- old
+ new
@@ -47,21 +47,25 @@
y1 = 0 - @margin
x2 = @width + @margin
y2 = @height + @margin
extra_lines = @extra_lines.join("\n")
+ as = @global[:h_gap_between_nodes] / 4 * 0.8
as2 = @global[:h_gap_between_nodes] / 2 * 0.8
- as = as2 / 2
+ as3 = @global[:h_gap_between_nodes] / 2 * 0.5
header = <<~HDR
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="#{@width}" height="#{@height}" viewBox="#{x1}, #{y1}, #{x2}, #{y2}" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrow" markerUnits="strokeWidth" markerWidth="#{as2}" markerHeight="#{as2}" viewBox="0 0 #{as2} #{as2}" refX="#{as}" refY="0">
<polyline fill="none" stroke="#{@col_path}" stroke-width="1" points="0,#{as2},#{as},0,#{as2},#{as2}" />
</marker>
+ <marker id="arrowSolid" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="#{as3}" markerHeight="#{as3}" orient="auto-start-reverse">
+ <path d="M 0 0 L 10 5 L 0 10 z" stroke="#{@col_line}"/>
+ </marker>
<pattern id="hatchBlack" x="10" y="10" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<line x1="0" y="0" x2="0" y2="10" stroke="black" stroke-width="4"></line>
</pattern>
<pattern id="hatchForNode" x="10" y="10" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<line x1="0" y="0" x2="0" y2="10" stroke="#{@col_node}" stroke-width="4"></line>
@@ -83,13 +87,17 @@
else
header + rect + @tree_data + extra_lines + footer
end
end
+ def draw_direct_line(s_x, s_y, t_x, t_y, s_arrow = false, t_arrow = false)
+ @extra_lines << generate_connector(s_x, s_y, t_x, t_y, @col_line, false, s_arrow, t_arrow)
+ end
+
def draw_a_path(s_x, s_y, t_x, t_y, target_arrow = :none)
x_spacing = @global[:h_gap_between_nodes] * 1.25
- y_spacing = @global[:height_connector] * 0.65
+ y_spacing = @global[:height_connector] * 0.75
ymax = [s_y, t_y].max
new_y = if ymax < @height
@height + y_spacing
else
@@ -110,19 +118,20 @@
else
new_t_x = t_x
@visited_x[t_x] = 1
end
- s_y += @global[:h_gap_between_nodes] / 2
- t_y += @global[:h_gap_between_nodes] / 2
- new_y += @global[:h_gap_between_nodes] / 2
+ # s_y += @global[:h_gap_between_nodes] / 2
+ # t_y += @global[:h_gap_between_nodes] / 2
+ # new_y += @global[:h_gap_between_nodes] / 2
dashed = true if target_arrow == :none
case target_arrow
when :single
@extra_lines << generate_line(new_s_x, s_y, new_s_x, new_y, @col_path, dashed)
+ @extra_lines << generate_line(new_s_x, s_y, new_s_x, new_y, @col_path, dashed)
@extra_lines << generate_line(new_s_x, new_y, new_t_x, new_y, @col_path, dashed)
@extra_lines << generate_line(new_t_x, new_y, new_t_x, t_y, @col_path, dashed, true)
when :double
@extra_lines << generate_line(new_s_x, new_y, new_s_x, s_y, @col_path, dashed, true)
@extra_lines << generate_line(new_s_x, new_y, new_t_x, new_y, @col_path, dashed)
@@ -360,49 +369,75 @@
end
"<tspan x='#{this_x}' y='#{this_y}' #{style} #{decoration} font-family=\"#{fontstyle}\">#{text}</tspan>\n"
end
def draw_paths
+ paths = []
path_pool_target = {}
path_pool_other = {}
path_pool_source = {}
path_flags = []
+
+ line_pool = {}
+ line_flags = []
+
# elist = @element_list.get_elements.reverse
elist = @element_list.get_elements
- elist.each do |element|
+ elist.each_with_index do |element, i|
+ x0 = element.horizontal_indent - @global[:h_gap_between_nodes]
x1 = element.horizontal_indent + element.content_width / 2
+ x2 = element.horizontal_indent + element.content_width + @global[:h_gap_between_nodes]
+ y0 = if /nothing|none/ =~ @leafstyle && element.type == ETYPE_LEAF && element.enclosure != :none
+ element.vertical_indent - @global[:height_connector_to_text]
+ else
+ element.vertical_indent + @global[:height_connector_to_text] / 2
+ end
y1 = element.vertical_indent + element.content_height
- y1 += @global[:height_connector_to_text] if element.enclosure != :none
+ if i == elist.size - 1 && /nothing|none/ =~ @leafstyle
+ y1 -= @global[:height_connector_to_text] / 2
+ else
+ y1 += @global[:height_connector_to_text]
+ end
et = element.path
et.each do |tr|
- if /\A>(\d+)\z/ =~ tr
+ if /\A-(>)?(\d+)\z/ =~ tr
+ arrow = $1
+ tr = $2
+ if line_pool[tr]
+ line_pool[tr] << { x: { left: x0, center: x1, right: x2 }, y: { top: y0, center: y0 + (y1 - y0) / 2, bottom: y1 }, arrow: arrow }
+ else
+ line_pool[tr] = [{ x: { left: x0, center: x1, right: x2 }, y: { top: y0, center: y0 + (y1 - y0) / 2, bottom: y1 }, arrow: arrow }]
+ end
+ line_flags << tr
+ elsif /\A>(\d+)\z/ =~ tr
tr = $1
if path_pool_target[tr]
path_pool_target[tr] << [x1, y1]
else
path_pool_target[tr] = [[x1, y1]]
end
+ path_flags << tr
elsif path_pool_source[tr]
if path_pool_other[tr]
path_pool_other[tr] << [x1, y1]
else
path_pool_other[tr] = [[x1, y1]]
end
+ path_flags << tr
else
path_pool_source[tr] = [x1, y1]
+ path_flags << tr
end
- path_flags << tr
- raise RSTError, "Error: input text contains a path having more than two ends:\n > #{tr}" if path_flags.tally.any? { |_k, v| v > 2 }
+ raise RSTError, "Error: input text contains a path having more than two ends:\n > #{tr}" if path_flags.tally.any? { |_k, v| v > 2 } || line_flags.tally.any? { |_k, v| v > 2 }
end
end
path_flags.tally.each do |k, v|
raise RSTError, "Error: input text contains a path having only one end:\n > #{k}" if v == 1
end
- paths = []
path_pool_source.each do |k, v|
path_flags.delete(k)
if (targets = path_pool_target[k])
targets.each do |t|
paths << { x1: v[0], y1: v[1], x2: t[0], y2: t[1], arrow: :single }
@@ -421,15 +456,46 @@
paths << { x1: fst[0], y1: fst[1], x2: t[0], y2: t[1], arrow: :double }
end
end
paths.each do |t|
- draw_a_path(t[:x1], t[:y1] + @global[:height_connector_to_text] / 2,
- t[:x2], t[:y2] + @global[:height_connector_to_text] / 2,
- t[:arrow])
+ # draw_a_path(t[:x1], t[:y1] - @global[:height_connector_to_text], t[:x2], t[:y2] - @global[:height_connector_to_text], t[:arrow])
+ draw_a_path(t[:x1], t[:y1], t[:x2], t[:y2], t[:arrow])
end
- paths.size
+ line_pool.each do |_k, v|
+ a = v[0]
+ b = v[1]
+
+ if (a[:y][:bottom] > b[:y][:top] && a[:y][:top] < b[:y][:bottom]) || (b[:y][:bottom] > a[:y][:top] && b[:y][:top] < a[:y][:bottom])
+ y_top = [a[:y][:top], b[:y][:top]].max
+ y_bottom = [a[:y][:bottom], b[:y][:bottom]].min
+ y_center = y_top + (y_top - y_bottom).abs / 2
+ if a[:x][:center] < b[:x][:center]
+ draw_direct_line(a[:x][:right], y_center, b[:x][:left], y_center, a[:arrow], b[:arrow])
+ else
+ draw_direct_line(b[:x][:right], y_center, a[:x][:left], y_center, b[:arrow], a[:arrow])
+ end
+ next
+ end
+
+ if a[:y][:bottom] < b[:y][:bottom]
+ draw_direct_line(a[:x][:center], a[:y][:bottom], b[:x][:center], b[:y][:top], a[:arrow], b[:arrow])
+ else
+ draw_direct_line(a[:x][:center], a[:y][:top], b[:x][:center], b[:y][:bottom], a[:arrow], b[:arrow])
+ end
+ end
+ paths.size + line_pool.keys.size
+ end
+
+ def generate_connector(x1, y1, x2, y2, col, dashed = false, s_arrow = false, t_arrow = false, stroke_width = 1)
+ string = +""
+ string << "marker-start='url(#arrowSolid)' " if s_arrow
+ string << "marker-end='url(#arrowSolid)' " if t_arrow
+ dasharray = dashed ? "stroke-dasharray='8 8'" : ""
+ swidth = FONT_SCALING * stroke_width
+
+ "<line x1='#{x1}' y1='#{y1}' x2='#{x2}' y2='#{y2}' style='fill: none; stroke: #{col}; stroke-width:#{swidth}' #{dasharray} #{string}/>"
end
def generate_line(x1, y1, x2, y2, col, dashed = false, arrow = false, stroke_width = 1)
string = if arrow
"marker-end='url(#arrow)' "