Feature #3436 » gantt-relations-r10943.diff
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, ' '.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/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::TYPES.to_json.html_safe %>; |
|
6 | 7 |
$(document).ready(drawGanttHandler); |
7 | 8 |
$(window).resize(drawGanttHandler); |
9 |
$(function() { |
|
10 |
$.each($("#draw_rels").children("label").children("input"), function(index, element) { |
|
11 |
$(element).change(drawGanttHandler); |
|
12 |
}); |
|
13 |
}); |
|
8 | 14 |
<% end %> |
9 | 15 |
<% @gantt.view = self %> |
10 | 16 |
<h2><%= @query.new_record? ? l(:label_gantt) : h(@query.name) %></h2> |
... | ... | |
20 | 26 |
<%= render :partial => 'queries/filters', :locals => {:query => @query} %> |
21 | 27 |
</div> |
22 | 28 |
</fieldset> |
29 |
<fieldset id="filters" class="collapsible"> |
|
30 |
<legend onclick="toggleFieldset(this);"><%= l(:label_display) %></legend> |
|
31 |
<div> |
|
32 |
<table> |
|
33 |
<tr> |
|
34 |
<td> |
|
35 |
<fieldset id = "draw_rels"> |
|
36 |
<legend><%= l(:label_related_issues) %></legend> |
|
37 |
<% |
|
38 |
rels = IssueRelation::TYPES.to_a.select {|rel| |
|
39 |
rel[1][:reverse].nil? |
|
40 |
} |
|
41 |
rels_sorted = rels.sort {|x, y| |
|
42 |
x[1][:order] <=> y[1][:order] |
|
43 |
}.collect{|v| [v[0], l(v[1][:name])]} |
|
44 |
%> |
|
45 |
<% rels_sorted.each do |rel| %> |
|
46 |
<label> |
|
47 |
<%= check_box_tag "draw_#{rel[0]}", 0, params["draw_#{rel[0]}"] %> |
|
48 |
<%= content_tag(:span, ' '.html_safe, |
|
49 |
:id => "gantt_draw_rel_color_#{rel[0]}") %> |
|
50 |
<%= rel[1] %> |
|
51 |
</label> |
|
52 |
<% end %> |
|
53 |
</fieldset> |
|
54 |
</tr> |
|
55 |
</table> |
|
56 |
</div> |
|
57 |
</fieldset> |
|
23 | 58 | |
24 | 59 |
<p class="contextual"> |
25 | 60 |
<%= 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 |
(relations[relation_type] ||= []) << relation.issue_from_id |
|
713 |
end |
|
714 |
relations |
|
715 |
end |
|
716 | ||
708 | 717 |
def html_task(params, coords, options={}) |
709 | 718 |
output = '' |
710 | 719 |
# Renders the task bar, with progress and late |
... | ... | |
714 | 723 |
style << "top:#{params[:top]}px;" |
715 | 724 |
style << "left:#{coords[:bar_start]}px;" |
716 | 725 |
style << "width:#{width}px;" |
717 |
output << view.content_tag(:div, ' '.html_safe, |
|
718 |
:style => style, |
|
719 |
:class => "#{options[:css]} task_todo") |
|
726 |
html_id = "task-todo-issue-#{options[:issue].id}" if options[:issue] |
|
727 |
content_opt = {:style => style, |
|
728 |
:class => "#{options[:css]} task_todo", |
|
729 |
:id => html_id} |
|
730 |
if options[:issue] |
|
731 |
relations = issue_relations(options[:issue]) |
|
732 |
relations.each do |k, v| |
|
733 |
content_opt[:"data-rel-#{k}"] = v.join(',') |
|
734 |
end |
|
735 |
end |
|
736 |
output << view.content_tag(:div, ' '.html_safe, content_opt) |
|
720 | 737 |
if coords[:bar_late_end] |
721 | 738 |
width = coords[:bar_late_end] - coords[:bar_start] - 2 |
722 | 739 |
style = "" |
public/javascripts/gantt.js | ||
---|---|---|
3 | 3 |
var draw_right; |
4 | 4 |
var draw_left; |
5 | 5 | |
6 |
var draw_relations = { |
|
7 |
"relates": { |
|
8 |
"landscape_mergin": 8, |
|
9 |
"no_arrow": true, |
|
10 |
}, |
|
11 |
"duplicates": { |
|
12 |
"landscape_mergin": 12, |
|
13 |
}, |
|
14 |
"blocks": { |
|
15 |
"landscape_mergin": 16, |
|
16 |
}, |
|
17 |
"precedes": { |
|
18 |
"landscape_mergin": 20, |
|
19 |
}, |
|
20 |
"copied_to": { |
|
21 |
"landscape_mergin": 24, |
|
22 |
}, |
|
23 |
}; |
|
24 | ||
6 | 25 |
function setDrawArea() { |
7 | 26 |
draw_top = $("#gantt_draw_area").position().top; |
8 | 27 |
draw_right = $("#gantt_draw_area").width(); |
9 | 28 |
draw_left = $("#gantt_area").scrollLeft(); |
10 | 29 |
} |
11 | 30 | |
31 |
function getRelationsArray() { |
|
32 |
var arr = new Array(); |
|
33 |
$.each($('div.task_todo'), function(index_div, element) { |
|
34 |
var element_id = $(element).attr("id"); |
|
35 |
if (element_id != null) { |
|
36 |
var issue_id = element_id.replace("task-todo-issue-", ""); |
|
37 |
for(rel_type_key in issue_relation_type) { |
|
38 |
var data_rel_attr = $(element).attr("data-rel-" + rel_type_key); |
|
39 |
if (data_rel_attr != null) { |
|
40 |
var issue_arr = data_rel_attr.split(","); |
|
41 |
if ("reverse" in issue_relation_type[rel_type_key]) { |
|
42 |
$.each(issue_arr, function(index_issue, element_issue) { |
|
43 |
arr.push({issue_from: element_issue, issue_to: issue_id, |
|
44 |
rel_type: issue_relation_type[rel_type_key]["reverse"]}); |
|
45 |
}); |
|
46 |
} else { |
|
47 |
$.each(issue_arr, function(index_issue, element_issue) { |
|
48 |
arr.push({issue_from: issue_id, issue_to: element_issue, |
|
49 |
rel_type: rel_type_key}); |
|
50 |
}); |
|
51 |
} |
|
52 |
} |
|
53 |
} |
|
54 |
} |
|
55 |
}); |
|
56 |
return arr; |
|
57 |
} |
|
58 | ||
59 |
function drawRelations() { |
|
60 |
var arr = getRelationsArray(); |
|
61 |
$.each(arr, function(index_issue, element_issue) { |
|
62 |
if (!$("#draw_" + element_issue["rel_type"]).attr('checked')) { |
|
63 |
return; |
|
64 |
} |
|
65 |
var issue_from = $("#task-todo-issue-" + element_issue["issue_from"]); |
|
66 |
var issue_to = $("#task-todo-issue-" + element_issue["issue_to"]); |
|
67 |
var issue_height = issue_from.height(); |
|
68 |
var issue_from_top = issue_from.position().top + (issue_height / 2) - draw_top; |
|
69 |
var issue_from_right = issue_from.position().left + issue_from.width(); |
|
70 |
var issue_to_top = issue_to.position().top + (issue_height / 2) - draw_top; |
|
71 |
var issue_to_left = issue_to.position().left; |
|
72 |
var color = $("#gantt_draw_rel_color_" + element_issue["rel_type"]) |
|
73 |
.css("background-color"); |
|
74 |
var landscape_mergin = draw_relations[element_issue["rel_type"]]["landscape_mergin"]; |
|
75 |
var issue_from_right_rel = issue_from_right + landscape_mergin; |
|
76 |
var issue_to_left_rel = issue_to_left - landscape_mergin; |
|
77 |
var x1, y1, x2, y2; |
|
78 |
draw_gantt.path(["M", issue_from_right + draw_left, issue_from_top, |
|
79 |
"L", issue_from_right_rel + draw_left, issue_from_top]) |
|
80 |
.attr({stroke: color, "stroke-width": 2}); |
|
81 |
if (issue_from_right_rel < issue_to_left_rel) { |
|
82 |
draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top, |
|
83 |
"L", issue_from_right_rel + draw_left, issue_to_top]) |
|
84 |
.attr({stroke: color, "stroke-width": 2}); |
|
85 |
draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_to_top, |
|
86 |
"L", issue_to_left + draw_left, issue_to_top]) |
|
87 |
.attr({stroke: color, |
|
88 |
"stroke-width": 2, |
|
89 |
"stroke-linecap": "butt", |
|
90 |
"stroke-linejoin": "miter", |
|
91 |
}); |
|
92 |
} else { |
|
93 |
var issue_middle_top = issue_to_top + |
|
94 |
(issue_height * |
|
95 |
((issue_from_top > issue_to_top) ? 1 : -1 )); |
|
96 |
draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top, |
|
97 |
"L", issue_from_right_rel + draw_left, issue_middle_top]) |
|
98 |
.attr({stroke: color, "stroke-width": 2}); |
|
99 |
draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_middle_top, |
|
100 |
"L", issue_to_left_rel + draw_left, issue_middle_top]) |
|
101 |
.attr({stroke:color, "stroke-width": 2}); |
|
102 |
draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_middle_top, |
|
103 |
"L", issue_to_left_rel + draw_left, issue_to_top]) |
|
104 |
.attr({stroke: color, "stroke-width": 2}); |
|
105 |
draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_to_top, |
|
106 |
"L", issue_to_left + draw_left, issue_to_top]) |
|
107 |
.attr({stroke: color, |
|
108 |
"stroke-width": 2, |
|
109 |
"stroke-linecap": "butt", |
|
110 |
"stroke-linejoin": "miter", |
|
111 |
}); |
|
112 |
} |
|
113 |
if (!("no_arrow" in draw_relations[element_issue["rel_type"]])) { |
|
114 |
draw_gantt.path(["M", issue_to_left + draw_left, issue_to_top, |
|
115 |
"l", -8, -4, "l", 0, 8, "z"]) |
|
116 |
.attr({stroke: "none", |
|
117 |
fill: color, |
|
118 |
"stroke-linecap": "butt", |
|
119 |
"stroke-linejoin": "miter", |
|
120 |
}); |
|
121 |
} |
|
122 |
}); |
|
123 |
} |
|
124 | ||
12 | 125 |
function drawGanttHandler() { |
13 | 126 |
var folder = document.getElementById('gantt_draw_area'); |
14 | 127 |
if(draw_gantt != null) |
... | ... | |
16 | 129 |
else |
17 | 130 |
draw_gantt = Raphael(folder); |
18 | 131 |
setDrawArea(); |
132 |
drawRelations(); |
|
19 | 133 |
} |
public/stylesheets/application.css | ||
---|---|---|
868 | 868 |
a.close-icon:hover {background-image:url('../images/close_hl.png');} |
869 | 869 | |
870 | 870 |
/***** Gantt chart *****/ |
871 |
#gantt_draw_rel_color_relates {background-color:#00c551;} |
|
872 |
#gantt_draw_rel_color_duplicates {background-color:#4f52c3;} |
|
873 |
#gantt_draw_rel_color_blocks {background-color:#fb7d2f;} |
|
874 |
#gantt_draw_rel_color_precedes {background-color:#df347c;} |
|
875 |
#gantt_draw_rel_color_copied_to {background-color:#00c5c2;} |
|
876 | ||
871 | 877 |
.gantt_hdr { |
872 | 878 |
position:absolute; |
873 | 879 |
top:0; |