335 |
335 |
end
|
336 |
336 |
|
337 |
337 |
# Generates a gantt image
|
338 |
|
# Only defined if RMagick is avalaible
|
|
338 |
# Only defined if MiniMagick is avalaible
|
339 |
339 |
def to_image(format='PNG')
|
340 |
340 |
date_to = (@date_from >> @months) - 1
|
341 |
341 |
show_weeks = @zoom > 1
|
... | ... | |
348 |
348 |
g_height = 20 * number_of_rows + 30
|
349 |
349 |
headers_height = (show_weeks ? 2 * header_height : header_height)
|
350 |
350 |
height = g_height + headers_height
|
351 |
|
imgl = Magick::ImageList.new
|
352 |
|
imgl.new_image(subject_width + g_width + 1, height)
|
353 |
|
gc = Magick::Draw.new
|
354 |
|
gc.font = Redmine::Configuration['rmagick_font_path'] || ""
|
355 |
|
# Subjects
|
356 |
|
gc.stroke('transparent')
|
357 |
|
subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image)
|
358 |
|
# Months headers
|
359 |
|
month_f = @date_from
|
360 |
|
left = subject_width
|
361 |
|
@months.times do
|
362 |
|
width = ((month_f >> 1) - month_f) * zoom
|
363 |
|
gc.fill('white')
|
364 |
|
gc.stroke('grey')
|
365 |
|
gc.stroke_width(1)
|
366 |
|
gc.rectangle(left, 0, left + width, height)
|
367 |
|
gc.fill('black')
|
|
351 |
Rails.logger.warn('rmagick_font_path option is deprecated. use minimagick_font_path instead.') \
|
|
352 |
unless Redmine::Configuration['rmagick_font_path'].nil?
|
|
353 |
font_path = Redmine::Configuration['minimagick_font_path'].presence || Redmine::Configuration['rmagick_font_path'].presence
|
|
354 |
img = MiniMagick::Image.create(".#{format}", false)
|
|
355 |
MiniMagick::Tool::Convert.new do |gc|
|
|
356 |
gc.size('%dx%d' % [subject_width + g_width + 1, height])
|
|
357 |
gc.xc('white')
|
|
358 |
gc.font(font_path) if font_path.present?
|
|
359 |
# Subjects
|
368 |
360 |
gc.stroke('transparent')
|
369 |
|
gc.stroke_width(1)
|
370 |
|
gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
|
371 |
|
left = left + width
|
372 |
|
month_f = month_f >> 1
|
373 |
|
end
|
374 |
|
# Weeks headers
|
375 |
|
if show_weeks
|
|
361 |
subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image)
|
|
362 |
# Months headers
|
|
363 |
month_f = @date_from
|
376 |
364 |
left = subject_width
|
377 |
|
height = header_height
|
378 |
|
if @date_from.cwday == 1
|
379 |
|
# date_from is monday
|
380 |
|
week_f = date_from
|
381 |
|
else
|
382 |
|
# find next monday after date_from
|
383 |
|
week_f = @date_from + (7 - @date_from.cwday + 1)
|
384 |
|
width = (7 - @date_from.cwday + 1) * zoom
|
385 |
|
gc.fill('white')
|
386 |
|
gc.stroke('grey')
|
387 |
|
gc.stroke_width(1)
|
388 |
|
gc.rectangle(left, header_height, left + width, 2 * header_height + g_height - 1)
|
389 |
|
left = left + width
|
390 |
|
end
|
391 |
|
while week_f <= date_to
|
392 |
|
width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
|
|
365 |
@months.times do
|
|
366 |
width = ((month_f >> 1) - month_f) * zoom
|
393 |
367 |
gc.fill('white')
|
394 |
368 |
gc.stroke('grey')
|
395 |
|
gc.stroke_width(1)
|
396 |
|
gc.rectangle(left.round, header_height, left.round + width, 2 * header_height + g_height - 1)
|
|
369 |
gc.strokewidth(1)
|
|
370 |
gc.draw('rectangle %d,%d %d,%d' % [
|
|
371 |
left, 0, left + width, height
|
|
372 |
])
|
397 |
373 |
gc.fill('black')
|
398 |
374 |
gc.stroke('transparent')
|
399 |
|
gc.stroke_width(1)
|
400 |
|
gc.text(left.round + 2, header_height + 14, week_f.cweek.to_s)
|
|
375 |
gc.strokewidth(1)
|
|
376 |
gc.draw('text %d,%d %s' % [
|
|
377 |
left.round + 8, 14, Redmine::Utils::Shell::shell_quote("#{month_f.year}-#{month_f.month}")
|
|
378 |
])
|
401 |
379 |
left = left + width
|
402 |
|
week_f = week_f + 7
|
|
380 |
month_f = month_f >> 1
|
|
381 |
end
|
|
382 |
# Weeks headers
|
|
383 |
if show_weeks
|
|
384 |
left = subject_width
|
|
385 |
height = header_height
|
|
386 |
if @date_from.cwday == 1
|
|
387 |
# date_from is monday
|
|
388 |
week_f = date_from
|
|
389 |
else
|
|
390 |
# find next monday after date_from
|
|
391 |
week_f = @date_from + (7 - @date_from.cwday + 1)
|
|
392 |
width = (7 - @date_from.cwday + 1) * zoom
|
|
393 |
gc.fill('white')
|
|
394 |
gc.stroke('grey')
|
|
395 |
gc.strokewidth(1)
|
|
396 |
gc.draw('rectangle %d,%d %d,%d' % [
|
|
397 |
left, header_height, left + width, 2 * header_height + g_height - 1
|
|
398 |
])
|
|
399 |
left = left + width
|
|
400 |
end
|
|
401 |
while week_f <= date_to
|
|
402 |
width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
|
|
403 |
gc.fill('white')
|
|
404 |
gc.stroke('grey')
|
|
405 |
gc.strokewidth(1)
|
|
406 |
gc.draw('rectangle %d,%d %d,%d' % [
|
|
407 |
left.round, header_height, left.round + width, 2 * header_height + g_height - 1
|
|
408 |
])
|
|
409 |
gc.fill('black')
|
|
410 |
gc.stroke('transparent')
|
|
411 |
gc.strokewidth(1)
|
|
412 |
gc.draw('text %d,%d %s' % [
|
|
413 |
left.round + 2, header_height + 14, Redmine::Utils::Shell::shell_quote(week_f.cweek.to_s)
|
|
414 |
])
|
|
415 |
left = left + width
|
|
416 |
week_f = week_f + 7
|
|
417 |
end
|
403 |
418 |
end
|
404 |
|
end
|
405 |
|
# Days details (week-end in grey)
|
406 |
|
if show_days
|
407 |
|
left = subject_width
|
408 |
|
height = g_height + header_height - 1
|
409 |
|
wday = @date_from.cwday
|
410 |
|
(date_to - @date_from + 1).to_i.times do
|
411 |
|
width = zoom
|
412 |
|
gc.fill(non_working_week_days.include?(wday) ? '#eee' : 'white')
|
413 |
|
gc.stroke('#ddd')
|
414 |
|
gc.stroke_width(1)
|
415 |
|
gc.rectangle(left, 2 * header_height, left + width, 2 * header_height + g_height - 1)
|
416 |
|
left = left + width
|
417 |
|
wday = wday + 1
|
418 |
|
wday = 1 if wday > 7
|
|
419 |
# Days details (week-end in grey)
|
|
420 |
if show_days
|
|
421 |
left = subject_width
|
|
422 |
height = g_height + header_height - 1
|
|
423 |
wday = @date_from.cwday
|
|
424 |
(date_to - @date_from + 1).to_i.times do
|
|
425 |
width = zoom
|
|
426 |
gc.fill(non_working_week_days.include?(wday) ? '#eee' : 'white')
|
|
427 |
gc.stroke('#ddd')
|
|
428 |
gc.strokewidth(1)
|
|
429 |
gc.draw('rectangle %d,%d %d,%d' % [
|
|
430 |
left, 2 * header_height, left + width, 2 * header_height + g_height - 1
|
|
431 |
])
|
|
432 |
left = left + width
|
|
433 |
wday = wday + 1
|
|
434 |
wday = 1 if wday > 7
|
|
435 |
end
|
419 |
436 |
end
|
420 |
|
end
|
421 |
|
# border
|
422 |
|
gc.fill('transparent')
|
423 |
|
gc.stroke('grey')
|
424 |
|
gc.stroke_width(1)
|
425 |
|
gc.rectangle(0, 0, subject_width + g_width, headers_height)
|
426 |
|
gc.stroke('black')
|
427 |
|
gc.rectangle(0, 0, subject_width + g_width, g_height + headers_height - 1)
|
428 |
|
# content
|
429 |
|
top = headers_height + 20
|
430 |
|
gc.stroke('transparent')
|
431 |
|
lines(:image => gc, :top => top, :zoom => zoom,
|
432 |
|
:subject_width => subject_width, :format => :image)
|
433 |
|
# today red line
|
434 |
|
if User.current.today >= @date_from and User.current.today <= date_to
|
435 |
|
gc.stroke('red')
|
436 |
|
x = (User.current.today - @date_from + 1) * zoom + subject_width
|
437 |
|
gc.line(x, headers_height, x, headers_height + g_height - 1)
|
438 |
|
end
|
439 |
|
gc.draw(imgl)
|
440 |
|
imgl.format = format
|
441 |
|
imgl.to_blob
|
442 |
|
end if Object.const_defined?(:Magick)
|
|
437 |
# border
|
|
438 |
gc.fill('transparent')
|
|
439 |
gc.stroke('grey')
|
|
440 |
gc.strokewidth(1)
|
|
441 |
gc.draw('rectangle %d,%d %d,%d' % [
|
|
442 |
0, 0, subject_width + g_width, headers_height
|
|
443 |
])
|
|
444 |
gc.stroke('black')
|
|
445 |
gc.draw('rectangle %d,%d %d,%d' % [
|
|
446 |
0, 0, subject_width + g_width, g_height + headers_height - 1
|
|
447 |
])
|
|
448 |
# content
|
|
449 |
top = headers_height + 20
|
|
450 |
gc.stroke('transparent')
|
|
451 |
lines(:image => gc, :top => top, :zoom => zoom,
|
|
452 |
:subject_width => subject_width, :format => :image)
|
|
453 |
# today red line
|
|
454 |
if User.current.today >= @date_from and User.current.today <= date_to
|
|
455 |
gc.stroke('red')
|
|
456 |
x = (User.current.today - @date_from + 1) * zoom + subject_width
|
|
457 |
gc.draw('line %g,%g %g,%g' % [
|
|
458 |
x, headers_height, x, headers_height + g_height - 1
|
|
459 |
])
|
|
460 |
end
|
|
461 |
gc << img.path
|
|
462 |
end
|
|
463 |
img.to_blob
|
|
464 |
ensure
|
|
465 |
img.destroy! if img
|
|
466 |
end if Object.const_defined?(:MiniMagick)
|
443 |
467 |
|
444 |
468 |
def to_pdf
|
445 |
469 |
pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language)
|
... | ... | |
735 |
759 |
def image_subject(params, subject, options={})
|
736 |
760 |
params[:image].fill('black')
|
737 |
761 |
params[:image].stroke('transparent')
|
738 |
|
params[:image].stroke_width(1)
|
739 |
|
params[:image].text(params[:indent], params[:top] + 2, subject)
|
|
762 |
params[:image].strokewidth(1)
|
|
763 |
params[:image].draw('text %d,%d %s' % [
|
|
764 |
params[:indent], params[:top] + 2, Redmine::Utils::Shell::shell_quote(subject)
|
|
765 |
])
|
740 |
766 |
end
|
741 |
767 |
|
742 |
768 |
def issue_relations(issue)
|
... | ... | |
912 |
938 |
# Renders the task bar, with progress and late
|
913 |
939 |
if coords[:bar_start] && coords[:bar_end]
|
914 |
940 |
params[:image].fill('#aaa')
|
915 |
|
params[:image].rectangle(params[:subject_width] + coords[:bar_start],
|
916 |
|
params[:top],
|
917 |
|
params[:subject_width] + coords[:bar_end],
|
918 |
|
params[:top] - height)
|
|
941 |
params[:image].draw('rectangle %d,%d %d,%d' % [
|
|
942 |
params[:subject_width] + coords[:bar_start],
|
|
943 |
params[:top],
|
|
944 |
params[:subject_width] + coords[:bar_end],
|
|
945 |
params[:top] - height
|
|
946 |
])
|
919 |
947 |
if coords[:bar_late_end]
|
920 |
948 |
params[:image].fill('#f66')
|
921 |
|
params[:image].rectangle(params[:subject_width] + coords[:bar_start],
|
922 |
|
params[:top],
|
923 |
|
params[:subject_width] + coords[:bar_late_end],
|
924 |
|
params[:top] - height)
|
|
949 |
params[:image].draw('rectangle %d,%d %d,%d' % [
|
|
950 |
params[:subject_width] + coords[:bar_start],
|
|
951 |
params[:top],
|
|
952 |
params[:subject_width] + coords[:bar_late_end],
|
|
953 |
params[:top] - height
|
|
954 |
])
|
925 |
955 |
end
|
926 |
956 |
if coords[:bar_progress_end]
|
927 |
957 |
params[:image].fill('#00c600')
|
928 |
|
params[:image].rectangle(params[:subject_width] + coords[:bar_start],
|
929 |
|
params[:top],
|
930 |
|
params[:subject_width] + coords[:bar_progress_end],
|
931 |
|
params[:top] - height)
|
|
958 |
params[:image].draw('rectangle %d,%d %d,%d' % [
|
|
959 |
params[:subject_width] + coords[:bar_start],
|
|
960 |
params[:top],
|
|
961 |
params[:subject_width] + coords[:bar_progress_end],
|
|
962 |
params[:top] - height
|
|
963 |
])
|
932 |
964 |
end
|
933 |
965 |
end
|
934 |
966 |
# Renders the markers
|
... | ... | |
937 |
969 |
x = params[:subject_width] + coords[:start]
|
938 |
970 |
y = params[:top] - height / 2
|
939 |
971 |
params[:image].fill('blue')
|
940 |
|
params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4)
|
|
972 |
params[:image].draw('polygon %d,%d %d,%d %d,%d %d,%d' % [
|
|
973 |
x - 4, y,
|
|
974 |
x, y - 4,
|
|
975 |
x + 4, y,
|
|
976 |
x, y + 4
|
|
977 |
])
|
941 |
978 |
end
|
942 |
979 |
if coords[:end]
|
943 |
980 |
x = params[:subject_width] + coords[:end] + params[:zoom]
|
944 |
981 |
y = params[:top] - height / 2
|
945 |
982 |
params[:image].fill('blue')
|
946 |
|
params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4)
|
|
983 |
params[:image].draw('polygon %d,%d %d,%d %d,%d %d,%d' % [
|
|
984 |
x - 4, y,
|
|
985 |
x, y - 4,
|
|
986 |
x + 4, y,
|
|
987 |
x, y + 4
|
|
988 |
])
|
947 |
989 |
end
|
948 |
990 |
end
|
949 |
991 |
# Renders the label on the right
|
950 |
992 |
if label
|
951 |
993 |
params[:image].fill('black')
|
952 |
|
params[:image].text(params[:subject_width] + (coords[:bar_end] || 0) + 5,
|
953 |
|
params[:top] + 1,
|
954 |
|
label)
|
|
994 |
params[:image].draw('text %d,%d %s' % [
|
|
995 |
params[:subject_width] + (coords[:bar_end] || 0) + 5, params[:top] + 1, Redmine::Utils::Shell::shell_quote(label)
|
|
996 |
])
|
955 |
997 |
end
|
956 |
998 |
end
|
957 |
999 |
end
|