# HG changeset patch
# User Toshi MARUYAMA
# Date 1354592875 -32400
# Node ID 287b115293ceb1e41388c34e5af97fb23a0cafa5
# Parent  4a143f7a4e6a9f589891f5acdbb13cb7222fc8a4
gantt: (#3436, #12122)

diff --git a/app/views/gantts/show.html.erb b/app/views/gantts/show.html.erb
--- a/app/views/gantts/show.html.erb
+++ b/app/views/gantts/show.html.erb
@@ -1,3 +1,11 @@
+<% content_for :header_tags do %>
+  <%= javascript_include_tag 'raphael' %>
+  <%= javascript_include_tag 'gantt' %>
+<% end %>
+<%= javascript_tag do %>
+  $(document).ready(drawGanttHandler);
+  $(window).resize(drawGanttHandler);
+<% end %>
 <% @gantt.view = self %>
 <h2><%= @query.new_record? ? l(:label_gantt) : h(@query.name) %></h2>
 
@@ -102,7 +110,7 @@
 </td>
 
 <td>
-<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;">
+<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;" id="gantt_area">
 <%
   style  = ""
   style += "width: #{g_width - 1}px;"
@@ -231,7 +239,15 @@
   %>
   <%= content_tag(:div, '&nbsp;'.html_safe, :style => style) %>
 <% end %>
-
+<%
+  style  = ""
+  style += "position: absolute;"
+  style += "height: #{g_height}px;"
+  style += "top: #{headers_height + 1}px;"
+  style += "left: 0px;"
+  style += "width: #{g_width - 1}px;"
+%>
+<%= content_tag(:div, '', :style => style, :id => "gantt_draw_area") %>
 </div>
 </td>
 </tr>
# HG changeset patch
# User Toshi MARUYAMA
# Date 1354543638 -32400
# Node ID d4603d81282bf876579f95598aed5d6e75faa5dc
# Parent  287b115293ceb1e41388c34e5af97fb23a0cafa5
gantt: (#3436, #12122)

diff --git a/public/javascripts/gantt.js b/public/javascripts/gantt.js
new file mode 100644
--- /dev/null
+++ b/public/javascripts/gantt.js
@@ -0,0 +1,19 @@
+var draw_gantt = null;
+var draw_top;
+var draw_right;
+var draw_left;
+
+function setDrawArea() {
+  draw_top   = $("#gantt_draw_area").position().top;
+  draw_right = $("#gantt_draw_area").width();
+  draw_left  = $("#gantt_area").scrollLeft();
+}
+
+function drawGanttHandler() {
+  var folder = document.getElementById('gantt_draw_area');
+  if(draw_gantt != null)
+    draw_gantt.clear();
+  else
+    draw_gantt = Raphael(folder);
+  setDrawArea();
+}
# HG changeset patch
# User Toshi MARUYAMA
# Date 1354543477 -32400
# Node ID 45c384d451765d7db569d86636ade253b7d2c03d
# Parent  d4603d81282bf876579f95598aed5d6e75faa5dc
gantt: relations (#3436)

diff --git a/app/views/gantts/show.html.erb b/app/views/gantts/show.html.erb
--- a/app/views/gantts/show.html.erb
+++ b/app/views/gantts/show.html.erb
@@ -3,8 +3,14 @@
   <%= javascript_include_tag 'gantt' %>
 <% end %>
 <%= javascript_tag do %>
+  var issue_relation_type = <%= IssueRelation::TYPES.to_json.html_safe %>;
   $(document).ready(drawGanttHandler);
   $(window).resize(drawGanttHandler);
+  $(function() {
+    $.each($("#draw_rels").children("label").children("input"), function(index, element) {
+      $(element).change(drawGanttHandler);
+    });
+  });
 <% end %>
 <% @gantt.view = self %>
 <h2><%= @query.new_record? ? l(:label_gantt) : h(@query.name) %></h2>
@@ -20,6 +26,30 @@
     <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
   </div>
 </fieldset>
+<fieldset id="filters" class="collapsible">
+  <legend onclick="toggleFieldset(this);"><%= l(:label_display) %></legend>
+  <div>
+    <fieldset id = "draw_rels">
+      <legend><%= l(:label_related_issues) %></legend>
+      <%
+        rels = IssueRelation::TYPES.to_a.select {|rel|
+          rel[1][:reverse].nil?
+        }
+        rels_sorted = rels.sort {|x, y|
+                        x[1][:order] <=> y[1][:order]
+                      }.collect{|v| [v[0], l(v[1][:name])]}
+      %>
+      <% rels_sorted.each do |rel| %>
+        <label>
+          <%= check_box_tag "draw_#{rel[0]}", 0, params["draw_#{rel[0]}"] %>
+          <%= content_tag(:span, '&nbsp;&nbsp;&nbsp;'.html_safe,
+                          :id => "gantt_draw_rel_color_#{rel[0]}") %>
+          <%= rel[1] %>
+        </label>
+      <% end %>
+    </fieldset>
+  </div>
+</fieldset>
 
 <p class="contextual">
   <%= gantt_zoom_link(@gantt, :in) %>
diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb
--- a/lib/redmine/helpers/gantt.rb
+++ b/lib/redmine/helpers/gantt.rb
@@ -705,6 +705,15 @@ module Redmine
         params[:image].text(params[:indent], params[:top] + 2, subject)
       end
 
+      def issue_relations(issue)
+        relations = {}
+        issue.relations_to.each do |relation|
+          relation_type = relation.relation_type_for(relation.issue_to)
+          (relations[relation_type] ||= []) << relation.issue_from_id
+        end
+        relations
+      end
+
       def html_task(params, coords, options={})
         output = ''
         # Renders the task bar, with progress and late
@@ -714,9 +723,17 @@ module Redmine
           style << "top:#{params[:top]}px;"
           style << "left:#{coords[:bar_start]}px;"
           style << "width:#{width}px;"
-          output << view.content_tag(:div, '&nbsp;'.html_safe,
-                                     :style => style,
-                                     :class => "#{options[:css]} task_todo")
+          html_id = "task-todo-issue-#{options[:issue].id}" if options[:issue]
+          content_opt = {:style => style,
+                         :class => "#{options[:css]} task_todo",
+                         :id => html_id}
+          if options[:issue]
+            relations = issue_relations(options[:issue])
+            relations.each do |k, v|
+              content_opt[:"data-rel-#{k}"] = v.join(',')
+            end
+          end
+          output << view.content_tag(:div, '&nbsp;'.html_safe, content_opt)
           if coords[:bar_late_end]
             width = coords[:bar_late_end] - coords[:bar_start] - 2
             style = ""
diff --git a/public/javascripts/gantt.js b/public/javascripts/gantt.js
--- a/public/javascripts/gantt.js
+++ b/public/javascripts/gantt.js
@@ -3,12 +3,125 @@ var draw_top;
 var draw_right;
 var draw_left;
 
+var draw_relations = {
+  "relates": {
+    "landscape_mergin": 8,
+    "no_arrow": true,
+  },
+  "duplicates": {
+    "landscape_mergin": 12,
+  },
+  "blocks": {
+    "landscape_mergin": 16,
+  },
+  "precedes": {
+    "landscape_mergin": 20,
+  },
+  "copied_to": {
+    "landscape_mergin": 24,
+  },
+};
+
 function setDrawArea() {
   draw_top   = $("#gantt_draw_area").position().top;
   draw_right = $("#gantt_draw_area").width();
   draw_left  = $("#gantt_area").scrollLeft();
 }
 
+function getRelationsArray() {
+  var arr = new Array();
+  $.each($('div.task_todo'), function(index_div, element) {
+    var element_id = $(element).attr("id");
+    if (element_id != null) {
+      var issue_id = element_id.replace("task-todo-issue-", "");
+      for(rel_type_key in issue_relation_type) {
+        var data_rel_attr = $(element).attr("data-rel-" + rel_type_key);
+        if (data_rel_attr != null) {
+          var issue_arr = data_rel_attr.split(",");
+          if ("reverse" in issue_relation_type[rel_type_key]) {
+            $.each(issue_arr, function(index_issue, element_issue) {
+              arr.push({issue_from: element_issue, issue_to: issue_id,
+                        rel_type: issue_relation_type[rel_type_key]["reverse"]});
+              });
+          } else {
+            $.each(issue_arr, function(index_issue, element_issue) {
+              arr.push({issue_from: issue_id, issue_to: element_issue,
+                        rel_type: rel_type_key});
+              });
+          }
+        }
+      }
+    }
+  });
+  return arr;
+}
+
+function drawRelations() {
+  var arr = getRelationsArray();
+  $.each(arr, function(index_issue, element_issue) {
+    if (!$("#draw_" + element_issue["rel_type"]).attr('checked')) {
+      return;
+    }
+    var issue_from = $("#task-todo-issue-" + element_issue["issue_from"]);
+    var issue_to   = $("#task-todo-issue-" + element_issue["issue_to"]);
+    var issue_height = issue_from.height();
+    var issue_from_top   = issue_from.position().top  + (issue_height / 2) - draw_top;
+    var issue_from_right = issue_from.position().left + issue_from.width();
+    var issue_to_top   = issue_to.position().top  + (issue_height / 2) - draw_top;
+    var issue_to_left  = issue_to.position().left;
+    var color = $("#gantt_draw_rel_color_" + element_issue["rel_type"])
+                    .css("background-color");
+    var landscape_mergin = draw_relations[element_issue["rel_type"]]["landscape_mergin"];
+    var issue_from_right_rel = issue_from_right + landscape_mergin;
+    var issue_to_left_rel    = issue_to_left    - landscape_mergin;
+    var x1, y1, x2, y2;
+    draw_gantt.path(["M", issue_from_right + draw_left,     issue_from_top,
+                     "L", issue_from_right_rel + draw_left, issue_from_top])
+                   .attr({stroke: color, "stroke-width": 2});
+    if (issue_from_right_rel < issue_to_left_rel) {
+      draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top,
+                       "L", issue_from_right_rel + draw_left, issue_to_top])
+                   .attr({stroke: color, "stroke-width": 2});
+      draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_to_top,
+                       "L", issue_to_left + draw_left,        issue_to_top])
+                   .attr({stroke: color,
+                          "stroke-width": 2,
+                          "stroke-linecap": "butt",
+                          "stroke-linejoin": "miter",
+                          });
+    } else {
+      var issue_middle_top = issue_to_top +
+                                (issue_height *
+                                   ((issue_from_top > issue_to_top) ? 1 : -1 ));
+      draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top,
+                       "L", issue_from_right_rel + draw_left, issue_middle_top])
+                   .attr({stroke: color, "stroke-width": 2});
+      draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_middle_top,
+                       "L", issue_to_left_rel + draw_left,    issue_middle_top])
+                   .attr({stroke:color, "stroke-width": 2});
+      draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_middle_top,
+                       "L", issue_to_left_rel + draw_left, issue_to_top])
+                   .attr({stroke: color, "stroke-width": 2});
+      draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_to_top,
+                       "L", issue_to_left + draw_left,     issue_to_top])
+                   .attr({stroke: color,
+                          "stroke-width": 2,
+                          "stroke-linecap": "butt",
+                          "stroke-linejoin": "miter",
+                          });
+    }
+    if (!("no_arrow" in draw_relations[element_issue["rel_type"]])) {
+      draw_gantt.path(["M", issue_to_left + draw_left, issue_to_top,
+                       "l", -8, -4, "l", 0, 8, "z"])
+                   .attr({stroke: "none",
+                        fill: color,
+                        "stroke-linecap": "butt",
+                        "stroke-linejoin": "miter",
+                          });
+    }
+  });
+}
+
 function drawGanttHandler() {
   var folder = document.getElementById('gantt_draw_area');
   if(draw_gantt != null)
@@ -16,4 +129,5 @@ function drawGanttHandler() {
   else
     draw_gantt = Raphael(folder);
   setDrawArea();
+  drawRelations();
 }
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -868,6 +868,12 @@ a.close-icon {
 a.close-icon:hover {background-image:url('../images/close_hl.png');}
 
 /***** Gantt chart *****/
+#gantt_draw_rel_color_relates    {background-color:#00c551;}
+#gantt_draw_rel_color_duplicates {background-color:#4f52c3;}
+#gantt_draw_rel_color_blocks     {background-color:#fb7d2f;}
+#gantt_draw_rel_color_precedes   {background-color:#df347c;}
+#gantt_draw_rel_color_copied_to  {background-color:#00c5c2;}
+
 .gantt_hdr {
   position:absolute;
   top:0;
