# HG changeset patch
# User Toshi MARUYAMA
# Date 1354592875 -32400
# Node ID 4bd9e13ad4e14c905a603b15207460a346065567
# Parent  3ebf2ed02a399e420fde6c7c39bc7763b687e8e9
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 af1ff26d76113133bd72bf77d81d06dd9331fc90
# Parent  4bd9e13ad4e14c905a603b15207460a346065567
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 7014171fd6332fd5bd1eb3310d39be83330f92a5
# Parent  af1ff26d76113133bd72bf77d81d06dd9331fc90
gantt: relations (#3436)

diff --git a/app/models/issue_relation.rb b/app/models/issue_relation.rb
--- a/app/models/issue_relation.rb
+++ b/app/models/issue_relation.rb
@@ -51,11 +51,13 @@ class IssueRelation < ActiveRecord::Base
     TYPE_DUPLICATED =>  { :name => :label_duplicated_by, :sym_name => :label_duplicates,
                           :order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES },
     TYPE_BLOCKS =>      { :name => :label_blocks, :sym_name => :label_blocked_by,
-                          :order => 4, :sym => TYPE_BLOCKED },
+                          :order => 4, :sym => TYPE_BLOCKED,
+                          :landscape_margin => 12 },
     TYPE_BLOCKED =>     { :name => :label_blocked_by, :sym_name => :label_blocks,
                           :order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS },
     TYPE_PRECEDES =>    { :name => :label_precedes, :sym_name => :label_follows,
-                          :order => 6, :sym => TYPE_FOLLOWS },
+                          :order => 6, :sym => TYPE_FOLLOWS,
+                          :landscape_margin => 16 },
     TYPE_FOLLOWS =>     { :name => :label_follows, :sym_name => :label_precedes,
                           :order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES },
     TYPE_COPIED_TO =>   { :name => :label_copied_to, :sym_name => :label_copied_from,
@@ -64,6 +66,15 @@ class IssueRelation < ActiveRecord::Base
                           :order => 9, :sym => TYPE_COPIED_TO, :reverse => TYPE_COPIED_TO }
   }.freeze
 
+  DRAW_TYPES = {
+    TYPE_BLOCKS   => TYPES[TYPE_BLOCKS],
+    TYPE_BLOCKED  => TYPES[TYPE_BLOCKED],
+    TYPE_PRECEDES => TYPES[TYPE_PRECEDES],
+    TYPE_FOLLOWS  => TYPES[TYPE_FOLLOWS],
+  }.freeze
+
+  DRAW_TYPES_JSON = DRAW_TYPES.to_json
+
   validates_presence_of :issue_from, :issue_to, :relation_type
   validates_inclusion_of :relation_type, :in => TYPES.keys
   validates_numericality_of :delay, :allow_nil => true
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,12 @@
   <%= javascript_include_tag 'gantt' %>
 <% end %>
 <%= javascript_tag do %>
+  var issue_relation_type = <%= IssueRelation::DRAW_TYPES_JSON.html_safe %>;
   $(document).ready(drawGanttHandler);
   $(window).resize(drawGanttHandler);
+  $(function() {
+    $("#draw_rels").change(drawGanttHandler);
+  });
 <% end %>
 <% @gantt.view = self %>
 <h2><%= @query.new_record? ? l(:label_gantt) : h(@query.name) %></h2>
@@ -20,6 +24,23 @@
     <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
   </div>
 </fieldset>
+<fieldset id="filters" class="collapsible">
+  <legend onclick="toggleFieldset(this);"><%= l(:label_display) %></legend>
+  <div>
+    <fieldset>
+      <legend><%= l(:label_related_issues) %></legend>
+      <label>
+        <%= check_box_tag "draw_rels", 0, params["draw_rels"] %>
+        <% rels = [IssueRelation::TYPE_BLOCKS, IssueRelation::TYPE_PRECEDES] %>
+        <% rels.each do |rel| %>
+          <%= content_tag(:span, '&nbsp;&nbsp;&nbsp;'.html_safe,
+                          :id => "gantt_draw_rel_color_#{rel}") %>
+          <%= l(IssueRelation::TYPES[rel][:name]) %>
+        <% end %>
+      </label>
+    </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,17 @@ 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)
+          if !IssueRelation::DRAW_TYPES[relation_type].nil?
+            (relations[relation_type] ||= []) << relation.issue_from_id
+          end
+        end
+        relations
+      end
+
       def html_task(params, coords, options={})
         output = ''
         # Renders the task bar, with progress and late
@@ -714,9 +725,18 @@ 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]
+            rels_hash = {}
+            issue_relations(options[:issue]).each do |k, v|
+              rels_hash[k] = v.join(',')
+            end
+            content_opt[:data] = {"rels" => rels_hash}
+          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
@@ -9,6 +9,99 @@ function setDrawArea() {
   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-", "");
+      var data_rels = $(element).data("rels");
+      if (data_rels != null) {
+        for (rel_type_key in issue_relation_type) {
+          if (rel_type_key in data_rels) {
+            var issue_arr = data_rels[rel_type_key].toString().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) {
+    var issue_from = $("#task-todo-issue-" + element_issue["issue_from"]);
+    var issue_to   = $("#task-todo-issue-" + element_issue["issue_to"]);
+    if (issue_from.size() == 0 || issue_to.size() == 0) {
+      return;
+    }
+    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_margin = issue_relation_type[element_issue["rel_type"]]["landscape_margin"];
+    var issue_from_right_rel = issue_from_right + landscape_margin;
+    var issue_to_left_rel    = issue_to_left    - landscape_margin;
+    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",
+                          });
+    }
+    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 +109,6 @@ function drawGanttHandler() {
   else
     draw_gantt = Raphael(folder);
   setDrawArea();
+  if ($("#draw_rels").attr('checked'))
+    drawRelations();
 }
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -879,6 +879,9 @@ a.close-icon {
 a.close-icon:hover {background-image:url('../images/close_hl.png');}
 
 /***** Gantt chart *****/
+#gantt_draw_rel_color_blocks     {background-color:#fb7d2f;}
+#gantt_draw_rel_color_precedes   {background-color:#df347c;}
+
 .gantt_hdr {
   position:absolute;
   top:0;
