Mercurial > hg > soundsoftware-site
comparison lib/redmine/helpers/gantt.rb @ 1295:622f24f53b42 redmine-2.3
Update to Redmine SVN revision 11972 on 2.3-stable branch
author | Chris Cannam |
---|---|
date | Fri, 14 Jun 2013 09:02:21 +0100 |
parents | 433d4f72a19b |
children |
comparison
equal
deleted
inserted
replaced
1294:3e4c3460b6ca | 1295:622f24f53b42 |
---|---|
1 # Redmine - project management software | 1 # Redmine - project management software |
2 # Copyright (C) 2006-2012 Jean-Philippe Lang | 2 # Copyright (C) 2006-2013 Jean-Philippe Lang |
3 # | 3 # |
4 # This program is free software; you can redistribute it and/or | 4 # This program is free software; you can redistribute it and/or |
5 # modify it under the terms of the GNU General Public License | 5 # modify it under the terms of the GNU General Public License |
6 # as published by the Free Software Foundation; either version 2 | 6 # as published by the Free Software Foundation; either version 2 |
7 # of the License, or (at your option) any later version. | 7 # of the License, or (at your option) any later version. |
21 class Gantt | 21 class Gantt |
22 include ERB::Util | 22 include ERB::Util |
23 include Redmine::I18n | 23 include Redmine::I18n |
24 include Redmine::Utils::DateCalculation | 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 # :nodoc: | 32 # :nodoc: |
27 # Some utility methods for the PDF export | 33 # Some utility methods for the PDF export |
28 class PDF | 34 class PDF |
29 MaxCharactorsForSubject = 45 | 35 MaxCharactorsForSubject = 45 |
30 TotalWidth = 280 | 36 TotalWidth = 280 |
132 @issues ||= @query.issues( | 138 @issues ||= @query.issues( |
133 :include => [:assigned_to, :tracker, :priority, :category, :fixed_version], | 139 :include => [:assigned_to, :tracker, :priority, :category, :fixed_version], |
134 :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC", | 140 :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC", |
135 :limit => @max_rows | 141 :limit => @max_rows |
136 ) | 142 ) |
143 end | |
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 | |
137 end | 157 end |
138 | 158 |
139 # Return all the project nodes that will be displayed | 159 # Return all the project nodes that will be displayed |
140 def projects | 160 def projects |
141 return @projects if @projects | 161 return @projects if @projects |
275 image_task(options, coords, :label => label, :markers => true, :height => 3) | 295 image_task(options, coords, :label => label, :markers => true, :height => 3) |
276 when :pdf | 296 when :pdf |
277 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8) | 297 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8) |
278 end | 298 end |
279 else | 299 else |
280 ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date" | |
281 '' | 300 '' |
282 end | 301 end |
283 end | 302 end |
284 | 303 |
285 def subject_for_version(version, options) | 304 def subject_for_version(version, options) |
287 when :html | 306 when :html |
288 html_class = "" | 307 html_class = "" |
289 html_class << 'icon icon-package ' | 308 html_class << 'icon icon-package ' |
290 html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " " | 309 html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " " |
291 html_class << (version.overdue? ? 'version-overdue' : '') | 310 html_class << (version.overdue? ? 'version-overdue' : '') |
311 html_class << ' version-closed' unless version.open? | |
312 if version.start_date && version.due_date && version.completed_pourcent | |
313 progress_date = calc_progress_date(version.start_date, | |
314 version.due_date, version.completed_pourcent) | |
315 html_class << ' behind-start-date' if progress_date < self.date_from | |
316 html_class << ' over-end-date' if progress_date > self.date_to | |
317 end | |
292 s = view.link_to_version(version).html_safe | 318 s = view.link_to_version(version).html_safe |
293 subject = view.content_tag(:span, s, | 319 subject = view.content_tag(:span, s, |
294 :class => html_class).html_safe | 320 :class => html_class).html_safe |
295 html_subject(options, subject, :css => "version-name") | 321 html_subject(options, subject, :css => "version-name", |
322 :id => "version-#{version.id}") | |
296 when :image | 323 when :image |
297 image_subject(options, version.to_s_with_project) | 324 image_subject(options, version.to_s_with_project) |
298 when :pdf | 325 when :pdf |
299 pdf_new_page?(options) | 326 pdf_new_page?(options) |
300 pdf_subject(options, version.to_s_with_project) | 327 pdf_subject(options, version.to_s_with_project) |
301 end | 328 end |
302 end | 329 end |
303 | 330 |
304 def line_for_version(version, options) | 331 def line_for_version(version, options) |
305 # Skip versions that don't have a start_date | 332 # Skip versions that don't have a start_date |
306 if version.is_a?(Version) && version.start_date && version.due_date | 333 if version.is_a?(Version) && version.due_date && version.start_date |
307 options[:zoom] ||= 1 | 334 options[:zoom] ||= 1 |
308 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] | 335 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] |
309 coords = coordinates(version.start_date, | 336 coords = coordinates(version.start_date, |
310 version.due_date, version.completed_pourcent, | 337 version.due_date, version.completed_percent, |
311 options[:zoom]) | 338 options[:zoom]) |
312 label = "#{h version} #{h version.completed_pourcent.to_i.to_s}%" | 339 label = "#{h version} #{h version.completed_percent.to_i.to_s}%" |
313 label = h("#{version.project} -") + label unless @project && @project == version.project | 340 label = h("#{version.project} -") + label unless @project && @project == version.project |
314 case options[:format] | 341 case options[:format] |
315 when :html | 342 when :html |
316 html_task(options, coords, :css => "version task", :label => label, :markers => true) | 343 html_task(options, coords, :css => "version task", |
344 :label => label, :markers => true, :version => version) | |
317 when :image | 345 when :image |
318 image_task(options, coords, :label => label, :markers => true, :height => 3) | 346 image_task(options, coords, :label => label, :markers => true, :height => 3) |
319 when :pdf | 347 when :pdf |
320 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8) | 348 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8) |
321 end | 349 end |
322 else | 350 else |
323 ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date" | |
324 '' | 351 '' |
325 end | 352 end |
326 end | 353 end |
327 | 354 |
328 def subject_for_issue(issue, options) | 355 def subject_for_issue(issue, options) |
334 when :html | 361 when :html |
335 css_classes = '' | 362 css_classes = '' |
336 css_classes << ' issue-overdue' if issue.overdue? | 363 css_classes << ' issue-overdue' if issue.overdue? |
337 css_classes << ' issue-behind-schedule' if issue.behind_schedule? | 364 css_classes << ' issue-behind-schedule' if issue.behind_schedule? |
338 css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to | 365 css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to |
366 css_classes << ' issue-closed' if issue.closed? | |
367 if issue.start_date && issue.due_before && issue.done_ratio | |
368 progress_date = calc_progress_date(issue.start_date, | |
369 issue.due_before, issue.done_ratio) | |
370 css_classes << ' behind-start-date' if progress_date < self.date_from | |
371 css_classes << ' over-end-date' if progress_date > self.date_to | |
372 end | |
339 s = "".html_safe | 373 s = "".html_safe |
340 if issue.assigned_to.present? | 374 if issue.assigned_to.present? |
341 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name | 375 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name |
342 s << view.avatar(issue.assigned_to, | 376 s << view.avatar(issue.assigned_to, |
343 :class => 'gravatar icon-gravatar', | 377 :class => 'gravatar icon-gravatar', |
345 :title => assigned_string).to_s.html_safe | 379 :title => assigned_string).to_s.html_safe |
346 end | 380 end |
347 s << view.link_to_issue(issue).html_safe | 381 s << view.link_to_issue(issue).html_safe |
348 subject = view.content_tag(:span, s, :class => css_classes).html_safe | 382 subject = view.content_tag(:span, s, :class => css_classes).html_safe |
349 html_subject(options, subject, :css => "issue-subject", | 383 html_subject(options, subject, :css => "issue-subject", |
350 :title => issue.subject) + "\n" | 384 :title => issue.subject, :id => "issue-#{issue.id}") + "\n" |
351 when :image | 385 when :image |
352 image_subject(options, issue.subject) | 386 image_subject(options, issue.subject) |
353 when :pdf | 387 when :pdf |
354 pdf_new_page?(options) | 388 pdf_new_page?(options) |
355 pdf_subject(options, issue.subject) | 389 pdf_subject(options, issue.subject) |
376 image_task(options, coords, :label => label) | 410 image_task(options, coords, :label => label) |
377 when :pdf | 411 when :pdf |
378 pdf_task(options, coords, :label => label) | 412 pdf_task(options, coords, :label => label) |
379 end | 413 end |
380 else | 414 else |
381 ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before" | |
382 '' | 415 '' |
383 end | 416 end |
384 end | 417 end |
385 | 418 |
386 # Generates a gantt image | 419 # Generates a gantt image |
609 coords[:bar_end] = end_date - self.date_from + 1 | 642 coords[:bar_end] = end_date - self.date_from + 1 |
610 else | 643 else |
611 coords[:bar_end] = self.date_to - self.date_from + 1 | 644 coords[:bar_end] = self.date_to - self.date_from + 1 |
612 end | 645 end |
613 if progress | 646 if progress |
614 progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0) | 647 progress_date = calc_progress_date(start_date, end_date, progress) |
615 if progress_date > self.date_from && progress_date > start_date | 648 if progress_date > self.date_from && progress_date > start_date |
616 if progress_date < self.date_to | 649 if progress_date < self.date_to |
617 coords[:bar_progress_end] = progress_date - self.date_from | 650 coords[:bar_progress_end] = progress_date - self.date_from |
618 else | 651 else |
619 coords[:bar_progress_end] = self.date_to - self.date_from + 1 | 652 coords[:bar_progress_end] = self.date_to - self.date_from + 1 |
636 coords[key] = (coords[key] * zoom).floor | 669 coords[key] = (coords[key] * zoom).floor |
637 end | 670 end |
638 coords | 671 coords |
639 end | 672 end |
640 | 673 |
641 # Sorts a collection of issues by start_date, due_date, id for gantt rendering | 674 def calc_progress_date(start_date, end_date, progress) |
675 start_date + (end_date - start_date + 1) * (progress / 100.0) | |
676 end | |
677 | |
678 # TODO: Sorts a collection of issues by start_date, due_date, id for gantt rendering | |
642 def sort_issues!(issues) | 679 def sort_issues!(issues) |
643 issues.sort! { |a, b| gantt_issue_compare(a, b) } | 680 issues.sort! { |a, b| gantt_issue_compare(a, b) } |
644 end | 681 end |
645 | 682 |
646 # TODO: top level issues should be sorted by start date | 683 # TODO: top level issues should be sorted by start date |
676 end | 713 end |
677 | 714 |
678 def html_subject(params, subject, options={}) | 715 def html_subject(params, subject, options={}) |
679 style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;" | 716 style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;" |
680 style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width] | 717 style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width] |
681 output = view.content_tag('div', subject, | 718 output = view.content_tag(:div, subject, |
682 :class => options[:css], :style => style, | 719 :class => options[:css], :style => style, |
683 :title => options[:title]) | 720 :title => options[:title], |
721 :id => options[:id]) | |
684 @subjects << output | 722 @subjects << output |
685 output | 723 output |
686 end | 724 end |
687 | 725 |
688 def pdf_subject(params, subject, options={}) | 726 def pdf_subject(params, subject, options={}) |
703 params[:image].stroke('transparent') | 741 params[:image].stroke('transparent') |
704 params[:image].stroke_width(1) | 742 params[:image].stroke_width(1) |
705 params[:image].text(params[:indent], params[:top] + 2, subject) | 743 params[:image].text(params[:indent], params[:top] + 2, subject) |
706 end | 744 end |
707 | 745 |
746 def issue_relations(issue) | |
747 rels = {} | |
748 if relations[issue.id] | |
749 relations[issue.id].each do |relation| | |
750 (rels[relation.relation_type] ||= []) << relation.issue_to_id | |
751 end | |
752 end | |
753 rels | |
754 end | |
755 | |
708 def html_task(params, coords, options={}) | 756 def html_task(params, coords, options={}) |
709 output = '' | 757 output = '' |
710 # Renders the task bar, with progress and late | 758 # Renders the task bar, with progress and late |
711 if coords[:bar_start] && coords[:bar_end] | 759 if coords[:bar_start] && coords[:bar_end] |
712 width = coords[:bar_end] - coords[:bar_start] - 2 | 760 width = coords[:bar_end] - coords[:bar_start] - 2 |
713 style = "" | 761 style = "" |
714 style << "top:#{params[:top]}px;" | 762 style << "top:#{params[:top]}px;" |
715 style << "left:#{coords[:bar_start]}px;" | 763 style << "left:#{coords[:bar_start]}px;" |
716 style << "width:#{width}px;" | 764 style << "width:#{width}px;" |
717 output << view.content_tag(:div, ' '.html_safe, | 765 html_id = "task-todo-issue-#{options[:issue].id}" if options[:issue] |
718 :style => style, | 766 html_id = "task-todo-version-#{options[:version].id}" if options[:version] |
719 :class => "#{options[:css]} task_todo") | 767 content_opt = {:style => style, |
768 :class => "#{options[:css]} task_todo", | |
769 :id => html_id} | |
770 if options[:issue] | |
771 rels = issue_relations(options[:issue]) | |
772 if rels.present? | |
773 content_opt[:data] = {"rels" => rels.to_json} | |
774 end | |
775 end | |
776 output << view.content_tag(:div, ' '.html_safe, content_opt) | |
720 if coords[:bar_late_end] | 777 if coords[:bar_late_end] |
721 width = coords[:bar_late_end] - coords[:bar_start] - 2 | 778 width = coords[:bar_late_end] - coords[:bar_start] - 2 |
722 style = "" | 779 style = "" |
723 style << "top:#{params[:top]}px;" | 780 style << "top:#{params[:top]}px;" |
724 style << "left:#{coords[:bar_start]}px;" | 781 style << "left:#{coords[:bar_start]}px;" |
731 width = coords[:bar_progress_end] - coords[:bar_start] - 2 | 788 width = coords[:bar_progress_end] - coords[:bar_start] - 2 |
732 style = "" | 789 style = "" |
733 style << "top:#{params[:top]}px;" | 790 style << "top:#{params[:top]}px;" |
734 style << "left:#{coords[:bar_start]}px;" | 791 style << "left:#{coords[:bar_start]}px;" |
735 style << "width:#{width}px;" | 792 style << "width:#{width}px;" |
793 html_id = "task-done-issue-#{options[:issue].id}" if options[:issue] | |
794 html_id = "task-done-version-#{options[:version].id}" if options[:version] | |
736 output << view.content_tag(:div, ' '.html_safe, | 795 output << view.content_tag(:div, ' '.html_safe, |
737 :style => style, | 796 :style => style, |
738 :class => "#{options[:css]} task_done") | 797 :class => "#{options[:css]} task_done", |
798 :id => html_id) | |
739 end | 799 end |
740 end | 800 end |
741 # Renders the markers | 801 # Renders the markers |
742 if options[:markers] | 802 if options[:markers] |
743 if coords[:start] | 803 if coords[:start] |