Project

General

Profile

Feature #3436 » gantt-relations-r11086.diff

Toshi MARUYAMA, 2012-12-28 05:27

View differences:

app/views/gantts/show.html.erb
1
<% content_for :header_tags do %>
2
  <%= javascript_include_tag 'raphael' %>
3
  <%= javascript_include_tag 'gantt' %>
4
<% end %>
5
<%= javascript_tag do %>
6
  $(document).ready(drawGanttHandler);
7
  $(window).resize(drawGanttHandler);
8
<% end %>
1 9
<% @gantt.view = self %>
2 10
<h2><%= @query.new_record? ? l(:label_gantt) : h(@query.name) %></h2>
3 11

  
......
102 110
</td>
103 111

  
104 112
<td>
105
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;">
113
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;" id="gantt_area">
106 114
<%
107 115
  style  = ""
108 116
  style += "width: #{g_width - 1}px;"
......
231 239
  %>
232 240
  <%= content_tag(:div, '&nbsp;'.html_safe, :style => style) %>
233 241
<% end %>
234

  
242
<%
243
  style  = ""
244
  style += "position: absolute;"
245
  style += "height: #{g_height}px;"
246
  style += "top: #{headers_height + 1}px;"
247
  style += "left: 0px;"
248
  style += "width: #{g_width - 1}px;"
249
%>
250
<%= content_tag(:div, '', :style => style, :id => "gantt_draw_area") %>
235 251
</div>
236 252
</td>
237 253
</tr>
public/javascripts/gantt.js
1
var draw_gantt = null;
2
var draw_top;
3
var draw_right;
4
var draw_left;
5

  
6
function setDrawArea() {
7
  draw_top   = $("#gantt_draw_area").position().top;
8
  draw_right = $("#gantt_draw_area").width();
9
  draw_left  = $("#gantt_area").scrollLeft();
10
}
11

  
12
function drawGanttHandler() {
13
  var folder = document.getElementById('gantt_draw_area');
14
  if(draw_gantt != null)
15
    draw_gantt.clear();
16
  else
17
    draw_gantt = Raphael(folder);
18
  setDrawArea();
19
}
app/models/issue_relation.rb
51 51
    TYPE_DUPLICATED =>  { :name => :label_duplicated_by, :sym_name => :label_duplicates,
52 52
                          :order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES },
53 53
    TYPE_BLOCKS =>      { :name => :label_blocks, :sym_name => :label_blocked_by,
54
                          :order => 4, :sym => TYPE_BLOCKED },
54
                          :order => 4, :sym => TYPE_BLOCKED,
55
                          :landscape_margin => 16 },
55 56
    TYPE_BLOCKED =>     { :name => :label_blocked_by, :sym_name => :label_blocks,
56 57
                          :order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS },
57 58
    TYPE_PRECEDES =>    { :name => :label_precedes, :sym_name => :label_follows,
58
                          :order => 6, :sym => TYPE_FOLLOWS },
59
                          :order => 6, :sym => TYPE_FOLLOWS,
60
                          :landscape_margin => 20 },
59 61
    TYPE_FOLLOWS =>     { :name => :label_follows, :sym_name => :label_precedes,
60 62
                          :order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES },
61 63
    TYPE_COPIED_TO =>   { :name => :label_copied_to, :sym_name => :label_copied_from,
......
64 66
                          :order => 9, :sym => TYPE_COPIED_TO, :reverse => TYPE_COPIED_TO }
65 67
  }.freeze
66 68

  
69
  DRAW_TYPES = {
70
    TYPE_BLOCKS   => TYPES[TYPE_BLOCKS],
71
    TYPE_BLOCKED  => TYPES[TYPE_BLOCKED],
72
    TYPE_PRECEDES => TYPES[TYPE_PRECEDES],
73
    TYPE_FOLLOWS  => TYPES[TYPE_FOLLOWS],
74
  }.freeze
75

  
76
  DRAW_TYPES_JSON = DRAW_TYPES.to_json
77

  
67 78
  validates_presence_of :issue_from, :issue_to, :relation_type
68 79
  validates_inclusion_of :relation_type, :in => TYPES.keys
69 80
  validates_numericality_of :delay, :allow_nil => true
app/views/gantts/show.html.erb
3 3
  <%= javascript_include_tag 'gantt' %>
4 4
<% end %>
5 5
<%= javascript_tag do %>
6
  var issue_relation_type = <%= IssueRelation::DRAW_TYPES_JSON.html_safe %>;
6 7
  $(document).ready(drawGanttHandler);
7 8
  $(window).resize(drawGanttHandler);
9
  $(function() {
10
    $("#draw_rels").change(drawGanttHandler);
11
  });
8 12
<% end %>
9 13
<% @gantt.view = self %>
10 14
<h2><%= @query.new_record? ? l(:label_gantt) : h(@query.name) %></h2>
......
20 24
    <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
21 25
  </div>
22 26
</fieldset>
27
<fieldset id="filters" class="collapsible">
28
  <legend onclick="toggleFieldset(this);"><%= l(:label_display) %></legend>
29
  <div>
30
    <fieldset>
31
      <legend><%= l(:label_related_issues) %></legend>
32
      <label>
33
        <%= check_box_tag "draw_rels", 0, params["draw_rels"] %>
34
        <% rels = [IssueRelation::TYPE_BLOCKS, IssueRelation::TYPE_PRECEDES] %>
35
        <% rels.each do |rel| %>
36
          <%= content_tag(:span, '&nbsp;&nbsp;&nbsp;'.html_safe,
37
                          :id => "gantt_draw_rel_color_#{rel}") %>
38
          <%= l(IssueRelation::TYPES[rel][:name]) %>
39
        <% end %>
40
      </label>
41
    </fieldset>
42
  </div>
43
</fieldset>
23 44

  
24 45
<p class="contextual">
25 46
  <%= gantt_zoom_link(@gantt, :in) %>
lib/redmine/helpers/gantt.rb
705 705
        params[:image].text(params[:indent], params[:top] + 2, subject)
706 706
      end
707 707

  
708
      def issue_relations(issue)
709
        relations = {}
710
        issue.relations_to.each do |relation|
711
          relation_type = relation.relation_type_for(relation.issue_to)
712
          if !IssueRelation::DRAW_TYPES[relation_type].nil?
713
            (relations[relation_type] ||= []) << relation.issue_from_id
714
          end
715
        end
716
        relations
717
      end
718

  
708 719
      def html_task(params, coords, options={})
709 720
        output = ''
710 721
        # Renders the task bar, with progress and late
......
714 725
          style << "top:#{params[:top]}px;"
715 726
          style << "left:#{coords[:bar_start]}px;"
716 727
          style << "width:#{width}px;"
717
          output << view.content_tag(:div, '&nbsp;'.html_safe,
718
                                     :style => style,
719
                                     :class => "#{options[:css]} task_todo")
728
          html_id = "task-todo-issue-#{options[:issue].id}" if options[:issue]
729
          content_opt = {:style => style,
730
                         :class => "#{options[:css]} task_todo",
731
                         :id => html_id}
732
          if options[:issue]
733
            rels_hash = {}
734
            issue_relations(options[:issue]).each do |k, v|
735
              rels_hash[k] = v.join(',')
736
            end
737
            content_opt[:data] = {"rels" => rels_hash}
738
          end
739
          output << view.content_tag(:div, '&nbsp;'.html_safe, content_opt)
720 740
          if coords[:bar_late_end]
721 741
            width = coords[:bar_late_end] - coords[:bar_start] - 2
722 742
            style = ""
public/javascripts/gantt.js
3 3
var draw_right;
4 4
var draw_left;
5 5

  
6
var rels_stroke_width = 3;
7

  
6 8
function setDrawArea() {
7 9
  draw_top   = $("#gantt_draw_area").position().top;
8 10
  draw_right = $("#gantt_draw_area").width();
9 11
  draw_left  = $("#gantt_area").scrollLeft();
10 12
}
11 13

  
14
function getRelationsArray() {
15
  var arr = new Array();
16
  $.each($('div.task_todo'), function(index_div, element) {
17
    var element_id = $(element).attr("id");
18
    if (element_id != null) {
19
      var issue_id = element_id.replace("task-todo-issue-", "");
20
      var data_rels = $(element).data("rels");
21
      if (data_rels != null) {
22
        for (rel_type_key in issue_relation_type) {
23
          if (rel_type_key in data_rels) {
24
            var issue_arr = data_rels[rel_type_key].toString().split(",");
25
            if ("reverse" in issue_relation_type[rel_type_key]) {
26
              $.each(issue_arr, function(index_issue, element_issue) {
27
                arr.push({issue_from: element_issue, issue_to: issue_id,
28
                          rel_type: issue_relation_type[rel_type_key]["reverse"]});
29
              });
30
            } else {
31
              $.each(issue_arr, function(index_issue, element_issue) {
32
                arr.push({issue_from: issue_id, issue_to: element_issue,
33
                          rel_type: rel_type_key});
34
              });
35
            }
36
          }
37
        }
38
      }
39
    }
40
  });
41
  return arr;
42
}
43

  
44
function drawRelations() {
45
  var arr = getRelationsArray();
46
  $.each(arr, function(index_issue, element_issue) {
47
    var issue_from = $("#task-todo-issue-" + element_issue["issue_from"]);
48
    var issue_to   = $("#task-todo-issue-" + element_issue["issue_to"]);
49
    if (issue_from.size() == 0 || issue_to.size() == 0) {
50
      return;
51
    }
52
    var issue_height = issue_from.height();
53
    var issue_from_top   = issue_from.position().top  + (issue_height / 2) - draw_top;
54
    var issue_from_right = issue_from.position().left + issue_from.width();
55
    var issue_to_top   = issue_to.position().top  + (issue_height / 2) - draw_top;
56
    var issue_to_left  = issue_to.position().left;
57
    var color = $("#gantt_draw_rel_color_" + element_issue["rel_type"])
58
                    .css("background-color");
59
    var landscape_margin = issue_relation_type[element_issue["rel_type"]]["landscape_margin"];
60
    var issue_from_right_rel = issue_from_right + landscape_margin;
61
    var issue_to_left_rel    = issue_to_left    - landscape_margin;
62
    draw_gantt.path(["M", issue_from_right + draw_left,     issue_from_top,
63
                     "L", issue_from_right_rel + draw_left, issue_from_top])
64
                   .attr({stroke: color,
65
                          "stroke-width": rels_stroke_width,
66
                          "stroke-linecap": "round"
67
                          });
68
    if (issue_from_right_rel < issue_to_left_rel) {
69
      draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top,
70
                       "L", issue_from_right_rel + draw_left, issue_to_top])
71
                     .attr({stroke: color,
72
                          "stroke-width": rels_stroke_width,
73
                          "stroke-linecap": "round"
74
                          });
75
      draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_to_top,
76
                       "L", issue_to_left + draw_left,        issue_to_top])
77
                     .attr({stroke: color,
78
                          "stroke-width": rels_stroke_width,
79
                          "stroke-linecap": "round"
80
                          });
81
    } else {
82
      var issue_middle_top = issue_to_top +
83
                                (issue_height *
84
                                   ((issue_from_top > issue_to_top) ? 1 : -1));
85
      draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top,
86
                       "L", issue_from_right_rel + draw_left, issue_middle_top])
87
                     .attr({stroke: color,
88
                          "stroke-width": rels_stroke_width,
89
                          "stroke-linecap": "round"
90
                          });
91
      draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_middle_top,
92
                       "L", issue_to_left_rel + draw_left,    issue_middle_top])
93
                     .attr({stroke: color,
94
                          "stroke-width": rels_stroke_width,
95
                          "stroke-linecap": "round"
96
                          });
97
      draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_middle_top,
98
                       "L", issue_to_left_rel + draw_left, issue_to_top])
99
                     .attr({stroke: color,
100
                          "stroke-width": rels_stroke_width,
101
                          "stroke-linecap": "round"
102
                          });
103
      draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_to_top,
104
                       "L", issue_to_left + draw_left,     issue_to_top])
105
                     .attr({stroke: color,
106
                          "stroke-width": rels_stroke_width,
107
                          "stroke-linecap": "round"
108
                          });
109
    }
110
    draw_gantt.path(["M", issue_to_left + draw_left, issue_to_top,
111
                     "l", -4 * rels_stroke_width, -2 * rels_stroke_width,
112
                     "l", 0, 4 * rels_stroke_width, "z"])
113
                   .attr({stroke: "none",
114
                          fill: color,
115
                          "stroke-linecap": "butt",
116
                          "stroke-linejoin": "miter",
117
                          });
118
  });
119
}
120

  
12 121
function drawGanttHandler() {
13 122
  var folder = document.getElementById('gantt_draw_area');
14 123
  if(draw_gantt != null)
......
16 125
  else
17 126
    draw_gantt = Raphael(folder);
18 127
  setDrawArea();
128
  if ($("#draw_rels").attr('checked'))
129
    drawRelations();
19 130
}
public/stylesheets/application.css
879 879
a.close-icon:hover {background-image:url('../images/close_hl.png');}
880 880

  
881 881
/***** Gantt chart *****/
882
#gantt_draw_rel_color_blocks     {background-color:#fb7d2f;}
883
#gantt_draw_rel_color_precedes   {background-color:#df347c;}
884

  
882 885
.gantt_hdr {
883 886
  position:absolute;
884 887
  top:0;
app/models/issue_relation.rb
52 52
                          :order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES },
53 53
    TYPE_BLOCKS =>      { :name => :label_blocks, :sym_name => :label_blocked_by,
54 54
                          :order => 4, :sym => TYPE_BLOCKED,
55
                          :landscape_margin => 16 },
55
                          :landscape_margin => 16, :stroke_dasharray => "-"},
56 56
    TYPE_BLOCKED =>     { :name => :label_blocked_by, :sym_name => :label_blocks,
57 57
                          :order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS },
58 58
    TYPE_PRECEDES =>    { :name => :label_precedes, :sym_name => :label_follows,
59 59
                          :order => 6, :sym => TYPE_FOLLOWS,
60
                          :landscape_margin => 20 },
60
                          :landscape_margin => 20, :stroke_dasharray => "." },
61 61
    TYPE_FOLLOWS =>     { :name => :label_follows, :sym_name => :label_precedes,
62 62
                          :order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES },
63 63
    TYPE_COPIED_TO =>   { :name => :label_copied_to, :sym_name => :label_copied_from,
public/javascripts/gantt.js
59 59
    var landscape_margin = issue_relation_type[element_issue["rel_type"]]["landscape_margin"];
60 60
    var issue_from_right_rel = issue_from_right + landscape_margin;
61 61
    var issue_to_left_rel    = issue_to_left    - landscape_margin;
62
    var stroke_dasharray = issue_relation_type[element_issue["rel_type"]]["stroke_dasharray"];
62 63
    draw_gantt.path(["M", issue_from_right + draw_left,     issue_from_top,
63 64
                     "L", issue_from_right_rel + draw_left, issue_from_top])
64 65
                   .attr({stroke: color,
65 66
                          "stroke-width": rels_stroke_width,
66
                          "stroke-linecap": "round"
67
                          "stroke-dasharray": stroke_dasharray
67 68
                          });
68 69
    if (issue_from_right_rel < issue_to_left_rel) {
69 70
      draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top,
70 71
                       "L", issue_from_right_rel + draw_left, issue_to_top])
71 72
                     .attr({stroke: color,
72 73
                          "stroke-width": rels_stroke_width,
73
                          "stroke-linecap": "round"
74
                          "stroke-dasharray": stroke_dasharray
74 75
                          });
75 76
      draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_to_top,
76 77
                       "L", issue_to_left + draw_left,        issue_to_top])
77 78
                     .attr({stroke: color,
78 79
                          "stroke-width": rels_stroke_width,
79
                          "stroke-linecap": "round"
80
                          "stroke-dasharray": stroke_dasharray
80 81
                          });
81 82
    } else {
82 83
      var issue_middle_top = issue_to_top +
......
86 87
                       "L", issue_from_right_rel + draw_left, issue_middle_top])
87 88
                     .attr({stroke: color,
88 89
                          "stroke-width": rels_stroke_width,
89
                          "stroke-linecap": "round"
90
                          "stroke-dasharray": stroke_dasharray
90 91
                          });
91 92
      draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_middle_top,
92 93
                       "L", issue_to_left_rel + draw_left,    issue_middle_top])
93 94
                     .attr({stroke: color,
94 95
                          "stroke-width": rels_stroke_width,
95
                          "stroke-linecap": "round"
96
                          "stroke-dasharray": stroke_dasharray
96 97
                          });
97 98
      draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_middle_top,
98 99
                       "L", issue_to_left_rel + draw_left, issue_to_top])
99 100
                     .attr({stroke: color,
100 101
                          "stroke-width": rels_stroke_width,
101
                          "stroke-linecap": "round"
102
                          "stroke-dasharray": stroke_dasharray
102 103
                          });
103 104
      draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_to_top,
104 105
                       "L", issue_to_left + draw_left,     issue_to_top])
105 106
                     .attr({stroke: color,
106 107
                          "stroke-width": rels_stroke_width,
107
                          "stroke-linecap": "round"
108
                          "stroke-dasharray": stroke_dasharray
108 109
                          });
109 110
    }
110 111
    draw_gantt.path(["M", issue_to_left + draw_left, issue_to_top,
(10-10/13)