From 4ce2e6c5bfc88c0fdfb5c4d7027d3eba4130ef55 Mon Sep 17 00:00:00 2001 From: Katsuya HIDAKA Date: Tue, 27 Oct 2026 15:46:52 +0900 Subject: Use Open Color for Gantt charts --- app/assets/stylesheets/gantt.css | 90 +++++++++++--------------------- lib/redmine/helpers/gantt.rb | 48 ++++++++++------- 2 files changed, 58 insertions(+), 80 deletions(-) diff --git a/app/assets/stylesheets/gantt.css b/app/assets/stylesheets/gantt.css index 62cf7f1a1..9fba0d453 100644 --- a/app/assets/stylesheets/gantt.css +++ b/app/assets/stylesheets/gantt.css @@ -240,7 +240,7 @@ inset-inline-start: var(--gantt-today-start); inline-size: 10px; block-size: var(--gantt-content-height); - border-inline-start: 1px dashed red; + border-inline-start: 1px dashed var(--oc-red-7); pointer-events: none; } @@ -305,104 +305,74 @@ .gantt-task-late { block-size: 10px; - background: var(--oc-red-5) url(/task_late.png); - border: 1px solid var(--oc-red-5); + background-color: var(--oc-red-4); + border: 1px solid var(--oc-red-4); } .gantt-task-done { block-size: 10px; - background: var(--oc-green-7) url(/task_done.png); - border: 1px solid var(--oc-green-7); + background-color: var(--oc-green-6); + border: 1px solid var(--oc-green-6); } .gantt-task-todo { block-size: 10px; - background: var(--oc-gray-5) url(/task_todo.png); + background-color: var(--oc-gray-5); border: 1px solid var(--oc-gray-5); } .gantt-task-todo.gantt-task-parent { - background: var(--oc-gray-6); - border: 1px solid var(--oc-gray-6); - block-size: 5px; + background-color: var(--oc-gray-6); + border-color: var(--oc-gray-6); } -.gantt-task-late.gantt-task-parent, -.gantt-task-done.gantt-task-parent { +.gantt-task-parent:is(.gantt-task-todo, .gantt-task-late, .gantt-task-done) { block-size: 5px; } -.gantt-task.gantt-task-parent.gantt-task-marker { - position: absolute; - background: url(/task_parent_end.png) no-repeat 0 0; +.gantt-task-marker { + background-color: var(--oc-indigo-8); inline-size: 8px; - block-size: 16px; + block-size: 8px; margin-inline-start: -4px; + margin-block-start: 1px; + /* Match the filled area and flat tips of the original 8px marker. */ + clip-path: polygon(3.5px 0, 4.5px 0, 8px 3.5px, 8px 4.5px, 4.5px 8px, 3.5px 8px, 0 4.5px, 0 3.5px); } -.gantt-row[data-gantt-row-type="version"] .gantt-task-late { - background: var(--oc-red-5) url(/milestone_late.png); - border: 1px solid var(--oc-red-5); - block-size: 4px; - margin-block-start: 3px; +.gantt-task-parent.gantt-task-marker { + background-color: var(--oc-gray-6); + block-size: 16px; + margin-block-start: 0; + /* Keep the original body's size and align its top with the bar. */ + clip-path: polygon(0 0, 100% 0, 100% 6px, 50% 9px, 0 6px); } -.gantt-row[data-gantt-row-type="version"] .gantt-task-done { - background: var(--oc-green-7) url(/milestone_done.png); - border: 1px solid var(--oc-green-7); +.gantt-row[data-gantt-row-type="version"] .gantt-task:is(.gantt-task-todo, .gantt-task-late, .gantt-task-done) { block-size: 4px; margin-block-start: 3px; } .gantt-row[data-gantt-row-type="version"] .gantt-task-todo { - background: var(--oc-white) url(/milestone_todo.png); - border: 1px solid var(--oc-white); - block-size: 4px; - margin-block-start: 3px; + background-color: var(--oc-gray-5); + border-color: transparent; + background-clip: padding-box; } .gantt-row[data-gantt-row-type="version"] .gantt-task-marker { - background-image: url(/version_marker.png); - background-repeat: no-repeat; - border: 0; - margin-inline-start: -4px; - margin-block-start: 1px; + background-color: var(--oc-gray-9); } -.gantt-row[data-gantt-row-type="project"] .gantt-task-late { - background: var(--oc-red-5) url(/milestone_late.png); - border: 1px solid var(--oc-red-5); - block-size: 4px; - margin-block-start: 3px; -} - -.gantt-row[data-gantt-row-type="project"] .gantt-task-done { - background: var(--oc-green-7) url(/milestone_done.png); - border: 1px solid var(--oc-green-7); +.gantt-row[data-gantt-row-type="project"] .gantt-task:is(.gantt-task-todo, .gantt-task-late, .gantt-task-done) { block-size: 4px; margin-block-start: 3px; } .gantt-row[data-gantt-row-type="project"] .gantt-task-todo { - background: var(--oc-white) url(/milestone_todo.png); - border: 1px solid var(--oc-white); - block-size: 4px; - margin-block-start: 3px; -} - -.gantt-row[data-gantt-row-type="project"] .gantt-task-marker { - background-image: url(/project_marker.png); - background-repeat: no-repeat; - border: 0; - margin-inline-start: -4px; - margin-block-start: 1px; -} - -/* RTL adjustment (disabled while Gantt is forced LTR). Enable when RTL is supported. - [dir="rtl"] .gantt-task-marker { - background-position: right; + background-color: var(--oc-gray-5); + border-color: transparent; + background-clip: padding-box; } -*/ .version-behind-schedule a, .issue-behind-schedule a { diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb index 02dcc9454..21a6c5fa2 100644 --- a/lib/redmine/helpers/gantt.rb +++ b/lib/redmine/helpers/gantt.rb @@ -36,7 +36,7 @@ module Redmine }, IssueRelation::TYPE_PRECEDES => { :landscape_margin => 20, - :color => '#228be6' # oc-blue-6 + :color => '#1c7ed6' # oc-blue-7 } }.freeze @@ -496,7 +496,7 @@ module Redmine @months.times do width = ((month_f >> 1) - month_f) * zoom gc.fill('white') - gc.stroke('grey') + gc.stroke('#adb5bd') # oc-gray-5 gc.strokewidth(1) gc.draw('rectangle %d,%d %d,%d' % [ left, 0, left + width, height @@ -522,7 +522,7 @@ module Redmine week_f = @date_from + (7 - @date_from.cwday + 1) width = (7 - @date_from.cwday + 1) * zoom gc.fill('white') - gc.stroke('grey') + gc.stroke('#adb5bd') # oc-gray-5 gc.strokewidth(1) gc.draw('rectangle %d,%d %d,%d' % [ left, header_height, left + width, 2 * header_height + g_height - 1 @@ -532,7 +532,7 @@ module Redmine while week_f <= date_to width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom gc.fill('white') - gc.stroke('grey') + gc.stroke('#adb5bd') # oc-gray-5 gc.strokewidth(1) gc.draw('rectangle %d,%d %d,%d' % [ left.round, header_height, left.round + width, 2 * header_height + g_height - 1 @@ -553,8 +553,8 @@ module Redmine height = g_height + header_height - 1 (@date_from..date_to).each do |g_date| width = zoom - gc.fill(non_working_day?(g_date) ? '#eee' : 'white') - gc.stroke('#ddd') + gc.fill(non_working_day?(g_date) ? '#f1f3f5' : 'white') # oc-gray-1 + gc.stroke('#dee2e6') # oc-gray-3 gc.strokewidth(1) gc.draw('rectangle %d,%d %d,%d' % [ left, 2 * header_height, left + width, 2 * header_height + g_height - 1 @@ -564,7 +564,7 @@ module Redmine end # border gc.fill('transparent') - gc.stroke('grey') + gc.stroke('#adb5bd') # oc-gray-5 gc.strokewidth(1) gc.draw('rectangle %d,%d %d,%d' % [ 0, 0, subject_width + g_width, headers_height @@ -580,7 +580,7 @@ module Redmine :subject_width => subject_width, :format => :image) # today red line if User.current.today.between?(@date_from, date_to) - gc.stroke('red') + gc.stroke('#f03e3e') # oc-red-7 x = (User.current.today - @date_from + 1) * zoom + subject_width gc.draw('line %g,%g %g,%g' % [ x, headers_height, x, headers_height + g_height - 1 @@ -673,7 +673,11 @@ module Redmine width = zoom pdf.SetY(y_start + header_height * 2) pdf.SetX(left) - pdf.SetTextColor(non_working_day?(g_date) ? 150 : 0) + if non_working_day?(g_date) + pdf.SetTextColor(134, 142, 150) # oc-gray-6 + else + pdf.SetTextColor(0) + end pdf.RDMCell(width, height, day_num.day.to_s, "LTR", 0, "C") left = left + width day_num = day_num + 1 @@ -688,7 +692,11 @@ module Redmine width = zoom pdf.SetY(y_start + header_height * (show_day_num ? 3 : 2)) pdf.SetX(left) - pdf.SetTextColor(non_working_day?(g_date) ? 150 : 0) + if non_working_day?(g_date) + pdf.SetTextColor(134, 142, 150) # oc-gray-6 + else + pdf.SetTextColor(0) + end pdf.RDMCell(width, height, day_name(g_date.cwday).first, "LTR", 0, "C") left = left + width end @@ -1033,20 +1041,20 @@ module Redmine width = [1, coords[:bar_end] - coords[:bar_start]].max params[:pdf].SetY(params[:top] + 1.5) params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) - params[:pdf].SetFillColor(200, 200, 200) + params[:pdf].SetFillColor(206, 212, 218) # oc-gray-4 params[:pdf].RDMCell(width, height, "", 0, 0, "", 1) if coords[:bar_late_end] width = [1, coords[:bar_late_end] - coords[:bar_start]].max params[:pdf].SetY(params[:top] + 1.5) params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) - params[:pdf].SetFillColor(255, 100, 100) + params[:pdf].SetFillColor(255, 107, 107) # oc-red-5 params[:pdf].RDMCell(width, height, "", 0, 0, "", 1) end if coords[:bar_progress_end] width = [1, coords[:bar_progress_end] - coords[:bar_start]].max params[:pdf].SetY(params[:top] + 1.5) params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) - params[:pdf].SetFillColor(90, 200, 90) + params[:pdf].SetFillColor(81, 207, 102) # oc-green-5 params[:pdf].RDMCell(width, height, "", 0, 0, "", 1) end end @@ -1055,13 +1063,13 @@ module Redmine if coords[:start] params[:pdf].SetY(params[:top] + 1) params[:pdf].SetX(params[:subject_width] + coords[:start] - 1) - params[:pdf].SetFillColor(50, 50, 200) + params[:pdf].SetFillColor(54, 79, 199) # oc-indigo-9 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1) end if coords[:end] params[:pdf].SetY(params[:top] + 1) params[:pdf].SetX(params[:subject_width] + coords[:end] - 1) - params[:pdf].SetFillColor(50, 50, 200) + params[:pdf].SetFillColor(54, 79, 199) # oc-indigo-9 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1) end end @@ -1079,7 +1087,7 @@ module Redmine height /= 2 if markers # Renders the task bar, with progress and late if coords[:bar_start] && coords[:bar_end] - params[:image].fill('#aaa') + params[:image].fill('#adb5bd') # oc-gray-5 params[:image].draw('rectangle %d,%d %d,%d' % [ params[:subject_width] + coords[:bar_start], params[:top], @@ -1087,7 +1095,7 @@ module Redmine params[:top] - height ]) if coords[:bar_late_end] - params[:image].fill('#f66') + params[:image].fill('#ff6b6b') # oc-red-5 params[:image].draw('rectangle %d,%d %d,%d' % [ params[:subject_width] + coords[:bar_start], params[:top], @@ -1096,7 +1104,7 @@ module Redmine ]) end if coords[:bar_progress_end] - params[:image].fill('#00c600') + params[:image].fill('#40c057') # oc-green-6 params[:image].draw('rectangle %d,%d %d,%d' % [ params[:subject_width] + coords[:bar_start], params[:top], @@ -1110,7 +1118,7 @@ module Redmine if coords[:start] x = params[:subject_width] + coords[:start] y = params[:top] - height / 2 - params[:image].fill('blue') + params[:image].fill('#364fc7') # oc-indigo-9 params[:image].draw('polygon %d,%d %d,%d %d,%d %d,%d' % [ x - 4, y, x, y - 4, @@ -1121,7 +1129,7 @@ module Redmine if coords[:end] x = params[:subject_width] + coords[:end] y = params[:top] - height / 2 - params[:image].fill('blue') + params[:image].fill('#364fc7') # oc-indigo-9 params[:image].draw('polygon %d,%d %d,%d %d,%d %d,%d' % [ x - 4, y, x, y - 4, -- 2.51.0