Feature #3436 » gantt-relations-r11060.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/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 => 12 }, |
|
| 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 => 16 }, |
|
| 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, ' '.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, ' '.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, ' '.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 | ||
|---|---|---|
| 9 | 9 |
draw_left = $("#gantt_area").scrollLeft();
|
| 10 | 10 |
} |
| 11 | 11 | |
| 12 |
function getRelationsArray() {
|
|
| 13 |
var arr = new Array(); |
|
| 14 |
$.each($('div.task_todo'), function(index_div, element) {
|
|
| 15 |
var element_id = $(element).attr("id");
|
|
| 16 |
if (element_id != null) {
|
|
| 17 |
var issue_id = element_id.replace("task-todo-issue-", "");
|
|
| 18 |
var data_rels = $(element).data("rels");
|
|
| 19 |
if (data_rels != null) {
|
|
| 20 |
for (rel_type_key in issue_relation_type) {
|
|
| 21 |
if (rel_type_key in data_rels) {
|
|
| 22 |
var issue_arr = data_rels[rel_type_key].toString().split(",");
|
|
| 23 |
if ("reverse" in issue_relation_type[rel_type_key]) {
|
|
| 24 |
$.each(issue_arr, function(index_issue, element_issue) {
|
|
| 25 |
arr.push({issue_from: element_issue, issue_to: issue_id,
|
|
| 26 |
rel_type: issue_relation_type[rel_type_key]["reverse"]}); |
|
| 27 |
}); |
|
| 28 |
} else {
|
|
| 29 |
$.each(issue_arr, function(index_issue, element_issue) {
|
|
| 30 |
arr.push({issue_from: issue_id, issue_to: element_issue,
|
|
| 31 |
rel_type: rel_type_key}); |
|
| 32 |
}); |
|
| 33 |
} |
|
| 34 |
} |
|
| 35 |
} |
|
| 36 |
} |
|
| 37 |
} |
|
| 38 |
}); |
|
| 39 |
return arr; |
|
| 40 |
} |
|
| 41 | ||
| 42 |
function drawRelations() {
|
|
| 43 |
var arr = getRelationsArray(); |
|
| 44 |
$.each(arr, function(index_issue, element_issue) {
|
|
| 45 |
var issue_from = $("#task-todo-issue-" + element_issue["issue_from"]);
|
|
| 46 |
var issue_to = $("#task-todo-issue-" + element_issue["issue_to"]);
|
|
| 47 |
if (issue_from.size() == 0 || issue_to.size() == 0) {
|
|
| 48 |
return; |
|
| 49 |
} |
|
| 50 |
var issue_height = issue_from.height(); |
|
| 51 |
var issue_from_top = issue_from.position().top + (issue_height / 2) - draw_top; |
|
| 52 |
var issue_from_right = issue_from.position().left + issue_from.width(); |
|
| 53 |
var issue_to_top = issue_to.position().top + (issue_height / 2) - draw_top; |
|
| 54 |
var issue_to_left = issue_to.position().left; |
|
| 55 |
var color = $("#gantt_draw_rel_color_" + element_issue["rel_type"])
|
|
| 56 |
.css("background-color");
|
|
| 57 |
var landscape_margin = issue_relation_type[element_issue["rel_type"]]["landscape_margin"]; |
|
| 58 |
var issue_from_right_rel = issue_from_right + landscape_margin; |
|
| 59 |
var issue_to_left_rel = issue_to_left - landscape_margin; |
|
| 60 |
draw_gantt.path(["M", issue_from_right + draw_left, issue_from_top, |
|
| 61 |
"L", issue_from_right_rel + draw_left, issue_from_top]) |
|
| 62 |
.attr({stroke: color, "stroke-width": 2});
|
|
| 63 |
if (issue_from_right_rel < issue_to_left_rel) {
|
|
| 64 |
draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top, |
|
| 65 |
"L", issue_from_right_rel + draw_left, issue_to_top]) |
|
| 66 |
.attr({stroke: color, "stroke-width": 2});
|
|
| 67 |
draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_to_top, |
|
| 68 |
"L", issue_to_left + draw_left, issue_to_top]) |
|
| 69 |
.attr({stroke: color,
|
|
| 70 |
"stroke-width": 2, |
|
| 71 |
"stroke-linecap": "butt", |
|
| 72 |
"stroke-linejoin": "miter", |
|
| 73 |
}); |
|
| 74 |
} else {
|
|
| 75 |
var issue_middle_top = issue_to_top + |
|
| 76 |
(issue_height * |
|
| 77 |
((issue_from_top > issue_to_top) ? 1 : -1)); |
|
| 78 |
draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top, |
|
| 79 |
"L", issue_from_right_rel + draw_left, issue_middle_top]) |
|
| 80 |
.attr({stroke: color, "stroke-width": 2});
|
|
| 81 |
draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_middle_top, |
|
| 82 |
"L", issue_to_left_rel + draw_left, issue_middle_top]) |
|
| 83 |
.attr({stroke:color, "stroke-width": 2});
|
|
| 84 |
draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_middle_top, |
|
| 85 |
"L", issue_to_left_rel + draw_left, issue_to_top]) |
|
| 86 |
.attr({stroke: color, "stroke-width": 2});
|
|
| 87 |
draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_to_top, |
|
| 88 |
"L", issue_to_left + draw_left, issue_to_top]) |
|
| 89 |
.attr({stroke: color,
|
|
| 90 |
"stroke-width": 2, |
|
| 91 |
"stroke-linecap": "butt", |
|
| 92 |
"stroke-linejoin": "miter", |
|
| 93 |
}); |
|
| 94 |
} |
|
| 95 |
draw_gantt.path(["M", issue_to_left + draw_left, issue_to_top, |
|
| 96 |
"l", -8, -4, "l", 0, 8, "z"]) |
|
| 97 |
.attr({stroke: "none",
|
|
| 98 |
fill: color, |
|
| 99 |
"stroke-linecap": "butt", |
|
| 100 |
"stroke-linejoin": "miter", |
|
| 101 |
}); |
|
| 102 |
}); |
|
| 103 |
} |
|
| 104 | ||
| 12 | 105 |
function drawGanttHandler() {
|
| 13 | 106 |
var folder = document.getElementById('gantt_draw_area');
|
| 14 | 107 |
if(draw_gantt != null) |
| ... | ... | |
| 16 | 109 |
else |
| 17 | 110 |
draw_gantt = Raphael(folder); |
| 18 | 111 |
setDrawArea(); |
| 112 |
if ($("#draw_rels").attr('checked'))
|
|
| 113 |
drawRelations(); |
|
| 19 | 114 |
} |
| 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; |