Feature #12122 » gantt-progress-line-html-r10927.diff
lib/redmine/helpers/gantt.rb | ||
---|---|---|
289 | 289 |
html_class << 'icon icon-package ' |
290 | 290 |
html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " " |
291 | 291 |
html_class << (version.overdue? ? 'version-overdue' : '') |
292 |
html_class << ' version-closed' unless version.open? |
|
293 |
if version.start_date && version.due_date && version.completed_pourcent |
|
294 |
progress_date = calc_progress_date(version.start_date, |
|
295 |
version.due_date, version.completed_pourcent) |
|
296 |
html_class << ' behind-start-date' if progress_date < self.date_from |
|
297 |
html_class << ' over-end-date' if progress_date > self.date_to |
|
298 |
end |
|
292 | 299 |
s = view.link_to_version(version).html_safe |
293 | 300 |
subject = view.content_tag(:span, s, |
294 | 301 |
:class => html_class).html_safe |
295 |
html_subject(options, subject, :css => "version-name") |
|
302 |
html_subject(options, subject, :css => "version-name", |
|
303 |
:id => "version-#{version.id}") |
|
296 | 304 |
when :image |
297 | 305 |
image_subject(options, version.to_s_with_project) |
298 | 306 |
when :pdf |
... | ... | |
313 | 321 |
label = h("#{version.project} -") + label unless @project && @project == version.project |
314 | 322 |
case options[:format] |
315 | 323 |
when :html |
316 |
html_task(options, coords, :css => "version task", :label => label, :markers => true) |
|
324 |
html_task(options, coords, :css => "version task", |
|
325 |
:label => label, :markers => true, :version => version) |
|
317 | 326 |
when :image |
318 | 327 |
image_task(options, coords, :label => label, :markers => true, :height => 3) |
319 | 328 |
when :pdf |
... | ... | |
336 | 345 |
css_classes << ' issue-overdue' if issue.overdue? |
337 | 346 |
css_classes << ' issue-behind-schedule' if issue.behind_schedule? |
338 | 347 |
css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to |
348 |
css_classes << ' issue-closed' if issue.closed? |
|
349 |
if issue.start_date && issue.due_before && issue.done_ratio |
|
350 |
progress_date = calc_progress_date(issue.start_date, |
|
351 |
issue.due_before, issue.done_ratio) |
|
352 |
css_classes << ' behind-start-date' if progress_date < self.date_from |
|
353 |
css_classes << ' over-end-date' if progress_date > self.date_to |
|
354 |
end |
|
339 | 355 |
s = "".html_safe |
340 | 356 |
if issue.assigned_to.present? |
341 | 357 |
assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name |
... | ... | |
347 | 363 |
s << view.link_to_issue(issue).html_safe |
348 | 364 |
subject = view.content_tag(:span, s, :class => css_classes).html_safe |
349 | 365 |
html_subject(options, subject, :css => "issue-subject", |
350 |
:title => issue.subject) + "\n" |
|
366 |
:title => issue.subject, :id => "issue-#{issue.id}") + "\n"
|
|
351 | 367 |
when :image |
352 | 368 |
image_subject(options, issue.subject) |
353 | 369 |
when :pdf |
... | ... | |
611 | 627 |
coords[:bar_end] = self.date_to - self.date_from + 1 |
612 | 628 |
end |
613 | 629 |
if progress |
614 |
progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0)
|
|
630 |
progress_date = calc_progress_date(start_date, end_date, progress)
|
|
615 | 631 |
if progress_date > self.date_from && progress_date > start_date |
616 | 632 |
if progress_date < self.date_to |
617 | 633 |
coords[:bar_progress_end] = progress_date - self.date_from |
... | ... | |
638 | 654 |
coords |
639 | 655 |
end |
640 | 656 | |
657 |
def calc_progress_date(start_date, end_date, progress) |
|
658 |
start_date + (end_date - start_date + 1) * (progress / 100.0) |
|
659 |
end |
|
660 | ||
641 | 661 |
# Sorts a collection of issues by start_date, due_date, id for gantt rendering |
642 | 662 |
def sort_issues!(issues) |
643 | 663 |
issues.sort! { |a, b| gantt_issue_compare(a, b) } |
... | ... | |
678 | 698 |
def html_subject(params, subject, options={}) |
679 | 699 |
style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;" |
680 | 700 |
style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width] |
681 |
output = view.content_tag('div', subject,
|
|
701 |
output = view.content_tag(:div, subject,
|
|
682 | 702 |
:class => options[:css], :style => style, |
683 |
:title => options[:title]) |
|
703 |
:title => options[:title], |
|
704 |
:id => options[:id]) |
|
684 | 705 |
@subjects << output |
685 | 706 |
output |
686 | 707 |
end |
... | ... | |
714 | 735 |
style << "top:#{params[:top]}px;" |
715 | 736 |
style << "left:#{coords[:bar_start]}px;" |
716 | 737 |
style << "width:#{width}px;" |
738 |
html_id = "task-todo-issue-#{options[:issue].id}" if options[:issue] |
|
739 |
html_id = "task-todo-version-#{options[:version].id}" if options[:version] |
|
717 | 740 |
output << view.content_tag(:div, ' '.html_safe, |
718 | 741 |
:style => style, |
719 |
:class => "#{options[:css]} task_todo") |
|
742 |
:class => "#{options[:css]} task_todo", |
|
743 |
:id => html_id) |
|
720 | 744 |
if coords[:bar_late_end] |
721 | 745 |
width = coords[:bar_late_end] - coords[:bar_start] - 2 |
722 | 746 |
style = "" |
... | ... | |
733 | 757 |
style << "top:#{params[:top]}px;" |
734 | 758 |
style << "left:#{coords[:bar_start]}px;" |
735 | 759 |
style << "width:#{width}px;" |
760 |
html_id = "task-done-issue-#{options[:issue].id}" if options[:issue] |
|
761 |
html_id = "task-done-version-#{options[:version].id}" if options[:version] |
|
736 | 762 |
output << view.content_tag(:div, ' '.html_safe, |
737 | 763 |
:style => style, |
738 |
:class => "#{options[:css]} task_done") |
|
764 |
:class => "#{options[:css]} task_done", |
|
765 |
:id => html_id) |
|
739 | 766 |
end |
740 | 767 |
end |
741 | 768 |
# Renders the markers |
app/views/gantts/show.html.erb | ||
---|---|---|
12 | 12 |
<%= render :partial => 'queries/filters', :locals => {:query => @query} %> |
13 | 13 |
</div> |
14 | 14 |
</fieldset> |
15 |
<fieldset id="filters" class="collapsible"> |
|
16 |
<legend onclick="toggleFieldset(this);"><%= l(:label_display) %></legend> |
|
17 |
<div> |
|
18 |
<label> |
|
19 |
<%= check_box_tag "draw_progress_line", 0, params[:draw_progress_line] %> |
|
20 |
<%= l(:label_gantt_progress_line) %> |
|
21 |
</label> |
|
22 |
</div> |
|
23 |
</fieldset> |
|
15 | 24 | |
16 | 25 |
<p class="contextual"> |
17 | 26 |
<%= gantt_zoom_link(@gantt, :in) %> |
config/locales/en.yml | ||
---|---|---|
889 | 889 |
label_cross_project_tree: With project tree |
890 | 890 |
label_cross_project_hierarchy: With project hierarchy |
891 | 891 |
label_cross_project_system: With all projects |
892 |
label_gantt_progress_line: Progress line |
|
892 | 893 | |
893 | 894 |
button_login: Login |
894 | 895 |
button_submit: Submit |
config/locales/ja.yml | ||
---|---|---|
840 | 840 |
label_git_report_last_commit: ファイルとディレクトリの最新コミットを表示する |
841 | 841 |
label_parent_revision: 親 |
842 | 842 |
label_child_revision: 子 |
843 |
label_gantt_progress_line: イナズマ線 |
|
843 | 844 | |
844 | 845 |
button_login: ログイン |
845 | 846 |
button_submit: 送信 |
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 |
$(function() { |
|
9 |
$("#draw_progress_line").change(drawGanttHandler); |
|
10 |
}); |
|
11 |
<% end %> |
|
1 | 12 |
<% @gantt.view = self %> |
2 | 13 |
<h2><%= @query.new_record? ? l(:label_gantt) : h(@query.name) %></h2> |
3 | 14 | |
... | ... | |
111 | 122 |
</td> |
112 | 123 | |
113 | 124 |
<td> |
114 |
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;"> |
|
125 |
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;" id="gantt_area">
|
|
115 | 126 |
<% |
116 | 127 |
style = "" |
117 | 128 |
style += "width: #{g_width - 1}px;" |
... | ... | |
238 | 249 |
style += "width:10px;" |
239 | 250 |
style += "border-left: 1px dashed red;" |
240 | 251 |
%> |
241 |
<%= content_tag(:div, ' '.html_safe, :style => style) %> |
|
252 |
<%= content_tag(:div, ' '.html_safe, :style => style, :id => 'today_line') %>
|
|
242 | 253 |
<% end %> |
243 | ||
254 |
<% |
|
255 |
style = "" |
|
256 |
style += "position: absolute;" |
|
257 |
style += "height: #{g_height}px;" |
|
258 |
style += "top: #{headers_height + 1}px;" |
|
259 |
style += "left: 0px;" |
|
260 |
style += "width: #{g_width - 1}px;" |
|
261 |
%> |
|
262 |
<%= content_tag(:div, '', :style => style, :id => "gantt_draw_area") %> |
|
244 | 263 |
</div> |
245 | 264 |
</td> |
246 | 265 |
</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 getProgressLinesArray() { |
|
13 |
var arr = new Array(); |
|
14 |
var today_left = $('#today_line').position().left; |
|
15 |
arr.push({left: today_left, top: 0}); |
|
16 |
$.each($('div.issue-subject, div.version-name'), function(index, element) { |
|
17 |
var t = $(element).position().top - draw_top ; |
|
18 |
var h = ($(element).height() / 9); |
|
19 |
var element_top_upper = t - h; |
|
20 |
var element_top_center = t + (h * 3); |
|
21 |
var element_top_lower = t + (h * 8); |
|
22 |
var issue_closed = $(element).children('span').hasClass('issue-closed'); |
|
23 |
var version_closed = $(element).children('span').hasClass('version-closed'); |
|
24 |
if (issue_closed || version_closed) { |
|
25 |
arr.push({left: today_left, top: element_top_center}); |
|
26 |
} else { |
|
27 |
var issue_done = $("#task-done-" + $(element).attr("id")); |
|
28 |
var is_behind_start = $(element).children('span').hasClass('behind-start-date'); |
|
29 |
var is_over_end = $(element).children('span').hasClass('over-end-date'); |
|
30 |
if (is_over_end) { |
|
31 |
arr.push({left: draw_right, top: element_top_upper, is_right_edge: true}); |
|
32 |
arr.push({left: draw_right, top: element_top_lower, is_right_edge: true, none_stroke: true}); |
|
33 |
} else if (issue_done.size() > 0) { |
|
34 |
var done_left = issue_done.first().position().left + |
|
35 |
issue_done.first().width(); |
|
36 |
arr.push({left: done_left, top: element_top_center}); |
|
37 |
} else if (is_behind_start) { |
|
38 |
arr.push({left: 0 , top: element_top_upper, is_left_edge: true}); |
|
39 |
arr.push({left: 0 , top: element_top_lower, is_left_edge: true, none_stroke: true}); |
|
40 |
} else { |
|
41 |
var todo_left = today_left; |
|
42 |
var issue_todo = $("#task-todo-" + $(element).attr("id")); |
|
43 |
if (issue_todo.size() > 0){ |
|
44 |
todo_left = issue_todo.first().position().left; |
|
45 |
} |
|
46 |
arr.push({left: Math.min(today_left, todo_left), top: element_top_center}); |
|
47 |
} |
|
48 |
} |
|
49 |
}); |
|
50 |
return arr; |
|
51 |
} |
|
52 | ||
53 |
function drawGanttProgressLines() { |
|
54 |
var arr = getProgressLinesArray(); |
|
55 |
var i; |
|
56 |
for(i = 1 ; i < arr.length ; i++) { |
|
57 |
if (!("none_stroke" in arr[i]) && |
|
58 |
(!("is_right_edge" in arr[i - 1] && "is_right_edge" in arr[i]) && |
|
59 |
!("is_left_edge" in arr[i - 1] && "is_left_edge" in arr[i])) |
|
60 |
) { |
|
61 |
var x1 = (arr[i - 1].left == 0) ? 0 : arr[i - 1].left + draw_left; |
|
62 |
var x2 = (arr[i].left == 0) ? 0 : arr[i].left + draw_left; |
|
63 |
draw_gantt.path(["M", x1, arr[i - 1].top, |
|
64 |
"L", x2, arr[i].top]) |
|
65 |
.attr({stroke: "#ff0000", "stroke-width": 2}); |
|
66 |
} |
|
67 |
} |
|
68 |
} |
|
69 | ||
70 |
function drawGanttHandler() { |
|
71 |
var folder = document.getElementById('gantt_draw_area'); |
|
72 |
if(draw_gantt != null) |
|
73 |
draw_gantt.clear(); |
|
74 |
else |
|
75 |
draw_gantt = Raphael(folder); |
|
76 |
setDrawArea(); |
|
77 |
if ($("#draw_progress_line").attr('checked')) |
|
78 |
drawGanttProgressLines(); |
|
79 |
} |