@@ -0,0 +1,114 | |||
|
1 | var draw_gantt = null; | |
|
2 | var draw_top; | |
|
3 | var draw_right; | |
|
4 | var draw_left; | |
|
5 | ||
|
6 | var rels_stroke_width = 2; | |
|
7 | ||
|
8 | function setDrawArea() { | |
|
9 | draw_top = $("#gantt_draw_area").position().top; | |
|
10 | draw_right = $("#gantt_draw_area").width(); | |
|
11 | draw_left = $("#gantt_area").scrollLeft(); | |
|
12 | } | |
|
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 | $.each(issue_arr, function(index_issue, element_issue) { | |
|
26 | arr.push({issue_from: issue_id, issue_to: element_issue, | |
|
27 | rel_type: rel_type_key}); | |
|
28 | }); | |
|
29 | } | |
|
30 | } | |
|
31 | } | |
|
32 | } | |
|
33 | }); | |
|
34 | return arr; | |
|
35 | } | |
|
36 | ||
|
37 | function drawRelations() { | |
|
38 | var arr = getRelationsArray(); | |
|
39 | $.each(arr, function(index_issue, element_issue) { | |
|
40 | var issue_from = $("#task-todo-issue-" + element_issue["issue_from"]); | |
|
41 | var issue_to = $("#task-todo-issue-" + element_issue["issue_to"]); | |
|
42 | if (issue_from.size() == 0 || issue_to.size() == 0) { | |
|
43 | return; | |
|
44 | } | |
|
45 | var issue_height = issue_from.height(); | |
|
46 | var issue_from_top = issue_from.position().top + (issue_height / 2) - draw_top; | |
|
47 | var issue_from_right = issue_from.position().left + issue_from.width(); | |
|
48 | var issue_to_top = issue_to.position().top + (issue_height / 2) - draw_top; | |
|
49 | var issue_to_left = issue_to.position().left; | |
|
50 | var color = issue_relation_type[element_issue["rel_type"]]["color"]; | |
|
51 | var landscape_margin = issue_relation_type[element_issue["rel_type"]]["landscape_margin"]; | |
|
52 | var issue_from_right_rel = issue_from_right + landscape_margin; | |
|
53 | var issue_to_left_rel = issue_to_left - landscape_margin; | |
|
54 | draw_gantt.path(["M", issue_from_right + draw_left, issue_from_top, | |
|
55 | "L", issue_from_right_rel + draw_left, issue_from_top]) | |
|
56 | .attr({stroke: color, | |
|
57 | "stroke-width": rels_stroke_width | |
|
58 | }); | |
|
59 | if (issue_from_right_rel < issue_to_left_rel) { | |
|
60 | draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top, | |
|
61 | "L", issue_from_right_rel + draw_left, issue_to_top]) | |
|
62 | .attr({stroke: color, | |
|
63 | "stroke-width": rels_stroke_width | |
|
64 | }); | |
|
65 | draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_to_top, | |
|
66 | "L", issue_to_left + draw_left, issue_to_top]) | |
|
67 | .attr({stroke: color, | |
|
68 | "stroke-width": rels_stroke_width | |
|
69 | }); | |
|
70 | } else { | |
|
71 | var issue_middle_top = issue_to_top + | |
|
72 | (issue_height * | |
|
73 | ((issue_from_top > issue_to_top) ? 1 : -1)); | |
|
74 | draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top, | |
|
75 | "L", issue_from_right_rel + draw_left, issue_middle_top]) | |
|
76 | .attr({stroke: color, | |
|
77 | "stroke-width": rels_stroke_width | |
|
78 | }); | |
|
79 | draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_middle_top, | |
|
80 | "L", issue_to_left_rel + draw_left, issue_middle_top]) | |
|
81 | .attr({stroke: color, | |
|
82 | "stroke-width": rels_stroke_width | |
|
83 | }); | |
|
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, | |
|
87 | "stroke-width": rels_stroke_width | |
|
88 | }); | |
|
89 | draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_to_top, | |
|
90 | "L", issue_to_left + draw_left, issue_to_top]) | |
|
91 | .attr({stroke: color, | |
|
92 | "stroke-width": rels_stroke_width | |
|
93 | }); | |
|
94 | } | |
|
95 | draw_gantt.path(["M", issue_to_left + draw_left, issue_to_top, | |
|
96 | "l", -4 * rels_stroke_width, -2 * rels_stroke_width, | |
|
97 | "l", 0, 4 * rels_stroke_width, "z"]) | |
|
98 | .attr({stroke: "none", | |
|
99 | fill: color, | |
|
100 | "stroke-linecap": "butt", | |
|
101 | "stroke-linejoin": "miter", | |
|
102 | }); | |
|
103 | }); | |
|
104 | } | |
|
105 | ||
|
106 | function drawGanttHandler() { | |
|
107 | var folder = document.getElementById('gantt_draw_area'); | |
|
108 | if(draw_gantt != null) | |
|
109 | draw_gantt.clear(); | |
|
110 | else | |
|
111 | draw_gantt = Raphael(folder); | |
|
112 | setDrawArea(); | |
|
113 | drawRelations(); | |
|
114 | } |
@@ -102,7 +102,7 | |||
|
102 | 102 | </td> |
|
103 | 103 | |
|
104 | 104 | <td> |
|
105 | <div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;"> | |
|
105 | <div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;" id="gantt_area"> | |
|
106 | 106 | <% |
|
107 | 107 | style = "" |
|
108 | 108 | style += "width: #{g_width - 1}px;" |
@@ -231,7 +231,15 | |||
|
231 | 231 | %> |
|
232 | 232 | <%= content_tag(:div, ' '.html_safe, :style => style) %> |
|
233 | 233 | <% end %> |
|
234 | ||
|
234 | <% | |
|
235 | style = "" | |
|
236 | style += "position: absolute;" | |
|
237 | style += "height: #{g_height}px;" | |
|
238 | style += "top: #{headers_height + 1}px;" | |
|
239 | style += "left: 0px;" | |
|
240 | style += "width: #{g_width - 1}px;" | |
|
241 | %> | |
|
242 | <%= content_tag(:div, '', :style => style, :id => "gantt_draw_area") %> | |
|
235 | 243 | </div> |
|
236 | 244 | </td> |
|
237 | 245 | </tr> |
@@ -261,3 +269,14 | |||
|
261 | 269 | <% end %> |
|
262 | 270 | |
|
263 | 271 | <% html_title(l(:label_gantt)) -%> |
|
272 | ||
|
273 | <% content_for :header_tags do %> | |
|
274 | <%= javascript_include_tag 'raphael' %> | |
|
275 | <%= javascript_include_tag 'gantt' %> | |
|
276 | <% end %> | |
|
277 | ||
|
278 | <%= javascript_tag do %> | |
|
279 | var issue_relation_type = <%= raw Redmine::Helpers::Gantt::DRAW_TYPES.to_json %>; | |
|
280 | $(document).ready(drawGanttHandler); | |
|
281 | $(window).resize(drawGanttHandler); | |
|
282 | <% end %> |
@@ -23,6 +23,12 module Redmine | |||
|
23 | 23 | include Redmine::I18n |
|
24 | 24 | include Redmine::Utils::DateCalculation |
|
25 | 25 | |
|
26 | # Relation types that are rendered | |
|
27 | DRAW_TYPES = { | |
|
28 | IssueRelation::TYPE_BLOCKS => { :landscape_margin => 16, :color => '#F34F4F' }, | |
|
29 | IssueRelation::TYPE_PRECEDES => { :landscape_margin => 20, :color => '#628FEA' } | |
|
30 | }.freeze | |
|
31 | ||
|
26 | 32 | # :nodoc: |
|
27 | 33 | # Some utility methods for the PDF export |
|
28 | 34 | class PDF |
@@ -136,6 +142,20 module Redmine | |||
|
136 | 142 | ) |
|
137 | 143 | end |
|
138 | 144 | |
|
145 | # Returns a hash of the relations between the issues that are present on the gantt | |
|
146 | # and that should be displayed, grouped by issue ids. | |
|
147 | def relations | |
|
148 | return @relations if @relations | |
|
149 | if issues.any? | |
|
150 | issue_ids = issues.map(&:id) | |
|
151 | @relations = IssueRelation. | |
|
152 | where(:issue_from_id => issue_ids, :issue_to_id => issue_ids, :relation_type => DRAW_TYPES.keys). | |
|
153 | group_by(&:issue_from_id) | |
|
154 | else | |
|
155 | @relations = {} | |
|
156 | end | |
|
157 | end | |
|
158 | ||
|
139 | 159 | # Return all the project nodes that will be displayed |
|
140 | 160 | def projects |
|
141 | 161 | return @projects if @projects |
@@ -705,6 +725,16 module Redmine | |||
|
705 | 725 | params[:image].text(params[:indent], params[:top] + 2, subject) |
|
706 | 726 | end |
|
707 | 727 | |
|
728 | def issue_relations(issue) | |
|
729 | rels = {} | |
|
730 | if relations[issue.id] | |
|
731 | relations[issue.id].each do |relation| | |
|
732 | (rels[relation.relation_type] ||= []) << relation.issue_to_id | |
|
733 | end | |
|
734 | end | |
|
735 | rels | |
|
736 | end | |
|
737 | ||
|
708 | 738 | def html_task(params, coords, options={}) |
|
709 | 739 | output = '' |
|
710 | 740 | # Renders the task bar, with progress and late |
@@ -714,9 +744,18 module Redmine | |||
|
714 | 744 | style << "top:#{params[:top]}px;" |
|
715 | 745 | style << "left:#{coords[:bar_start]}px;" |
|
716 | 746 | style << "width:#{width}px;" |
|
717 | output << view.content_tag(:div, ' '.html_safe, | |
|
718 | :style => style, | |
|
719 |
|
|
|
747 | html_id = "task-todo-issue-#{options[:issue].id}" if options[:issue] | |
|
748 | content_opt = {:style => style, | |
|
749 | :class => "#{options[:css]} task_todo", | |
|
750 | :id => html_id} | |
|
751 | if options[:issue] | |
|
752 | rels_hash = {} | |
|
753 | issue_relations(options[:issue]).each do |k, v| | |
|
754 | rels_hash[k] = v.join(',') | |
|
755 | end | |
|
756 | content_opt[:data] = {"rels" => rels_hash} | |
|
757 | end | |
|
758 | output << view.content_tag(:div, ' '.html_safe, content_opt) | |
|
720 | 759 | if coords[:bar_late_end] |
|
721 | 760 | width = coords[:bar_late_end] - coords[:bar_start] - 2 |
|
722 | 761 | style = "" |
@@ -81,6 +81,22 class GanttsControllerTest < ActionController::TestCase | |||
|
81 | 81 | assert_no_tag 'a', :content => /Private child of eCookbook/ |
|
82 | 82 | end |
|
83 | 83 | |
|
84 | def test_gantt_should_display_relations | |
|
85 | IssueRelation.delete_all | |
|
86 | issue1 = Issue.generate!(:start_date => 1.day.from_now, :due_date => 3.day.from_now) | |
|
87 | issue2 = Issue.generate!(:start_date => 1.day.from_now, :due_date => 3.day.from_now) | |
|
88 | IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => 'precedes') | |
|
89 | ||
|
90 | get :show | |
|
91 | assert_response :success | |
|
92 | ||
|
93 | relations = assigns(:gantt).relations | |
|
94 | assert_kind_of Hash, relations | |
|
95 | assert relations.present? | |
|
96 | assert_select 'div.task_todo[id=?][data-rels*=?]', "task-todo-issue-#{issue1.id}", issue2.id.to_s | |
|
97 | assert_select 'div.task_todo[id=?][data-rels=?]', "task-todo-issue-#{issue2.id}", '{}' | |
|
98 | end | |
|
99 | ||
|
84 | 100 | def test_gantt_should_export_to_pdf |
|
85 | 101 | get :show, :project_id => 1, :format => 'pdf' |
|
86 | 102 | assert_response :success |
General Comments 0
You need to be logged in to leave comments.
Login now